From 6fac549f483a613b5ace808f7e99873906e1f3bd Mon Sep 17 00:00:00 2001 From: Frank Rousseau Date: Mon, 6 Jul 2026 14:52:25 +0200 Subject: [PATCH] [timesheets] Fix week list truncation at the ISO year boundary In the last days of December, moment().week() already returns 1 (the locale week of the next year), so the weekly timesheet collapsed to a single wrong column for the whole year (#1928). getWeekRange now uses isoWeek()/isoWeekYear(), matching the backend which keys the week table with date.isocalendar(), and falls back to the full year range when today's ISO week belongs to another year. Align the other week computations of the timesheet pages on the same ISO convention: the header tooltip used today's year instead of the displayed one, and the side panel derived the week interval from locale weeks, showing a range shifted from the one the backend aggregates for years starting on Friday or Saturday. Co-Authored-By: Claude Fable 5 --- src/components/lists/PeopleTimesheetList.vue | 2 +- src/components/pages/Timesheets.vue | 4 ++-- src/components/sides/PeopleTimesheetInfo.vue | 20 ++++++++------------ src/lib/time.js | 8 ++++++-- tests/unit/lib/time.spec.js | 14 ++++++++++++++ 5 files changed, 31 insertions(+), 17 deletions(-) diff --git a/src/components/lists/PeopleTimesheetList.vue b/src/components/lists/PeopleTimesheetList.vue index 3d03061b78..c28564e206 100644 --- a/src/components/lists/PeopleTimesheetList.vue +++ b/src/components/lists/PeopleTimesheetList.vue @@ -369,7 +369,7 @@ export default { }, getWeekTitle(week) { - const beginning = moment(this.currentYear + '-' + week, 'YYYY-W') + const beginning = moment(this.year + '-' + week, 'YYYY-W') const end = beginning.clone().add(6, 'days') return beginning.format('YYYY-MM-DD') + ' - ' + end.format('YYYY-MM-DD') }, diff --git a/src/components/pages/Timesheets.vue b/src/components/pages/Timesheets.vue index 8dc6315d7f..78ea019b59 100644 --- a/src/components/pages/Timesheets.vue +++ b/src/components/pages/Timesheets.vue @@ -168,7 +168,7 @@ export default { currentYear: moment().year(), currentMonth: moment().month() + 1, - currentWeek: moment().week(), + currentWeek: moment().isoWeek(), currentDay: moment().date(), currentPerson: this.getCurrentPerson(), @@ -441,7 +441,7 @@ export default { todayMonth: this.currentMonth, year: moment().year(), month: moment().month() + 1, - week: moment().week() + week: moment().isoWeek() }) }, diff --git a/src/components/sides/PeopleTimesheetInfo.vue b/src/components/sides/PeopleTimesheetInfo.vue index accc559094..5529035b8d 100644 --- a/src/components/sides/PeopleTimesheetInfo.vue +++ b/src/components/sides/PeopleTimesheetInfo.vue @@ -107,25 +107,21 @@ export default { emits: ['close'], computed: { + // Monday of the displayed ISO week, as aggregated by the backend. + weekStartDate() { + return moment(this.year + '-' + this.week, 'YYYY-W') + }, + startDay() { - return moment().day('Monday').year(this.year).week(this.week).date() + return this.weekStartDate.date() }, endDay() { - return moment() - .day('Monday') - .year(this.year) - .week(this.week) - .add(6, 'days') - .date() + return this.weekStartDate.clone().add(6, 'days').date() }, weekMonth() { - return moment() - .day('Monday') - .year(this.year) - .week(this.week) - .format('MMM') + return this.weekStartDate.format('MMM') }, monthString() { diff --git a/src/lib/time.js b/src/lib/time.js index 95f6b468d3..5ab5194dbd 100644 --- a/src/lib/time.js +++ b/src/lib/time.js @@ -112,8 +112,12 @@ export const getDayRange = (year, month, currentYear, currentMonth) => { } export const getWeekRange = (year, currentYear) => { - if (currentYear === year) { - return range(1, moment().week()) + const now = moment() + // Late December days can belong to ISO week 1 of the next year: only + // truncate the range at the current week when today's ISO week still + // belongs to the displayed year. + if (currentYear === year && now.isoWeekYear() <= year) { + return range(1, now.isoWeek()) } else { return range(1, moment(String(year), 'YYYY').isoWeeksInYear()) } diff --git a/tests/unit/lib/time.spec.js b/tests/unit/lib/time.spec.js index b050c39976..38ca4210c0 100644 --- a/tests/unit/lib/time.spec.js +++ b/tests/unit/lib/time.spec.js @@ -36,6 +36,10 @@ describe('time', () => { vi.setSystemTime(new Date('2019-09-10T12:00:00Z')) }) + afterEach(() => { + vi.setSystemTime(new Date('2019-09-10T12:00:00Z')) + }) + afterAll(() => { vi.useRealTimers() }) @@ -114,6 +118,16 @@ describe('time', () => { expect(getWeekRange(2018, 2019)).toEqual(range(1, 52)) // 2019-09-10 (frozen clock) falls in week 37. expect(getWeekRange(2019, 2019)).toEqual(range(1, 37)) + // 2020 has 53 ISO weeks. + expect(getWeekRange(2020, 2026)).toEqual(range(1, 53)) + // Regression #1928: 2025-12-30 belongs to ISO week 1 of 2026, all the + // weeks of 2025 must stay visible. + vi.setSystemTime(new Date('2025-12-30T12:00:00Z')) + expect(getWeekRange(2025, 2025)).toEqual(range(1, 52)) + // 2027-01-01 belongs to ISO week 53 of 2026, where the backend files + // its hours in the 2027 table. + vi.setSystemTime(new Date('2027-01-01T12:00:00Z')) + expect(getWeekRange(2027, 2027)).toEqual(range(1, 53)) }) test('getDayRange', () => {