import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:marco/helpers/widgets/my_spacing.dart'; import 'package:marco/helpers/widgets/my_text.dart'; import 'package:marco/helpers/widgets/my_container.dart'; class MyPaginatedTable extends StatefulWidget { final String? title; final List columns; final List rows; final double columnSpacing; final double horizontalMargin; final bool isLoading; final Widget? footer; const MyPaginatedTable({ super.key, this.title, required this.columns, required this.rows, this.columnSpacing = 20, this.horizontalMargin = 0, this.isLoading = false, this.footer, }); @override State createState() => _MyPaginatedTableState(); } class _MyPaginatedTableState extends State { int _start = 0; int _rowsPerPage = 10; @override Widget build(BuildContext context) { final totalRows = widget.rows.length; final totalPages = (totalRows / _rowsPerPage).ceil(); final currentPage = (_start ~/ _rowsPerPage) + 1; final visibleRows = widget.rows.skip(_start).take(_rowsPerPage).toList(); if (widget.isLoading) { return const Center(child: CircularProgressIndicator()); } return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ if (widget.title != null) Padding( padding: MySpacing.xy(8, 6), child: MyText.titleMedium( widget.title!, fontWeight: 600, fontSize: 20, ), ), if (widget.rows.isEmpty) Padding( padding: MySpacing.all(16), child: MyText.bodySmall('No data available'), ), if (widget.rows.isNotEmpty) LayoutBuilder( builder: (context, constraints) { final spacing = _calculateSmartSpacing(constraints.maxWidth); return SingleChildScrollView( scrollDirection: Axis.horizontal, child: ConstrainedBox( constraints: BoxConstraints(minWidth: constraints.maxWidth), child: MyContainer.bordered( borderColor: Colors.black.withAlpha(40), padding: const EdgeInsets.all(10), child: DataTable( columns: widget.columns, rows: visibleRows, columnSpacing: spacing, horizontalMargin: widget.horizontalMargin, headingRowHeight: 48, dataRowHeight: 44, ), ), ), ); }, ), const SizedBox(height: 8), widget.footer ?? PaginatedFooter( currentPage: currentPage, totalPages: totalPages, totalRows: totalRows, onPrevious: _handlePrevious, onNext: _handleNext, onPageChanged: _handlePageChanged, onPageSizeChanged: _handlePageSizeChanged, ), ], ); } void _handlePrevious() { setState(() { _start = (_start - _rowsPerPage) .clamp(0, math.max(0, widget.rows.length - _rowsPerPage)); }); } void _handleNext() { setState(() { _start = (_start + _rowsPerPage) .clamp(0, math.max(0, widget.rows.length - _rowsPerPage)); }); } void _handlePageChanged(int page) { setState(() { _start = (page - 1) * _rowsPerPage; }); } void _handlePageSizeChanged(int newRowsPerPage) { setState(() { _rowsPerPage = newRowsPerPage; _start = 0; }); } double _calculateSmartSpacing(double maxWidth) { final columnCount = widget.columns.length; final horizontalPadding = widget.horizontalMargin * 2; final availableWidth = maxWidth - horizontalPadding; const double minSpacing = 16; const double maxSpacing = 64; double spacing = (availableWidth / columnCount) - 40; return spacing.clamp(minSpacing, maxSpacing); } } class PaginatedFooter extends StatelessWidget { final int currentPage; final int totalPages; final int totalRows; final VoidCallback onPrevious; final VoidCallback onNext; final Function(int) onPageChanged; final Function(int) onPageSizeChanged; const PaginatedFooter({ super.key, required this.currentPage, required this.totalPages, required this.totalRows, required this.onPrevious, required this.onNext, required this.onPageChanged, required this.onPageSizeChanged, }); List _buildPageButtons() { List pages = []; void addPageButton(int page) { pages.add( Padding( padding: const EdgeInsets.symmetric(horizontal: 2), child: TextButton( onPressed: () => onPageChanged(page), style: TextButton.styleFrom( backgroundColor: currentPage == page ? Colors.blue : null, foregroundColor: currentPage == page ? Colors.white : Colors.black, padding: const EdgeInsets.symmetric( horizontal: 6, vertical: 2), minimumSize: const Size(32, 28), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), textStyle: const TextStyle(fontSize: 12), ), child: Text('$page'), ), ), ); } if (totalPages <= 5) { for (int i = 1; i <= totalPages; i++) { addPageButton(i); } } else { addPageButton(1); if (currentPage > 3) { pages.add(const Padding( padding: EdgeInsets.symmetric(horizontal: 4), child: Text('...'), )); } for (int i = math.max(2, currentPage - 1); i <= math.min(totalPages - 1, currentPage + 1); i++) { addPageButton(i); } if (currentPage < totalPages - 2) { pages.add(const Padding( padding: EdgeInsets.symmetric(horizontal: 4), child: Text('...'), )); } addPageButton(totalPages); } return pages; } @override Widget build(BuildContext context) { return Padding( padding: MySpacing.all(0), child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ IconButton( onPressed: currentPage > 1 ? onPrevious : null, icon: const Icon(Icons.chevron_left), tooltip: 'Previous', ), ..._buildPageButtons(), IconButton( onPressed: currentPage < totalPages ? onNext : null, icon: const Icon(Icons.chevron_right), tooltip: 'Next', ), ], ), ); } }