188 lines
6.6 KiB
HTML
188 lines
6.6 KiB
HTML
<section class="tasks-page">
|
|
<header class="hero">
|
|
<div class="hero-copy">
|
|
<div class="hero-badges">
|
|
<p class="eyebrow">Signal Store Feature</p>
|
|
<span class="hero-chip">Devtools Connected</span>
|
|
</div>
|
|
<h1>Delivery Board</h1>
|
|
<p class="lead">
|
|
A realistic feature slice with entity state, asynchronous updates,
|
|
persistence, and a clean operator flow for scalable Angular
|
|
teams.
|
|
</p>
|
|
</div>
|
|
|
|
<dl class="summary-grid">
|
|
<div>
|
|
<dt>Total</dt>
|
|
<dd>{{ store.totalCount() }}</dd>
|
|
</div>
|
|
<div>
|
|
<dt>Active</dt>
|
|
<dd>{{ store.activeCount() }}</dd>
|
|
</div>
|
|
<div>
|
|
<dt>Completed</dt>
|
|
<dd>{{ store.completedCount() }}</dd>
|
|
</div>
|
|
<div>
|
|
<dt>Synced</dt>
|
|
<dd>{{ store.lastSyncedLabel() }}</dd>
|
|
</div>
|
|
</dl>
|
|
</header>
|
|
|
|
<section class="workspace">
|
|
<form
|
|
class="composer"
|
|
(submit)="createTask(); $event.preventDefault()">
|
|
<div class="section-heading">
|
|
<div>
|
|
<p class="section-kicker">Compose</p>
|
|
<h2>Capture the next delivery item</h2>
|
|
</div>
|
|
<p class="section-note">
|
|
Fast input for planning work without leaving the board.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="composer-grid">
|
|
<label>
|
|
<span>Task title</span>
|
|
<input
|
|
type="text"
|
|
placeholder="Add a task with product value"
|
|
[value]="draftTitle()"
|
|
(input)="updateDraftTitle($event)" />
|
|
</label>
|
|
|
|
<label>
|
|
<span>Priority</span>
|
|
<select
|
|
[value]="store.draftPriority()"
|
|
(change)="updateDraftPriority($event)">
|
|
@for (priority of priorities; track priority) {
|
|
<option [value]="priority">{{ priority }}</option>
|
|
}
|
|
</select>
|
|
</label>
|
|
|
|
<button
|
|
type="submit"
|
|
class="primary-action"
|
|
[disabled]="!canCreateTask()">
|
|
Create task
|
|
</button>
|
|
</div>
|
|
</form>
|
|
|
|
<div class="toolbar">
|
|
<div class="section-heading">
|
|
<div>
|
|
<p class="section-kicker">Refine</p>
|
|
<h2>Focus the board</h2>
|
|
</div>
|
|
<p class="section-note">
|
|
Search, segment, refresh, or archive completed work.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="toolbar-grid">
|
|
<label class="search">
|
|
<span>Search</span>
|
|
<input
|
|
type="search"
|
|
placeholder="Filter tasks"
|
|
[value]="store.searchTerm()"
|
|
(input)="updateSearchTerm($event)" />
|
|
</label>
|
|
|
|
<div
|
|
class="filters"
|
|
role="group"
|
|
aria-label="Task filters">
|
|
@for (filter of filters; track filter) {
|
|
<button
|
|
type="button"
|
|
[class.active]="store.filter() === filter"
|
|
(click)="store.setFilter(filter)">
|
|
{{ filter }}
|
|
</button>
|
|
}
|
|
</div>
|
|
|
|
<div
|
|
class="toolbar-divider"
|
|
aria-hidden="true"></div>
|
|
|
|
<div
|
|
class="toolbar-actions"
|
|
role="group"
|
|
aria-label="Task actions">
|
|
<button
|
|
type="button"
|
|
class="ghost-action"
|
|
(click)="store.loadTasks()">
|
|
Refresh
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="primary-action"
|
|
[disabled]="!store.hasCompletedTasks() || store.loading()"
|
|
(click)="store.archiveCompleted()">
|
|
Archive completed
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
@if (store.error(); as errorMessage) {
|
|
<p
|
|
class="feedback error"
|
|
role="alert">
|
|
{{ errorMessage }}
|
|
</p>
|
|
}
|
|
|
|
@if (store.loading()) {
|
|
<p class="feedback">Synchronizing tasks...</p>
|
|
}
|
|
|
|
<ul class="task-list">
|
|
@for (task of store.filteredTasks(); track task.id) {
|
|
<li
|
|
class="task-card"
|
|
[class.done]="task.completed">
|
|
<div class="task-main">
|
|
<div class="task-status">
|
|
<span
|
|
class="priority-mark"
|
|
[class.high]="task.priority === 'high'"
|
|
[class.medium]="task.priority === 'medium'"
|
|
[class.low]="task.priority === 'low'"></span>
|
|
<button
|
|
type="button"
|
|
class="toggle"
|
|
[attr.aria-pressed]="task.completed"
|
|
(click)="store.toggleTask(task.id)">
|
|
{{ task.completed ? 'Completed' : 'Open' }}
|
|
</button>
|
|
</div>
|
|
|
|
<div class="task-copy">
|
|
<h2>{{ task.title }}</h2>
|
|
<div class="task-meta">
|
|
<span class="meta-pill">{{ task.priority }}</span>
|
|
<span>Updated {{ task.updatedAt }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</li>
|
|
} @empty {
|
|
<li class="empty-state">{{ store.emptyStateMessage() }}</li>
|
|
}
|
|
</ul>
|
|
</section>
|
|
</section>
|