Skip to content

Replace global year selector with dashboard-local date range filter #279

@daveharmswebdev

Description

@daveharmswebdev

Feature Request

The year selector currently lives in the sidebar navigation (global position), implying it affects all features. In reality, its primary purpose is filtering the financial summary totals on the dashboard and property detail pages. As the app has grown from an expense tracker into a full property management suite, the year selector's global placement is misleading.

Proposal: Remove the year selector from the sidebar and replace it with an inline date range filter on the dashboard, similar to the existing DateRangeFilterComponent used in the expenses list view.


Bug: Global Year Selector Silently Overrides Income Date Range Filter

This is the most urgent reason to do this refactoring.

The Income list view has its own DateRangeFilterComponent (presets: This Month, This Quarter, This Year, Custom). But the global year selector in the sidebar silently interferes with it via a reactive effect:

// income.component.ts:463-467
private yearEffect = effect(() => {
  const year = this.yearService.selectedYear();
  this.incomeStore.setYear(year);  // forces store reload with new year
});

This causes two problems:

Problem 1: "This Year" means the globally selected year, not the calendar year

When the user picks the "This Year" preset in the income date range filter, getDateRangeFromPreset() uses the global year selector value — not the actual current calendar year. If the sidebar says 2025, "This Year" silently becomes Jan 1–Dec 31, 2025.

Problem 2: Contradicting filters return zero results silently

The backend applies BOTH the year param AND dateFrom/dateTo as cumulative AND filters:

// GetAllIncome.cs — year filter applied first
if (request.Year.HasValue)
{
    query = query.Where(i => i.Date >= yearStart && i.Date <= yearEnd);
}
// Then date range further narrows
if (request.DateFrom.HasValue)
{
    query = query.Where(i => i.Date >= request.DateFrom.Value);
}

If the user sets global year to 2025 but picks a custom date range of Jan–Jun 2026, the backend applies:

WHERE Date.Year == 2025 AND Date >= '2026-01-01' AND Date <= '2026-06-30'

This returns zero results with no error or explanation. The user sees an empty list and has no idea why.


Investigation: Year Selector Is Not Dead Code

A thorough audit confirms the year selector is actively consumed and wired into real API calls across the app:

Consumer What year controls API endpoint Dead code?
Dashboard Expense/income totals per property GET /properties?year= No — filters financial summaries
Properties list Same financial summaries (not the property list itself) GET /properties?year= No — filters financial summaries
Property detail YTD stats + "Recent Expenses/Income" lists GET /properties/{id}?year= No — filters stats + recent activity
Income list Feeds into date range calculation AND sent as separate backend param GET /income?year=&dateFrom=&dateTo= No — actively causes confusion
Report dialog Reads global year as initial default, then fully independent N/A at runtime Semi — only seeds initial value
Batch report dialog Same — snapshots global year on open, independent after N/A at runtime Semi — only seeds initial value

Key finding: The report dialogs already have their own year selectors. The expenses list already uses DateRangeFilterComponent without any year selector dependency. The income list is the only view where the global year selector creates an active conflict with a local date range filter.


Current State

Where the year selector lives

  • Sidebar nav (sidebar-nav.component.html) — renders the YearSelectorComponent
  • YearSelectorService (core/services/year-selector.service.ts) — global singleton, persists to localStorage

What currently consumes the year selector

Consumer What it filters Actually needs global year?
Dashboard (dashboard.component.ts) Summary totals (Total Expenses YTD, Total Income YTD, Net Income YTD) No — should use local date range
Properties list (properties.component.ts) YTD expense/income per property No — should use local date range
Property detail (property-detail.component.ts) YTD Expenses, YTD Income, Net Income cards No — should use local date range
Income list (income.component.ts) Filters income records by year No — should have its own date filter (like expenses already does)
Report dialog (report-dialog.component.ts) Year for Schedule E report generation Already has its own year selector in the dialog — independent
Batch report dialog (batch-report-dialog.component.ts) Year for batch report generation Seeds from global year but has its own selector — independent

Proposed Changes

1. Remove year selector from sidebar

  • Remove <app-year-selector> from sidebar-nav.component.html
  • This reclaims sidebar space and eliminates the misleading "global filter" impression

2. Add date range filter to dashboard

  • Place a DateRangeFilterComponent (already exists in shared/components/) inline on the dashboard page, above or alongside the summary cards
  • Presets: This Month, This Quarter, This Year (default), Last Year, Custom
  • Controls the summary totals (Total Expenses, Total Income, Net Income) and the per-property running totals
  • The "YTD" labels should update to reflect the selected range (e.g., "Total Expenses" instead of "Total Expenses YTD" when using a custom range)

3. Add date range filter to property detail

  • Same DateRangeFilterComponent on the property detail page, controlling the YTD Expenses / YTD Income / Net Income cards
  • Could default to current year or inherit from dashboard selection

4. Fix income list — remove global year dependency

  • Remove the yearEffect that watches YearSelectorService and forces store reloads
  • Income list already has DateRangeFilterComponent — let it be the sole filter authority
  • Stop sending redundant year param to backend when dateFrom/dateTo already cover it

5. Deprecate/remove YearSelectorService

  • Once all consumers migrate to local date range filters, remove:
    • core/services/year-selector.service.ts
    • shared/components/year-selector/year-selector.component.ts
    • Associated spec files
    • localStorage key propertyManager.selectedYear

Existing Pattern to Follow

The expenses list already implements this pattern via:

  • shared/components/date-range-filter/date-range-filter.component.ts — reusable date range UI
  • expense-filters.component.ts — integrates the date range filter
  • expense-list.store.ts — manages DateRangePreset, dateFrom, dateTo state

Files Affected

Remove/modify:

  • frontend/src/app/core/components/sidebar-nav/sidebar-nav.component.html — remove year selector
  • frontend/src/app/core/components/sidebar-nav/sidebar-nav.component.ts — remove year selector import
  • frontend/src/app/core/components/sidebar-nav/sidebar-nav.component.scss — remove year selector styles
  • frontend/src/app/core/services/year-selector.service.ts — deprecate/remove
  • frontend/src/app/shared/components/year-selector/year-selector.component.ts — remove
  • frontend/src/app/features/income/income.component.ts — remove yearEffect, decouple from global year

Add date range filter to:

  • frontend/src/app/features/dashboard/dashboard.component.ts
  • frontend/src/app/features/properties/property-detail/property-detail.component.ts
  • frontend/src/app/features/properties/properties.component.ts

Backend may need:

  • Dashboard and property summary endpoints may need to accept dateFrom/dateTo params instead of just year
  • Income endpoint: stop requiring year when dateFrom/dateTo are provided

Acceptance Criteria

  • Year selector removed from sidebar navigation
  • Dashboard has inline date range filter with presets (This Month, This Quarter, This Year, Last Year, Custom)
  • Dashboard summary totals respect selected date range
  • Per-property running totals on dashboard respect selected date range
  • Property detail financial cards respect a local date range filter
  • Income list decoupled from global year selector — yearEffect removed
  • Income date range filter works independently without contradicting year param
  • Report dialogs continue to work with their own built-in year selectors (no regression)
  • Labels update appropriately (e.g., no "YTD" when custom range selected)
  • YearSelectorService and YearSelectorComponent removed once all consumers migrated

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions