diff --git a/lib/screens/reports/report_date_filter.dart b/lib/screens/reports/report_date_filter.dart index faf68055..e213e523 100644 --- a/lib/screens/reports/report_date_filter.dart +++ b/lib/screens/reports/report_date_filter.dart @@ -5,14 +5,47 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../providers/reports_provider.dart'; import '../../utils/app_time.dart'; +/// Returns a [ReportDateRange] spanning the calendar month that contains [now]. +/// start = DateTime(y, m, 1), end = DateTime(y, m+1, 1), label = 'This Month'. +ReportDateRange thisMonthRange(DateTime now) { + return ReportDateRange( + start: DateTime(now.year, now.month, 1), + end: DateTime(now.year, now.month + 1, 1), + label: 'This Month', + ); +} + /// A Metabase-inspired date filter with quick presets, relative offsets, /// and a custom date-range picker. +/// +/// Two construction modes: +/// - Default `ReportDateFilter({key})` — provider-bound, reads/writes +/// [reportDateRangeProvider]. +/// - `ReportDateFilter.controlled({key, value, onChanged})` — controlled; +/// displays [value] and calls [onChanged] on apply without touching the +/// provider. class ReportDateFilter extends ConsumerWidget { - const ReportDateFilter({super.key}); + /// Provider-bound mode. + const ReportDateFilter({super.key}) + : _value = null, + _onChanged = null; + + /// Controlled mode: does NOT read or write [reportDateRangeProvider]. + const ReportDateFilter.controlled({ + super.key, + required ReportDateRange value, + required ValueChanged onChanged, + }) : _value = value, + _onChanged = onChanged; + + final ReportDateRange? _value; + final ValueChanged? _onChanged; + + bool get _isControlled => _value != null; @override Widget build(BuildContext context, WidgetRef ref) { - final range = ref.watch(reportDateRangeProvider); + final range = _isControlled ? _value! : ref.watch(reportDateRangeProvider); final theme = Theme.of(context); final colors = theme.colorScheme; final text = theme.textTheme; @@ -51,12 +84,18 @@ class ReportDateFilter extends ConsumerWidget { } void _showDateFilterDialog(BuildContext context, WidgetRef ref) { + final current = + _isControlled ? _value! : ref.read(reportDateRangeProvider); m3ShowDialog( context: context, builder: (ctx) => _DateFilterDialog( - current: ref.read(reportDateRangeProvider), + current: current, onApply: (newRange) { - ref.read(reportDateRangeProvider.notifier).state = newRange; + if (_isControlled) { + _onChanged!(newRange); + } else { + ref.read(reportDateRangeProvider.notifier).state = newRange; + } }, ), ); diff --git a/test/report_date_filter_test.dart b/test/report_date_filter_test.dart new file mode 100644 index 00000000..ab75a2b1 --- /dev/null +++ b/test/report_date_filter_test.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:tasq/providers/reports_provider.dart'; +import 'package:tasq/screens/reports/report_date_filter.dart'; +import 'package:tasq/utils/app_time.dart'; + +void main() { + setUpAll(() { + AppTime.initialize(); + }); + + group('ReportDateFilter.controlled', () { + testWidgets( + 'emits via onChanged and never writes provider', + (tester) async { + final now = DateTime(2025, 1, 15); + final captured = []; + final container = ProviderContainer(); + addTearDown(container.dispose); + + await tester.pumpWidget( + UncontrolledProviderScope( + container: container, + child: MaterialApp( + home: Scaffold( + body: ReportDateFilter.controlled( + value: thisMonthRange(now), + onChanged: captured.add, + ), + ), + ), + ), + ); + + // Tap the "Change" button to open the dialog + await tester.tap(find.text('Change')); + await tester.pumpAndSettle(); + + // Tap the "Yesterday" preset chip + await tester.tap(find.text('Yesterday')); + await tester.pumpAndSettle(); + + // The onChanged callback received Yesterday + expect(captured.single.label, 'Yesterday'); + + // The provider was NOT touched — still at its default label + expect(container.read(reportDateRangeProvider).label, 'Last 30 Days'); + }, + ); + }); +}