/// Admin-only dialog to create a local test bank transaction. library; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; import '../models/bank.dart'; import '../models/transaction_meta.dart'; import '../providers/bank_provider.dart'; import '../utils/helpers.dart'; Future showAdminCreateTransactionDialog( BuildContext context, ) { return showDialog( context: context, builder: (context) => const _AdminCreateTransactionDialog(), ); } class _AdminCreateTransactionDialog extends StatefulWidget { const _AdminCreateTransactionDialog(); @override State<_AdminCreateTransactionDialog> createState() => _AdminCreateTransactionDialogState(); } class _AdminCreateTransactionDialogState extends State<_AdminCreateTransactionDialog> { late DateTime _date; late final TextEditingController _description; late final TextEditingController _amount; late final TextEditingController _currency; var _flow = TxFlowFilter.expense; var _pending = false; var _saving = false; String? _error; @override void initState() { super.initState(); final now = DateTime.now(); _date = DateTime(now.year, now.month, now.day); _description = TextEditingController(text: 'Test merchant'); _amount = TextEditingController(text: '12.50'); _currency = TextEditingController(text: 'EUR'); } @override void dispose() { _description.dispose(); _amount.dispose(); _currency.dispose(); super.dispose(); } Future _pickDate() async { final picked = await showDatePicker( context: context, initialDate: _date, firstDate: DateTime(2015), lastDate: DateTime.now().add(const Duration(days: 365)), ); if (picked == null || !mounted) return; setState(() => _date = DateTime(picked.year, picked.month, picked.day)); } Future _save() async { final raw = _amount.text.trim().replaceAll(',', '.'); final parsed = double.tryParse(raw); if (parsed == null || parsed <= 0) { setState(() => _error = 'Enter a positive amount'); return; } final signed = _flow == TxFlowFilter.income ? parsed : -parsed; setState(() { _saving = true; _error = null; }); try { final bank = context.read(); final tx = await bank.addManualTransaction( date: _date, description: _description.text, amount: signed, currency: _currency.text, pending: _pending, ); if (!mounted) return; Navigator.pop(context, tx); } catch (error) { if (!mounted) return; setState(() { _saving = false; _error = '$error'; }); } } @override Widget build(BuildContext context) { final theme = Theme.of(context); return AlertDialog( title: const Text('Create test transaction'), content: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text( 'Saved on this device and included in the next PocketBase sync.', style: theme.textTheme.bodySmall?.copyWith( color: theme.colorScheme.onSurfaceVariant, ), ), const SizedBox(height: 16), ListTile( contentPadding: EdgeInsets.zero, title: const Text('Date'), subtitle: Text(formatDate(_date)), trailing: const Icon(Icons.calendar_today_outlined, size: 20), onTap: _saving ? null : _pickDate, ), TextField( controller: _description, enabled: !_saving, textCapitalization: TextCapitalization.sentences, decoration: const InputDecoration( labelText: 'Description', ), ), const SizedBox(height: 12), SegmentedButton( segments: const [ ButtonSegment( value: TxFlowFilter.expense, label: Text('Expense'), icon: Icon(Icons.north_east, size: 16), ), ButtonSegment( value: TxFlowFilter.income, label: Text('Income'), icon: Icon(Icons.south_west, size: 16), ), ], selected: {_flow}, onSelectionChanged: _saving ? null : (selection) { setState(() => _flow = selection.first); }, ), const SizedBox(height: 12), Row( children: [ Expanded( flex: 2, child: TextField( controller: _amount, enabled: !_saving, keyboardType: const TextInputType.numberWithOptions( decimal: true, ), inputFormatters: [ FilteringTextInputFormatter.allow(RegExp(r'[0-9.,]')), ], decoration: const InputDecoration( labelText: 'Amount', ), ), ), const SizedBox(width: 12), Expanded( child: TextField( controller: _currency, enabled: !_saving, textCapitalization: TextCapitalization.characters, decoration: const InputDecoration( labelText: 'Currency', ), ), ), ], ), SwitchListTile( contentPadding: EdgeInsets.zero, title: const Text('Pending'), value: _pending, onChanged: _saving ? null : (value) => setState(() => _pending = value), ), if (_error != null) Padding( padding: const EdgeInsets.only(top: 4), child: Text( _error!, style: theme.textTheme.bodySmall?.copyWith( color: theme.colorScheme.error, ), ), ), ], ), ), actions: [ TextButton( onPressed: _saving ? null : () => Navigator.pop(context), child: const Text('Cancel'), ), FilledButton( onPressed: _saving ? null : _save, child: _saving ? const SizedBox( width: 18, height: 18, child: CircularProgressIndicator(strokeWidth: 2), ) : const Text('Create'), ), ], ); } }