marco.pms.mobileapp/lib/model/my_paginated_table.dart

245 lines
6.8 KiB
Dart

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<DataColumn> columns;
final List<DataRow> 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<MyPaginatedTable> createState() => _MyPaginatedTableState();
}
class _MyPaginatedTableState extends State<MyPaginatedTable> {
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<Widget> _buildPageButtons() {
List<Widget> 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',
),
],
),
);
}
}