Add the Home Assistant REST calendar client and the idempotent sync that writes important school events to Sam's Home Assistant calendar (FR-8). The school stays read-only; the only write is to the calendar (rule 7). - internal/calendar: HA REST client (states, create_event, get_events, delete_event) and the sync that selects important future events, adopts an existing event by its description marker, records the HA event id on the item, and records every attempt. - internal/store: migration 003 (items.ha_event_uid, items.ha_pushed_at and the append-only calendar_pushes table), candidates, mark-synced and the push log. - internal/parse: a school event is important; a routine class period is not. - cmd/school: `school calendar entities|status|push|events`. - web: an "Add to calendar" item action and a fail-loud calendar banner, so a failed write is never swallowed (FR-12). - scripts/calendar-push.sh, Makefile target, env example, docs and tests. The target is configurable (SCHOOL_HA_CALENDAR_ENTITY). Home Assistant has no non-Google writable calendar today, so the entity is asked on AUD-110. Co-Authored-By: Paperclip <noreply@paperclip.ing>
94 lines
4.0 KiB
HTML
94 lines
4.0 KiB
HTML
{{define "item.html"}}<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<meta name="color-scheme" content="light dark">
|
|
<title>{{.Title}} — Compass / Teams</title>
|
|
<link rel="stylesheet" href="/static/tokens.css">
|
|
<link rel="stylesheet" href="/static/app.css">
|
|
<script src="/static/htmx.min.js" defer></script>
|
|
<script src="/static/theme.js" defer></script>
|
|
</head>
|
|
<body class="app">
|
|
<nav class="nav-bar">
|
|
<a class="nav-bar__brand" href="/">
|
|
<span class="nav-bar__mark" aria-hidden="true"></span>
|
|
Compass / Teams
|
|
</a>
|
|
<div class="nav-bar__right">
|
|
<button class="button-utility" type="button" data-theme-toggle>Dark</button>
|
|
<span class="nav-bar__user">{{.Username}}</span>
|
|
<form method="post" action="/logout" class="nav-bar__logout">
|
|
<input type="hidden" name="csrf" value="{{.CSRF}}">
|
|
<button class="button-utility" type="submit">Sign out</button>
|
|
</form>
|
|
</div>
|
|
</nav>
|
|
|
|
<main class="container">
|
|
<p class="breadcrumb"><a href="/?child={{.Child.ID}}">← {{.Child.Name}}</a></p>
|
|
|
|
<article class="detail">
|
|
{{if .Flash}}<p class="flash flash--{{if eq .FlashLevel "ok"}}ok{{else}}error{{end}}" role="alert">{{.Flash}}</p>{{end}}
|
|
<header class="detail__head">
|
|
<h1 class="heading-1">{{.Item.Title}}</h1>
|
|
<p class="detail__meta">
|
|
<span class="tag tag--type">{{.Item.TypeLabel}}</span>
|
|
{{if .Item.Subject}}<span>{{.Item.Subject}}</span>{{end}}
|
|
{{if .Item.Teacher}}<span>Teacher: {{.Item.Teacher}}</span>{{end}}
|
|
{{if .Item.DueLabel}}<span class="item__due">due {{.Item.DueLabel}}</span>{{end}}
|
|
{{if .Item.StatusLabel}}<span class="tag tag--status">{{.Item.StatusLabel}}</span>{{end}}
|
|
</p>
|
|
</header>
|
|
|
|
{{if .Item.BodyLines}}
|
|
<section class="detail__body">
|
|
{{range .Item.BodyLines}}<p>{{.}}</p>{{end}}
|
|
</section>
|
|
{{end}}
|
|
|
|
<dl class="detail__facts">
|
|
<dt>Child</dt><dd>{{.Child.Name}}</dd>
|
|
<dt>Source</dt><dd>{{.Item.Source}}{{if .Item.SourcePage}} · {{.Item.SourcePage}}{{end}}</dd>
|
|
{{if .Item.Importance}}<dt>Importance</dt><dd>{{.Item.Importance}}</dd>{{end}}
|
|
<dt>Calendar</dt>
|
|
<dd>{{if .Calendar.InCalendar}}in the calendar{{if .Calendar.PushedAt}} · {{.Calendar.PushedAt}}{{end}}{{if .Calendar.UID}} · {{.Calendar.UID}}{{end}}
|
|
{{else if not .Calendar.Configured}}not configured
|
|
{{else if .Calendar.Eligible}}not written yet
|
|
{{else}}not a calendar item{{end}}</dd>
|
|
{{if .Item.ExternalRef}}<dt>Reference</dt><dd>{{.Item.ExternalRef}}</dd>{{end}}
|
|
{{if .Item.DueAt}}<dt>Due</dt><dd>{{.Item.DueLabel}}</dd>{{end}}
|
|
</dl>
|
|
|
|
<div class="detail__actions">
|
|
{{if .Item.URL}}<a class="button-primary" href="{{.Item.URL}}" rel="noreferrer noopener">Open in Compass</a>{{end}}
|
|
{{if and .Calendar.Configured .Calendar.Eligible (not .Calendar.InCalendar)}}
|
|
<form method="post" action="/items/{{.Item.ID}}/calendar">
|
|
<input type="hidden" name="csrf" value="{{.CSRF}}">
|
|
<button class="button-primary" type="submit">Add to calendar</button>
|
|
</form>
|
|
{{end}}
|
|
<form method="post" action="/items/{{.Item.ID}}/delete"
|
|
hx-post="/items/{{.Item.ID}}/delete" hx-confirm="Delete this item? It returns on the next scan if the school still lists it.">
|
|
<input type="hidden" name="csrf" value="{{.CSRF}}">
|
|
<button class="button-utility button-utility--danger" type="submit">Delete</button>
|
|
</form>
|
|
</div>
|
|
|
|
{{if not .Scan.OK}}
|
|
<p class="flash flash--error" role="alert">{{.Scan.Message}}</p>
|
|
{{end}}
|
|
</article>
|
|
</main>
|
|
|
|
<footer class="app-foot">
|
|
<span>Compass / Teams · {{.Version}} · {{.AppEnv}}</span>
|
|
<span class="app-foot__sep">·</span>
|
|
<span>up {{.ServerUp}}</span>
|
|
<span class="app-foot__sep">·</span>
|
|
<span>{{year}} Family Home Lab</span>
|
|
</footer>
|
|
</body>
|
|
</html>{{end}}
|