// Agenda en disposition bureau : la semaine en sept colonnes. // // L'écran de l'agenda garde tout ce qu'il sait faire : sa navigation de période, // son sélecteur de vue, son périmètre, ses salles, sa création et sa fiche. Seul // le corps des vues est recomposé ici. Les calculs ne sont pas réécrits : les // horaires, les positions, les chevauchements et les bornes de journée viennent // des fonctions pures de `window.VascoAgendaGeneral`, celles-là mêmes que la // timeline du téléphone utilise. // // Sous le seuil de bureau, chaque vue rend exactement la version enveloppée // précédente : le téléphone ne change pas d'un pixel. const VASCO_AGENDA_DESKTOP_WEEK = WeekView; // Le périmètre affiché et les congés lus par l’écran du téléphone : le bureau // les relit, il ne les recalcule pas. Le repli sert aux harnais qui montent // cette disposition sans l’écran Agenda. const VASCO_AGENDA_DESKTOP_SCOPE = window.VascoAgendaScope || { Context: React.createContext(null), visibleAbsences: () => [] }; // Une heure de bureau est plus courte qu'une heure de téléphone : sept journées // côte à côte n'ont pas la place d'en montrer une de 120 px. La géométrie reste // celle du contrat, à une hauteur d'heure près. function vascoAgendaDesktopGeometry() { return window.VascoAgendaGeneral.timelineGeometry( window.VascoUI.tokens.layout.agenda.hourHeight, ); } function vascoAgendaDesktopNow() { // Même règle que l'écran du téléphone : l'instant vient d'ici, le calcul du // contrat. Un contrat exécuté ailleurs ne décide pas de l'heure qu'il est. const timeZone = window.VASCO_RUNTIME?.dashboard?.time_zone || undefined; let formatter = null; try { formatter = new Intl.DateTimeFormat('en-CA', { timeZone, year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', hourCycle: 'h23', }); } catch (failure) { return null; } return window.VascoAgendaGeneral.nowInZone(timeZone, new Date(), formatter); } function vascoAgendaDesktopDayKey(date) { const value = date instanceof Date ? date : new Date(date); if (Number.isNaN(value.getTime())) return null; return `${value.getFullYear()}-${String(value.getMonth() + 1).padStart(2, '0')}-${String(value.getDate()).padStart(2, '0')}`; } function vascoAgendaDesktopWeekDays(date) { const monday = new Date(date instanceof Date ? date.getTime() : Date.now()); monday.setHours(0, 0, 0, 0); // Lundi comme premier jour, comme la bande de jours du téléphone. monday.setDate(monday.getDate() - ((monday.getDay() + 6) % 7)); return Array.from({ length: 7 }, (unused, index) => { const day = new Date(monday.getTime()); day.setDate(monday.getDate() + index); return day; }); } // L'étiquette d'un rendez-vous : ce que la carte du téléphone porte déjà. function vascoAgendaDesktopLabel(rdv) { return [rdv?.patient, rdv?.notes, rdv?.lieu].filter(Boolean).join(', '); } function VascoAgendaDesktopDayHeader({ date, dayKey, todayKey, selected, onSelect, count }) { const tokens = window.VascoUI.tokens; const { InteractiveRow } = window.VascoUI; const today = dayKey === todayKey; const weekday = new Intl.DateTimeFormat('fr-FR', { weekday: 'short' }).format(date); return onSelect(new Date(date.getTime()))} style={{ width: '100%', minHeight: tokens.layout.agenda.dayHeaderHeight, padding: `0 ${tokens.space.xs}px`, border: 'none', borderRadius: 0, background: 'transparent', justifyContent: 'center', gap: tokens.space.sm, }}> {weekday.toUpperCase()} {date.getDate()} {count > 0 && {count}} ; } // Une colonne de journée : les blocs déjà placés par le contrat, rendus par la // primitive officielle. Aucune règle d'horaire n'est réécrite ici. function vascoAgendaDesktopColumn({ date, rdvs, geometry, todayKey, selectedKey, onSelect, onSelectDay, absentKeys, }) { const contract = window.VascoAgendaGeneral; const { CalendarEvent } = window.VascoUI; const dayKey = vascoAgendaDesktopDayKey(date); const { timed } = contract.splitDayEvents(rdvs, dayKey); const laid = contract.layoutTimelineEvents(timed); const source = new Map(); (Array.isArray(rdvs) ? rdvs : []).forEach((rdv) => { if (rdv) source.set(rdv.id, rdv); }); // `TYPE_STYLE` est une liaison globale de la référence, pas une propriété // de `window` : la teinte d'un type reste celle que l'agenda emploie déjà. const typeStyle = typeof TYPE_STYLE === 'object' && TYPE_STYLE ? TYPE_STYLE : {}; return { key: dayKey, muted: dayKey !== todayKey && (date.getDay() === 0 || date.getDay() === 6), // Une journée de congé : la primitive la hachure sous ses blocs et grise // son en-tête. Le calcul des journées couvertes reste au contrat. absent: Boolean(absentKeys && absentKeys.has(dayKey)), header: , children: laid.map((event) => { const top = contract.minutesToOffset(event.startMinutes, geometry); const bottom = contract.minutesToOffset(event.endMinutes, geometry); const range = contract.timelineRangeLabel(event.startMinutes, event.endMinutes - event.startMinutes); return onSelect(source.get(event.id) || event) : null} />; }), }; } function VascoAgendaDesktopGrid({ days, rdvs, selectedKey, onSelectRdv, onSelectDay, ariaLabel }) { const contract = window.VascoAgendaGeneral; const { TimeGrid } = window.VascoUI; const geometry = vascoAgendaDesktopGeometry(); const hours = contract.timelineHours(geometry); const now = vascoAgendaDesktopNow(); const todayKey = contract.todayKeyFrom( now?.dayKey, window.VASCO_RUNTIME?.dashboard?.local_date || null, ); const scope = React.useContext(VASCO_AGENDA_DESKTOP_SCOPE.Context); const absentKeys = contract.absenceDayKeySet( VASCO_AGENDA_DESKTOP_SCOPE.visibleAbsences(scope), ); const columns = days.map((date) => vascoAgendaDesktopColumn({ date, rdvs, geometry, todayKey, selectedKey, onSelect: onSelectRdv, onSelectDay, absentKeys, })); // Le repère d'heure courante n'a de sens que si aujourd'hui est affiché. const showsToday = columns.some((column) => column.key === todayKey); const nowOffset = showsToday && now && Number.isFinite(now.minutes) && now.minutes > geometry.startMinutes && now.minutes < geometry.endMinutes ? contract.minutesToOffset(now.minutes, geometry) : null; // La grille s'ouvre là où la journée a un sens : sur l'heure qu'il est, sur le // premier rendez-vous sinon, sur l'heure de cabinet en dernier recours. La // cible et le centrage sont ceux du contrat, pas une règle inventée ici. const scrollRef = React.useRef(null); const focusKey = selectedKey || (days[0] ? vascoAgendaDesktopDayKey(days[0]) : null); const focused = columns.find((column) => column.key === focusKey) || columns[0] || null; const target = contract.timelineScrollTarget({ events: contract.splitDayEvents(rdvs, focusKey).timed, geometry, dayKey: focusKey, todayKey, nowMinutes: now && now.dayKey === focusKey ? now.minutes : null, }); React.useEffect(() => { const node = scrollRef.current; if (!node || !focused) return; node.scrollTop = contract.centeredScrollTop( contract.minutesToOffset(target, geometry), node.clientHeight, Math.max(0, node.scrollHeight - node.clientHeight), ); }, [focusKey, target, geometry.height]); return ; } function VascoAgendaDesktopWeek({ date, setDate, rdvs, onSelectRdv }) { const tokens = window.VascoUI.tokens; const contract = window.VascoAgendaGeneral; const days = vascoAgendaDesktopWeekDays(date); // L'écran figé compte les rendez-vous du jour choisi sans le dire. Au bureau, // sept journées sont sous les yeux : le seul décompte qui ne prête pas à // confusion est celui de la période affichée, et il le dit. const weekCount = days.reduce( (total, day) => total + contract.splitDayEvents(rdvs, vascoAgendaDesktopDayKey(day)).timed.length, 0, ); // La semaine prend la hauteur que la page lui laisse : c'est la grille, et // elle seule, qui défile. Les en-têtes de jours vivent alors au-dessus de ce // défilement et ne le quittent jamais. return
{weekCount} {weekCount > 1 ? 'rendez-vous cette semaine' : 'rendez-vous cette semaine'}
; } WeekView = function VascoWeekViewForLayout(props) { if (window.VascoUI.useLayout() !== 'desktop') { return ; } return ; }; window.WeekView = WeekView; // Le mois n'est pas recomposé : sa grille de la référence est en `1fr` et // s'élargit sans rien casser, et elle ouvre déjà la journée choisie sous elle. // La recomposer reviendrait à réécrire ce qu'elle fait déjà bien.