import { api, protectedFetch } from "./api.js?v=__ASSET_VERSION__"; import { initSideNav, loadNavUser } from "./nav.js?v=__ASSET_VERSION__"; const root = document.getElementById("workspace-content"); const n = (tag, text = "", cls = "") => Object.assign(document.createElement(tag), {textContent:text,className:cls}); const link = (text, href, cls = "") => Object.assign(n("a",text,cls), {href}); const now = new Date(); const initial = new URLSearchParams(location.search).get("month"); let month = /^20\d{2}-(0[1-9]|1[0-2])$/.test(initial || "") ? initial : `${now.getFullYear()}-${String(now.getMonth()+1).padStart(2,"0")}`; let events = [], generation = 0; const labels = {roast:"Roasted",cupping:"Cupped",brew:"Brewed",plan:"Plan created"}; const toolbar = n("div","","calendar-toolbar"); const previous = n("button","← Previous","ghost-btn"), next = n("button","Next →","ghost-btn"), current = n("button","This month","ghost-btn"); const heading = n("h2"); const filter = n("select","","field-input"); filter.setAttribute("aria-label","Activity type"); for (const [value,label] of [["","All activity"],...Object.entries(labels)]) filter.append(Object.assign(n("option",label),{value})); toolbar.append(previous,heading,next,current,filter); const status = n("p","","muted"); status.setAttribute("role","status"); const content = n("div"); const shortcuts = n("div","","calendar-shortcuts"); shortcuts.append(link("Plan a roast","/plans","primary-btn"),link("Log a roast","/roasts","ghost-btn"),link("Cup coffee","/cupping","ghost-btn"),link("Log a brew","/brews","ghost-btn")); root.replaceChildren(shortcuts,toolbar,status,content); function render() { const selected = events.filter(e=>!filter.value || e.kind===filter.value); status.textContent = `${selected.length} ${selected.length===1?"activity":"activities"} this month`; content.replaceChildren(); const [year,m] = month.split("-").map(Number); heading.textContent = new Date(year,m-1,1).toLocaleDateString(undefined,{month:"long",year:"numeric"}); const grid = n("div","","activity-calendar"); for (const day of ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"]) grid.append(n("div",day,"calendar-weekday")); const offset = new Date(year,m-1,1).getDay(), count = new Date(year,m,0).getDate(); for(let i=0;ie.date===key)) { const a = link(labels[event.kind]+" · "+event.title,event.href,"calendar-event event-"+event.kind); a.title = a.textContent; cell.append(a); } grid.append(cell); } for(let i=(offset+count)%7;i>0 && i<7;i++) grid.append(n("div","","calendar-day calendar-padding")); content.append(grid); const agenda = n("section","","calendar-agenda"); agenda.append(n("h2","Activity log")); if(!selected.length) agenda.append(n("p","No activity this month. Log a roast, cupping or brew to see it here.")); let lastDate; for(const event of [...selected].reverse()) { if(event.date!==lastDate) { agenda.append(n("h3",new Date(event.date+"T12:00:00").toLocaleDateString(undefined,{weekday:"long",month:"short",day:"numeric"}))); lastDate=event.date; } const row = n("div","","calendar-log-row"); row.append(n("span",labels[event.kind],"calendar-kind event-"+event.kind),link(event.title,event.href)); if(event.planHref) row.append(link("View plan",event.planHref,"muted")); if(event.dateSource==="logged") row.append(n("small","Date logged","muted")); agenda.append(row); } content.append(agenda); } async function load() { const token = ++generation; filter.disabled = true; events = []; status.textContent="Loading activity…"; content.replaceChildren(); const [year,m] = month.split("-").map(Number); heading.textContent=new Date(year,m-1,1).toLocaleDateString(undefined,{month:"long",year:"numeric"}); previous.disabled=month==="2000-01"; next.disabled=month==="2099-12"; history.replaceState(null,"","/home?month="+month); try { const data = await api("/api/activity?"+new URLSearchParams({month,timeZone:Intl.DateTimeFormat().resolvedOptions().timeZone})); if(token!==generation)return; events=data.events; filter.disabled = false; render(); } catch(error) { if(token!==generation)return; status.textContent="Could not load activity. "+error.message; const retry=n("button","Retry","ghost-btn");retry.onclick=load;content.append(retry); } } function move(delta) { const [year,m]=month.split("-").map(Number), date=new Date(year,m-1+delta,1); month=`${date.getFullYear()}-${String(date.getMonth()+1).padStart(2,"0")}`; load(); } previous.onclick=()=>move(-1);next.onclick=()=>move(1); current.onclick=()=>{month=`${now.getFullYear()}-${String(now.getMonth()+1).padStart(2,"0")}`;load();}; filter.onchange=()=>{if(!status.textContent.startsWith("Loading"))render();}; initSideNav(); await loadNavUser(); document.getElementById("btn-logout").onclick=async()=>{await protectedFetch("/api/auth/logout",{method:"POST"});location.assign("/");}; await load();