Phase 5: per-child dashboard (overdue first), item detail, delete
- internal/dashboard: classify items into overdue / due today / due this week /
no due date / messages; render the plain-text brief (FR-6, FR-7, FR-9).
- internal/store: ItemsForChild, ItemByID, DeleteItemByID, ScanSummaryForChild
and the stored-brief CRUD helpers (FR-10).
- internal/web: routes /?child=, /items/{id}, POST /items/{id}/delete,
POST /summaries/build, POST /summaries/{id}/delete, plus a per-child scan
banner (FR-12) and a phone-first server-rendered template.
- Calendar events are never "overdue": a past lesson is history, not work.
- cmd/school brief build|list; scripts/screenshot-phase5.mjs for phone evidence.
- Tests: bucket classification, stored-brief round-trip, and HTTP routes
including the NO SCAN / SCAN FAILED / healthy banner behaviour.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -29,8 +29,8 @@
|
||||
<main class="container">
|
||||
<header class="page-head">
|
||||
<h1 class="heading-1">What is due</h1>
|
||||
<p class="page-head__sub">Overdue work first. Compass for the schedule, Teams for homework.
|
||||
This is Phase 1: the skeleton is live, the school scan is not connected yet.</p>
|
||||
<p class="page-head__sub">Overdue first. Compass for the schedule and notices,
|
||||
Teams for homework.</p>
|
||||
</header>
|
||||
|
||||
{{if .ErrMsg}}<p class="flash flash--error" role="alert">{{.ErrMsg}}</p>{{end}}
|
||||
@@ -39,32 +39,116 @@
|
||||
{{template "scan-status.html" .Scan}}
|
||||
</div>
|
||||
|
||||
<section class="section" aria-labelledby="children-title">
|
||||
<div class="section__head">
|
||||
<h2 id="children-title" class="heading-2">Children</h2>
|
||||
<span class="category-pill"><span class="dot dot--faint"></span>{{len .Children}} on file</span>
|
||||
{{if .Tabs}}
|
||||
<nav class="child-tabs" aria-label="Children">
|
||||
{{range .Tabs}}
|
||||
<a class="child-tab{{if .Active}} child-tab--active{{end}}" href="/?child={{.ID}}"
|
||||
{{if .Active}}aria-current="page"{{end}}>
|
||||
{{.Name}}
|
||||
{{if .Overdue}}<span class="child-tab__badge">{{.Overdue}}</span>{{end}}
|
||||
</a>
|
||||
{{end}}
|
||||
</nav>
|
||||
{{end}}
|
||||
|
||||
{{if .HasChild}}
|
||||
<section class="child" aria-labelledby="child-title">
|
||||
<div class="child__head">
|
||||
<div>
|
||||
<h2 id="child-title" class="heading-2">{{.Child.Name}}</h2>
|
||||
<p class="child__meta">{{if .Child.Year}}Year {{.Child.Year}} · {{end}}{{.Child.Headline}}</p>
|
||||
</div>
|
||||
<span class="category-pill scan__pill scan__pill--{{.Child.Scan.Level}}">
|
||||
{{if eq .Child.Scan.Level "ok"}}scan healthy
|
||||
{{else if eq .Child.Scan.Level "error"}}scan failed
|
||||
{{else}}no scan yet{{end}}
|
||||
</span>
|
||||
</div>
|
||||
{{if .Children}}
|
||||
<ul class="child-list">
|
||||
{{range .Children}}
|
||||
<li class="child-card">
|
||||
<p class="child-card__name">{{.Name}}</p>
|
||||
<p class="child-card__meta">
|
||||
{{if .Year}}Year {{.Year}}{{else}}Year not set{{end}}
|
||||
{{if not .CompassID}}· Compass ID not set{{end}}
|
||||
</p>
|
||||
<p class="child-card__body">No items stored yet. The Compass scan arrives in Phase 2.</p>
|
||||
|
||||
{{if not .Child.Scan.OK}}
|
||||
<p class="flash flash--error" role="alert">
|
||||
{{.Child.Scan.Message}} The lists below may be incomplete — this is not a clean
|
||||
“nothing due”.
|
||||
</p>
|
||||
{{end}}
|
||||
|
||||
{{range .Child.Sections}}
|
||||
<section class="section" id="sec-{{.Key}}" aria-labelledby="sec-{{.Key}}-title">
|
||||
<div class="section__head">
|
||||
<h3 id="sec-{{.Key}}-title" class="heading-3{{if eq .Tone "danger"}} heading-3--danger{{end}}">{{.Title}}</h3>
|
||||
<span class="category-pill"><span class="dot{{if eq .Tone "danger"}} dot--danger{{end}}"></span>{{len .Items}}</span>
|
||||
</div>
|
||||
{{if .Items}}
|
||||
<ul class="item-list">
|
||||
{{range .Items}}
|
||||
<li class="item{{if eq .StatusLabel "Overdue"}} item--overdue{{end}}" id="item-{{.ID}}">
|
||||
<div class="item__body">
|
||||
<p class="item__title"><a href="/items/{{.ID}}">{{.Title}}</a></p>
|
||||
<p class="item__meta">
|
||||
<span class="tag tag--type">{{.TypeLabel}}</span>
|
||||
{{if .Subject}}<span class="item__subject">{{.Subject}}</span>{{end}}
|
||||
{{if .DueLabel}}<span class="item__due">due {{.DueLabel}}</span>{{end}}
|
||||
{{if .StatusLabel}}<span class="tag tag--status">{{.StatusLabel}}</span>{{end}}
|
||||
</p>
|
||||
</div>
|
||||
<form class="item__actions" method="post" action="/items/{{.ID}}/delete"
|
||||
hx-post="/items/{{.ID}}/delete" hx-target="closest .item" hx-swap="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>
|
||||
</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
{{else}}
|
||||
<div class="empty-state">
|
||||
<p class="empty-state__title">No children on file</p>
|
||||
<p class="empty-state__body">Harry and Finn are seeded on first start. If this is empty,
|
||||
the database was not seeded.</p>
|
||||
</div>
|
||||
{{else}}
|
||||
<p class="section__empty">{{.Empty}}</p>
|
||||
{{end}}
|
||||
</section>
|
||||
{{end}}
|
||||
|
||||
<div class="child__foot">
|
||||
{{if .Child.LaterCount}}<span>{{.Child.LaterCount}} due later</span>{{end}}
|
||||
{{if .Child.DoneCount}}<span>{{.Child.DoneCount}} submitted</span>{{end}}
|
||||
<form method="post" action="/summaries/build" class="inline-form">
|
||||
<input type="hidden" name="csrf" value="{{.CSRF}}">
|
||||
<input type="hidden" name="child" value="{{.Child.ID}}">
|
||||
<button class="button-utility" type="submit">Build {{.Child.Name}}'s brief (draft)</button>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
{{else}}
|
||||
<div class="empty-state">
|
||||
<p class="empty-state__title">No children on file</p>
|
||||
<p class="empty-state__body">Harry and Finn are seeded on first start. If this is empty,
|
||||
the database was not seeded.</p>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if .Summaries}}
|
||||
<section class="section" aria-labelledby="summaries-title">
|
||||
<div class="section__head">
|
||||
<h2 id="summaries-title" class="heading-2">Briefs</h2>
|
||||
<span class="category-pill"><span class="dot dot--faint"></span>{{len .Summaries}}</span>
|
||||
</div>
|
||||
<ul class="item-list">
|
||||
{{range .Summaries}}
|
||||
<li class="item" id="summary-{{.ID}}">
|
||||
<div class="item__body">
|
||||
<p class="item__title">{{.Recipient}} · {{.Sent}}</p>
|
||||
<p class="item__meta"><span class="item__subject">built {{.Created}}</span></p>
|
||||
<pre class="brief">{{.Text}}</pre>
|
||||
</div>
|
||||
<form class="item__actions" method="post" action="/summaries/{{.ID}}/delete"
|
||||
hx-post="/summaries/{{.ID}}/delete" hx-target="closest .item" hx-swap="delete"
|
||||
hx-confirm="Delete this brief?">
|
||||
<input type="hidden" name="csrf" value="{{$.CSRF}}">
|
||||
<button class="button-utility button-utility--danger" type="submit">Delete</button>
|
||||
</form>
|
||||
</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
</section>
|
||||
{{end}}
|
||||
</main>
|
||||
|
||||
<footer class="app-foot">
|
||||
|
||||
81
internal/web/templates/item.html
Normal file
81
internal/web/templates/item.html
Normal file
@@ -0,0 +1,81 @@
|
||||
{{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">
|
||||
<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}}
|
||||
{{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}}
|
||||
<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}}
|
||||
Reference in New Issue
Block a user