bharat_erp/lib/shared/widgets/app_data_table.dart

207 lines
5.3 KiB
Dart

import 'package:flutter/material.dart';
import 'app_card.dart';
class AppDataColumn<T> {
const AppDataColumn({
required this.label,
required this.cellBuilder,
this.sortKey,
this.flex = 1,
this.alignment = Alignment.centerLeft,
});
final String label;
final Widget Function(BuildContext context, T row) cellBuilder;
final String? sortKey;
final int flex;
final Alignment alignment;
}
class AppDataTable<T> extends StatelessWidget {
const AppDataTable({
super.key,
required this.columns,
required this.rows,
this.sortColumn,
this.sortAscending = true,
this.onSort,
this.emptyMessage = 'No records found',
this.wrapInCard = true,
this.shrinkWrap = false,
});
final List<AppDataColumn<T>> columns;
final List<T> rows;
final String? sortColumn;
final bool sortAscending;
final void Function(String column, bool ascending)? onSort;
final String emptyMessage;
final bool wrapInCard;
/// Set true when the table is placed inside another scrollable.
final bool shrinkWrap;
@override
Widget build(BuildContext context) {
if (rows.isEmpty) {
final empty = Center(
child: Padding(
padding: const EdgeInsets.all(32),
child: Text(
emptyMessage,
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
),
);
if (!wrapInCard) return empty;
return AppCard(clipBehavior: Clip.antiAlias, child: empty);
}
final table = LayoutBuilder(
builder: (context, constraints) {
return ListView(
padding: EdgeInsets.zero,
shrinkWrap: shrinkWrap,
physics: shrinkWrap
? const NeverScrollableScrollPhysics()
: null,
children: [
_TableHeaderRow<T>(
columns: columns,
sortColumn: sortColumn,
sortAscending: sortAscending,
onSort: onSort,
),
...rows.map(
(row) => _TableDataRow<T>(
columns: columns,
row: row,
),
),
],
);
},
);
if (!wrapInCard) return table;
return AppCard(
clipBehavior: Clip.antiAlias,
child: table,
);
}
}
class _TableHeaderRow<T> extends StatelessWidget {
const _TableHeaderRow({
required this.columns,
required this.sortColumn,
required this.sortAscending,
required this.onSort,
});
final List<AppDataColumn<T>> columns;
final String? sortColumn;
final bool sortAscending;
final void Function(String column, bool ascending)? onSort;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Container(
width: double.infinity,
color: theme.colorScheme.surfaceContainerHighest.withValues(alpha: 0.4),
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 14),
child: Row(
children: columns.map((col) {
final isSorted = col.sortKey != null && col.sortKey == sortColumn;
final label = Text(
col.label.toUpperCase(),
style: theme.textTheme.labelSmall?.copyWith(
fontWeight: FontWeight.w700,
letterSpacing: 0.6,
color: theme.colorScheme.onSurfaceVariant,
),
);
Widget header = label;
if (col.sortKey != null && onSort != null) {
header = InkWell(
onTap: () => onSort!(
col.sortKey!,
isSorted ? !sortAscending : true,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
label,
if (isSorted) ...[
const SizedBox(width: 4),
Icon(
sortAscending
? Icons.arrow_upward
: Icons.arrow_downward,
size: 14,
color: theme.colorScheme.onSurfaceVariant,
),
],
],
),
);
}
return Expanded(
flex: col.flex,
child: Align(
alignment: col.alignment,
child: header,
),
);
}).toList(),
),
);
}
}
class _TableDataRow<T> extends StatelessWidget {
const _TableDataRow({
required this.columns,
required this.row,
});
final List<AppDataColumn<T>> columns;
final T row;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Container(
width: double.infinity,
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: theme.colorScheme.outline.withValues(alpha: 0.08),
),
),
),
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 14),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: columns.map((col) {
return Expanded(
flex: col.flex,
child: Align(
alignment: col.alignment,
child: col.cellBuilder(context, row),
),
);
}).toList(),
),
);
}
}