From 10cc246053be4d7c9c30084d1d8203a9e4aa4f4d Mon Sep 17 00:00:00 2001 From: daveharms Date: Sat, 7 Mar 2026 09:00:00 -0600 Subject: [PATCH 1/4] feat: replace global year selector with per-page date range filters (Story 17.12) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the global YearSelectorService/YearSelectorComponent (sidebar widget → singleton service → effects in 4 components) with local DateRangeFilterComponent instances on Dashboard, Properties list, and Property detail pages. Add dateFrom/dateTo query params to backend endpoints with year-based fallback for backward compatibility. Decouple Income and Report dialogs from the removed global year state. Co-Authored-By: Claude Opus 4.6 --- .../Controllers/DashboardController.cs | 8 +- .../Controllers/PropertiesController.cs | 12 +- .../Dashboard/GetDashboardTotals.cs | 7 +- .../Properties/GetAllProperties.cs | 8 +- .../Properties/GetPropertyById.cs | 8 +- .../components/shell/shell.component.html | 2 - .../core/components/shell/shell.component.ts | 2 - .../sidebar-nav/sidebar-nav.component.html | 7 - .../sidebar-nav/sidebar-nav.component.scss | 8 - .../sidebar-nav/sidebar-nav.component.ts | 2 - .../services/year-selector.service.spec.ts | 251 ------------------ .../core/services/year-selector.service.ts | 105 -------- .../dashboard/dashboard.component.spec.ts | 4 - .../features/dashboard/dashboard.component.ts | 54 +++- .../expenses/stores/expense-list.store.ts | 1 + .../features/income/income.component.spec.ts | 31 --- .../app/features/income/income.component.ts | 8 - .../income/stores/income-list.store.spec.ts | 13 - .../income/stores/income-list.store.ts | 15 +- .../properties/properties.component.spec.ts | 23 +- .../properties/properties.component.ts | 54 +++- .../property-detail.component.ts | 61 ++++- .../services/property.service.spec.ts | 4 +- .../properties/services/property.service.ts | 22 +- .../properties/stores/property.store.spec.ts | 47 ++-- .../properties/stores/property.store.ts | 30 +-- .../batch-report-dialog.component.spec.ts | 24 +- .../batch-report-dialog.component.ts | 6 +- .../date-range-filter.component.ts | 1 + .../year-selector.component.spec.ts | 189 ------------- .../year-selector/year-selector.component.ts | 138 ---------- .../src/app/shared/utils/date-range.utils.ts | 9 +- 32 files changed, 241 insertions(+), 913 deletions(-) delete mode 100644 frontend/src/app/core/services/year-selector.service.spec.ts delete mode 100644 frontend/src/app/core/services/year-selector.service.ts delete mode 100644 frontend/src/app/shared/components/year-selector/year-selector.component.spec.ts delete mode 100644 frontend/src/app/shared/components/year-selector/year-selector.component.ts diff --git a/backend/src/PropertyManager.Api/Controllers/DashboardController.cs b/backend/src/PropertyManager.Api/Controllers/DashboardController.cs index 0a86ef1b..cf3536f1 100644 --- a/backend/src/PropertyManager.Api/Controllers/DashboardController.cs +++ b/backend/src/PropertyManager.Api/Controllers/DashboardController.cs @@ -30,16 +30,18 @@ public DashboardController( /// Get dashboard totals for the specified tax year (AC-4.4.1, AC-4.4.2, AC-4.4.6). /// Returns aggregated expenses, income, net income, and property count. /// - /// Tax year to aggregate totals for (required) + /// Optional tax year to aggregate totals for (defaults to current year) + /// Optional start date filter (YYYY-MM-DD) + /// Optional end date filter (YYYY-MM-DD) /// Dashboard totals including expenses, income, net income, and property count /// Returns the dashboard totals /// If user is not authenticated [HttpGet("totals")] [ProducesResponseType(typeof(DashboardTotalsDto), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status401Unauthorized)] - public async Task GetTotals([FromQuery] int year) + public async Task GetTotals([FromQuery] int? year = null, [FromQuery] DateOnly? dateFrom = null, [FromQuery] DateOnly? dateTo = null) { - var query = new GetDashboardTotalsQuery(year); + var query = new GetDashboardTotalsQuery(year, dateFrom, dateTo); var response = await _mediator.Send(query); _logger.LogInformation( diff --git a/backend/src/PropertyManager.Api/Controllers/PropertiesController.cs b/backend/src/PropertyManager.Api/Controllers/PropertiesController.cs index 0cd890c3..6f4703de 100644 --- a/backend/src/PropertyManager.Api/Controllers/PropertiesController.cs +++ b/backend/src/PropertyManager.Api/Controllers/PropertiesController.cs @@ -38,15 +38,17 @@ public PropertiesController( /// Get all properties for the current user (AC-2.1.4, AC-2.2.6). /// /// Optional tax year filter for expense/income totals + /// Optional start date filter (YYYY-MM-DD) + /// Optional end date filter (YYYY-MM-DD) /// List of properties with summary information /// Returns the list of properties /// If user is not authenticated [HttpGet] [ProducesResponseType(typeof(GetAllPropertiesResponse), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status401Unauthorized)] - public async Task GetAllProperties([FromQuery] int? year = null) + public async Task GetAllProperties([FromQuery] int? year = null, [FromQuery] DateOnly? dateFrom = null, [FromQuery] DateOnly? dateTo = null) { - var query = new GetAllPropertiesQuery(year); + var query = new GetAllPropertiesQuery(year, dateFrom, dateTo); var response = await _mediator.Send(query); _logger.LogInformation( @@ -63,6 +65,8 @@ public async Task GetAllProperties([FromQuery] int? year = null) /// /// Property GUID /// Optional tax year filter for expense totals (defaults to current year) + /// Optional start date filter (YYYY-MM-DD) + /// Optional end date filter (YYYY-MM-DD) /// Property detail information /// Returns the property detail /// If user is not authenticated @@ -71,9 +75,9 @@ public async Task GetAllProperties([FromQuery] int? year = null) [ProducesResponseType(typeof(PropertyDetailDto), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status401Unauthorized)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] - public async Task GetPropertyById(Guid id, [FromQuery] int? year = null) + public async Task GetPropertyById(Guid id, [FromQuery] int? year = null, [FromQuery] DateOnly? dateFrom = null, [FromQuery] DateOnly? dateTo = null) { - var query = new GetPropertyByIdQuery(id, year); + var query = new GetPropertyByIdQuery(id, year, dateFrom, dateTo); var property = await _mediator.Send(query); if (property == null) diff --git a/backend/src/PropertyManager.Application/Dashboard/GetDashboardTotals.cs b/backend/src/PropertyManager.Application/Dashboard/GetDashboardTotals.cs index 1bce2163..b4b0a0d1 100644 --- a/backend/src/PropertyManager.Application/Dashboard/GetDashboardTotals.cs +++ b/backend/src/PropertyManager.Application/Dashboard/GetDashboardTotals.cs @@ -8,7 +8,7 @@ namespace PropertyManager.Application.Dashboard; /// Query to get dashboard totals for the current user's account (AC-4.4.1, AC-4.4.2). /// /// Tax year to aggregate totals for -public record GetDashboardTotalsQuery(int Year) : IRequest; +public record GetDashboardTotalsQuery(int? Year = null, DateOnly? DateFrom = null, DateOnly? DateTo = null) : IRequest; /// /// Dashboard totals DTO containing aggregated financial data. @@ -39,8 +39,9 @@ public GetDashboardTotalsQueryHandler( public async Task Handle(GetDashboardTotalsQuery request, CancellationToken cancellationToken) { - var yearStart = new DateOnly(request.Year, 1, 1); - var yearEnd = new DateOnly(request.Year, 12, 31); + var year = request.Year ?? DateTime.UtcNow.Year; + var yearStart = request.DateFrom ?? new DateOnly(year, 1, 1); + var yearEnd = request.DateTo ?? new DateOnly(year, 12, 31); // Get total expenses for the year var totalExpenses = await _dbContext.Expenses diff --git a/backend/src/PropertyManager.Application/Properties/GetAllProperties.cs b/backend/src/PropertyManager.Application/Properties/GetAllProperties.cs index 5b3f4ee2..e9aa6ade 100644 --- a/backend/src/PropertyManager.Application/Properties/GetAllProperties.cs +++ b/backend/src/PropertyManager.Application/Properties/GetAllProperties.cs @@ -8,7 +8,7 @@ namespace PropertyManager.Application.Properties; /// Query to get all properties for the current user's account. /// /// Optional tax year filter for expense/income totals (defaults to current year) -public record GetAllPropertiesQuery(int? Year = null) : IRequest; +public record GetAllPropertiesQuery(int? Year = null, DateOnly? DateFrom = null, DateOnly? DateTo = null) : IRequest; /// /// Response containing list of properties. @@ -56,6 +56,8 @@ public GetAllPropertiesQueryHandler( public async Task Handle(GetAllPropertiesQuery request, CancellationToken cancellationToken) { var year = request.Year ?? DateTime.UtcNow.Year; + var dateStart = request.DateFrom ?? new DateOnly(year, 1, 1); + var dateEnd = request.DateTo ?? new DateOnly(year, 12, 31); // Query properties with primary photo thumbnail storage key var propertiesData = await _dbContext.Properties @@ -73,13 +75,13 @@ public async Task Handle(GetAllPropertiesQuery request .Where(e => e.PropertyId == p.Id && e.AccountId == _currentUser.AccountId && e.DeletedAt == null - && e.Date.Year == year) + && e.Date >= dateStart && e.Date <= dateEnd) .Sum(e => (decimal?)e.Amount) ?? 0m, IncomeTotal = _dbContext.Income .Where(i => i.PropertyId == p.Id && i.AccountId == _currentUser.AccountId && i.DeletedAt == null - && i.Date.Year == year) + && i.Date >= dateStart && i.Date <= dateEnd) .Sum(i => (decimal?)i.Amount) ?? 0m, PrimaryPhotoThumbnailStorageKey = _dbContext.PropertyPhotos .Where(pp => pp.PropertyId == p.Id && pp.IsPrimary) diff --git a/backend/src/PropertyManager.Application/Properties/GetPropertyById.cs b/backend/src/PropertyManager.Application/Properties/GetPropertyById.cs index 10752f2c..0d67e3c5 100644 --- a/backend/src/PropertyManager.Application/Properties/GetPropertyById.cs +++ b/backend/src/PropertyManager.Application/Properties/GetPropertyById.cs @@ -10,7 +10,7 @@ namespace PropertyManager.Application.Properties; /// /// Property GUID /// Optional tax year filter (defaults to current year) (AC-3.5.6) -public record GetPropertyByIdQuery(Guid Id, int? Year = null) : IRequest; +public record GetPropertyByIdQuery(Guid Id, int? Year = null, DateOnly? DateFrom = null, DateOnly? DateTo = null) : IRequest; /// /// Detail DTO for property view page (AC-2.3.2, AC-13.3a.9). @@ -74,10 +74,10 @@ public GetPropertyByIdQueryHandler( public async Task Handle(GetPropertyByIdQuery request, CancellationToken cancellationToken) { - // Use provided year or default to current year (AC-3.5.6) + // Use provided date range, or fall back to year-based range (AC-3.5.6) var year = request.Year ?? DateTime.UtcNow.Year; - var yearStart = new DateOnly(year, 1, 1); - var yearEnd = new DateOnly(year, 12, 31); + var yearStart = request.DateFrom ?? new DateOnly(year, 1, 1); + var yearEnd = request.DateTo ?? new DateOnly(year, 12, 31); var propertyData = await _dbContext.Properties .Where(p => p.Id == request.Id && p.AccountId == _currentUser.AccountId && p.DeletedAt == null) diff --git a/frontend/src/app/core/components/shell/shell.component.html b/frontend/src/app/core/components/shell/shell.component.html index 12d05cf0..ff218ff9 100644 --- a/frontend/src/app/core/components/shell/shell.component.html +++ b/frontend/src/app/core/components/shell/shell.component.html @@ -28,7 +28,6 @@ - {{ userDisplayName }} + + + + + ('this-year'); + readonly dateFrom = signal(null); + readonly dateTo = signal(null); + constructor() { - // React to year changes and reload properties (AC-3.5.3) + const initial = getDateRangeFromPreset('this-year'); + this.dateFrom.set(initial.dateFrom); + this.dateTo.set(initial.dateTo); + effect(() => { - const year = this.yearService.selectedYear(); - this.propertyStore.loadProperties(year); + const from = this.dateFrom(); + const to = this.dateTo(); + this.propertyStore.loadProperties({ dateFrom: from ?? undefined, dateTo: to ?? undefined }); }); } ngOnInit(): void { - // Initial load happens via effect when selectedYear signal is read + // Initial load happens via effect when dateFrom/dateTo signals are read } loadProperties(): void { - this.propertyStore.loadProperties(this.yearService.selectedYear()); + this.propertyStore.loadProperties({ dateFrom: this.dateFrom() ?? undefined, dateTo: this.dateTo() ?? undefined }); + } + + onDateRangePresetChange(preset: DateRangePreset): void { + this.dateRangePreset.set(preset); + const { dateFrom, dateTo } = getDateRangeFromPreset(preset); + this.dateFrom.set(dateFrom); + this.dateTo.set(dateTo); + } + + onCustomDateRangeChange(range: { dateFrom: string; dateTo: string }): void { + this.dateRangePreset.set('custom'); + this.dateFrom.set(range.dateFrom); + this.dateTo.set(range.dateTo); } navigateToProperty(propertyId: string): void { diff --git a/frontend/src/app/features/expenses/stores/expense-list.store.ts b/frontend/src/app/features/expenses/stores/expense-list.store.ts index 098c63df..e08aad4e 100644 --- a/frontend/src/app/features/expenses/stores/expense-list.store.ts +++ b/frontend/src/app/features/expenses/stores/expense-list.store.ts @@ -159,6 +159,7 @@ export const ExpenseListStore = signalStore( 'this-month': 'This Month', 'this-quarter': 'This Quarter', 'this-year': 'This Year', + 'last-year': 'Last Year', 'custom': 'Custom Range', 'all': 'All Time', }; diff --git a/frontend/src/app/features/income/income.component.spec.ts b/frontend/src/app/features/income/income.component.spec.ts index fd3c7855..0eb70006 100644 --- a/frontend/src/app/features/income/income.component.spec.ts +++ b/frontend/src/app/features/income/income.component.spec.ts @@ -10,7 +10,6 @@ import { By } from '@angular/platform-browser'; import { IncomeComponent } from './income.component'; import { IncomeListStore } from './stores/income-list.store'; import { PropertyStore } from '../properties/stores/property.store'; -import { YearSelectorService } from '../../core/services/year-selector.service'; /** * Unit tests for IncomeComponent (AC-4.3.1, AC-4.3.2, AC-4.3.3, AC-4.3.4, AC-4.3.5, AC-4.3.6) @@ -68,10 +67,6 @@ describe('IncomeComponent', () => { loadProperties: vi.fn(), }; - const mockYearService = { - selectedYear: signal(2026), - }; - beforeEach(async () => { vi.clearAllMocks(); @@ -85,7 +80,6 @@ describe('IncomeComponent', () => { provideRouter([]), { provide: IncomeListStore, useValue: mockIncomeListStore }, { provide: PropertyStore, useValue: mockPropertyStore }, - { provide: YearSelectorService, useValue: mockYearService }, ], }).compileComponents(); @@ -217,10 +211,6 @@ describe('IncomeComponent loading state', () => { loadProperties: vi.fn(), }; - const mockYearService = { - selectedYear: signal(2026), - }; - beforeEach(async () => { await TestBed.configureTestingModule({ imports: [IncomeComponent], @@ -232,7 +222,6 @@ describe('IncomeComponent loading state', () => { provideRouter([]), { provide: IncomeListStore, useValue: mockIncomeListStore }, { provide: PropertyStore, useValue: mockPropertyStore }, - { provide: YearSelectorService, useValue: mockYearService }, ], }).compileComponents(); @@ -280,10 +269,6 @@ describe('IncomeComponent error state', () => { loadProperties: vi.fn(), }; - const mockYearService = { - selectedYear: signal(2026), - }; - beforeEach(async () => { await TestBed.configureTestingModule({ imports: [IncomeComponent], @@ -295,7 +280,6 @@ describe('IncomeComponent error state', () => { provideRouter([]), { provide: IncomeListStore, useValue: mockIncomeListStore }, { provide: PropertyStore, useValue: mockPropertyStore }, - { provide: YearSelectorService, useValue: mockYearService }, ], }).compileComponents(); @@ -349,10 +333,6 @@ describe('IncomeComponent truly empty state (AC-4.3.5)', () => { loadProperties: vi.fn(), }; - const mockYearService = { - selectedYear: signal(2026), - }; - beforeEach(async () => { await TestBed.configureTestingModule({ imports: [IncomeComponent], @@ -364,7 +344,6 @@ describe('IncomeComponent truly empty state (AC-4.3.5)', () => { provideRouter([]), { provide: IncomeListStore, useValue: mockIncomeListStore }, { provide: PropertyStore, useValue: mockPropertyStore }, - { provide: YearSelectorService, useValue: mockYearService }, ], }).compileComponents(); @@ -420,10 +399,6 @@ describe('IncomeComponent filtered empty state (AC-4.3.5)', () => { loadProperties: vi.fn(), }; - const mockYearService = { - selectedYear: signal(2026), - }; - beforeEach(async () => { vi.clearAllMocks(); @@ -437,7 +412,6 @@ describe('IncomeComponent filtered empty state (AC-4.3.5)', () => { provideRouter([]), { provide: IncomeListStore, useValue: mockIncomeListStore }, { provide: PropertyStore, useValue: mockPropertyStore }, - { provide: YearSelectorService, useValue: mockYearService }, ], }).compileComponents(); @@ -509,10 +483,6 @@ describe('IncomeComponent property filter (AC-4.3.4)', () => { loadProperties: vi.fn(), }; - const mockYearService = { - selectedYear: signal(2026), - }; - beforeEach(async () => { vi.clearAllMocks(); @@ -526,7 +496,6 @@ describe('IncomeComponent property filter (AC-4.3.4)', () => { provideRouter([]), { provide: IncomeListStore, useValue: mockIncomeListStore }, { provide: PropertyStore, useValue: mockPropertyStore }, - { provide: YearSelectorService, useValue: mockYearService }, ], }).compileComponents(); diff --git a/frontend/src/app/features/income/income.component.ts b/frontend/src/app/features/income/income.component.ts index b8e3770a..d228676e 100644 --- a/frontend/src/app/features/income/income.component.ts +++ b/frontend/src/app/features/income/income.component.ts @@ -25,7 +25,6 @@ import { ConfirmDialogComponent, ConfirmDialogData, } from '../../shared/components/confirm-dialog/confirm-dialog.component'; -import { YearSelectorService } from '../../core/services/year-selector.service'; import { formatDateShort } from '../../shared/utils/date.utils'; import { DateRangePreset } from '../../shared/utils/date-range.utils'; import { DateRangeFilterComponent } from '../../shared/components/date-range-filter/date-range-filter.component'; @@ -478,7 +477,6 @@ import { ListTotalDisplayComponent } from '../../shared/components/list-total-di export class IncomeComponent implements OnInit, OnDestroy { readonly incomeStore = inject(IncomeListStore); readonly propertyStore = inject(PropertyStore); - private readonly yearService = inject(YearSelectorService); private readonly router = inject(Router); private readonly dialog = inject(MatDialog); private readonly snackBar = inject(MatSnackBar); @@ -492,12 +490,6 @@ export class IncomeComponent implements OnInit, OnDestroy { // Computed signal for date range preset dateRangePreset = computed(() => this.incomeStore.dateRangePreset()); - // Effect to react to year changes (AC-4.3.2 - respects global tax year selector) - private yearEffect = effect(() => { - const year = this.yearService.selectedYear(); - this.incomeStore.setYear(year); - }); - // Sync search input with store state private searchEffect = effect(() => { const search = this.incomeStore.searchText(); diff --git a/frontend/src/app/features/income/stores/income-list.store.spec.ts b/frontend/src/app/features/income/stores/income-list.store.spec.ts index b0dfff08..33107a48 100644 --- a/frontend/src/app/features/income/stores/income-list.store.spec.ts +++ b/frontend/src/app/features/income/stores/income-list.store.spec.ts @@ -177,19 +177,6 @@ describe('IncomeListStore (AC-4.3.1, AC-4.3.3, AC-4.3.4, AC-4.3.5, AC-4.3.6)', ( }); }); - describe('setYear', () => { - it('should filter by year', () => { - // Act - store.setYear(2025); - - // Assert - expect(incomeServiceMock.getAllIncome).toHaveBeenCalledWith( - expect.objectContaining({ - year: 2025, - }) - ); - }); - }); describe('clearFilters (AC-4.3.5)', () => { it('should clear all filters', () => { diff --git a/frontend/src/app/features/income/stores/income-list.store.ts b/frontend/src/app/features/income/stores/income-list.store.ts index 80a18de5..7fc9ab17 100644 --- a/frontend/src/app/features/income/stores/income-list.store.ts +++ b/frontend/src/app/features/income/stores/income-list.store.ts @@ -42,7 +42,6 @@ interface IncomeListState { dateTo: string | null; selectedPropertyId: string | null; searchText: string; - year: number | null; // Loading states isLoading: boolean; @@ -70,7 +69,6 @@ const initialState: IncomeListState = { dateTo: null, selectedPropertyId: null, searchText: '', - year: null, // Loading states isLoading: false, @@ -155,14 +153,13 @@ export const IncomeListStore = signalStore( currentFilters: computed((): IncomeFilterParams => { const { dateFrom, dateTo } = store.dateRangePreset() === 'custom' ? { dateFrom: store.dateFrom(), dateTo: store.dateTo() } - : getDateRangeFromPreset(store.dateRangePreset(), store.year()); + : getDateRangeFromPreset(store.dateRangePreset()); return { dateFrom: dateFrom ?? undefined, dateTo: dateTo ?? undefined, propertyId: store.selectedPropertyId() ?? undefined, search: store.searchText().trim() || undefined, - year: store.year() ?? undefined, }; }), @@ -210,7 +207,7 @@ export const IncomeListStore = signalStore( * Set date range preset and reload (AC-4.3.3) */ setDateRangePreset(preset: DateRangePreset): void { - const { dateFrom, dateTo } = getDateRangeFromPreset(preset, store.year()); + const { dateFrom, dateTo } = getDateRangeFromPreset(preset); patchState(store, { dateRangePreset: preset, dateFrom, dateTo }); persistIncomeDateFilter(preset, dateFrom, dateTo); this.loadIncome(store.currentFilters()); @@ -242,14 +239,6 @@ export const IncomeListStore = signalStore( this.loadIncome(store.currentFilters()); }, - /** - * Set tax year filter and reload (AC-4.3.2) - */ - setYear(year: number | null): void { - patchState(store, { year }); - this.loadIncome(store.currentFilters()); - }, - /** * Clear all filters (AC-4.3.5) */ diff --git a/frontend/src/app/features/properties/properties.component.spec.ts b/frontend/src/app/features/properties/properties.component.spec.ts index d39ab511..7caa4733 100644 --- a/frontend/src/app/features/properties/properties.component.spec.ts +++ b/frontend/src/app/features/properties/properties.component.spec.ts @@ -5,7 +5,6 @@ import { signal } from '@angular/core'; import { By } from '@angular/platform-browser'; import { PropertiesComponent } from './properties.component'; import { PropertyStore } from './stores/property.store'; -import { YearSelectorService } from '../../core/services/year-selector.service'; /** * Unit tests for PropertiesComponent (AC-2.1.1) @@ -35,10 +34,6 @@ describe('PropertiesComponent', () => { loadProperties: vi.fn(), }; - const mockYearService = { - selectedYear: signal(2026), - }; - beforeEach(async () => { vi.clearAllMocks(); @@ -50,7 +45,6 @@ describe('PropertiesComponent', () => { { path: 'properties/:id', component: PropertiesComponent }, ]), { provide: PropertyStore, useValue: mockPropertyStore }, - { provide: YearSelectorService, useValue: mockYearService }, ], }).compileComponents(); @@ -127,17 +121,12 @@ describe('PropertiesComponent loading state', () => { loadProperties: vi.fn(), }; - const mockYearService = { - selectedYear: signal(2026), - }; - beforeEach(async () => { await TestBed.configureTestingModule({ imports: [PropertiesComponent], providers: [ provideRouter([]), { provide: PropertyStore, useValue: mockPropertyStore }, - { provide: YearSelectorService, useValue: mockYearService }, ], }).compileComponents(); @@ -169,10 +158,6 @@ describe('PropertiesComponent error state', () => { loadProperties: vi.fn(), }; - const mockYearService = { - selectedYear: signal(2026), - }; - beforeEach(async () => { vi.clearAllMocks(); @@ -181,7 +166,6 @@ describe('PropertiesComponent error state', () => { providers: [ provideRouter([]), { provide: PropertyStore, useValue: mockPropertyStore }, - { provide: YearSelectorService, useValue: mockYearService }, ], }).compileComponents(); @@ -197,7 +181,7 @@ describe('PropertiesComponent error state', () => { it('should call loadProperties when retry is triggered', () => { component.loadProperties(); - expect(mockPropertyStore.loadProperties).toHaveBeenCalledWith(2026); + expect(mockPropertyStore.loadProperties).toHaveBeenCalled(); }); }); @@ -213,17 +197,12 @@ describe('PropertiesComponent empty state', () => { loadProperties: vi.fn(), }; - const mockYearService = { - selectedYear: signal(2026), - }; - beforeEach(async () => { await TestBed.configureTestingModule({ imports: [PropertiesComponent], providers: [ provideRouter([]), { provide: PropertyStore, useValue: mockPropertyStore }, - { provide: YearSelectorService, useValue: mockYearService }, ], }).compileComponents(); diff --git a/frontend/src/app/features/properties/properties.component.ts b/frontend/src/app/features/properties/properties.component.ts index bace0fa3..002511c1 100644 --- a/frontend/src/app/features/properties/properties.component.ts +++ b/frontend/src/app/features/properties/properties.component.ts @@ -1,15 +1,16 @@ -import { Component, inject, OnInit, effect } from '@angular/core'; +import { Component, inject, OnInit, effect, signal } from '@angular/core'; import { CommonModule } from '@angular/common'; import { Router, RouterLink } from '@angular/router'; import { MatCardModule } from '@angular/material/card'; import { MatIconModule } from '@angular/material/icon'; import { MatButtonModule } from '@angular/material/button'; import { PropertyStore } from './stores/property.store'; -import { YearSelectorService } from '../../core/services/year-selector.service'; import { PropertyRowComponent } from '../../shared/components/property-row/property-row.component'; import { EmptyStateComponent } from '../../shared/components/empty-state/empty-state.component'; import { ErrorCardComponent } from '../../shared/components/error-card/error-card.component'; import { LoadingSpinnerComponent } from '../../shared/components/loading-spinner/loading-spinner.component'; +import { DateRangeFilterComponent } from '../../shared/components/date-range-filter/date-range-filter.component'; +import { DateRangePreset, getDateRangeFromPreset } from '../../shared/utils/date-range.utils'; /** * Properties list component (AC-2.1.1) @@ -28,6 +29,7 @@ import { LoadingSpinnerComponent } from '../../shared/components/loading-spinner EmptyStateComponent, ErrorCardComponent, LoadingSpinnerComponent, + DateRangeFilterComponent, ], template: `
@@ -39,6 +41,17 @@ import { LoadingSpinnerComponent } from '../../shared/components/loading-spinner + + + + + @if (propertyStore.isLoading()) { @@ -113,6 +126,11 @@ import { LoadingSpinnerComponent } from '../../shared/components/loading-spinner } } + .filters-card { + margin-bottom: 24px; + padding: 16px; + } + .properties-content { display: flex; justify-content: center; @@ -155,22 +173,42 @@ import { LoadingSpinnerComponent } from '../../shared/components/loading-spinner export class PropertiesComponent implements OnInit { private readonly router = inject(Router); readonly propertyStore = inject(PropertyStore); - readonly yearService = inject(YearSelectorService); + + readonly dateRangePreset = signal('this-year'); + readonly dateFrom = signal(null); + readonly dateTo = signal(null); constructor() { - // React to year changes and reload properties (AC-3.5.8) + const initial = getDateRangeFromPreset('this-year'); + this.dateFrom.set(initial.dateFrom); + this.dateTo.set(initial.dateTo); + effect(() => { - const year = this.yearService.selectedYear(); - this.propertyStore.loadProperties(year); + const from = this.dateFrom(); + const to = this.dateTo(); + this.propertyStore.loadProperties({ dateFrom: from ?? undefined, dateTo: to ?? undefined }); }); } ngOnInit(): void { - // Initial load happens via effect when selectedYear signal is read + // Initial load happens via effect when dateFrom/dateTo signals are read } loadProperties(): void { - this.propertyStore.loadProperties(this.yearService.selectedYear()); + this.propertyStore.loadProperties({ dateFrom: this.dateFrom() ?? undefined, dateTo: this.dateTo() ?? undefined }); + } + + onDateRangePresetChange(preset: DateRangePreset): void { + this.dateRangePreset.set(preset); + const { dateFrom, dateTo } = getDateRangeFromPreset(preset); + this.dateFrom.set(dateFrom); + this.dateTo.set(dateTo); + } + + onCustomDateRangeChange(range: { dateFrom: string; dateTo: string }): void { + this.dateRangePreset.set('custom'); + this.dateFrom.set(range.dateFrom); + this.dateTo.set(range.dateTo); } navigateToProperty(propertyId: string): void { diff --git a/frontend/src/app/features/properties/property-detail/property-detail.component.ts b/frontend/src/app/features/properties/property-detail/property-detail.component.ts index 57d3e5fb..982e38a8 100644 --- a/frontend/src/app/features/properties/property-detail/property-detail.component.ts +++ b/frontend/src/app/features/properties/property-detail/property-detail.component.ts @@ -12,7 +12,8 @@ import { BreakpointObserver } from '@angular/cdk/layout'; import { Subject, takeUntil } from 'rxjs'; import { PropertyStore } from '../stores/property.store'; import { PropertyPhotoStore } from '../stores/property-photo.store'; -import { YearSelectorService } from '../../../core/services/year-selector.service'; +import { DateRangeFilterComponent } from '../../../shared/components/date-range-filter/date-range-filter.component'; +import { DateRangePreset, getDateRangeFromPreset } from '../../../shared/utils/date-range.utils'; import { PropertyPhotoGalleryComponent, PropertyPhoto } from '../components/property-photo-gallery/property-photo-gallery.component'; import { PropertyPhotoUploadComponent } from '../components/property-photo-upload/property-photo-upload.component'; import { PropertyWorkOrdersComponent } from '../components/property-work-orders/property-work-orders.component'; @@ -60,6 +61,7 @@ import { PropertyPhotoUploadComponent, PropertyWorkOrdersComponent, PropertyIncomeComponent, + DateRangeFilterComponent, ], template: `
@@ -173,6 +175,17 @@ import {
+ + + + +
@@ -181,7 +194,7 @@ import { trending_down
- YTD Expenses + {{ expenseLabel() }} {{ propertyStore.selectedProperty()!.expenseTotal | currency }}
@@ -193,7 +206,7 @@ import { trending_up
- YTD Income + {{ incomeLabel() }} {{ propertyStore.selectedProperty()!.incomeTotal | currency }}
@@ -392,6 +405,11 @@ import { } } + .filters-card { + margin-bottom: 24px; + padding: 16px; + } + .stats-section { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); @@ -644,7 +662,6 @@ export class PropertyDetailComponent implements OnInit, OnDestroy { private readonly breakpointObserver = inject(BreakpointObserver); readonly propertyStore = inject(PropertyStore); readonly photoStore = inject(PropertyPhotoStore); - readonly yearService = inject(YearSelectorService); private propertyId: string | null = null; private readonly destroy$ = new Subject(); @@ -655,6 +672,15 @@ export class PropertyDetailComponent implements OnInit, OnDestroy { /** Show upload dialog overlay */ showUploadDialog = false; + /** Date range filter state */ + readonly dateRangePreset = signal('this-year'); + readonly dateFrom = signal(null); + readonly dateTo = signal(null); + + /** Dynamic stat labels based on preset */ + readonly expenseLabel = computed(() => this.dateRangePreset() === 'this-year' ? 'YTD Expenses' : 'Expenses'); + readonly incomeLabel = computed(() => this.dateRangePreset() === 'this-year' ? 'YTD Income' : 'Income'); + /** * Convert store photos to gallery-compatible format */ @@ -672,11 +698,15 @@ export class PropertyDetailComponent implements OnInit, OnDestroy { ); constructor() { - // React to year changes and reload property detail (AC-3.5.6) + const initial = getDateRangeFromPreset('this-year'); + this.dateFrom.set(initial.dateFrom); + this.dateTo.set(initial.dateTo); + effect(() => { - const year = this.yearService.selectedYear(); + const from = this.dateFrom(); + const to = this.dateTo(); if (this.propertyId) { - this.propertyStore.loadPropertyById({ id: this.propertyId, year }); + this.propertyStore.loadPropertyById({ id: this.propertyId, dateFrom: from ?? undefined, dateTo: to ?? undefined }); } }); } @@ -684,7 +714,7 @@ export class PropertyDetailComponent implements OnInit, OnDestroy { ngOnInit(): void { // Get property ID from route and load (AC-2.3.1) this.propertyId = this.route.snapshot.paramMap.get('id'); - // Initial load happens via effect when selectedYear signal is read + // Initial load happens via effect when dateFrom/dateTo signals are read // Load photos for the property (AC-13.3b.2) if (this.propertyId) { @@ -712,6 +742,19 @@ export class PropertyDetailComponent implements OnInit, OnDestroy { this.router.navigate(['/properties']); } + onDateRangePresetChange(preset: DateRangePreset): void { + this.dateRangePreset.set(preset); + const { dateFrom, dateTo } = getDateRangeFromPreset(preset); + this.dateFrom.set(dateFrom); + this.dateTo.set(dateTo); + } + + onCustomDateRangeChange(range: { dateFrom: string; dateTo: string }): void { + this.dateRangePreset.set('custom'); + this.dateFrom.set(range.dateFrom); + this.dateTo.set(range.dateTo); + } + /** * Format net income with accounting format for negative values (AC-4.4.4) * Positive: $1,234.00 @@ -744,7 +787,7 @@ export class PropertyDetailComponent implements OnInit, OnDestroy { const dialogData: ReportDialogData = { propertyId: property.id, propertyName: property.name, - currentYear: this.yearService.selectedYear() + currentYear: new Date().getFullYear() }; this.dialog.open(ReportDialogComponent, { diff --git a/frontend/src/app/features/properties/services/property.service.spec.ts b/frontend/src/app/features/properties/services/property.service.spec.ts index 4105338c..12473d3e 100644 --- a/frontend/src/app/features/properties/services/property.service.spec.ts +++ b/frontend/src/app/features/properties/services/property.service.spec.ts @@ -113,7 +113,7 @@ describe('PropertyService', () => { totalCount: 1 }; - service.getProperties(2024).subscribe(response => { + service.getProperties({ year: 2024 }).subscribe(response => { expect(response.items).toHaveLength(1); }); @@ -172,7 +172,7 @@ describe('PropertyService', () => { }); it('should get a property by ID filtered by year', () => { - service.getPropertyById('prop-1', 2024).subscribe(response => { + service.getPropertyById('prop-1', { year: 2024 }).subscribe(response => { expect(response.id).toBe('prop-1'); expect(response.expenseTotal).toBe(5000); expect(response.incomeTotal).toBe(18000); diff --git a/frontend/src/app/features/properties/services/property.service.ts b/frontend/src/app/features/properties/services/property.service.ts index 7e496c9b..7eab723f 100644 --- a/frontend/src/app/features/properties/services/property.service.ts +++ b/frontend/src/app/features/properties/services/property.service.ts @@ -75,9 +75,14 @@ export class PropertyService { * @param year Optional tax year filter for expense/income totals * @returns Observable with properties list and total count */ - getProperties(year?: number): Observable { - const params = year ? { year: year.toString() } : undefined; - return this.http.get(this.baseUrl, { params }); + getProperties(params?: { year?: number; dateFrom?: string; dateTo?: string }): Observable { + const httpParams: Record = {}; + if (params?.year) httpParams['year'] = params.year.toString(); + if (params?.dateFrom) httpParams['dateFrom'] = params.dateFrom; + if (params?.dateTo) httpParams['dateTo'] = params.dateTo; + return this.http.get(this.baseUrl, { + params: Object.keys(httpParams).length > 0 ? httpParams : undefined, + }); } /** @@ -86,9 +91,14 @@ export class PropertyService { * @param year Optional tax year filter for expense/income totals * @returns Observable with property detail or 404 error */ - getPropertyById(id: string, year?: number): Observable { - const params = year ? { year: year.toString() } : undefined; - return this.http.get(`${this.baseUrl}/${id}`, { params }); + getPropertyById(id: string, params?: { year?: number; dateFrom?: string; dateTo?: string }): Observable { + const httpParams: Record = {}; + if (params?.year) httpParams['year'] = params.year.toString(); + if (params?.dateFrom) httpParams['dateFrom'] = params.dateFrom; + if (params?.dateTo) httpParams['dateTo'] = params.dateTo; + return this.http.get(`${this.baseUrl}/${id}`, { + params: Object.keys(httpParams).length > 0 ? httpParams : undefined, + }); } /** diff --git a/frontend/src/app/features/properties/stores/property.store.spec.ts b/frontend/src/app/features/properties/stores/property.store.spec.ts index 2304bde4..dd95db82 100644 --- a/frontend/src/app/features/properties/stores/property.store.spec.ts +++ b/frontend/src/app/features/properties/stores/property.store.spec.ts @@ -82,8 +82,12 @@ describe('PropertyStore', () => { expect(store.error()).toBeNull(); }); - it('should have null selected year initially', () => { - expect(store.selectedYear()).toBeNull(); + it('should have null dateFrom initially', () => { + expect(store.dateFrom()).toBeNull(); + }); + + it('should have null dateTo initially', () => { + expect(store.dateTo()).toBeNull(); }); }); @@ -168,18 +172,19 @@ describe('PropertyStore', () => { expect(propertyServiceSpy.getProperties).toHaveBeenCalledWith(undefined); }); - it('should call service with year parameter', async () => { - store.loadProperties(2024); + it('should call service with date range parameters', async () => { + store.loadProperties({ dateFrom: '2024-01-01', dateTo: '2024-12-31' }); await new Promise(resolve => setTimeout(resolve, 0)); - expect(propertyServiceSpy.getProperties).toHaveBeenCalledWith(2024); + expect(propertyServiceSpy.getProperties).toHaveBeenCalledWith({ dateFrom: '2024-01-01', dateTo: '2024-12-31' }); }); - it('should set selectedYear when loading with year', async () => { - store.loadProperties(2024); + it('should set dateFrom/dateTo when loading with date range', async () => { + store.loadProperties({ dateFrom: '2024-01-01', dateTo: '2024-12-31' }); await new Promise(resolve => setTimeout(resolve, 0)); - expect(store.selectedYear()).toBe(2024); + expect(store.dateFrom()).toBe('2024-01-01'); + expect(store.dateTo()).toBe('2024-12-31'); }); it('should handle error gracefully', async () => { @@ -237,7 +242,7 @@ describe('PropertyStore', () => { it('should reset store to initial state', async () => { propertyServiceSpy.getProperties.mockReturnValue(of(mockPropertiesResponse)); - store.loadProperties(2024); + store.loadProperties({ dateFrom: '2024-01-01', dateTo: '2024-12-31' }); await new Promise(resolve => setTimeout(resolve, 0)); expect(store.properties().length).toBeGreaterThan(0); @@ -247,20 +252,8 @@ describe('PropertyStore', () => { expect(store.properties()).toEqual([]); expect(store.isLoading()).toBe(false); expect(store.error()).toBeNull(); - expect(store.selectedYear()).toBeNull(); - }); - }); - - describe('setSelectedYear', () => { - it('should set selected year', () => { - store.setSelectedYear(2024); - expect(store.selectedYear()).toBe(2024); - }); - - it('should allow setting null year', () => { - store.setSelectedYear(2024); - store.setSelectedYear(null); - expect(store.selectedYear()).toBeNull(); + expect(store.dateFrom()).toBeNull(); + expect(store.dateTo()).toBeNull(); }); }); @@ -293,14 +286,14 @@ describe('PropertyStore', () => { store.loadPropertyById({ id: 'test-id' }); await new Promise(resolve => setTimeout(resolve, 0)); - expect(propertyServiceSpy.getPropertyById).toHaveBeenCalledWith('test-id', undefined); + expect(propertyServiceSpy.getPropertyById).toHaveBeenCalledWith('test-id', { dateFrom: undefined, dateTo: undefined }); }); - it('should call service with property ID and year', async () => { - store.loadPropertyById({ id: 'test-id', year: 2024 }); + it('should call service with property ID and date range', async () => { + store.loadPropertyById({ id: 'test-id', dateFrom: '2024-01-01', dateTo: '2024-12-31' }); await new Promise(resolve => setTimeout(resolve, 0)); - expect(propertyServiceSpy.getPropertyById).toHaveBeenCalledWith('test-id', 2024); + expect(propertyServiceSpy.getPropertyById).toHaveBeenCalledWith('test-id', { dateFrom: '2024-01-01', dateTo: '2024-12-31' }); }); it('should handle 404 error with specific message', async () => { diff --git a/frontend/src/app/features/properties/stores/property.store.ts b/frontend/src/app/features/properties/stores/property.store.ts index 38a7c5bf..78448c7c 100644 --- a/frontend/src/app/features/properties/stores/property.store.ts +++ b/frontend/src/app/features/properties/stores/property.store.ts @@ -24,7 +24,8 @@ interface PropertyState { properties: PropertySummaryDto[]; isLoading: boolean; error: string | null; - selectedYear: number | null; + dateFrom: string | null; + dateTo: string | null; // Property detail state (AC-2.3.2) selectedProperty: PropertyDetailDto | null; isLoadingDetail: boolean; @@ -44,7 +45,8 @@ const initialState: PropertyState = { properties: [], isLoading: false, error: null, - selectedYear: null, + dateFrom: null, + dateTo: null, // Property detail initial state selectedProperty: null, isLoadingDetail: false, @@ -137,7 +139,7 @@ export const PropertyStore = signalStore( * Load properties from API * @param year Optional tax year filter */ - loadProperties: rxMethod( + loadProperties: rxMethod<{ dateFrom?: string; dateTo?: string } | undefined>( pipe( tap(() => patchState(store, { @@ -145,13 +147,14 @@ export const PropertyStore = signalStore( error: null, }) ), - switchMap((year) => - propertyService.getProperties(year).pipe( + switchMap((params) => + propertyService.getProperties(params).pipe( tap((response) => patchState(store, { properties: response.items, isLoading: false, - selectedYear: year ?? null, + dateFrom: params?.dateFrom ?? null, + dateTo: params?.dateTo ?? null, }) ), catchError((error) => { @@ -181,18 +184,11 @@ export const PropertyStore = signalStore( patchState(store, initialState); }, - /** - * Set selected year filter - */ - setSelectedYear(year: number | null): void { - patchState(store, { selectedYear: year }); - }, - /** * Load a single property by ID (AC-2.3.2, AC-2.3.5, AC-3.5.6) - * @param params Object containing id and optional year filter + * @param params Object containing id and optional date range filter */ - loadPropertyById: rxMethod<{ id: string; year?: number }>( + loadPropertyById: rxMethod<{ id: string; dateFrom?: string; dateTo?: string }>( pipe( tap(() => patchState(store, { @@ -201,8 +197,8 @@ export const PropertyStore = signalStore( selectedProperty: null, }) ), - switchMap(({ id, year }) => - propertyService.getPropertyById(id, year).pipe( + switchMap(({ id, dateFrom, dateTo }) => + propertyService.getPropertyById(id, { dateFrom, dateTo }).pipe( tap((property) => patchState(store, { selectedProperty: property, diff --git a/frontend/src/app/features/reports/components/batch-report-dialog/batch-report-dialog.component.spec.ts b/frontend/src/app/features/reports/components/batch-report-dialog/batch-report-dialog.component.spec.ts index 5ddfcc16..b504bfdb 100644 --- a/frontend/src/app/features/reports/components/batch-report-dialog/batch-report-dialog.component.spec.ts +++ b/frontend/src/app/features/reports/components/batch-report-dialog/batch-report-dialog.component.spec.ts @@ -6,7 +6,7 @@ import { signal } from '@angular/core'; import { BatchReportDialogComponent } from './batch-report-dialog.component'; import { ReportService } from '../../services/report.service'; import { PropertyStore } from '../../../properties/stores/property.store'; -import { YearSelectorService } from '../../../../core/services/year-selector.service'; + describe('BatchReportDialogComponent', () => { let component: BatchReportDialogComponent; @@ -27,11 +27,6 @@ describe('BatchReportDialogComponent', () => { loadProperties: vi.fn(), }; - const mockYearService = { - selectedYear: signal(2024), - availableYears: signal([2024, 2023, 2022]), - }; - beforeEach(async () => { mockReportService = { generateBatchScheduleE: vi.fn(), @@ -51,7 +46,7 @@ describe('BatchReportDialogComponent', () => { providers: [ { provide: ReportService, useValue: mockReportService }, { provide: PropertyStore, useValue: mockPropertyStore }, - { provide: YearSelectorService, useValue: mockYearService }, + { provide: MatDialogRef, useValue: mockDialogRef }, { provide: MatSnackBar, useValue: mockSnackBar }, ], @@ -124,7 +119,7 @@ describe('BatchReportDialogComponent', () => { expect(mockReportService.generateBatchScheduleE).toHaveBeenCalledWith( ['prop-1', 'prop-2', 'prop-3'], - 2024 + new Date().getFullYear() ); }); @@ -134,7 +129,7 @@ describe('BatchReportDialogComponent', () => { await component.generate(); - expect(mockReportService.downloadZip).toHaveBeenCalledWith(mockBlob, 2024); + expect(mockReportService.downloadZip).toHaveBeenCalledWith(mockBlob, new Date().getFullYear()); }); it('should show snackbar on success', async () => { @@ -200,7 +195,7 @@ describe('BatchReportDialogComponent', () => { expect(mockReportService.generateBatchScheduleE).toHaveBeenCalledWith( ['prop-1', 'prop-3'], - 2024 + new Date().getFullYear() ); }); }); @@ -213,13 +208,4 @@ describe('BatchReportDialogComponent', () => { }); }); - describe('year selector', () => { - it('should have 10 year options', () => { - expect(component.availableYears.length).toBe(10); - }); - - it('should default to the current year from service', () => { - expect(component.selectedYear).toBe(2024); - }); - }); }); diff --git a/frontend/src/app/features/reports/components/batch-report-dialog/batch-report-dialog.component.ts b/frontend/src/app/features/reports/components/batch-report-dialog/batch-report-dialog.component.ts index 45366706..c4d78aec 100644 --- a/frontend/src/app/features/reports/components/batch-report-dialog/batch-report-dialog.component.ts +++ b/frontend/src/app/features/reports/components/batch-report-dialog/batch-report-dialog.component.ts @@ -10,7 +10,6 @@ import { MatTooltipModule } from '@angular/material/tooltip'; import { MatSnackBar } from '@angular/material/snack-bar'; import { ReportService } from '../../services/report.service'; import { PropertyStore } from '../../../properties/stores/property.store'; -import { YearSelectorService } from '../../../../core/services/year-selector.service'; /** * Property selection item for the batch dialog. @@ -263,11 +262,10 @@ interface PropertySelection { export class BatchReportDialogComponent implements OnInit { private readonly reportService = inject(ReportService); private readonly propertyStore = inject(PropertyStore); - private readonly yearService = inject(YearSelectorService); private readonly snackBar = inject(MatSnackBar); private readonly dialogRef = inject(MatDialogRef); - selectedYear = this.yearService.selectedYear(); + selectedYear = new Date().getFullYear(); availableYears = this.generateYearOptions(); readonly properties = signal([]); @@ -299,7 +297,7 @@ export class BatchReportDialogComponent implements OnInit { ngOnInit(): void { // Ensure properties are loaded - trigger load if store is empty if (this.propertyStore.properties().length === 0 && !this.propertyStore.isLoading()) { - this.propertyStore.loadProperties(this.selectedYear); + this.propertyStore.loadProperties(undefined); } // Also initialize immediately if properties already exist if (this.propertyStore.properties().length > 0) { diff --git a/frontend/src/app/shared/components/date-range-filter/date-range-filter.component.ts b/frontend/src/app/shared/components/date-range-filter/date-range-filter.component.ts index a7dd9fd7..da93086a 100644 --- a/frontend/src/app/shared/components/date-range-filter/date-range-filter.component.ts +++ b/frontend/src/app/shared/components/date-range-filter/date-range-filter.component.ts @@ -38,6 +38,7 @@ import { formatLocalDate } from '../../utils/date.utils'; This Month This Quarter This Year + Last Year Custom Range diff --git a/frontend/src/app/shared/components/year-selector/year-selector.component.spec.ts b/frontend/src/app/shared/components/year-selector/year-selector.component.spec.ts deleted file mode 100644 index 1234eb9c..00000000 --- a/frontend/src/app/shared/components/year-selector/year-selector.component.spec.ts +++ /dev/null @@ -1,189 +0,0 @@ -import { TestBed, ComponentFixture } from '@angular/core/testing'; -import { describe, it, expect, beforeEach, vi } from 'vitest'; -import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { signal } from '@angular/core'; -import { By } from '@angular/platform-browser'; -import { YearSelectorComponent } from './year-selector.component'; -import { YearSelectorService } from '../../../core/services/year-selector.service'; - -describe('YearSelectorComponent', () => { - let component: YearSelectorComponent; - let fixture: ComponentFixture; - let mockYearService: { - selectedYear: ReturnType>; - availableYears: ReturnType>; - setYear: ReturnType; - }; - - const currentYear = new Date().getFullYear(); - const availableYears = [ - currentYear, - currentYear - 1, - currentYear - 2, - currentYear - 3, - currentYear - 4, - currentYear - 5, - ]; - - beforeEach(async () => { - mockYearService = { - selectedYear: signal(currentYear), - availableYears: signal(availableYears), - setYear: vi.fn(), - }; - - await TestBed.configureTestingModule({ - imports: [YearSelectorComponent, NoopAnimationsModule], - providers: [{ provide: YearSelectorService, useValue: mockYearService }], - }).compileComponents(); - - fixture = TestBed.createComponent(YearSelectorComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); - - it('should inject YearSelectorService', () => { - expect(component.yearService).toBeTruthy(); - }); - - it('should have year-selector-wrapper container', () => { - const wrapper = fixture.debugElement.query(By.css('.year-selector-wrapper')); - expect(wrapper).toBeTruthy(); - }); - - it('should display calendar icon (AC-3.5.1)', () => { - const icon = fixture.debugElement.query(By.css('.calendar-icon')); - expect(icon).toBeTruthy(); - expect(icon.nativeElement.textContent.trim()).toBe('calendar_today'); - }); - - it('should render mat-select element', () => { - const select = fixture.debugElement.query(By.css('mat-select')); - expect(select).toBeTruthy(); - }); - - it('should have aria-label for accessibility', () => { - const select = fixture.debugElement.query(By.css('mat-select')); - expect(select.attributes['aria-label']).toBe('Select tax year'); - }); - - it('should have data-testid attribute', () => { - const select = fixture.debugElement.query(By.css('[data-testid="year-selector"]')); - expect(select).toBeTruthy(); - }); - - it('should display current year from service', () => { - expect(component.yearService.selectedYear()).toBe(currentYear); - }); - - it('should call setYear when year is changed (AC-3.5.5)', () => { - const newYear = currentYear - 1; - component.onYearChange(newYear); - - expect(mockYearService.setYear).toHaveBeenCalledWith(newYear); - }); - - it('should call setYear with correct year value', () => { - const targetYear = currentYear - 2; - component.onYearChange(targetYear); - - expect(mockYearService.setYear).toHaveBeenCalledWith(targetYear); - }); -}); - -describe('YearSelectorComponent with different selected year', () => { - let fixture: ComponentFixture; - let mockYearService: { - selectedYear: ReturnType>; - availableYears: ReturnType>; - setYear: ReturnType; - }; - - const currentYear = new Date().getFullYear(); - const selectedYear = currentYear - 2; - - beforeEach(async () => { - mockYearService = { - selectedYear: signal(selectedYear), - availableYears: signal([ - currentYear, - currentYear - 1, - currentYear - 2, - currentYear - 3, - currentYear - 4, - currentYear - 5, - ]), - setYear: vi.fn(), - }; - - await TestBed.configureTestingModule({ - imports: [YearSelectorComponent, NoopAnimationsModule], - providers: [{ provide: YearSelectorService, useValue: mockYearService }], - }).compileComponents(); - - fixture = TestBed.createComponent(YearSelectorComponent); - fixture.detectChanges(); - }); - - it('should display selected year from service', () => { - expect(fixture.componentInstance.yearService.selectedYear()).toBe(selectedYear); - }); -}); - -describe('YearSelectorComponent available years (AC-3.5.1)', () => { - let component: YearSelectorComponent; - let fixture: ComponentFixture; - let mockYearService: { - selectedYear: ReturnType>; - availableYears: ReturnType>; - setYear: ReturnType; - }; - - const currentYear = new Date().getFullYear(); - const availableYears = [ - currentYear, - currentYear - 1, - currentYear - 2, - currentYear - 3, - currentYear - 4, - currentYear - 5, - ]; - - beforeEach(async () => { - mockYearService = { - selectedYear: signal(currentYear), - availableYears: signal(availableYears), - setYear: vi.fn(), - }; - - await TestBed.configureTestingModule({ - imports: [YearSelectorComponent, NoopAnimationsModule], - providers: [{ provide: YearSelectorService, useValue: mockYearService }], - }).compileComponents(); - - fixture = TestBed.createComponent(YearSelectorComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should have 6 available years from service', () => { - expect(component.yearService.availableYears().length).toBe(6); - }); - - it('should include current year in available years', () => { - expect(component.yearService.availableYears()).toContain(currentYear); - }); - - it('should include 5 previous years', () => { - const years = component.yearService.availableYears(); - expect(years).toContain(currentYear - 1); - expect(years).toContain(currentYear - 2); - expect(years).toContain(currentYear - 3); - expect(years).toContain(currentYear - 4); - expect(years).toContain(currentYear - 5); - }); -}); diff --git a/frontend/src/app/shared/components/year-selector/year-selector.component.ts b/frontend/src/app/shared/components/year-selector/year-selector.component.ts deleted file mode 100644 index d4705e3f..00000000 --- a/frontend/src/app/shared/components/year-selector/year-selector.component.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { Component, inject } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { FormsModule } from '@angular/forms'; -import { MatSelectModule } from '@angular/material/select'; -import { MatFormFieldModule } from '@angular/material/form-field'; -import { MatIconModule } from '@angular/material/icon'; - -import { YearSelectorService } from '../../../core/services/year-selector.service'; - -/** - * Year Selector Component (AC-3.5.1) - * - * Compact dropdown for selecting tax year. - * Designed for placement in toolbar/header areas. - * Binds to YearSelectorService for global state. - */ -@Component({ - selector: 'app-year-selector', - standalone: true, - imports: [ - CommonModule, - FormsModule, - MatSelectModule, - MatFormFieldModule, - MatIconModule, - ], - template: ` -
- calendar_today - - - @for (year of yearService.availableYears(); track year) { - - {{ year }} - - } - - -
- `, - styles: [` - .year-selector-wrapper { - display: flex; - align-items: center; - gap: 12px; - } - - .calendar-icon { - color: rgba(255, 255, 255, 0.8); - font-size: 24px; - width: 24px; - height: 24px; - } - - .year-selector { - width: 90px; - - ::ng-deep { - .mat-mdc-form-field-subscript-wrapper { - display: none; - } - - .mat-mdc-text-field-wrapper { - padding: 0 8px; - background: rgba(255, 255, 255, 0.1); - border-radius: 8px; - } - - .mat-mdc-form-field-flex { - height: 36px; - align-items: center; - } - - .mat-mdc-select-value { - font-size: 14px; - font-weight: 500; - color: white !important; - } - - .mat-mdc-select-value-text { - color: white !important; - } - - .mdc-notched-outline__leading, - .mdc-notched-outline__notch, - .mdc-notched-outline__trailing { - border-color: rgba(255, 255, 255, 0.3) !important; - } - - .mat-mdc-select-arrow { - color: rgba(255, 255, 255, 0.7); - } - } - } - - :host-context(.light-theme) { - .calendar-icon { - color: rgba(0, 0, 0, 0.54); - } - - .year-selector { - ::ng-deep { - .mat-mdc-text-field-wrapper { - background: rgba(0, 0, 0, 0.05); - } - - .mat-mdc-select-value, - .mat-mdc-select-value-text { - color: rgba(0, 0, 0, 0.87) !important; - } - - .mdc-notched-outline__leading, - .mdc-notched-outline__notch, - .mdc-notched-outline__trailing { - border-color: rgba(0, 0, 0, 0.2) !important; - } - - .mat-mdc-select-arrow { - color: rgba(0, 0, 0, 0.54); - } - } - } - } - `] -}) -export class YearSelectorComponent { - readonly yearService = inject(YearSelectorService); - - onYearChange(year: number): void { - this.yearService.setYear(year); - } -} diff --git a/frontend/src/app/shared/utils/date-range.utils.ts b/frontend/src/app/shared/utils/date-range.utils.ts index 5187c3a1..84697e94 100644 --- a/frontend/src/app/shared/utils/date-range.utils.ts +++ b/frontend/src/app/shared/utils/date-range.utils.ts @@ -3,7 +3,7 @@ import { formatLocalDate } from './date.utils'; /** * Date range preset options for filtering (AC-3.4.3) */ -export type DateRangePreset = 'this-month' | 'this-quarter' | 'this-year' | 'custom' | 'all'; +export type DateRangePreset = 'this-month' | 'this-quarter' | 'this-year' | 'last-year' | 'custom' | 'all'; /** * Calculate date range from preset. @@ -38,6 +38,13 @@ export function getDateRangeFromPreset( dateTo: `${currentYear}-12-31`, }; } + case 'last-year': { + const lastYear = today.getFullYear() - 1; + return { + dateFrom: `${lastYear}-01-01`, + dateTo: `${lastYear}-12-31`, + }; + } case 'all': case 'custom': default: From 85939d377df2f90a46faad610d80bd43888b6302 Mon Sep 17 00:00:00 2001 From: daveharms Date: Sat, 7 Mar 2026 09:02:05 -0600 Subject: [PATCH 2/4] chore: update sprint artifacts and add story 17.12 spec Co-Authored-By: Claude Opus 4.6 --- .../17-10-inline-status-dropdown-wo-detail.md | 3 +- .../17-11-wo-list-primary-photo-thumbnail.md | 2 +- .../17-12-replace-year-selector-date-range.md | 424 ++++++++++++++++++ .../sprint-status.yaml | 4 +- story-17-10-status-dropdown.png | Bin 0 -> 139757 bytes 5 files changed, 429 insertions(+), 4 deletions(-) create mode 100644 _bmad-output/implementation-artifacts/17-12-replace-year-selector-date-range.md create mode 100644 story-17-10-status-dropdown.png diff --git a/_bmad-output/implementation-artifacts/17-10-inline-status-dropdown-wo-detail.md b/_bmad-output/implementation-artifacts/17-10-inline-status-dropdown-wo-detail.md index 39aa8f89..9af7c0d8 100644 --- a/_bmad-output/implementation-artifacts/17-10-inline-status-dropdown-wo-detail.md +++ b/_bmad-output/implementation-artifacts/17-10-inline-status-dropdown-wo-detail.md @@ -1,6 +1,6 @@ # Story 17.10: Inline Status Dropdown on Work Order Detail -Status: review +Status: done ## Story @@ -282,3 +282,4 @@ Claude Opus 4.6 ## Change Log - 2026-03-04: All 6 tasks completed, all 2618 unit tests passing, Playwright visual verification done, story set to review +- 2026-03-05: Code review fixes merged (rollback bug, null guards, exhaustMap, test coverage), 2619 tests passing, story done diff --git a/_bmad-output/implementation-artifacts/17-11-wo-list-primary-photo-thumbnail.md b/_bmad-output/implementation-artifacts/17-11-wo-list-primary-photo-thumbnail.md index 985efb0d..73e8a09b 100644 --- a/_bmad-output/implementation-artifacts/17-11-wo-list-primary-photo-thumbnail.md +++ b/_bmad-output/implementation-artifacts/17-11-wo-list-primary-photo-thumbnail.md @@ -1,6 +1,6 @@ # Story 17.11: Work Order List — Primary Photo Thumbnail -Status: review +Status: done ## Story diff --git a/_bmad-output/implementation-artifacts/17-12-replace-year-selector-date-range.md b/_bmad-output/implementation-artifacts/17-12-replace-year-selector-date-range.md new file mode 100644 index 00000000..191a203b --- /dev/null +++ b/_bmad-output/implementation-artifacts/17-12-replace-year-selector-date-range.md @@ -0,0 +1,424 @@ +# Story 17.12: Replace Global Year Selector with Dashboard Date Range Filter + +Status: ready-for-dev + +## Story + +As a property owner viewing financial summaries, +I want date range filters on the dashboard and property detail pages instead of a global year selector, +so that I can see financial data for any time period and filters don't silently conflict with each other. + +**GitHub Issue:** #279 +**Effort:** L — multi-page refactor, backend dateFrom/dateTo params, frontend component integration, service + component removal + +## Acceptance Criteria + +**AC-1: Year selector removed from sidebar** +Given I view the application sidebar +When the sidebar renders +Then the year selector is no longer present + +**AC-2: Dashboard date range filter** +Given I am on the Dashboard +When I view the page +Then I see an inline `DateRangeFilterComponent` with presets: All Time, This Month, This Quarter, This Year (default), Last Year, Custom +And the summary totals (Total Expenses, Total Income, Net Income) respect the selected range + +**AC-3: Property detail date range filter** +Given I am on a Property detail page +When I view the financial summary cards +Then a local `DateRangeFilterComponent` controls Expenses, Income, Net Income totals +And labels update based on range (e.g., "YTD Expenses" for this-year, "Expenses" for custom/other ranges) + +**AC-4: Properties list date range filter** +Given I am on the Properties list +When I view per-property financial summaries +Then they respect a local date range filter (default: This Year) + +**AC-5: Income list decoupled from global year** +Given I am on the Income list with its own date range filter +When I select a date range +Then only the local date range filter applies — no global year interference +And the `yearEffect` watching `YearSelectorService` is removed + +**AC-6: Report dialogs unaffected** +Given I open a Schedule E report dialog (single or batch) +When the dialog loads +Then it still has its own year selector and works independently (no regression) +And it defaults to the current calendar year (previously from YearSelectorService) + +**AC-7: Cleanup** +Given all consumers are migrated +When migration is complete +Then `YearSelectorService`, `YearSelectorComponent`, and localStorage key `propertyManager.selectedYear` are removed +And no references remain anywhere in the codebase + +## Tasks / Subtasks + +### Task 1: Add "Last Year" preset to DateRangeFilterComponent (AC: 2) + +- [ ] 1.1: Add `'last-year'` to the `DateRangePreset` union type in `frontend/src/app/shared/utils/date-range.utils.ts` +- [ ] 1.2: Add `case 'last-year'` to `getDateRangeFromPreset()` — return Jan 1 to Dec 31 of `currentYear - 1` +- [ ] 1.3: Add `Last Year` to `DateRangeFilterComponent` template (between "This Year" and "Custom Range") +- [ ] 1.4: Add unit tests for the new preset in `date-range.utils.spec.ts` (if exists) and `date-range-filter.component.spec.ts` + +### Task 2: Backend — Add dateFrom/dateTo to property and dashboard endpoints (AC: 2, 3, 4) + +- [ ] 2.1: Update `GetAllPropertiesQuery` record to add `DateOnly? DateFrom = null, DateOnly? DateTo = null` parameters +- [ ] 2.2: Update `GetAllPropertiesQueryHandler.Handle()` — when `DateFrom`/`DateTo` are provided, use them for date range filtering instead of `year`. Keep existing `Year` fallback for backward compat +- [ ] 2.3: Update `GetPropertyByIdQuery` record to add `DateOnly? DateFrom = null, DateOnly? DateTo = null` parameters +- [ ] 2.4: Update `GetPropertyByIdQueryHandler.Handle()` — when `DateFrom`/`DateTo` are provided, use `request.DateFrom`/`request.DateTo` instead of computing `yearStart`/`yearEnd` from year +- [ ] 2.5: Update `GetDashboardTotalsQuery` record to add `DateOnly? DateFrom = null, DateOnly? DateTo = null` parameters (keep `Year` for backward compat) +- [ ] 2.6: Update `GetDashboardTotalsQueryHandler.Handle()` — when `DateFrom`/`DateTo` are provided, use them; otherwise fall back to existing `Year`-based filtering +- [ ] 2.7: Update `PropertiesController.GetAllProperties()` — add `[FromQuery] DateOnly? dateFrom = null, [FromQuery] DateOnly? dateTo = null` params, pass to query +- [ ] 2.8: Update `PropertiesController.GetPropertyById()` (read the method — it has a `[FromQuery] int? year` param) — add `dateFrom`/`dateTo` params, pass to query +- [ ] 2.9: Update `DashboardController.GetTotals()` — add `dateFrom`/`dateTo` params, pass to query. Change `year` from required to optional (default to current year when nothing is provided) +- [ ] 2.10: Add backend unit tests for dateFrom/dateTo filtering in all three handlers +- [ ] 2.11: Regenerate NSwag API client: run `npm run generate-api` from `/frontend` + +### Task 3: Frontend — Update PropertyService and PropertyStore for date range (AC: 2, 3, 4) + +- [ ] 3.1: Update `PropertyService.getProperties()` signature to accept `params?: { year?: number; dateFrom?: string; dateTo?: string }` instead of `year?: number`. Build query params from the object +- [ ] 3.2: Update `PropertyService.getPropertyById()` signature to accept `id: string, params?: { year?: number; dateFrom?: string; dateTo?: string }` instead of `id: string, year?: number` +- [ ] 3.3: Update `PropertyStore.loadProperties` rxMethod — change input type from `number | undefined` to `{ dateFrom?: string; dateTo?: string } | undefined`. Update `switchMap` to call `propertyService.getProperties(params)` +- [ ] 3.4: Update `PropertyStore.loadPropertyById` rxMethod — change input type from `{ id: string; year?: number }` to `{ id: string; dateFrom?: string; dateTo?: string }`. Update `switchMap` accordingly +- [ ] 3.5: Replace `selectedYear: number | null` in `PropertyState` with `dateFrom: string | null` and `dateTo: string | null`. Update `initialState` accordingly +- [ ] 3.6: Update property store unit tests for new parameter shapes + +### Task 4: Dashboard — Add DateRangeFilterComponent, remove year dependency (AC: 2) + +- [ ] 4.1: Import `DateRangeFilterComponent` in DashboardComponent imports array +- [ ] 4.2: Add date range state signals: `dateRangePreset = signal('this-year')`, `dateFrom = signal(null)`, `dateTo = signal(null)`. Compute initial values from `getDateRangeFromPreset('this-year')` +- [ ] 4.3: Add `` to dashboard template — place between the header and the ``, inside a `` wrapper for visual consistency with other pages +- [ ] 4.4: Add `onDateRangePresetChange()` and `onCustomDateRangeChange()` handler methods — update signals, recalculate dateFrom/dateTo via `getDateRangeFromPreset()`, call `loadProperties()` +- [ ] 4.5: Replace the existing `effect()` (lines 192-195) that watches `yearService.selectedYear()` with a new `effect()` that watches `dateFrom()`/`dateTo()` and calls `propertyStore.loadProperties({ dateFrom, dateTo })` +- [ ] 4.6: Remove `YearSelectorService` import and injection +- [ ] 4.7: Update `loadProperties()` method to use date range signals instead of `yearService.selectedYear()` +- [ ] 4.8: Import `getDateRangeFromPreset`, `DateRangePreset` from shared utils +- [ ] 4.9: Add dashboard component unit tests for date range filter integration + +### Task 5: Properties list — Add DateRangeFilterComponent (AC: 4) + +- [ ] 5.1: Import `DateRangeFilterComponent` and `MatCardModule` in PropertiesComponent imports +- [ ] 5.2: Add date range state signals (same pattern as Task 4.2, default `'this-year'`) +- [ ] 5.3: Add `` to properties template — place below the page header, inside a `` +- [ ] 5.4: Add `onDateRangePresetChange()` and `onCustomDateRangeChange()` handler methods +- [ ] 5.5: Replace the existing `effect()` (lines 162-165) that watches `yearService.selectedYear()` with date range effect +- [ ] 5.6: Remove `YearSelectorService` import and injection +- [ ] 5.7: Update `loadProperties()` method to use date range signals +- [ ] 5.8: Add properties component unit tests for date range filter integration + +### Task 6: Property detail — Add DateRangeFilterComponent (AC: 3) + +- [ ] 6.1: Import `DateRangeFilterComponent` and `MatCardModule` in PropertyDetailComponent imports +- [ ] 6.2: Add date range state signals (same pattern, default `'this-year'`) +- [ ] 6.3: Add `` to template — place above the `.stats-section` (before line 177), inside a compact filter card +- [ ] 6.4: Add handler methods for preset and custom date range changes +- [ ] 6.5: Replace the existing `effect()` (lines 676-681) that watches `yearService.selectedYear()` with date range effect that calls `propertyStore.loadPropertyById({ id: this.propertyId, dateFrom, dateTo })` +- [ ] 6.6: Update stat card labels to be dynamic: use `'YTD Expenses'` when preset is `'this-year'`, otherwise `'Expenses'` (same for Income). Use a computed signal: `expenseLabel = computed(() => this.dateRangePreset() === 'this-year' ? 'YTD Expenses' : 'Expenses')` +- [ ] 6.7: Remove `YearSelectorService` import and injection +- [ ] 6.8: Update the `ReportDialogComponent` data passing (if property-detail passes `currentYear: this.yearService.selectedYear()` to it) — replace with `currentYear: new Date().getFullYear()` +- [ ] 6.9: Add property detail unit tests for date range filter integration and dynamic labels + +### Task 7: Income — Decouple from global year selector (AC: 5) + +- [ ] 7.1: Remove the `yearEffect` field (lines 496-499) from `IncomeComponent` +- [ ] 7.2: Remove `YearSelectorService` import and injection (line 28, 481) +- [ ] 7.3: Remove `year` state from `IncomeListStore` — remove `year: number | null` from state interface, remove from initialState, remove `setYear()` method +- [ ] 7.4: Update `IncomeListStore.currentFilters` computed — remove `year: store.year() ?? undefined` from the returned object. When preset is not custom, `getDateRangeFromPreset()` no longer needs a year param since the function uses `today.getFullYear()` by default +- [ ] 7.5: Update `IncomeListStore.setDateRangePreset()` — remove `store.year()` from `getDateRangeFromPreset()` call (just pass preset) +- [ ] 7.6: Update income component and store unit tests — remove YearSelectorService mocking, remove year-related test cases + +### Task 8: Report dialogs — Decouple from YearSelectorService (AC: 6) + +- [ ] 8.1: In `BatchReportDialogComponent` — change `selectedYear = this.yearService.selectedYear()` to `selectedYear = new Date().getFullYear()`. Remove `YearSelectorService` import and injection +- [ ] 8.2: Verify batch report dialog still has its own year dropdown with `generateYearOptions()` — no changes needed to that +- [ ] 8.3: Check `ReportDialogComponent` (single property report) — if it injects `YearSelectorService`, decouple it the same way. If it receives year via dialog data from property-detail, Task 6.8 already handles it +- [ ] 8.4: Update report dialog unit tests — remove YearSelectorService mocking + +### Task 9: Cleanup — Remove YearSelectorService and YearSelectorComponent (AC: 1, 7) + +- [ ] 9.1: Remove `` from `sidebar-nav.component.html` (lines 7-10, the `.year-selector-container` div) +- [ ] 9.2: Remove `` from `shell.component.html` tablet header (line 31) +- [ ] 9.3: Remove `` from `shell.component.html` mobile header (line 55) +- [ ] 9.4: Remove `YearSelectorComponent` from `SidebarNavComponent` imports array +- [ ] 9.5: Remove `YearSelectorComponent` from `ShellComponent` imports array and its import statement +- [ ] 9.6: Delete file: `frontend/src/app/core/services/year-selector.service.ts` +- [ ] 9.7: Delete file: `frontend/src/app/core/services/year-selector.service.spec.ts` +- [ ] 9.8: Delete file: `frontend/src/app/shared/components/year-selector/year-selector.component.ts` +- [ ] 9.9: Delete file: `frontend/src/app/shared/components/year-selector/year-selector.component.spec.ts` +- [ ] 9.10: Verify no remaining references — search codebase for `YearSelectorService`, `YearSelectorComponent`, `year-selector`, `propertyManager.selectedYear` +- [ ] 9.11: Remove any sidebar SCSS for `.year-selector-container` if present in sidebar component styles + +### Task 10: Final validation (AC: all) + +- [ ] 10.1: Run all frontend unit tests: `npm test` from `/frontend` — expect zero regressions +- [ ] 10.2: Run all backend unit tests: `dotnet test` from `/backend` +- [ ] 10.3: Manual smoke test: dashboard loads with "This Year" default, changing filter updates totals +- [ ] 10.4: Manual smoke test: properties list loads with date range filter, per-property totals update +- [ ] 10.5: Manual smoke test: property detail shows date range filter, stat cards update, labels change for non-YTD ranges +- [ ] 10.6: Manual smoke test: income list works without global year interference +- [ ] 10.7: Manual smoke test: batch report dialog opens, defaults to current year, generates correctly +- [ ] 10.8: Manual smoke test: sidebar has no year selector on desktop, tablet, and mobile + +## Dev Notes + +### Architecture Overview + +This story replaces a **global year selector** (sidebar widget → singleton service → effects in 4 components) with **local date range filters** per page. The existing `DateRangeFilterComponent` is already used by Expenses and Income lists — this story reuses it on Dashboard, Properties list, and Property detail. + +**Current data flow (REMOVE):** +``` +YearSelectorComponent (sidebar/toolbar) + → YearSelectorService (singleton, localStorage) + → effect() in DashboardComponent → propertyStore.loadProperties(year) + → effect() in PropertiesComponent → propertyStore.loadProperties(year) + → effect() in PropertyDetailComponent → propertyStore.loadPropertyById({id, year}) + → effect() in IncomeComponent → incomeStore.setYear(year) + → BatchReportDialogComponent → initializes selectedYear from service +``` + +**New data flow (ADD):** +``` +DateRangeFilterComponent (per page, local state) + → Page component state (dateRangePreset, dateFrom, dateTo signals) + → effect() → propertyStore.loadProperties({ dateFrom, dateTo }) + → effect() → propertyStore.loadPropertyById({ id, dateFrom, dateTo }) +``` + +### Existing DateRangeFilterComponent — Ready for Reuse + +**File:** `frontend/src/app/shared/components/date-range-filter/date-range-filter.component.ts` + +Standalone presentation component. No state — receives values via inputs, emits via outputs: +```typescript +// Inputs +dateRangePreset = input('all'); +dateFrom = input(null); +dateTo = input(null); + +// Outputs +dateRangePresetChange = output(); +customDateRangeChange = output<{ dateFrom: string; dateTo: string }>(); +``` + +**Current presets:** `'all' | 'this-month' | 'this-quarter' | 'this-year' | 'custom'` +**New preset to add:** `'last-year'` + +Utility function: `getDateRangeFromPreset(preset, year?)` in `shared/utils/date-range.utils.ts` — computes `{ dateFrom, dateTo }` strings from presets. + +### Backend Changes — dateFrom/dateTo Parameters + +All three backend handlers already use `DateOnly` internally for year-based filtering. Adding `dateFrom`/`dateTo` query params is straightforward. + +**Pattern (existing in GetPropertyByIdQueryHandler):** +```csharp +var yearStart = new DateOnly(year, 1, 1); +var yearEnd = new DateOnly(year, 12, 31); +``` + +**New pattern:** +```csharp +var dateStart = request.DateFrom ?? new DateOnly(year, 1, 1); +var dateEnd = request.DateTo ?? new DateOnly(year, 12, 31); +``` + +When `DateFrom`/`DateTo` are provided, use them directly. When absent, fall back to year (existing behavior). This ensures backward compatibility for any direct API consumers. + +**GetAllPropertiesQueryHandler** uses `e.Date.Year == year` instead of date range — change to `e.Date >= dateStart && e.Date <= dateEnd` for consistency with the other handlers. + +**DashboardController.GetTotals** currently requires `year` — make it optional with fallback to `DateTime.UtcNow.Year`. + +ASP.NET Core binds `DateOnly` from query strings natively (format: `YYYY-MM-DD`). No custom model binder needed. + +### Frontend PropertyService — Updated Signatures + +**Current:** +```typescript +getProperties(year?: number): Observable +getPropertyById(id: string, year?: number): Observable +``` + +**New:** +```typescript +getProperties(params?: { year?: number; dateFrom?: string; dateTo?: string }): Observable +getPropertyById(id: string, params?: { year?: number; dateFrom?: string; dateTo?: string }): Observable +``` + +Build HttpParams from the object — only include keys that have values. + +### Frontend PropertyStore — Updated rxMethod Signatures + +**Current:** +```typescript +loadProperties: rxMethod(...) +loadPropertyById: rxMethod<{ id: string; year?: number }>(...) +``` + +**New:** +```typescript +loadProperties: rxMethod<{ dateFrom?: string; dateTo?: string } | undefined>(...) +loadPropertyById: rxMethod<{ id: string; dateFrom?: string; dateTo?: string }>(...) +``` + +Replace `selectedYear: number | null` in PropertyState with `dateFrom: string | null; dateTo: string | null`. + +### Dashboard Integration Pattern + +Follow Income component's pattern for DateRangeFilterComponent integration. Key difference: dashboard defaults to `'this-year'` preset instead of `'all'`. + +```typescript +// State +dateRangePreset = signal('this-year'); +dateFrom = signal(null); +dateTo = signal(null); + +constructor() { + // Initialize date range from default preset + const initial = getDateRangeFromPreset('this-year'); + this.dateFrom.set(initial.dateFrom); + this.dateTo.set(initial.dateTo); + + // React to date range changes + effect(() => { + const from = this.dateFrom(); + const to = this.dateTo(); + this.propertyStore.loadProperties({ dateFrom: from ?? undefined, dateTo: to ?? undefined }); + }); +} + +onDateRangePresetChange(preset: DateRangePreset): void { + this.dateRangePreset.set(preset); + const { dateFrom, dateTo } = getDateRangeFromPreset(preset); + this.dateFrom.set(dateFrom); + this.dateTo.set(dateTo); +} + +onCustomDateRangeChange(range: { dateFrom: string; dateTo: string }): void { + this.dateRangePreset.set('custom'); + this.dateFrom.set(range.dateFrom); + this.dateTo.set(range.dateTo); +} +``` + +### Property Detail — Dynamic Stat Labels + +Current hardcoded labels: `"YTD Expenses"`, `"YTD Income"`, `"Net Income"`. + +Replace with computed signals: +```typescript +expenseLabel = computed(() => this.dateRangePreset() === 'this-year' ? 'YTD Expenses' : 'Expenses'); +incomeLabel = computed(() => this.dateRangePreset() === 'this-year' ? 'YTD Income' : 'Income'); +``` + +### Income Store — Year Removal + +The `IncomeListStore` has a `year: number | null` state field that is set by the `yearEffect` in `IncomeComponent`. After removing the year effect: +- Remove `year` from state interface and initial state +- Remove `setYear()` method +- In `currentFilters` computed, call `getDateRangeFromPreset(store.dateRangePreset())` without passing year — the function already uses `today.getFullYear()` as default +- In `setDateRangePreset()`, call `getDateRangeFromPreset(preset)` without year + +### Report Dialogs — Minimal Changes + +`BatchReportDialogComponent` (line 270): `selectedYear = this.yearService.selectedYear()` → `selectedYear = new Date().getFullYear()`. It already has its own year dropdown via `generateYearOptions()`. + +Check `ReportDialogComponent` — if it receives year via `@Inject(MAT_DIALOG_DATA)` from property-detail, update the caller (Task 6.8) to pass `new Date().getFullYear()`. + +### Execution Order + +Tasks should be executed in order (1→2→3→4→5→6→7→8→9→10) since: +- Task 1 (preset) is needed by Tasks 4-6 +- Task 2 (backend) is needed by Task 3 +- Task 3 (store/service) is needed by Tasks 4-6 +- Tasks 4-8 can be done in any order +- Task 9 (cleanup) must be last before validation + +### Files to Modify + +**Frontend (modify):** +- `frontend/src/app/shared/utils/date-range.utils.ts` — add `'last-year'` preset +- `frontend/src/app/shared/components/date-range-filter/date-range-filter.component.ts` — add mat-option +- `frontend/src/app/features/properties/services/property.service.ts` — update method signatures +- `frontend/src/app/features/properties/stores/property.store.ts` — update rxMethod types, state +- `frontend/src/app/features/dashboard/dashboard.component.ts` — add filter, remove year service +- `frontend/src/app/features/properties/properties.component.ts` — add filter, remove year service +- `frontend/src/app/features/properties/property-detail/property-detail.component.ts` — add filter, dynamic labels +- `frontend/src/app/features/income/income.component.ts` — remove yearEffect, year service +- `frontend/src/app/features/income/stores/income-list.store.ts` — remove year state +- `frontend/src/app/features/reports/components/batch-report-dialog/batch-report-dialog.component.ts` — decouple +- `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 import +- `frontend/src/app/core/components/shell/shell.component.html` — remove year selector (2 instances) +- `frontend/src/app/core/components/shell/shell.component.ts` — remove import + +**Frontend (delete):** +- `frontend/src/app/core/services/year-selector.service.ts` +- `frontend/src/app/core/services/year-selector.service.spec.ts` +- `frontend/src/app/shared/components/year-selector/year-selector.component.ts` +- `frontend/src/app/shared/components/year-selector/year-selector.component.spec.ts` + +**Frontend test files (modify):** +- `frontend/src/app/shared/components/date-range-filter/date-range-filter.component.spec.ts` — add last-year test +- `frontend/src/app/features/dashboard/dashboard.component.spec.ts` — remove year service mock, add filter tests +- `frontend/src/app/features/properties/properties.component.spec.ts` — same +- `frontend/src/app/features/properties/property-detail/property-detail.component.spec.ts` — same +- `frontend/src/app/features/income/income.component.spec.ts` — remove year service mock +- `frontend/src/app/features/income/stores/income-list.store.spec.ts` — remove year tests +- `frontend/src/app/features/properties/stores/property.store.spec.ts` — update for new param types +- `frontend/src/app/features/reports/components/batch-report-dialog/batch-report-dialog.component.spec.ts` — remove year service mock + +**Backend (modify):** +- `backend/src/PropertyManager.Application/Properties/GetAllProperties.cs` — add DateFrom/DateTo to query + handler +- `backend/src/PropertyManager.Application/Properties/GetPropertyById.cs` — add DateFrom/DateTo to query + handler +- `backend/src/PropertyManager.Application/Dashboard/GetDashboardTotals.cs` — add DateFrom/DateTo, make Year optional +- `backend/src/PropertyManager.Api/Controllers/PropertiesController.cs` — add query params +- `backend/src/PropertyManager.Api/Controllers/DashboardController.cs` — add query params, make year optional + +**Backend test files (modify/create):** +- `backend/tests/PropertyManager.Application.Tests/Properties/GetAllPropertiesHandlerTests.cs` — add dateFrom/dateTo tests +- `backend/tests/PropertyManager.Application.Tests/Properties/GetPropertyByIdHandlerTests.cs` — add dateFrom/dateTo tests +- `backend/tests/PropertyManager.Application.Tests/Dashboard/GetDashboardTotalsHandlerTests.cs` — add dateFrom/dateTo tests + +### Project Structure Notes + +- Aligned with Clean Architecture: backend query changes in Application layer, controller param changes in Api layer +- Frontend follows feature-based structure: stores, services, components all within their feature folders +- Shared DateRangeFilterComponent stays in `shared/components/` — no new shared components needed +- No new files created (except possibly test files if they don't exist) + +### References + +- [Source: `frontend/src/app/core/services/year-selector.service.ts` — full YearSelectorService implementation to be removed] +- [Source: `frontend/src/app/shared/components/year-selector/year-selector.component.ts` — full YearSelectorComponent to be removed] +- [Source: `frontend/src/app/shared/components/date-range-filter/date-range-filter.component.ts` — reusable DateRangeFilterComponent with inputs/outputs] +- [Source: `frontend/src/app/shared/utils/date-range.utils.ts` — DateRangePreset type and getDateRangeFromPreset utility] +- [Source: `frontend/src/app/features/dashboard/dashboard.component.ts` — lines 186-195 (yearService injection, effect)] +- [Source: `frontend/src/app/features/properties/properties.component.ts` — lines 157-165 (yearService injection, effect)] +- [Source: `frontend/src/app/features/properties/property-detail/property-detail.component.ts` — lines 647, 674-681 (yearService injection, effect)] +- [Source: `frontend/src/app/features/income/income.component.ts` — lines 481, 496-499 (yearService, yearEffect)] +- [Source: `frontend/src/app/features/income/stores/income-list.store.ts` — lines 45, 155-166, 248-251 (year state, currentFilters, setYear)] +- [Source: `frontend/src/app/features/reports/components/batch-report-dialog/batch-report-dialog.component.ts` — line 266, 270 (yearService injection, selectedYear init)] +- [Source: `frontend/src/app/features/properties/stores/property.store.ts` — lines 27, 140-168, 195-226 (selectedYear state, loadProperties, loadPropertyById)] +- [Source: `frontend/src/app/features/properties/services/property.service.ts` — lines 78-91 (getProperties, getPropertyById with year param)] +- [Source: `frontend/src/app/core/components/shell/shell.component.html` — lines 31, 55 (year selector in tablet/mobile headers)] +- [Source: `frontend/src/app/core/components/sidebar-nav/sidebar-nav.component.html` — lines 7-10 (year selector in sidebar)] +- [Source: `backend/src/PropertyManager.Application/Properties/GetAllProperties.cs` — line 11 (Year param), line 76 (e.Date.Year == year filtering)] +- [Source: `backend/src/PropertyManager.Application/Properties/GetPropertyById.cs` — lines 13, 78-80 (Year param, yearStart/yearEnd)] +- [Source: `backend/src/PropertyManager.Application/Dashboard/GetDashboardTotals.cs` — lines 11, 42-43 (Year param, yearStart/yearEnd)] +- [Source: `backend/src/PropertyManager.Api/Controllers/PropertiesController.cs` — line 47 (year query param)] +- [Source: `backend/src/PropertyManager.Api/Controllers/DashboardController.cs` — line 40 (year query param, required)] +- [Source: project-context.md — Clean Architecture patterns, Angular signals patterns, testing rules] +- [Source: GitHub Issue #279 — Replace global year selector with date range filter] + +## Dev Agent Record + +### Agent Model Used + +{{agent_model_name_version}} + +### Debug Log References + +### Completion Notes List + +### File List diff --git a/_bmad-output/implementation-artifacts/sprint-status.yaml b/_bmad-output/implementation-artifacts/sprint-status.yaml index ad1f8acb..69ada870 100644 --- a/_bmad-output/implementation-artifacts/sprint-status.yaml +++ b/_bmad-output/implementation-artifacts/sprint-status.yaml @@ -267,7 +267,7 @@ development_status: 17-8-full-size-add-vendor-form: done # Issue #274 - M — PR #291 merged 17-9-photo-upload-multi-file-support: done # Issue #276 - M — PR #293 merged 17-10-inline-status-dropdown-wo-detail: done # Issue #277 - M — PR #294 merged - 17-11-wo-list-primary-photo-thumbnail: review # Issue #270 - M - 17-12-replace-year-selector-date-range: pending # Issue #279 - L + 17-11-wo-list-primary-photo-thumbnail: done # Issue #270 - M — PR #295 merged + 17-12-replace-year-selector-date-range: ready-for-dev # Issue #279 - L 17-13-vendor-photo-support: pending # Issue #271 - L epic-17-retrospective: optional diff --git a/story-17-10-status-dropdown.png b/story-17-10-status-dropdown.png new file mode 100644 index 0000000000000000000000000000000000000000..3d2dcc6ff146896a214db2a3030f559cfe5d85c2 GIT binary patch literal 139757 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

(3_GlrE}&cWms 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þ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 literal 0 HcmV?d00001 From fb5cb9acfac4b7e8d3f4f38feeb1135b594ad56b Mon Sep 17 00:00:00 2001 From: daveharms Date: Sat, 7 Mar 2026 09:23:26 -0600 Subject: [PATCH 3/4] fix: update E2E test to expect 6 date range presets after Story 17.12 added All Time option Co-Authored-By: Claude Opus 4.6 --- frontend/e2e/tests/income/income-shared-components.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/e2e/tests/income/income-shared-components.spec.ts b/frontend/e2e/tests/income/income-shared-components.spec.ts index ae5c1a77..7eb31f76 100644 --- a/frontend/e2e/tests/income/income-shared-components.spec.ts +++ b/frontend/e2e/tests/income/income-shared-components.spec.ts @@ -41,7 +41,7 @@ test.describe('Story 16.6 — Income Page Shared Components', () => { await expect(presetDropdown).toBeVisible(); }); - test('should show 5 preset options when dropdown opened', async ({ + test('should show 6 preset options when dropdown opened', async ({ page, authenticatedUser, }) => { @@ -58,9 +58,9 @@ test.describe('Story 16.6 — Income Page Shared Components', () => { // WHEN: Opening the preset dropdown await page.locator('app-date-range-filter mat-select').click(); - // THEN: All 5 preset options are available + // THEN: All 6 preset options are available (All Time, This Month, This Quarter, This Year, Last Year, Custom Range) const options = page.locator('mat-option'); - await expect(options).toHaveCount(5); + await expect(options).toHaveCount(6); }); test('should show custom date inputs when Custom Range selected', async ({ From c041300262936e0b2c2de2de51ed6dc814ef4250 Mon Sep 17 00:00:00 2001 From: daveharms Date: Sat, 7 Mar 2026 10:09:15 -0600 Subject: [PATCH 4/4] =?UTF-8?q?fix:=20code=20review=20=E2=80=94=20batch=20?= =?UTF-8?q?date=20signals,=20decouple=20BatchReportDialog=20from=20shared?= =?UTF-8?q?=20store,=20cleanup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace 3 separate date signals with single dateRange signal + computed projections to prevent double effect fires on preset change (Dashboard, Properties, PropertyDetail) - Move propertyId assignment to constructor so effect has it on first fire (PropertyDetail) - BatchReportDialog now fetches properties via PropertyService with year-scoped dates instead of reading date-filtered data from shared PropertyStore singleton - Remove dead .year-selector-panel CSS from global styles (AC-7 cleanup) - Remove empty ngOnInit / implements OnInit from Dashboard and Properties components - Remove unused imports (effect, PropertyStore) from BatchReportDialog Co-Authored-By: Claude Opus 4.6 --- .../features/dashboard/dashboard.component.ts | 32 +++----- .../properties/properties.component.ts | 32 +++----- .../property-detail.component.ts | 26 +++---- .../batch-report-dialog.component.spec.ts | 52 +++++++++---- .../batch-report-dialog.component.ts | 76 +++++++++---------- frontend/src/styles.scss | 19 ----- 6 files changed, 107 insertions(+), 130 deletions(-) diff --git a/frontend/src/app/features/dashboard/dashboard.component.ts b/frontend/src/app/features/dashboard/dashboard.component.ts index 8a796660..cb30817f 100644 --- a/frontend/src/app/features/dashboard/dashboard.component.ts +++ b/frontend/src/app/features/dashboard/dashboard.component.ts @@ -1,4 +1,4 @@ -import { Component, inject, OnInit, effect, signal, computed } from '@angular/core'; +import { Component, inject, effect, signal, computed } from '@angular/core'; import { CommonModule } from '@angular/common'; import { Router, RouterLink } from '@angular/router'; import { MatCardModule } from '@angular/material/card'; @@ -197,7 +197,7 @@ import { DateRangePreset, getDateRangeFromPreset } from '../../shared/utils/date } `] }) -export class DashboardComponent implements OnInit { +export class DashboardComponent { private readonly authService = inject(AuthService); private readonly router = inject(Router); readonly propertyStore = inject(PropertyStore); @@ -205,40 +205,30 @@ export class DashboardComponent implements OnInit { readonly currentUser = this.authService.currentUser; readonly dateRangePreset = signal('this-year'); - readonly dateFrom = signal(null); - readonly dateTo = signal(null); + private readonly dateRange = signal(getDateRangeFromPreset('this-year')); + readonly dateFrom = computed(() => this.dateRange().dateFrom); + readonly dateTo = computed(() => this.dateRange().dateTo); constructor() { - const initial = getDateRangeFromPreset('this-year'); - this.dateFrom.set(initial.dateFrom); - this.dateTo.set(initial.dateTo); - effect(() => { - const from = this.dateFrom(); - const to = this.dateTo(); - this.propertyStore.loadProperties({ dateFrom: from ?? undefined, dateTo: to ?? undefined }); + const { dateFrom, dateTo } = this.dateRange(); + this.propertyStore.loadProperties({ dateFrom: dateFrom ?? undefined, dateTo: dateTo ?? undefined }); }); } - ngOnInit(): void { - // Initial load happens via effect when dateFrom/dateTo signals are read - } - loadProperties(): void { - this.propertyStore.loadProperties({ dateFrom: this.dateFrom() ?? undefined, dateTo: this.dateTo() ?? undefined }); + const { dateFrom, dateTo } = this.dateRange(); + this.propertyStore.loadProperties({ dateFrom: dateFrom ?? undefined, dateTo: dateTo ?? undefined }); } onDateRangePresetChange(preset: DateRangePreset): void { this.dateRangePreset.set(preset); - const { dateFrom, dateTo } = getDateRangeFromPreset(preset); - this.dateFrom.set(dateFrom); - this.dateTo.set(dateTo); + this.dateRange.set(getDateRangeFromPreset(preset)); } onCustomDateRangeChange(range: { dateFrom: string; dateTo: string }): void { this.dateRangePreset.set('custom'); - this.dateFrom.set(range.dateFrom); - this.dateTo.set(range.dateTo); + this.dateRange.set(range); } navigateToProperty(propertyId: string): void { diff --git a/frontend/src/app/features/properties/properties.component.ts b/frontend/src/app/features/properties/properties.component.ts index 002511c1..0cec0c90 100644 --- a/frontend/src/app/features/properties/properties.component.ts +++ b/frontend/src/app/features/properties/properties.component.ts @@ -1,4 +1,4 @@ -import { Component, inject, OnInit, effect, signal } from '@angular/core'; +import { Component, inject, effect, signal, computed } from '@angular/core'; import { CommonModule } from '@angular/common'; import { Router, RouterLink } from '@angular/router'; import { MatCardModule } from '@angular/material/card'; @@ -170,45 +170,35 @@ import { DateRangePreset, getDateRangeFromPreset } from '../../shared/utils/date } `] }) -export class PropertiesComponent implements OnInit { +export class PropertiesComponent { private readonly router = inject(Router); readonly propertyStore = inject(PropertyStore); readonly dateRangePreset = signal('this-year'); - readonly dateFrom = signal(null); - readonly dateTo = signal(null); + private readonly dateRange = signal(getDateRangeFromPreset('this-year')); + readonly dateFrom = computed(() => this.dateRange().dateFrom); + readonly dateTo = computed(() => this.dateRange().dateTo); constructor() { - const initial = getDateRangeFromPreset('this-year'); - this.dateFrom.set(initial.dateFrom); - this.dateTo.set(initial.dateTo); - effect(() => { - const from = this.dateFrom(); - const to = this.dateTo(); - this.propertyStore.loadProperties({ dateFrom: from ?? undefined, dateTo: to ?? undefined }); + const { dateFrom, dateTo } = this.dateRange(); + this.propertyStore.loadProperties({ dateFrom: dateFrom ?? undefined, dateTo: dateTo ?? undefined }); }); } - ngOnInit(): void { - // Initial load happens via effect when dateFrom/dateTo signals are read - } - loadProperties(): void { - this.propertyStore.loadProperties({ dateFrom: this.dateFrom() ?? undefined, dateTo: this.dateTo() ?? undefined }); + const { dateFrom, dateTo } = this.dateRange(); + this.propertyStore.loadProperties({ dateFrom: dateFrom ?? undefined, dateTo: dateTo ?? undefined }); } onDateRangePresetChange(preset: DateRangePreset): void { this.dateRangePreset.set(preset); - const { dateFrom, dateTo } = getDateRangeFromPreset(preset); - this.dateFrom.set(dateFrom); - this.dateTo.set(dateTo); + this.dateRange.set(getDateRangeFromPreset(preset)); } onCustomDateRangeChange(range: { dateFrom: string; dateTo: string }): void { this.dateRangePreset.set('custom'); - this.dateFrom.set(range.dateFrom); - this.dateTo.set(range.dateTo); + this.dateRange.set(range); } navigateToProperty(propertyId: string): void { diff --git a/frontend/src/app/features/properties/property-detail/property-detail.component.ts b/frontend/src/app/features/properties/property-detail/property-detail.component.ts index 982e38a8..7c05742c 100644 --- a/frontend/src/app/features/properties/property-detail/property-detail.component.ts +++ b/frontend/src/app/features/properties/property-detail/property-detail.component.ts @@ -674,8 +674,9 @@ export class PropertyDetailComponent implements OnInit, OnDestroy { /** Date range filter state */ readonly dateRangePreset = signal('this-year'); - readonly dateFrom = signal(null); - readonly dateTo = signal(null); + private readonly dateRange = signal(getDateRangeFromPreset('this-year')); + readonly dateFrom = computed(() => this.dateRange().dateFrom); + readonly dateTo = computed(() => this.dateRange().dateTo); /** Dynamic stat labels based on preset */ readonly expenseLabel = computed(() => this.dateRangePreset() === 'this-year' ? 'YTD Expenses' : 'Expenses'); @@ -698,23 +699,19 @@ export class PropertyDetailComponent implements OnInit, OnDestroy { ); constructor() { - const initial = getDateRangeFromPreset('this-year'); - this.dateFrom.set(initial.dateFrom); - this.dateTo.set(initial.dateTo); + // Read property ID from route early so the effect can use it + this.propertyId = this.route.snapshot.paramMap.get('id'); effect(() => { - const from = this.dateFrom(); - const to = this.dateTo(); + const { dateFrom, dateTo } = this.dateRange(); if (this.propertyId) { - this.propertyStore.loadPropertyById({ id: this.propertyId, dateFrom: from ?? undefined, dateTo: to ?? undefined }); + this.propertyStore.loadPropertyById({ id: this.propertyId, dateFrom: dateFrom ?? undefined, dateTo: dateTo ?? undefined }); } }); } ngOnInit(): void { - // Get property ID from route and load (AC-2.3.1) - this.propertyId = this.route.snapshot.paramMap.get('id'); - // Initial load happens via effect when dateFrom/dateTo signals are read + // Property ID already set in constructor for effect to use // Load photos for the property (AC-13.3b.2) if (this.propertyId) { @@ -744,15 +741,12 @@ export class PropertyDetailComponent implements OnInit, OnDestroy { onDateRangePresetChange(preset: DateRangePreset): void { this.dateRangePreset.set(preset); - const { dateFrom, dateTo } = getDateRangeFromPreset(preset); - this.dateFrom.set(dateFrom); - this.dateTo.set(dateTo); + this.dateRange.set(getDateRangeFromPreset(preset)); } onCustomDateRangeChange(range: { dateFrom: string; dateTo: string }): void { this.dateRangePreset.set('custom'); - this.dateFrom.set(range.dateFrom); - this.dateTo.set(range.dateTo); + this.dateRange.set(range); } /** diff --git a/frontend/src/app/features/reports/components/batch-report-dialog/batch-report-dialog.component.spec.ts b/frontend/src/app/features/reports/components/batch-report-dialog/batch-report-dialog.component.spec.ts index b504bfdb..e8d4faf0 100644 --- a/frontend/src/app/features/reports/components/batch-report-dialog/batch-report-dialog.component.spec.ts +++ b/frontend/src/app/features/reports/components/batch-report-dialog/batch-report-dialog.component.spec.ts @@ -2,10 +2,10 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { MatDialogRef } from '@angular/material/dialog'; import { MatSnackBar } from '@angular/material/snack-bar'; -import { signal } from '@angular/core'; +import { of, throwError } from 'rxjs'; import { BatchReportDialogComponent } from './batch-report-dialog.component'; import { ReportService } from '../../services/report.service'; -import { PropertyStore } from '../../../properties/stores/property.store'; +import { PropertyService } from '../../../properties/services/property.service'; describe('BatchReportDialogComponent', () => { @@ -14,25 +14,24 @@ describe('BatchReportDialogComponent', () => { let mockReportService: Partial; let mockDialogRef: Partial>; let mockSnackBar: Partial; + let mockPropertyService: Partial; const mockProperties = [ - { id: 'prop-1', name: 'Property 1', city: 'Austin', state: 'TX', incomeTotal: 1000, expenseTotal: 500 }, - { id: 'prop-2', name: 'Property 2', city: 'Dallas', state: 'TX', incomeTotal: 0, expenseTotal: 0 }, - { id: 'prop-3', name: 'Property 3', city: 'Houston', state: 'TX', incomeTotal: 2000, expenseTotal: 800 }, + { id: 'prop-1', name: 'Property 1', street: '123 Main', city: 'Austin', state: 'TX', zipCode: '78701', incomeTotal: 1000, expenseTotal: 500 }, + { id: 'prop-2', name: 'Property 2', street: '456 Oak', city: 'Dallas', state: 'TX', zipCode: '75201', incomeTotal: 0, expenseTotal: 0 }, + { id: 'prop-3', name: 'Property 3', street: '789 Elm', city: 'Houston', state: 'TX', zipCode: '77001', incomeTotal: 2000, expenseTotal: 800 }, ]; - const mockPropertyStore = { - properties: signal(mockProperties), - isLoading: signal(false), - loadProperties: vi.fn(), - }; - beforeEach(async () => { mockReportService = { generateBatchScheduleE: vi.fn(), downloadZip: vi.fn() }; + mockPropertyService = { + getProperties: vi.fn().mockReturnValue(of({ items: mockProperties, totalCount: 3 })), + }; + mockDialogRef = { close: vi.fn() }; @@ -45,8 +44,7 @@ describe('BatchReportDialogComponent', () => { imports: [BatchReportDialogComponent, NoopAnimationsModule], providers: [ { provide: ReportService, useValue: mockReportService }, - { provide: PropertyStore, useValue: mockPropertyStore }, - + { provide: PropertyService, useValue: mockPropertyService }, { provide: MatDialogRef, useValue: mockDialogRef }, { provide: MatSnackBar, useValue: mockSnackBar }, ], @@ -62,6 +60,14 @@ describe('BatchReportDialogComponent', () => { }); describe('initialization', () => { + it('should fetch properties with year-scoped date range on init', () => { + const year = new Date().getFullYear(); + expect(mockPropertyService.getProperties).toHaveBeenCalledWith({ + dateFrom: `${year}-01-01`, + dateTo: `${year}-12-31`, + }); + }); + it('should load all properties on init', () => { expect(component.properties().length).toBe(3); }); @@ -80,6 +86,26 @@ describe('BatchReportDialogComponent', () => { const hasDataProperty = component.properties().find(p => p.id === 'prop-1'); expect(hasDataProperty?.hasDataForYear).toBe(true); }); + + it('should set error if property fetch fails', () => { + (mockPropertyService.getProperties as ReturnType).mockReturnValue( + throwError(() => new Error('fail')) + ); + component.ngOnInit(); + expect(component.error()).toBe('Failed to load properties.'); + }); + }); + + describe('year change', () => { + it('should reload properties when year changes', () => { + (mockPropertyService.getProperties as ReturnType).mockClear(); + component.selectedYear = 2024; + component.onYearChange(); + expect(mockPropertyService.getProperties).toHaveBeenCalledWith({ + dateFrom: '2024-01-01', + dateTo: '2024-12-31', + }); + }); }); describe('property selection', () => { diff --git a/frontend/src/app/features/reports/components/batch-report-dialog/batch-report-dialog.component.ts b/frontend/src/app/features/reports/components/batch-report-dialog/batch-report-dialog.component.ts index c4d78aec..c8040bb5 100644 --- a/frontend/src/app/features/reports/components/batch-report-dialog/batch-report-dialog.component.ts +++ b/frontend/src/app/features/reports/components/batch-report-dialog/batch-report-dialog.component.ts @@ -1,4 +1,4 @@ -import { Component, inject, signal, computed, OnInit, effect } from '@angular/core'; +import { Component, inject, signal, computed, OnInit } from '@angular/core'; import { CommonModule } from '@angular/common'; import { MatDialogModule, MatDialogRef } from '@angular/material/dialog'; import { MatButtonModule } from '@angular/material/button'; @@ -9,7 +9,7 @@ import { MatIconModule } from '@angular/material/icon'; import { MatTooltipModule } from '@angular/material/tooltip'; import { MatSnackBar } from '@angular/material/snack-bar'; import { ReportService } from '../../services/report.service'; -import { PropertyStore } from '../../../properties/stores/property.store'; +import { PropertyService } from '../../../properties/services/property.service'; /** * Property selection item for the batch dialog. @@ -58,7 +58,7 @@ interface PropertySelection { Tax Year - + @for (year of availableYears; track year) { {{ year }} } @@ -261,7 +261,7 @@ interface PropertySelection { }) export class BatchReportDialogComponent implements OnInit { private readonly reportService = inject(ReportService); - private readonly propertyStore = inject(PropertyStore); + private readonly propertyService = inject(PropertyService); private readonly snackBar = inject(MatSnackBar); private readonly dialogRef = inject(MatDialogRef); @@ -281,47 +281,36 @@ export class BatchReportDialogComponent implements OnInit { this.properties().every(p => p.selected) ); - constructor() { - // Watch for property changes from the store and update local state - effect(() => { - const storeProperties = this.propertyStore.properties(); - const isLoading = this.propertyStore.isLoading(); - - // Only update when we have properties and loading is complete - if (storeProperties.length > 0 && !isLoading) { - this.initializeProperties(); - } - }); - } - ngOnInit(): void { - // Ensure properties are loaded - trigger load if store is empty - if (this.propertyStore.properties().length === 0 && !this.propertyStore.isLoading()) { - this.propertyStore.loadProperties(undefined); - } - // Also initialize immediately if properties already exist - if (this.propertyStore.properties().length > 0) { - this.initializeProperties(); - } + this.loadPropertiesForYear(); } /** - * Initialize property selection from store. - * Called on init and when store properties update. + * Fetch properties with year-scoped totals directly from API. + * Avoids reading from the shared PropertyStore whose data reflects + * whatever date filter the previous page applied. */ - private initializeProperties(): void { - // Load properties from store - const storeProperties = this.propertyStore.properties(); - this.properties.set( - storeProperties.map(p => ({ - id: p.id, - name: p.name, - address: this.formatAddress(p), - selected: true, - // Determine if property has data (income or expense > 0) - hasDataForYear: p.incomeTotal > 0 || p.expenseTotal > 0 - })) - ); + private loadPropertiesForYear(): void { + const year = this.selectedYear; + const dateFrom = `${year}-01-01`; + const dateTo = `${year}-12-31`; + + this.propertyService.getProperties({ dateFrom, dateTo }).subscribe({ + next: (response) => { + this.properties.set( + response.items.map(p => ({ + id: p.id, + name: p.name, + address: this.formatAddress(p), + selected: true, + hasDataForYear: p.incomeTotal > 0 || p.expenseTotal > 0, + })) + ); + }, + error: () => { + this.error.set('Failed to load properties.'); + }, + }); } /** @@ -342,6 +331,13 @@ export class BatchReportDialogComponent implements OnInit { return parts.join(', ') || 'No address'; } + /** + * Reload property data when tax year changes. + */ + onYearChange(): void { + this.loadPropertiesForYear(); + } + /** * Toggle selection for a single property. */ diff --git a/frontend/src/styles.scss b/frontend/src/styles.scss index cf8eb021..9585417f 100644 --- a/frontend/src/styles.scss +++ b/frontend/src/styles.scss @@ -107,25 +107,6 @@ a { } } -// Year selector dropdown panel - ensure full year is visible -.year-selector-panel { - min-width: 100px !important; - - .mat-mdc-option { - min-width: 90px; - - .mdc-list-item__primary-text { - overflow: visible !important; - text-overflow: clip !important; - } - - // Hide checkmark - green highlight is sufficient indicator - .mat-pseudo-checkbox { - display: none !important; - } - } -} - // Receipt lightbox panel - large modal .receipt-lightbox-panel { .mat-mdc-dialog-container {