/** * What the Schedules surface is allowed to SAY when the reader's view is narrowed to their own blocks. * *, measured as HR at a practice with no configured human-resources profile: * `GET /R4/Schedule` returned **200 with zero entries** while the receptionist saw 14, and the page said * *"0 schedule entries - No staff schedules found for this period."* at every time scale. * * THE SERVER IS CORRECT AND MUST NOT BE WIDENED. `resolveSchedulesScope` gives an unconfigured * non-front-desk role scope `own` - ruling, so an org that has granted nothing does not hand * out everyone's availability. HR then genuinely has no schedules of their own, so **zero is the honest * DATA**; only the SENTENCE was wrong. * * WHY A MODULE FOR THREE STRINGS. The refusal arrives as a 200 with an empty set and never a 403, so * nothing downstream can tell *"scoped out"* from *"this practice has none"* *. That makes the wording the entire fix, and a * wording inlined as a ternary in a 1,000-line page is not something a test can hold. Here it is a value, * and the invariant below - **no narrowed sentence may assert a practice-wide absence** - is checkable * rather than merely intended. * * It names the FIX, not just the limit, the same shape used on the staff page: the reader's * next question is always *"so how do I see the rest?"*. */ export interface SchedulesScopeCopy { /** The page subtitle. The org-wide promise is WITHDRAWN when narrowed, never left standing. */ subtitle: string; /** The calendar's own empty state. */ noEvents: string; /** Whether to render the explanatory notice. */ showNotice: boolean; /** The line above the list. Own scope has no other staff member for an entry to focus on. */ listHint?: string; } const ORG_WIDE_SUBTITLE = 'Working hours, locations and time off.'; /** * THE SENTENCE THIS CARD EXISTS TO DELETE. Exported so the guard can assert it never reaches a * narrowed reader, rather than the test carrying its own copy of the string and drifting from the page. */ export const PRACTICE_WIDE_EMPTY_SENTENCE = 'No staff schedules found for this period.'; /** * `mayViewAny` IS THE HALF THIS FUNCTION USED TO ASSUME, and the assumption was false. * * The scope answers WHOSE schedules; it says nothing about whether the reader may see any. A stored * profile of `{ schedules: level none, scope all }` is a real shape - measured on Great River's HR * role - and it arrived here as `onlyOwnSchedules: false`, which rendered the org-wide subtitle and * `PRACTICE_WIDE_EMPTY_SENTENCE` at somebody entitled to nothing. * * That is the exact sentence the comment above calls the one this module exists to delete, reaching * a narrowed reader through the LEVEL rather than through the scope. A false claim about the practice * is worse than a narrow one about the reader, so `none` gets its own words rather than borrowing * `own`'s - "only your own" is also untrue when the answer is none. */ export const schedulesScopeCopy = ( onlyOwnSchedules: boolean, mayViewAny = true, ): SchedulesScopeCopy => !mayViewAny ? { subtitle: 'Your role cannot see staff schedules.', noEvents: 'Your role cannot see staff schedules, so none are listed here.', showNotice: true, listHint: 'Working hours do not indicate free appointment slots.', } : onlyOwnSchedules ? { subtitle: 'Your role can see only your own schedules.', noEvents: 'You have no schedules of your own for this period.', showNotice: true, // No hint: the practitioner deck draws none under your own week, and each row already says Bookable. } : { subtitle: ORG_WIDE_SUBTITLE, noEvents: PRACTICE_WIDE_EMPTY_SENTENCE, showNotice: false, listHint: 'Select an entry to focus on that staff member. Working hours do not indicate free appointment slots.', }; // admin "A day with nobody on it": an empty day names itself, so a quiet Sunday never reads like a // page that lost its data. The date is drawn in the viewer's own timezone. export const emptyDaySentence = (day: Date): string => `Nobody is scheduled on ${day.toLocaleDateString('en-GB', { weekday: 'long', day: 'numeric', month: 'long' }).replace(',', '')}`; export const EMPTY_DAY_HINT = 'Coverage is set on each person’s own schedule, under Staff.'; export const scheduleEntriesLabel = (count: number): string => count === 0 ? 'no schedule entries' : `${count} ${count === 1 ? 'schedule entry' : 'schedule entries'}`;