Jump to today, and open a period on the dates it actually has

The month can now be reached in one click instead of paging back through
the year. The button sits left of the month dropdown and sizes to its
label, so Idag and Today both fit.

Opening a period showed the wish's first year rather than the occurrence
that was clicked: a 2028 week of a yearly wish opened on 2026, and a
flexible period opened on its bounding range instead of where the planner
put it. The popover now prefills from the clicked band's own dates.
Saving a yearly wish keeps its anchor year, since the edited dates
describe the recurring pattern and not one occurrence — moving a single
year is a separate choice that does not exist yet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ei7gCZzdd751uZtPzb1Pep
This commit is contained in:
2026-09-19 14:18:14 +02:00
parent f0f537c95f
commit 1837154225
7 changed files with 191 additions and 7 deletions
@@ -86,7 +86,13 @@ describe('PeriodPopover', () => {
})
it('prefills name/dates/parent/repeat from the wish', () => {
render(<PeriodPopover {...defaultProps} wish={wish} />)
// startDate/endDate here are the clicked band's real placed dates
// (the 2028 occurrence, which happens to equal the wish's first
// occurrence) — see the dedicated "later-year occurrence" tests below
// for the case where they diverge from `wish.firstYear`.
render(
<PeriodPopover {...defaultProps} wish={wish} startDate="2028-06-15" endDate="2028-07-26" />,
)
expect(screen.getByTestId('period-name-input')).toHaveValue('Sommar')
expect(screen.getByTestId('period-start-input')).toHaveValue('2028-06-15')
expect(screen.getByTestId('period-end-input')).toHaveValue('2028-07-26')
@@ -94,6 +100,12 @@ describe('PeriodPopover', () => {
expect(screen.getByTestId('period-repeat-yearly')).toBeChecked()
})
it('falls back to the wish\'s window-derived dates when no placed dates are supplied', () => {
render(<PeriodPopover {...defaultProps} wish={wish} startDate="" endDate="" />)
expect(screen.getByTestId('period-start-input')).toHaveValue('2028-06-15')
expect(screen.getByTestId('period-end-input')).toHaveValue('2028-07-26')
})
it('shows the wrapping window correctly for a Jul-style wish (end year = firstYear + 1)', () => {
const jul = makeLeaveWish({
windowStartMonth: 12,
@@ -102,11 +114,54 @@ describe('PeriodPopover', () => {
windowEndDay: 16,
firstYear: 2028,
})
render(<PeriodPopover {...defaultProps} wish={jul} />)
render(<PeriodPopover {...defaultProps} wish={jul} startDate="2028-12-20" endDate="2029-01-16" />)
expect(screen.getByTestId('period-start-input')).toHaveValue('2028-12-20')
expect(screen.getByTestId('period-end-input')).toHaveValue('2029-01-16')
})
it('prefills the clicked later-year occurrence\'s dates, not the wish\'s first-occurrence window', () => {
// The wish was first taken in 2026; the user clicked its 2028 band.
const yearlyFrom2026 = makeLeaveWish({
windowStartMonth: 6,
windowStartDay: 15,
windowEndMonth: 7,
windowEndDay: 26,
firstYear: 2026,
recurrence: 'yearly',
})
render(
<PeriodPopover
{...defaultProps}
wish={yearlyFrom2026}
startDate="2028-06-15"
endDate="2028-07-26"
/>,
)
expect(screen.getByTestId('period-start-input')).toHaveValue('2028-06-15')
expect(screen.getByTestId('period-end-input')).toHaveValue('2028-07-26')
// Non-date fields still come from the wish itself, unaffected by which
// occurrence was clicked.
expect(screen.getByTestId('period-repeat-yearly')).toBeChecked()
expect(screen.getByTestId('period-parent-select')).toHaveValue('either')
})
it('prefills a flexible wish\'s actual placed dates, not its bounding window', () => {
// A flexible/wrapping wish whose *window* spans Dec 20 - Jan 16, but
// the allocator actually placed this occurrence a few days later.
const flexible = makeLeaveWish({
windowStartMonth: 12,
windowStartDay: 20,
windowEndMonth: 1,
windowEndDay: 16,
firstYear: 2028,
})
render(
<PeriodPopover {...defaultProps} wish={flexible} startDate="2028-12-27" endDate="2029-01-10" />,
)
expect(screen.getByTestId('period-start-input')).toHaveValue('2028-12-27')
expect(screen.getByTestId('period-end-input')).toHaveValue('2029-01-10')
})
it('shows "until age 4" checked when lastYear is null', () => {
render(<PeriodPopover {...defaultProps} wish={wish} />)
expect(screen.getByTestId('period-until-age4')).toBeChecked()
@@ -64,13 +64,27 @@ function parseISO(date: string): { year: number; month: number; day: number } {
return { year: year ?? 1970, month: month ?? 1, day: day ?? 1 }
}
function formStateFromWish(wish: LeaveWish, t: (key: TranslationKey) => string): FormState {
function formStateFromWish(
wish: LeaveWish,
startDate: string,
endDate: string,
t: (key: TranslationKey) => string,
): FormState {
// Prefer the clicked band's actual placed dates (the `startDate`/`endDate`
// props — see `PeriodPopoverProps`'s doc comment) over recomputing from
// the wish's static recurring window. `wish.firstYear` + the window only
// describes the *first* occurrence; clicking a later-year occurrence of a
// yearly wish (or any occurrence of a flexible wish, whose placement can
// drift inside its bounding range) needs the real placed dates, not the
// anchor year's. Fall back to the window-derived dates if the caller
// didn't supply real placed dates (defensive — callers are expected to
// always pass them).
const wraps = windowWraps(wish.windowStartMonth, wish.windowStartDay, wish.windowEndMonth, wish.windowEndDay)
const endYear = wraps ? wish.firstYear + 1 : wish.firstYear
return {
name: resolveWishName(wish, t),
startDate: formatISO(wish.firstYear, wish.windowStartMonth, wish.windowStartDay),
endDate: formatISO(endYear, wish.windowEndMonth, wish.windowEndDay),
startDate: startDate || formatISO(wish.firstYear, wish.windowStartMonth, wish.windowStartDay),
endDate: endDate || formatISO(endYear, wish.windowEndMonth, wish.windowEndDay),
parentMode: wish.parentMode,
parentPersonalDataUuid: wish.parentPersonalDataUuid,
recurrence: wish.recurrence,
@@ -105,7 +119,7 @@ export function PeriodPopover({
useEffect(() => {
if (wish) {
setForm(formStateFromWish(wish, t))
setForm(formStateFromWish(wish, startDate, endDate, t))
} else {
setForm({
name: '',