feat : composable de matrice mensuelle avec n° de semaine ISO (#MUI-33)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
69
app/components/malio/date/composables/useMonthMatrix.test.ts
Normal file
69
app/components/malio/date/composables/useMonthMatrix.test.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest'
|
||||
import {ref} from 'vue'
|
||||
import {useMonthMatrix} from './useMonthMatrix'
|
||||
|
||||
describe('useMonthMatrix', () => {
|
||||
it('always produces 6 weeks of 7 days', () => {
|
||||
const {weeks} = useMonthMatrix(ref(4), ref(2026)) // mai 2026
|
||||
expect(weeks.value).toHaveLength(6)
|
||||
weeks.value.forEach(week => expect(week.days).toHaveLength(7))
|
||||
})
|
||||
|
||||
it('starts every week on a Monday', () => {
|
||||
const {weeks} = useMonthMatrix(ref(4), ref(2026))
|
||||
weeks.value.forEach(week => {
|
||||
const first = new Date(`${week.days[0].isoDate}T00:00:00`)
|
||||
expect(first.getDay()).toBe(1) // 1 = lundi
|
||||
})
|
||||
})
|
||||
|
||||
it('flags exactly the days of the current month', () => {
|
||||
const {weeks} = useMonthMatrix(ref(4), ref(2026)) // mai = 31 jours
|
||||
const currentMonthDays = weeks.value
|
||||
.flatMap(w => w.days)
|
||||
.filter(d => d.isCurrentMonth)
|
||||
expect(currentMonthDays).toHaveLength(31)
|
||||
expect(currentMonthDays.every(d => d.isoDate.startsWith('2026-05'))).toBe(true)
|
||||
})
|
||||
|
||||
it('handles leap year February (29 days)', () => {
|
||||
const {weeks} = useMonthMatrix(ref(1), ref(2024)) // février 2024
|
||||
const days = weeks.value.flatMap(w => w.days).filter(d => d.isCurrentMonth)
|
||||
expect(days).toHaveLength(29)
|
||||
})
|
||||
|
||||
it('assigns ISO week 1 to the week containing Jan 4th', () => {
|
||||
const {weeks} = useMonthMatrix(ref(0), ref(2026)) // janvier 2026
|
||||
const weekWithJan4 = weeks.value.find(w =>
|
||||
w.days.some(d => d.isoDate === '2026-01-04'),
|
||||
)
|
||||
expect(weekWithJan4?.weekNumber).toBe(1)
|
||||
})
|
||||
|
||||
it('reacts to month/year changes', () => {
|
||||
const month = ref(4)
|
||||
const year = ref(2026)
|
||||
const {weeks} = useMonthMatrix(month, year)
|
||||
const mayCount = weeks.value.flatMap(w => w.days).filter(d => d.isCurrentMonth).length
|
||||
month.value = 1 // février
|
||||
year.value = 2024
|
||||
const febCount = weeks.value.flatMap(w => w.days).filter(d => d.isCurrentMonth).length
|
||||
expect(mayCount).toBe(31)
|
||||
expect(febCount).toBe(29)
|
||||
})
|
||||
|
||||
describe('isToday', () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers()
|
||||
vi.setSystemTime(new Date(2026, 4, 19)) // 19 mai 2026
|
||||
})
|
||||
afterEach(() => vi.useRealTimers())
|
||||
|
||||
it('flags only today', () => {
|
||||
const {weeks} = useMonthMatrix(ref(4), ref(2026))
|
||||
const todays = weeks.value.flatMap(w => w.days).filter(d => d.isToday)
|
||||
expect(todays).toHaveLength(1)
|
||||
expect(todays[0].isoDate).toBe('2026-05-19')
|
||||
})
|
||||
})
|
||||
})
|
||||
60
app/components/malio/date/composables/useMonthMatrix.ts
Normal file
60
app/components/malio/date/composables/useMonthMatrix.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import {computed, type ComputedRef, type Ref} from 'vue'
|
||||
|
||||
export type DayCell = {
|
||||
isoDate: string
|
||||
day: number
|
||||
isCurrentMonth: boolean
|
||||
isToday: boolean
|
||||
}
|
||||
export type WeekRow = {
|
||||
weekNumber: number
|
||||
days: DayCell[]
|
||||
}
|
||||
|
||||
const toIso = (d: Date): string => {
|
||||
const y = d.getFullYear()
|
||||
const m = String(d.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(d.getDate()).padStart(2, '0')
|
||||
return `${y}-${m}-${day}`
|
||||
}
|
||||
|
||||
const isoWeek = (d: Date): number => {
|
||||
const target = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()))
|
||||
const dayNum = target.getUTCDay() || 7 // dimanche = 7
|
||||
target.setUTCDate(target.getUTCDate() + 4 - dayNum) // jeudi de la semaine
|
||||
const yearStart = new Date(Date.UTC(target.getUTCFullYear(), 0, 1))
|
||||
return Math.ceil((((target.getTime() - yearStart.getTime()) / 86400000) + 1) / 7)
|
||||
}
|
||||
|
||||
export function useMonthMatrix(
|
||||
month: Ref<number>,
|
||||
year: Ref<number>,
|
||||
): {weeks: ComputedRef<WeekRow[]>} {
|
||||
const weeks = computed<WeekRow[]>(() => {
|
||||
const todayIso = toIso(new Date())
|
||||
const first = new Date(year.value, month.value, 1)
|
||||
// recule jusqu'au lundi (getDay : 0 = dimanche)
|
||||
const offset = (first.getDay() + 6) % 7
|
||||
const start = new Date(year.value, month.value, 1 - offset)
|
||||
|
||||
const rows: WeekRow[] = []
|
||||
const cursor = new Date(start)
|
||||
for (let w = 0; w < 6; w++) {
|
||||
const days: DayCell[] = []
|
||||
for (let i = 0; i < 7; i++) {
|
||||
const iso = toIso(cursor)
|
||||
days.push({
|
||||
isoDate: iso,
|
||||
day: cursor.getDate(),
|
||||
isCurrentMonth: cursor.getMonth() === month.value,
|
||||
isToday: iso === todayIso,
|
||||
})
|
||||
cursor.setDate(cursor.getDate() + 1)
|
||||
}
|
||||
rows.push({weekNumber: isoWeek(new Date(`${days[0].isoDate}T00:00:00`)), days})
|
||||
}
|
||||
return rows
|
||||
})
|
||||
|
||||
return {weeks}
|
||||
}
|
||||
Reference in New Issue
Block a user