mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-23 07:26:30 +00:00
feat(settings): every date picker starts its week on the day you pick
Settings > General > Language & region gets "Week starts on" (Monday, Sunday or Saturday), and Admin > User Defaults a default for everyone who has not picked one. Monday stays the built-in value, so nothing changes for anyone who leaves it alone. Both calendar implementations follow it: CustomDatePicker, used by trip dates, bookings, costs, to-dos and the admin school holidays on desktop and phone, and the journey date picker, whose weekday headers were hardcoded English and now speak the app language. The grid math and the header row live in one helper (utils/calendarWeek.ts). The day names on the options come from Intl, so only the label and a hint needed translating. Vacay keeps its own per-plan week start. The value is validated like the other regional settings: VALID_VALUES for admin defaults, the display-settings MCP tool, and the managed key list. Addresses discussion #2029.
This commit is contained in:
@@ -787,3 +787,35 @@ describe('CustomDateTimePicker branches', () => {
|
||||
expect(onChange).toHaveBeenCalledWith('2026-03-15');
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Week start (#2029) ───────────────────────────────────────────────────────
|
||||
|
||||
describe('CustomDatePicker week start (#2029)', () => {
|
||||
const onChange = vi.fn();
|
||||
const original = useSettingsStore.getState().settings;
|
||||
afterEach(() => useSettingsStore.setState({ settings: original }));
|
||||
|
||||
/** The weekday header row and how many blanks precede the 1st, read off the open calendar. */
|
||||
async function openMarch2026(weekStart?: 'monday' | 'sunday' | 'saturday') {
|
||||
useSettingsStore.setState({ settings: { ...original, week_start: weekStart } });
|
||||
const user = userEvent.setup();
|
||||
render(<CustomDatePicker value="2026-03-15" onChange={onChange} />);
|
||||
await user.click(screen.getAllByRole('button')[0]);
|
||||
const day1 = screen.getAllByRole('button').find((b) => b.textContent?.trim() === '1')!;
|
||||
const grid = day1.parentElement!;
|
||||
return { header: grid.previousElementSibling!.textContent, blanks: Array.from(grid.children).indexOf(day1) };
|
||||
}
|
||||
|
||||
it('FE-COMP-DATEPICKER-033: rows start on Monday when nothing is set', async () => {
|
||||
// 1 March 2026 is a Sunday: the last column of a Monday-first row.
|
||||
expect(await openMarch2026(undefined)).toEqual({ header: 'MTWTFSS', blanks: 6 });
|
||||
});
|
||||
|
||||
it('FE-COMP-DATEPICKER-034: rows start on the day the user picked', async () => {
|
||||
expect(await openMarch2026('sunday')).toEqual({ header: 'SMTWTFS', blanks: 0 });
|
||||
});
|
||||
|
||||
it('FE-COMP-DATEPICKER-035: a Saturday start puts the Sunday 1st in the second column', async () => {
|
||||
expect(await openMarch2026('saturday')).toEqual({ header: 'SSMTWTF', blanks: 1 });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,13 +4,12 @@ import React, { useEffect, useRef, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { useTranslation } from '../../i18n';
|
||||
import { useRemeasureSignal } from '../../hooks/useAnchoredPosition';
|
||||
import { useWeekStartDay } from '../../hooks/useWeekStartDay';
|
||||
import { leadingBlanks, weekdayLabels } from '../../utils/calendarWeek';
|
||||
|
||||
function daysInMonth(year: number, month: number): number {
|
||||
return new Date(year, month + 1, 0).getDate();
|
||||
}
|
||||
function getWeekday(year: number, month: number, day: number): number {
|
||||
return new Date(year, month, day).getDay();
|
||||
}
|
||||
const YEAR_PAGE_SIZE = 12;
|
||||
type CalendarView = 'days' | 'months' | 'years';
|
||||
|
||||
@@ -41,6 +40,7 @@ export function CustomDatePicker({
|
||||
max,
|
||||
}: CustomDatePickerProps) {
|
||||
const { locale, t } = useTranslation();
|
||||
const weekStart = useWeekStartDay();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [view, setView] = useState<CalendarView>('days');
|
||||
const [yearPageStart, setYearPageStart] = useState(0);
|
||||
@@ -140,10 +140,8 @@ export function CustomDatePicker({
|
||||
};
|
||||
|
||||
const days = daysInMonth(viewYear, viewMonth);
|
||||
const startDay = (getWeekday(viewYear, viewMonth, 1) + 6) % 7;
|
||||
const weekdays = Array.from({ length: 7 }, (_, i) =>
|
||||
new Date(2024, 0, i + 1).toLocaleDateString(locale, { weekday: 'narrow' })
|
||||
);
|
||||
const startDay = leadingBlanks(viewYear, viewMonth, weekStart);
|
||||
const weekdays = weekdayLabels(locale, weekStart);
|
||||
|
||||
const monthNames = Array.from({ length: 12 }, (_, i) =>
|
||||
new Date(viewYear, i).toLocaleDateString(locale, { month: 'short' })
|
||||
|
||||
Reference in New Issue
Block a user