feat(calendar): "what's due this month" — dedicated screen + inventory filter
Reuses the existing per-variety crop-calendar masks (sow/transplant/ flowering/fruiting/seed-harvest) — no schema change, no migration — to answer "qué toca este mes" across the whole inventory. Since the months are the grower's own record, there's no hemisphere assumption. - Repo: CalendarEntry + watchCalendar() (varieties with any recorded phase); VarietyListItem now carries sowMonths. - Dedicated CalendarScreen (/calendar): a month strip (defaults to the current month, intl month names) and varieties grouped by phase — actions (sow/transplant/harvest seed) above informational (flowering/ fruiting), each tinted, tap → variety detail; empty-month state. - Inventory filter: a 'this month' chip (shown only when some variety has a calendar) keeps what sows in the current month; folded into the attributes group + clear-filters. - Entry points: a live Home card + a drawer item. - i18n calendar block + menu.calendar (en/es/pt/ast). - Tests: calendar_test (watchCalendar, list carries sow-months, pure filter), calendar_screen_test (phase grouping, month switch, empty), plus the screen added to the small-screen overflow guard.
This commit is contained in:
parent
28e8318026
commit
171daabce3
19 changed files with 681 additions and 5 deletions
|
|
@ -5,6 +5,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||
|
||||
import '../data/variety_repository.dart';
|
||||
import '../db/enums.dart';
|
||||
import '../domain/crop_calendar.dart';
|
||||
|
||||
/// Inventory list state: all items from the DB plus the current search query
|
||||
/// and active filters. [visibleItems] applies query ∧ category ∧ form; grouping
|
||||
|
|
@ -19,6 +20,8 @@ class InventoryState extends Equatable {
|
|||
this.organicOnly = false,
|
||||
this.needsReproductionOnly = false,
|
||||
this.sharingOnly = false,
|
||||
this.sowThisMonthOnly = false,
|
||||
this.filterMonth = 0,
|
||||
this.loading = true,
|
||||
this.selectionMode = false,
|
||||
this.selectedIds = const {},
|
||||
|
|
@ -49,6 +52,15 @@ class InventoryState extends Equatable {
|
|||
/// "what I share" view.
|
||||
final bool sharingOnly;
|
||||
|
||||
/// When true, keep only varieties whose recorded sow-months include
|
||||
/// [filterMonth] — the inventory "to sow this month" view.
|
||||
final bool sowThisMonthOnly;
|
||||
|
||||
/// The month (1..12) the "to sow this month" filter matches against — set to
|
||||
/// the current month by the cubit when the filter is turned on. Only read
|
||||
/// while [sowThisMonthOnly] is true.
|
||||
final int filterMonth;
|
||||
|
||||
final bool loading;
|
||||
|
||||
/// Whether the list is in multi-select mode — used to pick a subset of the
|
||||
|
|
@ -84,10 +96,17 @@ class InventoryState extends Equatable {
|
|||
if (organicOnly && !i.isOrganic) return false;
|
||||
if (needsReproductionOnly && !i.needsReproduction) return false;
|
||||
if (sharingOnly && !i.isShared) return false;
|
||||
if (sowThisMonthOnly && !maskHasMonth(i.sowMonths, filterMonth)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
/// Whether any variety has recorded sow-months, so the UI only offers the
|
||||
/// "to sow this month" chip when it would match something.
|
||||
bool get hasSowCalendar => items.any((i) => i.sowMonths != null);
|
||||
|
||||
/// Whether any item is flagged organic, so the UI only offers the eco filter
|
||||
/// chip when it would actually match something.
|
||||
bool get hasOrganic => items.any((i) => i.isOrganic);
|
||||
|
|
@ -109,6 +128,8 @@ class InventoryState extends Equatable {
|
|||
bool? organicOnly,
|
||||
bool? needsReproductionOnly,
|
||||
bool? sharingOnly,
|
||||
bool? sowThisMonthOnly,
|
||||
int? filterMonth,
|
||||
bool? loading,
|
||||
bool? selectionMode,
|
||||
Set<String>? selectedIds,
|
||||
|
|
@ -123,6 +144,8 @@ class InventoryState extends Equatable {
|
|||
needsReproductionOnly:
|
||||
needsReproductionOnly ?? this.needsReproductionOnly,
|
||||
sharingOnly: sharingOnly ?? this.sharingOnly,
|
||||
sowThisMonthOnly: sowThisMonthOnly ?? this.sowThisMonthOnly,
|
||||
filterMonth: filterMonth ?? this.filterMonth,
|
||||
loading: loading ?? this.loading,
|
||||
selectionMode: selectionMode ?? this.selectionMode,
|
||||
selectedIds: selectedIds ?? this.selectedIds,
|
||||
|
|
@ -139,6 +162,8 @@ class InventoryState extends Equatable {
|
|||
organicOnly,
|
||||
needsReproductionOnly,
|
||||
sharingOnly,
|
||||
sowThisMonthOnly,
|
||||
filterMonth,
|
||||
loading,
|
||||
selectionMode,
|
||||
selectedIds,
|
||||
|
|
@ -148,7 +173,9 @@ class InventoryState extends Equatable {
|
|||
/// Subscribes to the repository's reactive inventory stream. The list updates
|
||||
/// automatically after a quick-add — no manual refresh.
|
||||
class InventoryCubit extends Cubit<InventoryState> {
|
||||
InventoryCubit(this._repo) : super(const InventoryState()) {
|
||||
InventoryCubit(this._repo, {int Function()? nowMonth})
|
||||
: _nowMonth = nowMonth ?? (() => DateTime.now().month),
|
||||
super(const InventoryState()) {
|
||||
// One combined subscription (list + draft tray). Two separate StreamGroups
|
||||
// here re-emit in a loop that hangs widget tests — see watchInventoryView.
|
||||
_sub = _repo.watchInventoryView().listen(
|
||||
|
|
@ -159,6 +186,7 @@ class InventoryCubit extends Cubit<InventoryState> {
|
|||
}
|
||||
|
||||
final VarietyRepository _repo;
|
||||
final int Function() _nowMonth;
|
||||
late final StreamSubscription<
|
||||
({List<VarietyListItem> items, List<VarietyListItem> drafts})
|
||||
>
|
||||
|
|
@ -192,6 +220,12 @@ class InventoryCubit extends Cubit<InventoryState> {
|
|||
void toggleSharingOnly() =>
|
||||
emit(state.copyWith(sharingOnly: !state.sharingOnly));
|
||||
|
||||
/// Toggles the "to sow this month" filter, pinned to the current month.
|
||||
void toggleSowThisMonth() => emit(state.copyWith(
|
||||
sowThisMonthOnly: !state.sowThisMonthOnly,
|
||||
filterMonth: _nowMonth(),
|
||||
));
|
||||
|
||||
/// Enters multi-select mode with an empty selection (from the toolbar).
|
||||
void startSelection() =>
|
||||
emit(state.copyWith(selectionMode: true, selectedIds: const {}));
|
||||
|
|
@ -230,6 +264,7 @@ class InventoryCubit extends Cubit<InventoryState> {
|
|||
organicOnly: false,
|
||||
needsReproductionOnly: false,
|
||||
sharingOnly: false,
|
||||
sowThisMonthOnly: false,
|
||||
),
|
||||
);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue