Files
projax/web/templates/dashboard_section.tmpl
mAi 2eba37365b Merge branch 'mai/kahn/phase-5i-phase-a-design' (phase 5i slice A: project filter dim + descendants toggle)
# Conflicts:
#	web/dashboard.go
#	web/server.go
#	web/templates/dashboard_section.tmpl
2026-05-26 13:29:20 +02:00

272 lines
12 KiB
Cheetah
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{{define "dashboard-section"}}
<section id="dashboard-section" class="dashboard">
<section class="tagbar" id="dashboard-filterbar">
<form id="dashboard-filter" class="search"
hx-get="/dashboard{{if ne .View "tiles"}}?view={{.View}}{{end}}"
hx-target="#dashboard-section"
hx-swap="outerHTML"
hx-trigger="change from:select"
hx-push-url="true">
<label>tag&nbsp;
<select name="tag" multiple size="3">
{{$sel := .Filter.Tags}}
{{range $.Filter.Tags}}<option value="{{.}}" selected>{{.}}</option>{{end}}
</select>
</label>
<label>mgmt&nbsp;
<select name="mgmt" multiple size="4">
{{$selM := .Filter.Management}}
<option value="mai" {{if contains $selM "mai"}}selected{{end}}>mai</option>
<option value="self" {{if contains $selM "self"}}selected{{end}}>self</option>
<option value="external" {{if contains $selM "external"}}selected{{end}}>external</option>
</select>
</label>
<label>has&nbsp;
<select name="has" multiple size="2">
{{$selH := .Filter.HasLinks}}
<option value="caldav-list" {{if contains $selH "caldav-list"}}selected{{end}}>caldav</option>
<option value="gitea-repo" {{if contains $selH "gitea-repo"}}selected{{end}}>gitea</option>
</select>
</label>
{{if ne .View "tiles"}}<input type="hidden" name="view" value="{{.View}}">{{end}}
{{if .Filter.ProjectPath}}<input type="hidden" name="project" value="{{.Filter.ProjectPath}}">{{end}}
{{if and .Filter.ProjectPath (not .Filter.IncludeDescendants)}}<input type="hidden" name="project_descendants" value="0">{{end}}
{{if .Filter.Active}}<a class="clear" href="/dashboard{{if ne .View "tiles"}}?view={{.View}}{{end}}">clear filters</a>{{end}}
</form>
{{template "view-project-chip" .}}
<p class="counts muted">
{{if .P.Cached}}<small title="Served from 60s in-memory cache · built {{.P.BuiltAt.Format "15:04:05"}}">updated {{.UpdatedRel}} · cached</small>
{{else}}<small>updated {{.UpdatedRel}} · fresh</small>{{end}}
<a class="refresh" href="{{.RefreshURL}}"
hx-get="{{.RefreshURL}}"
hx-target="#dashboard-section"
hx-swap="outerHTML"
title="force-refresh: bust the 60s cache for this filter">↻ refresh</a>
</p>
</section>
<nav class="dash-tabs" aria-label="Dashboard view">
{{range .Tabs}}
<a href="{{.URL}}" class="dash-tab{{if .Active}} active{{end}}"
hx-get="{{.URL}}"
hx-target="#dashboard-section"
hx-swap="outerHTML"
hx-push-url="true">{{.Label}}</a>
{{end}}
{{if eq .View "tiles"}}
<a href="{{.ScopeURL}}" class="dash-scope-chip"
hx-get="{{.ScopeURL}}"
hx-target="#dashboard-section"
hx-swap="outerHTML"
hx-push-url="true"
title="Toggle between current projects and the full set">
{{if eq .Scope "current"}}◇ current{{else}}○ all{{end}}
</a>
{{end}}
</nav>
{{if eq .View "tasks"}}
{{template "dashboard-cards" .}}
{{else if eq .View "events"}}
{{template "dashboard-events-view" .}}
{{else}}
{{template "dashboard-tiles" .}}
{{end}}
</section>
{{end}}
{{define "dashboard-cards"}}
<section class="dash-grid">
{{$collapse := not .FilterActive}}
{{if or .P.Tasks (not $collapse)}}
<article class="card card-tasks">
<header>
<h2>Open tasks <small class="muted">({{.P.TaskTotal}})</small></h2>
{{if or .P.TaskGroups.Overdue .P.TaskGroups.Today .P.TaskGroups.Tomorrow .P.TaskGroups.Week .P.TaskGroups.NoDue}}
<p class="task-groups muted">
{{if .P.TaskGroups.Overdue}}<span class="overdue">Overdue ({{.P.TaskGroups.Overdue}})</span>{{end}}
{{if .P.TaskGroups.Today}}<span>Today ({{.P.TaskGroups.Today}})</span>{{end}}
{{if .P.TaskGroups.Tomorrow}}<span>Tomorrow ({{.P.TaskGroups.Tomorrow}})</span>{{end}}
{{if .P.TaskGroups.Week}}<span>This week ({{.P.TaskGroups.Week}})</span>{{end}}
{{if .P.TaskGroups.NoDue}}<span>No due ({{.P.TaskGroups.NoDue}})</span>{{end}}
</p>
{{end}}
</header>
{{if .P.Tasks}}
<ul class="task-list">
{{range .P.Tasks}}
{{$cal := .CalendarURL}}
{{$uid := .Todo.UID}}
{{$path := .Item.PrimaryPath}}
<li class="task-row bucket-{{.Bucket}}" id="task-{{$uid}}" data-item-path="{{$path}}" data-vtodo-uid="{{$uid}}">
<form class="check"
hx-post="/dashboard/task/done"
hx-target="#dashboard-section"
hx-swap="outerHTML">
<input type="hidden" name="calendar_url" value="{{$cal}}">
<input type="hidden" name="uid" value="{{$uid}}">
<button type="submit" title="mark complete">✓</button>
</form>
<a class="proj" href="/i/{{$path}}">{{$path}}</a>
<span class="summary">{{.Todo.Summary}}</span>
{{if .DueRel}}<span class="due {{if eq .Bucket "overdue"}}bad{{end}}" title="{{if .Todo.Due}}{{.Todo.Due.Format "2006-01-02"}}{{end}}">{{.DueRel}}</span>{{end}}
<details class="task-edit">
<summary class="muted" title="edit summary / due">✎</summary>
<form class="todo-edit inline"
hx-post="/dashboard/task/edit"
hx-target="#dashboard-section"
hx-swap="outerHTML">
<input type="hidden" name="calendar_url" value="{{$cal}}">
<input type="hidden" name="uid" value="{{$uid}}">
<input type="text" name="summary" value="{{.Todo.Summary}}" required>
<input type="date" name="due" value="{{if .Todo.Due}}{{.Todo.Due.Format "2006-01-02"}}{{end}}">
<button type="submit">Save</button>
</form>
</details>
<form class="todo-delete inline"
hx-post="/dashboard/task/delete"
hx-target="#dashboard-section"
hx-swap="outerHTML"
hx-confirm="Delete this task? This cannot be undone.">
<input type="hidden" name="calendar_url" value="{{$cal}}">
<input type="hidden" name="uid" value="{{$uid}}">
<button type="submit" class="x" title="Delete" aria-label="Delete">×</button>
</form>
</li>
{{end}}
</ul>
{{else}}
<p class="empty muted">Nothing open. Nice.</p>
{{end}}
</article>
{{else}}
<p class="card-collapsed muted">No open tasks.</p>
{{end}}
{{if or .P.Events (not $collapse)}}
<article class="card card-events">
<header>
<h2>Events <small class="muted">({{.P.EventsTotal}} upcoming, next 7d)</small></h2>
</header>
{{if .P.Events}}
{{range .P.Events}}
<div class="event-day">
<h3 class="muted">{{.DayLabel}} <small>({{len .Events}})</small></h3>
<ul class="event-list">
{{range .Events}}
<li class="event-row">
<span class="start">{{.StartLabel}}</span>
<a class="proj" href="/i/{{.Item.PrimaryPath}}">{{.Item.PrimaryPath}}</a>
<span class="summary">{{.Event.Summary}}</span>
{{if .Event.Location}}<span class="loc muted">· {{.Event.Location}}</span>{{end}}
{{if .Event.Recurring}}<span class="recurring" title="recurring — only literal DTSTART shown">↻</span>{{end}}
</li>
{{end}}
</ul>
</div>
{{end}}
{{else}}
<p class="empty muted">No events in the next 7 days.</p>
{{end}}
</article>
{{else}}
<p class="card-collapsed muted">No upcoming events.</p>
{{end}}
{{if or .P.Issues (not $collapse)}}
<article class="card card-issues">
<header>
<h2>Open issues <small class="muted">({{.P.IssueTotal}})</small></h2>
</header>
{{if .P.Issues}}
<ul class="issue-list">
{{range .P.Issues}}
<li class="issue-row">
<a class="proj" href="/i/{{.Item.PrimaryPath}}">{{.Item.PrimaryPath}}</a>
<a class="iss" href="{{.Issue.HTMLURL}}" target="_blank" rel="noopener">#{{.Issue.Number}} {{.Issue.Title}}</a>
{{range .Issue.Labels}}<span class="label">{{.}}</span>{{end}}
{{if .Issue.Assignees}}<small class="muted">@ {{range $i, $a := .Issue.Assignees}}{{if $i}}, {{end}}{{$a}}{{end}}</small>{{end}}
<span class="upd muted" title="{{.Issue.UpdatedAt.Format "2006-01-02 15:04"}}">{{.UpdRel}}</span>
</li>
{{end}}
</ul>
{{else}}
<p class="empty muted">No open issues across linked repos.</p>
{{end}}
</article>
{{else}}
<p class="card-collapsed muted">No open issues.</p>
{{end}}
{{if or .P.RecentDocs (not $collapse)}}
<article class="card card-docs">
<header>
<h2>Recent documents <small class="muted">({{.P.RecentDocsTotal}}, last 30d)</small></h2>
</header>
{{if .P.RecentDocs}}
<ul class="doc-list">
{{range .P.RecentDocs}}
<li class="doc-row">
<span class="per">{{.PER}}</span>
<span class="ref-type ref-type-{{.Link.RefType}}">{{.Link.RefType}}</span>
{{if .Link.Note}}<span class="note">{{deref .Link.Note}}</span>{{end}}
<a class="ref-id" href="{{.Link.RefID}}" target="_blank" rel="noopener">{{.Link.RefID}}</a>
<a class="proj muted" href="/i/{{.ItemPath}}">{{.ItemPath}}</a>
</li>
{{end}}
</ul>
{{else}}
<p class="empty muted">Nothing dated in the last 30 days.</p>
{{end}}
</article>
{{else}}
<p class="card-collapsed muted">No recent documents.</p>
{{end}}
{{/* Phase 5h: Stale card retired on the Tasks tab — m's pick was
to fold stale into the Quiet (N) ▾ section under Tiles. The
LastActivity stamp on each tile carries the staleness signal. */}}
</section>
{{end}}
{{define "dashboard-events-view"}}
<section class="dash-events-view">
{{if .P.Events}}
<header class="dash-events-summary">
<h2>{{.P.EventsTotal}} event{{if ne .P.EventsTotal 1}}s{{end}} <span class="muted">· next 7 days</span></h2>
</header>
{{range .P.Events}}
<section class="event-day-large">
<h3 class="event-day-heading">
<span class="event-day-label">{{.DayLabel}}</span>
<span class="muted event-day-date">{{.DayKey}}</span>
<span class="muted event-day-count">{{len .Events}} event{{if ne (len .Events) 1}}s{{end}}</span>
</h3>
<ul class="event-list">
{{range .Events}}
<li class="event-row">
<span class="start">{{if .StartLabel}}{{.StartLabel}}{{else}}—{{end}}</span>
<a class="proj" href="/i/{{.Item.PrimaryPath}}">{{.Item.PrimaryPath}}</a>
<span class="summary">{{.Event.Summary}}</span>
{{if .Event.Location}}<span class="loc muted">· {{.Event.Location}}</span>{{end}}
{{if .Event.Recurring}}<span class="recurring" title="recurring — only literal DTSTART shown">↻</span>{{end}}
</li>
{{end}}
</ul>
</section>
{{end}}
{{else}}
<p class="empty muted dash-events-empty">No events in the next 7 days. Link a CalDAV calendar from a project's detail page to start surfacing events here.</p>
{{end}}
</section>
{{end}}