budget screen: dynamic month display, editable budget amounts (tap to set)
This commit is contained in:
parent
e3baad4dfa
commit
da74308bad
|
|
@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import '../providers/db_provider.dart';
|
import '../providers/db_provider.dart';
|
||||||
import '../theme/app_theme.dart';
|
import '../theme/app_theme.dart';
|
||||||
|
import 'package:drift/drift.dart' show Value;
|
||||||
|
import '../data/db.dart';
|
||||||
|
|
||||||
class BudgetScreen extends ConsumerWidget {
|
class BudgetScreen extends ConsumerWidget {
|
||||||
const BudgetScreen({super.key});
|
const BudgetScreen({super.key});
|
||||||
|
|
@ -10,6 +12,9 @@ class BudgetScreen extends ConsumerWidget {
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final groups = ref.watch(categoryGroupsProvider).valueOrNull ?? [];
|
final groups = ref.watch(categoryGroupsProvider).valueOrNull ?? [];
|
||||||
final catsByGroup = ref.watch(categoriesByGroupProvider);
|
final catsByGroup = ref.watch(categoriesByGroupProvider);
|
||||||
|
final now = DateTime.now();
|
||||||
|
final months = ['', 'Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'];
|
||||||
|
final monthLabel = '${months[now.month]} ${now.year}';
|
||||||
|
|
||||||
return ListView(
|
return ListView(
|
||||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 90),
|
padding: const EdgeInsets.fromLTRB(16, 8, 16, 90),
|
||||||
|
|
@ -22,7 +27,7 @@ class BudgetScreen extends ConsumerWidget {
|
||||||
children: [
|
children: [
|
||||||
Text('Budget du mois', style: Theme.of(context).textTheme.titleMedium),
|
Text('Budget du mois', style: Theme.of(context).textTheme.titleMedium),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text('Juillet 2026', style: Theme.of(context).textTheme.bodySmall),
|
Text(monthLabel, style: Theme.of(context).textTheme.bodySmall),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -106,6 +111,51 @@ class _CategoryRow extends ConsumerWidget {
|
||||||
final remaining = ref.watch(categoryRemainingProvider(categoryId));
|
final remaining = ref.watch(categoryRemainingProvider(categoryId));
|
||||||
final pct = budgeted > 0 ? ((budgeted + spent) / budgeted).clamp(0.0, 1.0) : 0.0;
|
final pct = budgeted > 0 ? ((budgeted + spent) / budgeted).clamp(0.0, 1.0) : 0.0;
|
||||||
|
|
||||||
|
void _editBudget() async {
|
||||||
|
final month = ref.read(currentMonthProvider);
|
||||||
|
final db = ref.read(dbProvider);
|
||||||
|
final ctrl = TextEditingController(text: budgeted > 0 ? budgeted.toStringAsFixed(2) : '');
|
||||||
|
|
||||||
|
final newAmount = await showDialog<double>(
|
||||||
|
context: context,
|
||||||
|
builder: (ctx) => AlertDialog(
|
||||||
|
title: Text('Budget : ${category.name}'),
|
||||||
|
content: TextField(
|
||||||
|
controller: ctrl,
|
||||||
|
decoration: const InputDecoration(labelText: 'Montant', hintText: '0,00'),
|
||||||
|
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
||||||
|
autofocus: true,
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(onPressed: () => Navigator.pop(ctx), child: const Text('Annuler')),
|
||||||
|
FilledButton(
|
||||||
|
onPressed: () {
|
||||||
|
final v = double.tryParse(ctrl.text.trim().replaceAll(',', '.'));
|
||||||
|
if (v != null) Navigator.pop(ctx, v);
|
||||||
|
},
|
||||||
|
child: const Text('Enregistrer'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
ctrl.dispose();
|
||||||
|
|
||||||
|
if (newAmount == null) return;
|
||||||
|
final existing = await (db.select(db.budgets)
|
||||||
|
..where((b) => b.categoryId.equals(categoryId))
|
||||||
|
..where((b) => b.month.equals(month)))
|
||||||
|
.get();
|
||||||
|
if (existing.isNotEmpty) {
|
||||||
|
await db.update(db.budgets).replace(existing.first.copyWith(budgeted: newAmount));
|
||||||
|
} else {
|
||||||
|
await db.into(db.budgets).insert(BudgetsCompanion.insert(
|
||||||
|
categoryId: categoryId,
|
||||||
|
month: month,
|
||||||
|
budgeted: Value(newAmount),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(left: 8, bottom: 4),
|
padding: const EdgeInsets.only(left: 8, bottom: 4),
|
||||||
child: Card(
|
child: Card(
|
||||||
|
|
@ -144,8 +194,14 @@ class _CategoryRow extends ConsumerWidget {
|
||||||
const SizedBox(height: 2),
|
const SizedBox(height: 2),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Text('Budget: ${formatCurrency(budgeted)}',
|
GestureDetector(
|
||||||
style: Theme.of(context).textTheme.bodySmall),
|
onTap: _editBudget,
|
||||||
|
child: Text('Budget: ${formatCurrency(budgeted)}',
|
||||||
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
|
decoration: TextDecoration.underline,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
)),
|
||||||
|
),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Text('Dépensé: ${formatCurrency(spent.abs())}',
|
Text('Dépensé: ${formatCurrency(spent.abs())}',
|
||||||
style: Theme.of(context).textTheme.bodySmall),
|
style: Theme.of(context).textTheme.bodySmall),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue