feat: New Open Tab dialog with search and optional customer email. Drinks are now grouped per order. Payments trigger cha-ching and money rain.
This commit is contained in:
@@ -4,10 +4,17 @@ import '../../l10n/app_localizations.dart';
|
||||
import '../../models/bar_tab.dart';
|
||||
import '../../models/tab_item.dart';
|
||||
import '../../utils/money.dart';
|
||||
import '../../utils/tab_orders.dart';
|
||||
import '../widgets/order_divider.dart';
|
||||
|
||||
/// Changes how many of [item] are on the tab: [delta] > 0 rings up more,
|
||||
/// [delta] < 0 takes drinks of order [orderId] off.
|
||||
typedef ItemQuantityChanged =
|
||||
Future<void> Function(TabItem item, int delta, String? orderId);
|
||||
|
||||
class SelectedTabDetails extends StatelessWidget {
|
||||
final BarTab tab;
|
||||
final Future<void> Function(TabItem item, int delta) onItemQuantityChanged;
|
||||
final ItemQuantityChanged onItemQuantityChanged;
|
||||
final VoidCallback onCloseTabPressed;
|
||||
|
||||
const SelectedTabDetails({
|
||||
@@ -44,17 +51,11 @@ class SelectedTabDetails extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
)
|
||||
: ListView.separated(
|
||||
itemCount: tab.items.length,
|
||||
separatorBuilder: (_, _) => const Divider(height: 1),
|
||||
itemBuilder: (context, index) {
|
||||
final item = tab.items[index];
|
||||
|
||||
return TabItemRow(
|
||||
item: item,
|
||||
onQuantityChanged: onItemQuantityChanged,
|
||||
);
|
||||
},
|
||||
: _OrderList(
|
||||
// Another tab starts with its own orders folded
|
||||
key: ValueKey(tab.id),
|
||||
orders: ordersOfTab(tab),
|
||||
onItemQuantityChanged: onItemQuantityChanged,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
@@ -102,13 +103,82 @@ class SelectedTabDetails extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// The tab's orders, oldest first, each under the time it started.
|
||||
class _OrderList extends StatefulWidget {
|
||||
final List<TabOrder<TabItem>> orders;
|
||||
final ItemQuantityChanged onItemQuantityChanged;
|
||||
|
||||
const _OrderList({
|
||||
super.key,
|
||||
required this.orders,
|
||||
required this.onItemQuantityChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
State<_OrderList> createState() => _OrderListState();
|
||||
}
|
||||
|
||||
/// The current (latest) order is open, earlier ones are folded away under
|
||||
/// their time and total; tapping a header flips it.
|
||||
class _OrderListState extends State<_OrderList> {
|
||||
/// Orders opened or closed by hand, by [_key].
|
||||
final _toggled = <String, bool>{};
|
||||
|
||||
String _key(TabOrder<TabItem> order) =>
|
||||
order.orderId ?? order.startedAt.toIso8601String();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final orders = widget.orders;
|
||||
final children = <Widget>[];
|
||||
for (final (orderIndex, order) in orders.indexed) {
|
||||
final key = _key(order);
|
||||
final expanded = _toggled[key] ?? orderIndex == orders.length - 1;
|
||||
children.add(
|
||||
OrderDivider(
|
||||
key: ValueKey('order-$key'),
|
||||
startedAt: order.startedAt,
|
||||
totalInCents: order.rows.fold<int>(
|
||||
0,
|
||||
(total, row) => total + row.quantity * row.line.unitPriceInCents,
|
||||
),
|
||||
expanded: expanded,
|
||||
onTap: () => setState(() => _toggled[key] = !expanded),
|
||||
),
|
||||
);
|
||||
if (!expanded) continue;
|
||||
|
||||
for (final (index, row) in order.rows.indexed) {
|
||||
if (index > 0) children.add(const Divider(height: 1));
|
||||
children.add(
|
||||
TabItemRow(
|
||||
key: ValueKey('${order.orderId ?? order.startedAt}-${row.line.id}'),
|
||||
item: row.line,
|
||||
quantity: row.quantity,
|
||||
orderId: order.orderId,
|
||||
onQuantityChanged: widget.onItemQuantityChanged,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return ListView(children: children);
|
||||
}
|
||||
}
|
||||
|
||||
class TabItemRow extends StatelessWidget {
|
||||
final TabItem item;
|
||||
final Future<void> Function(TabItem item, int delta) onQuantityChanged;
|
||||
|
||||
/// How many of [item] are in this order.
|
||||
final int quantity;
|
||||
final String? orderId;
|
||||
final ItemQuantityChanged onQuantityChanged;
|
||||
|
||||
const TabItemRow({
|
||||
super.key,
|
||||
required this.item,
|
||||
required this.quantity,
|
||||
required this.orderId,
|
||||
required this.onQuantityChanged,
|
||||
});
|
||||
|
||||
@@ -116,7 +186,7 @@ class TabItemRow extends StatelessWidget {
|
||||
final l10n = AppLocalizations.of(context);
|
||||
|
||||
try {
|
||||
await onQuantityChanged(item, delta);
|
||||
await onQuantityChanged(item, delta, orderId);
|
||||
} catch (e) {
|
||||
if (!context.mounted) return;
|
||||
ScaffoldMessenger.of(
|
||||
@@ -145,7 +215,7 @@ class TabItemRow extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
'${item.quantity} × ${item.unitPriceInCents.asEuro}',
|
||||
'$quantity × ${item.unitPriceInCents.asEuro}',
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
],
|
||||
@@ -167,7 +237,7 @@ class TabItemRow extends StatelessWidget {
|
||||
SizedBox(
|
||||
width: 22,
|
||||
child: Text(
|
||||
'${item.quantity}',
|
||||
'$quantity',
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(fontWeight: FontWeight.w700),
|
||||
),
|
||||
@@ -183,7 +253,7 @@ class TabItemRow extends StatelessWidget {
|
||||
SizedBox(
|
||||
width: 72,
|
||||
child: Text(
|
||||
item.lineTotalInCents.asEuro,
|
||||
(quantity * item.unitPriceInCents).asEuro,
|
||||
textAlign: TextAlign.end,
|
||||
style: const TextStyle(fontWeight: FontWeight.w700),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user