1import{c as L,j as e,R as P,C as T,o as F,B as o,y as k,F as m,z as C,k as u,N as h,a as i}from"./index-DykHQTdy.js";import{a as c}from"./react-ai5N7DV-.js";import{S as U,R as r,O as j,a as V,m as S}from"./shared-BozaAQX4.js";import"./motion-BDgCEaEF.js";function M({me:x}){const[n,A]=c.useState(null),[y,g]=c.useState(null),[D,v]=c.useState(null),[t,_]=c.useState(null),[s,w]=c.useState(null),N=new Date().getFullYear(),[p,z]=c.useState(`${N}-01-01`),[f,E]=c.useState(`${N}-12-31`),I=L(x==null?void 0:x.permissions,"lessons.manage"),$=()=>i.calendar.status().then(A).catch(a=>g(S(a)));c.useEffect(()=>{$()},[]);async function d(a,l){g(null),v(null),_(a);try{v(await l()),await $()}catch(O){g(S(O))}finally{_(null)}}async function R(a){a.preventDefault();const l=new FormData(a.currentTarget);await d("save",async()=>(await i.calendar.configure({calendar_id:String(l.get("calendar_id")??"").trim(),timezone:String(l.get("timezone")??"").trim()||null,auto_sync:l.get("auto_sync")==="on",impersonate:String(l.get("impersonate")??"").trim()}),"Calendar settings saved."))}const b="font-mono [color-scheme:light_dark]";return e.jsx(P,{delay:.05,children:e.jsxs(T,{id:"calendar",className:U,children:[e.jsxs("div",{className:"flex items-start justify-between gap-3",children:[e.jsx(F,{eyebrow:"Timetable · Google Calendar",children:"Every lesson session as a calendar event"}),n&&(n.ready?e.jsx(o,{tone:"success",pulse:!0,children:"ready"}):e.jsx(o,{tone:"warning",children:"needs setup"}))]}),!n&&!y&&e.jsx("p",{className:"text-sm text-fg-muted",children:"Loadingâ¦"}),n&&e.jsxs("div",{className:"space-y-5",children:[e.jsxs(k,{as:"form",onSubmit:R,cols:"sm:grid-cols-2 xl:grid-cols-4",children:[e.jsx(m,{label:"Calendar ID",name:"calendar_id",defaultValue:n.calendar_id??"",placeholder:"[email protected]",hint:"Google Calendar â Settings and sharing â Calendar ID"}),e.jsx(m,{label:"Timezone",name:"timezone",defaultValue:n.timezone}),e.jsx(m,{label:"Act as (Workspace user)",name:"impersonate",defaultValue:n.impersonating??"",placeholder:"[email protected]",hint:"Only when 'Make changes to events' is greyed out for the account: domain-wide delegation, docs/calendar.md"}),e.jsxs(C,{className:"gap-3",children:[e.jsxs("label",{className:"flex h-11 items-center gap-2 text-sm text-fg",children:[e.jsx("input",{type:"checkbox",name:"auto_sync",defaultChecked:n.auto_sync,className:"accent-[var(--accent)]"})," push on every change"]}),e.jsx(u,{type:"submit",disabled:t!==null,children:t==="save"?"Savingâ¦":"Save"})]})]}),e.jsxs("dl",{className:"grid gap-x-8 gap-y-2 text-sm sm:grid-cols-2",children:[e.jsxs(r,{k:"Key",children:[n.secret_in_vault?e.jsx(j,{children:"in Vault"}):e.jsx(o,{tone:"danger",children:"missing"}),e.jsxs("span",{className:"ml-2 text-xs text-fg-muted",children:["Service-account JSON: ",e.jsx(V,{name:"GOOGLE_CALENDAR_SERVICE_ACCOUNT"})]})]}),e.jsx(r,{k:"Account",children:n.service_account_email?e.jsxs("span",{className:"break-all font-mono text-xs",children:[n.service_account_email,n.impersonating?e.jsxs("span",{className:"text-fg-muted",children:[" acting as ",n.impersonating]}):null]}):e.jsx("span",{className:"text-fg-muted",children:"â"})}),e.jsxs(r,{k:"Client ID",children:[n.service_account_client_id?e.jsx("span",{className:"break-all font-mono text-xs",children:n.service_account_client_id}):e.jsx("span",{className:"text-fg-muted",children:"â"}),e.jsx("span",{className:"ml-2 text-xs text-fg-muted",children:"for Admin console â Domain-wide delegation"})]}),e.jsx(r,{k:"Calendar",children:n.calendar?e.jsxs(j,{children:[n.calendar.summary," · ",n.calendar.timeZone]}):e.jsx(o,{tone:"warning",children:"not reachable"})}),e.jsx(r,{k:"Access",children:n.access_role?n.writable?e.jsxs(j,{children:[n.access_role,": can write events"]}):e.jsxs(o,{tone:"danger",children:[n.access_role,": read only"]}):e.jsx("span",{className:"text-fg-muted",children:"â"})}),e.jsxs(r,{k:"Upcoming",children:[n.upcoming.synced," on the calendar · ",n.upcoming.unsynced," not yet"]}),e.jsx(r,{k:"Last pull",children:n.last_pull?`${new Date(n.last_pull.at).toLocaleStr
1ing("en-AU")} · ${n.last_pull.events} events, ${n.last_pull.changed} changes${n.incremental?" · incremental":""}`:e.jsx("span",{className:"text-fg-muted",children:"never; calendar edits reach the timetable on pull"})})]}),n.error&&e.jsx(h,{tone:"danger",children:n.error}),!n.error&&n.last_failure&&e.jsxs(h,{tone:"warning",children:["Last push failed ",new Date(n.last_failure.at).toLocaleString("en-AU"),n.last_failure.action?` (${n.last_failure.action})`:"",": ",n.last_failure.error]}),!n.secret_in_vault&&e.jsx("p",{className:"text-xs text-fg-muted",children:"Setup takes ten minutes: enable the Calendar API, create a service account, share the calendar with it, then add the key here. Runbook: docs/calendar.md."}),I&&n.ready&&e.jsxs("div",{className:"space-y-3 border-t border-line pt-4",children:[e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.jsx(u,{variant:"secondary",disabled:t!==null,onClick:()=>d("sync",async()=>{const a=await i.calendar.sync();return`${a.pushed} pushed, ${a.removed} removed${a.failed.length?`, ${a.failed.length} failed: ${a.failed[0].error}`:""} (${a.from} â ${a.to}).`}),children:t==="sync"?"Syncingâ¦":"Push next 90 days"}),e.jsx(u,{variant:"secondary",disabled:t!==null,onClick:()=>d("pull",async()=>{const a=await i.calendar.pull(),l=a.moved.length+a.cancelled.length+a.created.length;return`${a.events} calendar change${a.events===1?"":"s"} read: ${a.moved.length} moved, ${a.cancelled.length} cancelled, ${a.created.length} new session${a.created.length===1?"":"s"}${a.pushed_back.length?`, ${a.pushed_back.length} pushed back (portal was newer)`:""}${a.conflicts.length?`, ${a.conflicts.length} conflict${a.conflicts.length===1?"":"s"}: ${a.conflicts[0].lesson_code} ${a.conflicts[0].on}`:""}${a.errors.length?`, ${a.errors.length} failed`:""}${l===0?" (nothing to apply)":""}.`}),children:t==="pull"?"Pullingâ¦":"Pull calendar changes"}),e.jsx("span",{className:"text-xs text-fg-muted",children:"Portal writes push at once; edits made on the calendar land on pull (also run on a schedule)."})]}),e.jsxs(k,{children:[e.jsx(m,{label:"Import from",type:"date",size:"sm",value:p,onChange:a=>z(a.target.value),className:b}),e.jsx(m,{label:"to",type:"date",size:"sm",value:f,onChange:a=>E(a.target.value),className:b}),e.jsxs(C,{children:[e.jsx(u,{variant:"secondary",size:"sm",disabled:t!==null,onClick:()=>d("dry",async()=>{const a=await i.calendar.import({start:p,end:f,dry_run:!0});return w(a),`Preview: ${a.created.length} would be created, ${a.linked.length} linked, ${a.unmatched.length} unmatched.`}),children:"Preview import"}),e.jsx(u,{variant:"ghost",size:"sm",disabled:t!==null||!s||s.dry_run===!1,onClick:()=>{confirm("Import matched events into the timetable?")&&d("imp",async()=>{const a=await i.calendar.import({start:p,end:f});return w(a),`${a.created.length} created, ${a.linked.length} linked, ${a.unmatched.length} unmatched.`})},children:"Import"})]})]}),(s==null?void 0:s.hint)&&e.jsx(h,{tone:"warning",children:s.hint}),s&&s.unmatched.length>0&&e.jsxs("div",{className:"text-xs",children:[e.jsx("div",{className:"mb-1 font-medium text-fg",children:"Unmatched events (no lesson code, or code unknown): create the lesson, then import again."}),e.jsx("ul",{className:"max-h-40 space-y-0.5 overflow-y-auto text-fg-muted",children:s.unmatched.slice(0,50).map(a=>e.jsxs("li",{children:[e.jsx("span",{className:"font-mono",children:a.start.slice(0,16)})," ",a.summary||"(no title visible)"]},a.event_id))})]})]})]}),e.jsx(h,{tone:"danger",className:"mt-3",children:y}),e.jsx(h,{tone:"success",className:"mt-3",children:D}),e.jsxs("p",{className:"mt-3 text-xs text-fg-muted",children:["Same via ",e.jsx("code",{className:"font-mono",children:"artin calendar status|set|sync|pull|import"}),"."]})]})})}export{M as CalendarCard};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.