import 'package:flutter/material.dart'; import 'package:miaoji_zhang/shared/api/api_client.dart'; import 'package:miaoji_zhang/shared/api/business_api.dart'; import 'package:miaoji_zhang/shared/services/current_ledger_store.dart'; import 'package:miaoji_zhang/shared/theme/app_theme.dart'; import 'package:miaoji_zhang/shared/widgets/app_controls.dart'; import 'package:miaoji_zhang/shared/widgets/category_icon.dart'; import 'package:miaoji_zhang/shared/services/shanghai_time.dart'; class TransactionEditPage extends StatefulWidget { final TxItem transaction; const TransactionEditPage({super.key, required this.transaction}); @override State createState() => _TransactionEditPageState(); } class _TransactionEditPageState extends State { late final TextEditingController _amount; late final TextEditingController _note; late final TextEditingController _payment; late String _type; late int _ledgerId; late int _categoryId; late DateTime _occurredAt; List _categories = const []; bool _loading = true; bool _saving = false; @override void initState() { super.initState(); final transaction = widget.transaction; _amount = TextEditingController( text: transaction.amount.toStringAsFixed(2), ); _note = TextEditingController(text: transaction.note ?? ''); _payment = TextEditingController(text: transaction.paymentMethod ?? ''); _type = transaction.type; _ledgerId = transaction.ledgerId; _categoryId = transaction.categoryId; _occurredAt = transaction.occurredAt; _loadCategories(); } @override void dispose() { _amount.dispose(); _note.dispose(); _payment.dispose(); super.dispose(); } Future _loadCategories() async { setState(() => _loading = true); try { await CurrentLedgerStore.instance.ensureLoaded(); final categories = await TxApi.categories(_type); if (!categories.any((category) => category.id == _categoryId) && categories.isNotEmpty) { _categoryId = categories.first.id; } if (mounted) setState(() => _categories = categories); } catch (error) { if (mounted) _showError(error); } finally { if (mounted) setState(() => _loading = false); } } Future _changeType(String type) async { if (type == _type) return; setState(() => _type = type); await _loadCategories(); } Future _pickDateTime() async { final value = await showJzDateTimeSheet( context, initial: _occurredAt, firstDate: DateTime(2000), lastDate: ShanghaiTime.now.add(const Duration(days: 1)), title: '选择发生时间', ); if (value != null && mounted) setState(() => _occurredAt = value); } Future _selectCategory() async { final value = await showJzOptionSheet( context, title: '选择分类', options: _categories .map( (category) => JzOption( value: category.id, label: category.name, leading: CategoryIconBox( iconKey: category.iconKey, colorKey: category.colorKey, size: 32, ), ), ) .toList(), selected: _categoryId, ); if (value != null && mounted) setState(() => _categoryId = value); } Future _selectLedger() async { final ledgers = CurrentLedgerStore.instance.ledgers; final value = await showJzOptionSheet( context, title: '选择所属账本', options: ledgers .map( (ledger) => JzOption( value: ledger.id, label: ledger.name, subtitle: ledger.isDefault ? '当前默认账本' : null, ), ) .toList(), selected: _ledgerId, ); if (value != null && mounted) setState(() => _ledgerId = value); } Future _save() async { final amount = double.tryParse(_amount.text.trim()); if (amount == null || amount <= 0) { _showError(StateError('请输入正确金额')); return; } setState(() => _saving = true); try { final updated = await TxApi.update( widget.transaction.id, ledgerId: _ledgerId, categoryId: _categoryId, type: _type, amount: amount, note: _note.text.trim(), paymentMethod: _payment.text.trim(), occurredAt: _occurredAt, ); if (mounted) Navigator.pop(context, updated); } catch (error) { if (mounted) _showError(error); } finally { if (mounted) setState(() => _saving = false); } } void _showError(Object error) { ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text(apiErrorMessage(error)))); } @override Widget build(BuildContext context) { final ledgers = CurrentLedgerStore.instance.ledgers; return Scaffold( appBar: AppBar(title: Text('编辑账单')), body: _loading ? Center( child: CircularProgressIndicator( color: AppTheme.primary, strokeWidth: 2, ), ) : ListView( padding: const EdgeInsets.all(16), children: [ JzSegmentedControl( value: _type, options: const [ JzOption(value: 'expense', label: '支出'), JzOption(value: 'income', label: '收入'), ], onChanged: _changeType, ), SizedBox(height: 14), TextField( controller: _amount, keyboardType: const TextInputType.numberWithOptions( decimal: true, ), decoration: InputDecoration( labelText: '金额', prefixText: '¥ ', ), ), SizedBox(height: 12), _SelectField( label: '分类', value: _categories .where((category) => category.id == _categoryId) .map((category) => category.name) .firstOrNull ?? '请选择', onTap: _selectCategory, ), SizedBox(height: 12), _SelectField( label: '所属账本', value: ledgers .where((ledger) => ledger.id == _ledgerId) .map((ledger) => ledger.name) .firstOrNull ?? '请选择', onTap: _selectLedger, ), SizedBox(height: 12), TextField( controller: _note, maxLength: 50, decoration: InputDecoration(labelText: '备注'), ), SizedBox(height: 4), TextField( controller: _payment, maxLength: 20, decoration: InputDecoration(labelText: '支付方式'), ), SizedBox(height: 4), ListTile( contentPadding: EdgeInsets.zero, title: Text('发生时间'), subtitle: Text(_occurredAt.toString().substring(0, 16)), trailing: Icon(Icons.chevron_right), onTap: _pickDateTime, ), if (widget.transaction.sourceText?.isNotEmpty == true) ...[ SizedBox(height: 8), InputDecorator( decoration: InputDecoration(labelText: 'AI 原话(只读)'), child: Text(widget.transaction.sourceText!), ), ], SizedBox(height: 24), FilledButton( onPressed: _saving ? null : _save, child: Text(_saving ? '保存中...' : '保存修改'), ), ], ), ); } } class _SelectField extends StatelessWidget { final String label; final String value; final VoidCallback onTap; const _SelectField({ required this.label, required this.value, required this.onTap, }); @override Widget build(BuildContext context) { return InkWell( onTap: onTap, borderRadius: BorderRadius.circular(14), child: InputDecorator( decoration: InputDecoration(labelText: label), child: Row( children: [ Expanded( child: Text( value, style: TextStyle(fontSize: 13.5, fontWeight: FontWeight.w600), ), ), Icon(Icons.keyboard_arrow_down_rounded, color: context.jz.text3), ], ), ), ); } }