245 lines
6.8 KiB
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',
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|