- Create colorUtils.ts: hexToContainer (15% alpha over white), hexToOnContainer
(darken 40%), deriveScheduleXColors() returning { main, container, onContainer }
- Create colorUtils.test.ts: hex blend math assertions for #4A90D9 and #F25C7A
- Create calendarConfig.ts: WEEK_START_DAY=0, SX_FIRST_DAY_OF_WEEK=7 (0→7 translation)
buildCalendarConfig() keyed by String(userId) + 'shared'; returns { firstDayOfWeek, calendars }
- calendarConfig.test.ts (Plan 01 RED stubs) now GREEN: all 4 assertions pass
93 lines
2.8 KiB
TypeScript
93 lines
2.8 KiB
TypeScript
/**
|
|
* Schedule-X calendar configuration factory.
|
|
*
|
|
* Week-start-day translation:
|
|
* Project convention: WEEK_START_DAY = 0 (Sunday, JS/date-fns convention)
|
|
* Schedule-X v4: firstDayOfWeek = 7 (Sunday, Temporal convention)
|
|
*
|
|
* Passing 0 to Schedule-X silently defaults to Monday. The constant
|
|
* WEEK_START_DAY === 0 ? 7 translation below encodes this as the source of
|
|
* truth so the pitfall cannot recur.
|
|
*
|
|
* calendarId routing contract (must align with hydrateEvents.ts):
|
|
* Shared-family calendar → key 'shared'
|
|
* Per-member calendars → key String(userId) (NOT String(db calendarId))
|
|
*
|
|
* Source: https://schedule-x.dev/docs/calendar/configuration
|
|
* Source: https://schedule-x.dev/docs/calendar/calendars
|
|
*/
|
|
|
|
import { deriveScheduleXColors } from './colorUtils.js'
|
|
|
|
/** Week start day in JS/date-fns convention: 0 = Sunday. */
|
|
export const WEEK_START_DAY = 0
|
|
|
|
/**
|
|
* firstDayOfWeek in Schedule-X/Temporal convention: 7 = Sunday, 1 = Monday.
|
|
*
|
|
* Translation: WEEK_START_DAY === 0 (JS Sunday) → 7 (Temporal Sunday).
|
|
* Hard-code to the project convention; one edit here when the user wants Monday.
|
|
*/
|
|
export const SX_FIRST_DAY_OF_WEEK: number = WEEK_START_DAY === 0 ? 7 : WEEK_START_DAY
|
|
|
|
/**
|
|
* Per-member calendar config entry passed to buildCalendarConfig.
|
|
*
|
|
* id = String(users.id) — the Schedule-X calendars config key
|
|
* name = users.displayName
|
|
* color = users.color hex
|
|
*/
|
|
export interface MemberCalendarConfig {
|
|
id: string // String(users.id)
|
|
name: string // users.displayName
|
|
color: string // hex from users.color
|
|
}
|
|
|
|
export interface ScheduleXCalendarEntry {
|
|
colorName: string
|
|
lightColors: {
|
|
main: string
|
|
container: string
|
|
onContainer: string
|
|
}
|
|
}
|
|
|
|
export interface CalendarConfig {
|
|
firstDayOfWeek: number
|
|
calendars: Record<string, ScheduleXCalendarEntry>
|
|
}
|
|
|
|
/**
|
|
* Build the Schedule-X calendars configuration object.
|
|
*
|
|
* Always includes a 'shared' entry (rose #F25C7A).
|
|
* Adds one entry per member, keyed by String(userId).
|
|
*
|
|
* The keys here are the routing contract: hydrateEvents() must produce
|
|
* calendarId values that exactly match these keys:
|
|
* isShared:true → 'shared'
|
|
* isShared:false → String(ownerUserId) (NOT String(db calendarId))
|
|
*/
|
|
export function buildCalendarConfig(members: MemberCalendarConfig[]): CalendarConfig {
|
|
const calendars: Record<string, ScheduleXCalendarEntry> = {}
|
|
|
|
// Shared-family calendar: reserved rose color, confirmed by user
|
|
calendars['shared'] = {
|
|
colorName: 'shared',
|
|
lightColors: deriveScheduleXColors('#F25C7A'),
|
|
}
|
|
|
|
// Per-member calendars keyed by String(userId)
|
|
for (const m of members) {
|
|
calendars[m.id] = {
|
|
colorName: `member-${m.id}`,
|
|
lightColors: deriveScheduleXColors(m.color),
|
|
}
|
|
}
|
|
|
|
return {
|
|
firstDayOfWeek: SX_FIRST_DAY_OF_WEEK, // 7 = Sunday in Temporal convention
|
|
calendars,
|
|
}
|
|
}
|