// Calendar: month grid of items by a date (or timeline) column; drag to reschedule. import { html, useState } from '../vendor/preact-htm.mjs'; import { api } from './api.js'; import { navigate, boardPath, mutateBoard } from './store.js'; import { Icon, ymd, parseDate, todayStr, textOn, Empty, useMobile } from './ui.js'; import { labelOf } from './cells.js'; import * as A from './actions.js'; export function CalendarView({ board, view, items, subs }) { const boardId = board.board.id; const mobile = useMobile(); const dateCols = board.columns.filter((c) => c.type === 'date' || c.type === 'timeline'); const col = dateCols.find((c) => c.id === view.settings?.column) || dateCols[0]; const status = board.columns.find((c) => c.type === 'status'); const [month, setMonth] = useState(() => { const d = new Date(); return new Date(d.getFullYear(), d.getMonth(), 1); }); const [drag, setDrag] = useState(null); if (!col) return html`<${Empty} icon="calendar" title="Calendar needs a date column">/>`; const all = [...items, ...items.flatMap((i) => subs[i.id] || [])]; const byDay = {}; for (const it of all) { const v = it.vals[col.id]; if (!v) continue; if (col.type === 'date') (byDay[String(v).slice(0, 10)] ||= []).push(it); else { const a = parseDate(v.from); const b = parseDate(v.to || v.from); for (let d = new Date(a); d <= b && (d - a) < 400 * 86400000; d.setDate(d.getDate() + 1)) (byDay[ymd(d)] ||= []).push(it); } } const start = new Date(month); start.setDate(1 - start.getDay()); const days = Array.from({ length: 42 }, (_, i) => { const d = new Date(start); d.setDate(start.getDate() + i); return d; }); const setCol = (id) => { mutateBoard(boardId, () => { view.settings = { ...view.settings, column: id }; }); api.patch(`/api/views/${view.id}`, { settings: { ...view.settings, column: id } }); }; const drop = (day) => { if (!drag) return; const v = drag.vals[col.id]; if (col.type === 'date') A.setValue(boardId, drag.id, col.id, String(v || '').includes('T') ? `${day}T${String(v).split('T')[1]}` : day); else { const len = v?.from ? (parseDate(v.to) - parseDate(v.from)) : 0; const to = new Date(parseDate(day).getTime() + len); A.setValue(boardId, drag.id, col.id, { from: day, to: ymd(to) }); } setDrag(null); }; const chip = (it) => { const l = status && labelOf(status, it.vals[status.id]); return html``; }; if (mobile) { // Agenda list on phones. const keys = Object.keys(byDay).filter((k) => k.slice(0, 7) === ymd(month).slice(0, 7)).sort(); return html`
Nothing scheduled this month.
` : null}Drag items to reschedule ยท double-click a day to add an item