added kanban and reporting
This commit is contained in:
@@ -38,6 +38,12 @@
|
||||
<a href="/requests" class="{{if eq .SelectedSection "requests"}}active{{end}}">
|
||||
<span class="nav-icon">◫</span> My requests
|
||||
</a>
|
||||
<a href="/updates" class="{{if eq .SelectedSection "updates"}}active{{end}}">
|
||||
<span class="nav-icon">✎</span> Work updates
|
||||
</a>
|
||||
<a href="/board" class="{{if eq .SelectedSection "board"}}active{{end}}">
|
||||
<span class="nav-icon">▦</span> Team board
|
||||
</a>
|
||||
{{if eq .User.Role "admin"}}
|
||||
<p class="nav-label">ADMIN</p>
|
||||
<a href="/admin/requests" class="{{if eq .SelectedSection "admin"}}active{{end}}">
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
{{define "board.html"}}
|
||||
{{template "shell-start" .}}
|
||||
<header class="page-head board-page-head">
|
||||
<div><p class="eyebrow">SHARED WORKSPACE</p><h1>Team board</h1><p>Plan, assign, and move work forward together.</p></div>
|
||||
<details class="new-task-panel" {{if .Error}}open{{end}}>
|
||||
<summary class="button primary">+ Add task</summary>
|
||||
<form method="post" action="/board/tasks" class="new-task-form">
|
||||
<input type="hidden" name="csrf" value="{{.CSRF}}">
|
||||
<div class="new-task-form-head"><div><p class="eyebrow">NEW CARD</p><h2>Add work to the board</h2></div></div>
|
||||
<label>Task title<input name="title" required maxlength="160" autofocus placeholder="What needs to be done?"></label>
|
||||
<label>Description <small>Optional</small><textarea name="description" rows="4" maxlength="4000" placeholder="Add context, acceptance criteria, or links…"></textarea></label>
|
||||
<div class="form-row">
|
||||
<label>Assignee<select name="assignee_id"><option value="">Unassigned</option>{{range .Users}}<option value="{{.ID}}">{{.DisplayName}}</option>{{end}}</select></label>
|
||||
<label>Column<select name="status"><option value="backlog">Backlog</option><option value="in_progress">In progress</option><option value="blocked">Blocked</option><option value="done">Done</option></select></label>
|
||||
</div>
|
||||
<label>Due date <small>Optional · Persian</small><span class="jalali-field"><input name="due_date" data-jalali-picker autocomplete="off" inputmode="numeric" placeholder="{{.TodayJalali}}" pattern="[0-9]{4}-[0-9]{2}-[0-9]{2}"><button type="button" class="jalali-trigger" aria-label="Choose due date" title="Open Persian calendar">▦</button></span></label>
|
||||
<button class="button dark full" type="submit">Create task</button>
|
||||
</form>
|
||||
</details>
|
||||
</header>
|
||||
{{template "notice" .}}
|
||||
|
||||
<div class="kanban-board" data-board data-csrf="{{.CSRF}}">
|
||||
{{range .Board}}
|
||||
<section class="kanban-column {{.Status}}" data-board-column="{{.Status}}">
|
||||
<header class="kanban-column-head">
|
||||
<div><span class="column-dot"></span><strong>{{.Title}}</strong><small>{{.Hint}}</small></div>
|
||||
<b>{{len .Tasks}}</b>
|
||||
</header>
|
||||
<div class="kanban-cards" data-drop-zone="{{.Status}}">
|
||||
{{range .Tasks}}
|
||||
<article class="kanban-card" draggable="true" data-task-id="{{.ID}}">
|
||||
<div class="card-topline"><span class="task-id">#{{.ID}}</span>{{if .DueDate}}<span class="task-due">Due {{dateFA .DueDate}}</span>{{end}}</div>
|
||||
<h3>{{.Title}}</h3>
|
||||
{{if .Description}}<p>{{.Description}}</p>{{end}}
|
||||
<div class="task-meta">
|
||||
{{if .AssigneeID.Valid}}<span class="task-assignee"><i class="avatar">{{initial .AssigneeName}}</i><span>{{.AssigneeName}}</span></span>{{else}}<span class="task-assignee unassigned"><i>?</i><span>Unassigned</span></span>{{end}}
|
||||
<span class="task-creator">by {{.CreatorName}}</span>
|
||||
</div>
|
||||
<div class="task-actions">
|
||||
<form method="post" action="/board/tasks/{{.ID}}/move">
|
||||
<input type="hidden" name="csrf" value="{{$.CSRF}}">
|
||||
<select name="status" aria-label="Move task">
|
||||
<option value="backlog" {{if eq .Status "backlog"}}selected{{end}}>Backlog</option>
|
||||
<option value="in_progress" {{if eq .Status "in_progress"}}selected{{end}}>In progress</option>
|
||||
<option value="blocked" {{if eq .Status "blocked"}}selected{{end}}>Blocked</option>
|
||||
<option value="done" {{if eq .Status "done"}}selected{{end}}>Done</option>
|
||||
</select>
|
||||
<button class="text-button task-move-button" type="submit">Move</button>
|
||||
</form>
|
||||
{{if or (eq .CreatorID $.User.ID) (eq $.User.Role "admin")}}
|
||||
<details class="task-remove">
|
||||
<summary>Remove</summary>
|
||||
<form method="post" action="/board/tasks/{{.ID}}/delete">
|
||||
<input type="hidden" name="csrf" value="{{$.CSRF}}">
|
||||
<button class="text-button" type="submit">Confirm</button>
|
||||
</form>
|
||||
</details>
|
||||
{{end}}
|
||||
</div>
|
||||
</article>
|
||||
{{end}}
|
||||
{{if not .Tasks}}<div class="kanban-empty"><span>Drop tasks here</span></div>{{end}}
|
||||
</div>
|
||||
</section>
|
||||
{{end}}
|
||||
</div>
|
||||
<script src="/static/board.js" defer></script>
|
||||
{{template "shell-end" .}}
|
||||
{{end}}
|
||||
@@ -1,6 +1,47 @@
|
||||
{{define "reports.html"}}
|
||||
{{template "shell-start" .}}
|
||||
<header class="page-head"><div><p class="eyebrow">ADMIN</p><h1>Reports</h1><p>Export team presence data for payroll or analysis.</p></div></header>
|
||||
<header class="page-head report-page-head">
|
||||
<div><p class="eyebrow">ADMIN</p><h1>Reports</h1><p>Review accumulated attendance and work updates for a selected period.</p></div>
|
||||
<form method="get" action="/reports" class="report-range-filter">
|
||||
<label>From<input type="date" name="start" value="{{.ReportStart}}" required></label>
|
||||
<label>To<input type="date" name="end" value="{{.ReportEnd}}" required></label>
|
||||
<button class="button dark">Apply period</button>
|
||||
</form>
|
||||
</header>
|
||||
|
||||
<section class="card accumulated-report">
|
||||
<header class="accumulated-head">
|
||||
<div><p class="eyebrow">ACCUMULATED RESULT</p><h2>Team summary</h2><p>{{dateFA .ReportStart}} — {{dateFA .ReportEnd}} · {{len .ReportSummary}} teammates</p></div>
|
||||
<a class="button secondary" href="/reports/summary.csv?start={{.ReportStart}}&end={{.ReportEnd}}">Download combined CSV</a>
|
||||
</header>
|
||||
<div class="accumulated-totals">
|
||||
<div><span class="stat-icon mint">✓</span><small>OFFICE DAYS</small><strong>{{.ReportTotals.Present}}</strong></div>
|
||||
<div><span class="stat-icon blue">⌂</span><small>REMOTE DAYS</small><strong>{{.ReportTotals.Remote}}</strong></div>
|
||||
<div><span class="stat-icon sand">☼</span><small>TIME OFF</small><strong>{{.ReportTotals.Leave}}</strong></div>
|
||||
<div><span class="stat-icon mint">✓</span><small>DONE UPDATES</small><strong>{{.ReportTotals.Done}}</strong></div>
|
||||
<div><span class="stat-icon blocked-icon">!</span><small>BLOCKED</small><strong>{{.ReportTotals.Blocked}}</strong></div>
|
||||
<div><span class="stat-icon pending-icon">…</span><small>PENDING</small><strong>{{.ReportTotals.Pending}}</strong></div>
|
||||
</div>
|
||||
<div class="summary-table-wrap">
|
||||
<table class="summary-table">
|
||||
<thead><tr><th>Teammate</th><th>Office</th><th>Remote</th><th>Time off</th><th>Done</th><th>Blocked</th><th>Pending</th><th>Updates</th></tr></thead>
|
||||
<tbody>
|
||||
{{range .ReportSummary}}
|
||||
<tr>
|
||||
<td><span class="summary-person">{{if .User.AvatarURL}}<img class="avatar" src="{{.User.AvatarURL}}" alt="">{{else}}<span class="avatar">{{initial .User.DisplayName}}</span>{{end}}<span><strong>{{.User.DisplayName}}</strong><small>@{{.User.Username}}</small></span></span></td>
|
||||
<td>{{.PresentDays}}</td><td>{{.RemoteDays}}</td><td>{{.LeaveDays}}</td>
|
||||
<td><span class="metric done">{{.DoneUpdates}}</span></td>
|
||||
<td><span class="metric blocked">{{.Blocked}}</span></td>
|
||||
<td><span class="metric pending">{{.Pending}}</span></td>
|
||||
<td><strong>{{.TotalUpdates}}</strong></td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="report-export-grid">
|
||||
<section class="card report-card">
|
||||
<div class="report-illustration">↧</div>
|
||||
<div><p class="eyebrow">ATTENDANCE EXPORT</p><h2>Download presence records</h2><p class="muted">The UTF-8 CSV includes Gregorian and Persian dates, check-in/out time, location, and approved requests. It opens directly in Excel.</p></div>
|
||||
@@ -10,5 +51,15 @@
|
||||
<button class="button primary">Download CSV</button>
|
||||
</form>
|
||||
</section>
|
||||
<section class="card report-card secondary-report">
|
||||
<div class="report-illustration">✎</div>
|
||||
<div><p class="eyebrow">WORK UPDATE EXPORT</p><h2>Download teammate reports</h2><p class="muted">Includes each teammate’s period, status, and free-form notes about completed, blocked, or pending work.</p></div>
|
||||
<form method="get" action="/reports/work-updates.csv" class="report-form">
|
||||
<label>From<input type="date" name="start" value="{{.ReportStart}}" required></label>
|
||||
<label>To<input type="date" name="end" value="{{.ReportEnd}}" required></label>
|
||||
<button class="button primary">Download CSV</button>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
{{template "shell-end" .}}
|
||||
{{end}}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
{{define "updates.html"}}
|
||||
{{template "shell-start" .}}
|
||||
<header class="page-head">
|
||||
<div><p class="eyebrow">TEAM REPORTING</p><h1>Work updates</h1><p>Share completed tasks, pending work, and anything blocking progress.</p></div>
|
||||
</header>
|
||||
{{template "notice" .}}
|
||||
<div class="two-column updates-layout">
|
||||
<section class="card form-card update-form-card">
|
||||
<p class="eyebrow">NEW UPDATE</p><h2>What did you work on?</h2>
|
||||
<form method="post" action="/updates" class="stack-form">
|
||||
<input type="hidden" name="csrf" value="{{.CSRF}}">
|
||||
<fieldset class="choice-cards update-status-choices">
|
||||
<label><input type="radio" name="status" value="done" checked><span><b>✓</b><strong>Done</strong><small>Completed work</small></span></label>
|
||||
<label><input type="radio" name="status" value="blocked"><span><b>!</b><strong>Blocked</strong><small>Needs attention</small></span></label>
|
||||
<label><input type="radio" name="status" value="pending"><span><b>…</b><strong>Pending</strong><small>Still in progress</small></span></label>
|
||||
</fieldset>
|
||||
<div class="form-row">
|
||||
<label>Period start <small>Persian date</small><span class="jalali-field"><input name="period_start" value="{{.TodayJalali}}" data-jalali-picker data-jalali-role="start" required autocomplete="off" inputmode="numeric" pattern="[0-9]{4}-[0-9]{2}-[0-9]{2}"><button type="button" class="jalali-trigger" aria-label="Choose period start" title="Open Persian calendar">▦</button></span></label>
|
||||
<label>Period end <small>Persian date</small><span class="jalali-field"><input name="period_end" value="{{.TodayJalali}}" data-jalali-picker data-jalali-role="end" required autocomplete="off" inputmode="numeric" pattern="[0-9]{4}-[0-9]{2}-[0-9]{2}"><button type="button" class="jalali-trigger" aria-label="Choose period end" title="Open Persian calendar">▦</button></span></label>
|
||||
</div>
|
||||
<label>Update <small>Required</small><textarea name="note" rows="7" maxlength="4000" required placeholder="Example: Completed task ABC and deployed the API. Task XYZ is pending review. Blocked on access to the staging server."></textarea></label>
|
||||
<button class="button primary full">Share update</button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="card update-feed-card">
|
||||
<header class="section-head"><div><p class="eyebrow">TEAM FEED</p><h2>Latest updates</h2></div><span class="feed-count">{{len .WorkUpdates}} reports</span></header>
|
||||
{{if .WorkUpdates}}
|
||||
<div class="update-feed">
|
||||
{{range .WorkUpdates}}
|
||||
<article class="update-entry">
|
||||
<div class="update-author">
|
||||
{{if .AvatarURL}}<img class="avatar" src="{{.AvatarURL}}" alt="">{{else}}<span class="avatar">{{initial .UserName}}</span>{{end}}
|
||||
<div><strong>{{.UserName}}</strong><small>@{{.Username}}</small></div>
|
||||
<span class="badge {{.Status}}">{{statusLabel .Status}}</span>
|
||||
</div>
|
||||
<p class="update-period">{{dateFA .PeriodStart}}{{if ne .PeriodStart .PeriodEnd}} — {{dateFA .PeriodEnd}}{{end}}</p>
|
||||
<p class="update-note">{{.Note}}</p>
|
||||
{{if eq .UserID $.User.ID}}
|
||||
<form method="post" action="/updates/{{.ID}}/delete" class="update-delete">
|
||||
<input type="hidden" name="csrf" value="{{$.CSRF}}">
|
||||
<button class="text-button" type="submit">Remove my update</button>
|
||||
</form>
|
||||
{{end}}
|
||||
</article>
|
||||
{{end}}
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="empty roomy"><span>✎</span><h3>No updates yet</h3><p>Share the first update with your team.</p></div>
|
||||
{{end}}
|
||||
</section>
|
||||
</div>
|
||||
{{template "shell-end" .}}
|
||||
{{end}}
|
||||
Reference in New Issue
Block a user