180 lines
5.5 KiB
Dart
180 lines
5.5 KiB
Dart
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;
|
|
|
|
const MyPaginatedTable({
|
|
super.key,
|
|
this.title,
|
|
required this.columns,
|
|
required this.rows,
|
|
this.columnSpacing = 23,
|
|
this.horizontalMargin = 35,
|
|
});
|
|
|
|
@override
|
|
_MyPaginatedTableState createState() => _MyPaginatedTableState();
|
|
}
|
|
|
|
class _MyPaginatedTableState extends State<MyPaginatedTable> {
|
|
int _start = 0;
|
|
int _rowsPerPage = 10;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final visibleRows = widget.rows.skip(_start).take(_rowsPerPage).toList();
|
|
final totalRows = widget.rows.length;
|
|
final totalPages = (totalRows / _rowsPerPage).ceil();
|
|
final currentPage = (_start / _rowsPerPage).ceil() + 1;
|
|
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
children: [
|
|
if (widget.title != null)
|
|
Padding(
|
|
padding: MySpacing.xy(8, 6), // Using standard spacing for title
|
|
child: MyText.titleMedium(widget.title!, fontWeight: 600, fontSize: 20),
|
|
),
|
|
if (widget.rows.isEmpty)
|
|
Padding(
|
|
padding: MySpacing.all(16), // Standard padding for empty state
|
|
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: MyContainer.bordered(
|
|
borderColor: Colors.black.withAlpha(40),
|
|
padding: EdgeInsets.zero,
|
|
child: DataTable(
|
|
columns: widget.columns,
|
|
rows: visibleRows,
|
|
columnSpacing: spacing,
|
|
horizontalMargin: widget.horizontalMargin,
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
MySpacing.height(8), // Standard height spacing after table
|
|
PaginatedFooter(
|
|
currentPage: currentPage,
|
|
totalPages: totalPages,
|
|
onPrevious: () {
|
|
setState(() {
|
|
_start = (_start - _rowsPerPage).clamp(0, totalRows - _rowsPerPage);
|
|
});
|
|
},
|
|
onNext: () {
|
|
setState(() {
|
|
_start = (_start + _rowsPerPage).clamp(0, totalRows - _rowsPerPage);
|
|
});
|
|
},
|
|
onPageSizeChanged: (newRowsPerPage) {
|
|
setState(() {
|
|
_rowsPerPage = newRowsPerPage;
|
|
_start = 0;
|
|
});
|
|
},
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
double _calculateSmartSpacing(double maxWidth) {
|
|
int columnCount = widget.columns.length;
|
|
double horizontalPadding = widget.horizontalMargin * 2;
|
|
double availableWidth = maxWidth - horizontalPadding;
|
|
|
|
// Desired min/max column spacing
|
|
const double minSpacing = 16;
|
|
const double maxSpacing = 80;
|
|
|
|
// Total width assuming minimal spacing
|
|
double minTotalWidth = (columnCount * minSpacing) + horizontalPadding;
|
|
|
|
if (minTotalWidth >= availableWidth) {
|
|
// Not enough room — return minimal spacing
|
|
return minSpacing;
|
|
}
|
|
|
|
// Fit evenly within the available width
|
|
double spacing = (availableWidth / columnCount) - 40; // 40 for estimated cell content width
|
|
return spacing.clamp(minSpacing, maxSpacing);
|
|
}
|
|
}
|
|
|
|
class PaginatedFooter extends StatelessWidget {
|
|
final int currentPage;
|
|
final int totalPages;
|
|
final VoidCallback onPrevious;
|
|
final VoidCallback onNext;
|
|
final Function(int) onPageSizeChanged;
|
|
|
|
const PaginatedFooter({
|
|
required this.currentPage,
|
|
required this.totalPages,
|
|
required this.onPrevious,
|
|
required this.onNext,
|
|
required this.onPageSizeChanged,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Padding(
|
|
padding: MySpacing.x(16), // Standard horizontal spacing for footer
|
|
child: SingleChildScrollView(
|
|
scrollDirection: Axis.horizontal,
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.end,
|
|
children: [
|
|
if (currentPage > 1)
|
|
IconButton(
|
|
onPressed: onPrevious,
|
|
icon: Icon(Icons.chevron_left),
|
|
),
|
|
Text(
|
|
'Page $currentPage of $totalPages',
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
color: Theme.of(context).colorScheme.onBackground,
|
|
),
|
|
),
|
|
SizedBox(width: 8),
|
|
if (currentPage < totalPages)
|
|
IconButton(
|
|
onPressed: onNext,
|
|
icon: Icon(Icons.chevron_right),
|
|
),
|
|
SizedBox(width: 16),
|
|
PopupMenuButton<int>(
|
|
icon: Icon(Icons.more_vert),
|
|
onSelected: (value) {
|
|
onPageSizeChanged(value);
|
|
},
|
|
itemBuilder: (BuildContext context) {
|
|
return [5, 10, 20, 50].map((e) {
|
|
return PopupMenuItem<int>(
|
|
value: e,
|
|
child: Text('$e rows per page'),
|
|
);
|
|
}).toList();
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|