// On-call: who is on duty now, the week around it, and your own next shifts. // Read-only for now; the TUI edits the schedule. import * as api from './api.js'; import { h, clear, icon, spinner } from './ui.js'; import { isoDate, mondayOf, addDays, isoWeek, initial } from './format.js'; import { myID } from './state.js'; const view = () => document.getElementById('view-oncall'); let weekStart = mondayOf(new Date()); let data = null; let error = null; const dayName = new Intl.DateTimeFormat(undefined, { weekday: 'short' }); const dayDate = new Intl.DateTimeFormat(undefined, { day: 'numeric', month: 'short' }); export function show() { if (!data) clear(view(), spinner()); refresh(); } export async function refresh() { const start = weekStart; const today = new Date(); try { const [now, week, upcoming] = await Promise.all([ api.onCallNow(), api.schedule(isoDate(start), isoDate(addDays(start, 6))), api.schedule(isoDate(today), isoDate(addDays(today, 60))), ]); if (start !== weekStart) return; data = { now, week, upcoming }; error = null; } catch (err) { error = err.message; } render(); } function shiftWeek(n) { weekStart = addDays(weekStart, 7 * n); refresh(); } function render() { if (!data) { clear(view(), error ? h('div', { class: 'load-error', text: error }) : spinner()); return; } clear(view(), error && h('div', { class: 'load-error', text: `Showing older data: ${error}` }), nowCard(), weekCard(), myShifts(), ); } function you(userID) { return userID === myID() ? h('span', { class: 'you', text: 'you' }) : null; } function nowCard() { const n = data.now; return h('div', { class: 'card now-card' }, h('div', { class: `avatar ${n ? '' : 'none'}`, text: n ? initial(n.username) : '–' }), h('div', {}, h('div', { class: 'now-label', text: 'On call now' }), h('div', { class: 'now-name' }, n ? n.username : 'Nobody', n && you(n.user_id)), ), ); } function weekCard() { const byDate = new Map(data.week.map((e) => [e.date, e])); const today = isoDate(new Date()); const days = []; for (let i = 0; i < 7; i++) { const d = addDays(weekStart, i); const key = isoDate(d); const e = byDate.get(key); days.push(h('li', { class: `day ${key === today ? 'today' : ''} ${key < today ? 'past' : ''}` }, h('span', { class: 'day-name', text: dayName.format(d) }), h('span', { class: 'day-date', text: dayDate.format(d) }), h('span', { class: `day-who ${e ? '' : 'nobody'}` }, e ? e.username : 'nobody', e && you(e.user_id)), )); } const thisWeek = isoDate(weekStart) === isoDate(mondayOf(new Date())); return [ h('div', { class: 'page-head' }, h('h2', { text: thisWeek ? 'This week' : 'Week' }), h('div', { class: 'week-nav' }, h('button', { class: 'btn btn-ghost btn-icon', type: 'button', 'aria-label': 'Previous week', onclick: () => shiftWeek(-1) }, icon('chevronLeft')), h('button', { class: 'btn btn-ghost label', type: 'button', title: 'Back to this week', onclick: () => { weekStart = mondayOf(new Date()); refresh(); }, text: `Week ${isoWeek(weekStart)}`, }), h('button', { class: 'btn btn-ghost btn-icon', type: 'button', 'aria-label': 'Next week', onclick: () => shiftWeek(1) }, icon('chevronRight')), ), ), h('ul', { class: 'card days' }, days), ]; } // myShifts groups your upcoming dates into runs of consecutive days. function myShifts() { const mine = data.upcoming.filter((e) => e.user_id === myID()).map((e) => e.date).sort(); const runs = []; for (const date of mine) { const last = runs[runs.length - 1]; if (last && isoDate(addDays(parse(last.to), 1)) === date) last.to = date; else runs.push({ from: date, to: date }); } const fmt = (s) => `${dayName.format(parse(s))} ${dayDate.format(parse(s))}`; return [ h('div', { class: 'page-head' }, h('h2', { text: 'Your next shifts' })), h('div', { class: 'card' }, runs.length ? h('ul', { class: 'shift-list' }, runs.slice(0, 8).map((r) => h('li', {}, h('span', { text: r.from === r.to ? fmt(r.from) : `${fmt(r.from)} – ${fmt(r.to)}` }), h('span', { class: 'muted', text: days(r) })), )) : h('div', { class: 'empty', text: 'Nothing scheduled in the next 60 days.' })), ]; } function parse(s) { const [y, m, d] = s.split('-').map(Number); return new Date(y, m - 1, d); } function days(r) { const n = Math.round((parse(r.to) - parse(r.from)) / 86400000) + 1; return n === 1 ? '1 day' : `${n} days`; }