taskunity 2026.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- taskunity/__init__.py +1 -0
- taskunity/app.py +1268 -0
- taskunity/cli.py +43 -0
- taskunity/models.py +103 -0
- taskunity/render.py +371 -0
- taskunity/static/app.css +1394 -0
- taskunity/static/chart.umd.min.js +14 -0
- taskunity/static/chartjs-adapter-date-fns.bundle.min.js +7 -0
- taskunity/static/htmx.min.js +68 -0
- taskunity/task_store.py +598 -0
- taskunity/templates/base.html +397 -0
- taskunity/templates/index.html +4 -0
- taskunity/templates/partials/board.html +24 -0
- taskunity/templates/partials/calendar.html +25 -0
- taskunity/templates/partials/main.html +283 -0
- taskunity/templates/partials/milestone_banner.html +34 -0
- taskunity/templates/partials/milestone_panel.html +222 -0
- taskunity/templates/partials/milestones.html +55 -0
- taskunity/templates/partials/projects.html +41 -0
- taskunity/templates/partials/task_list.html +52 -0
- taskunity/templates/partials/task_panel.html +310 -0
- taskunity/templates/partials/timeline.html +24 -0
- taskunity-2026.1.dist-info/METADATA +238 -0
- taskunity-2026.1.dist-info/RECORD +27 -0
- taskunity-2026.1.dist-info/WHEEL +5 -0
- taskunity-2026.1.dist-info/entry_points.txt +2 -0
- taskunity-2026.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
<main id="app-main" class="layout" data-resizable-layout>
|
|
2
|
+
<section class="content">
|
|
3
|
+
<section class="summary-grid">
|
|
4
|
+
<div class="summary-card">
|
|
5
|
+
<span class="label">Total tasks</span>
|
|
6
|
+
<strong>{{ model.total }}</strong>
|
|
7
|
+
</div>
|
|
8
|
+
<div class="summary-card">
|
|
9
|
+
<span class="label">Done</span>
|
|
10
|
+
<strong>{{ model.counts.get('done', 0) }}</strong>
|
|
11
|
+
</div>
|
|
12
|
+
<div class="summary-card">
|
|
13
|
+
<span class="label">Working</span>
|
|
14
|
+
<strong>{{ model.counts.get('working', 0) }}</strong>
|
|
15
|
+
</div>
|
|
16
|
+
<div class="summary-card">
|
|
17
|
+
<span class="label">Blocked</span>
|
|
18
|
+
<strong>{{ model.counts.get('blocked', 0) }}</strong>
|
|
19
|
+
</div>
|
|
20
|
+
</section>
|
|
21
|
+
|
|
22
|
+
<div class="filter-bar">
|
|
23
|
+
<details class="filter-menu">
|
|
24
|
+
<summary>
|
|
25
|
+
<svg viewBox="0 0 24 24" width="15" height="15" aria-hidden="true"><path d="M3 5h18l-7 8v6l-4 2v-8z" fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round"/></svg>
|
|
26
|
+
Filter
|
|
27
|
+
</summary>
|
|
28
|
+
<div class="filter-pop">
|
|
29
|
+
<form class="add-filter" hx-get="/partials/main" hx-target="#app-main" hx-swap="outerHTML">
|
|
30
|
+
{% for p in filters.projects %}<input type="hidden" name="project" value="{{ p }}">{% endfor %}
|
|
31
|
+
{% if filters.date_from %}<input type="hidden" name="date_from" value="{{ filters.date_from }}">{% endif %}
|
|
32
|
+
{% if filters.date_to %}<input type="hidden" name="date_to" value="{{ filters.date_to }}">{% endif %}
|
|
33
|
+
<input type="hidden" name="sort" value="{{ filters.sort }}">
|
|
34
|
+
<input type="hidden" name="view" value="{{ filters.view }}">
|
|
35
|
+
{% if filters.calendar_month %}<input type="hidden" name="calendar_month" value="{{ filters.calendar_month }}">{% endif %}
|
|
36
|
+
{% if filters.calendar_year %}<input type="hidden" name="calendar_year" value="{{ filters.calendar_year }}">{% endif %}
|
|
37
|
+
{% if filters.show_closed %}<input type="hidden" name="show_closed" value="1">{% endif %}
|
|
38
|
+
<input type="hidden" name="stale_days" value="{{ filters.stale_days }}">
|
|
39
|
+
<span class="add-label">Keyword</span>
|
|
40
|
+
<div class="add-row">
|
|
41
|
+
<input name="q" value="{{ filters.q }}" placeholder="Search title, tags, notes…">
|
|
42
|
+
<button type="submit">Search</button>
|
|
43
|
+
</div>
|
|
44
|
+
</form>
|
|
45
|
+
|
|
46
|
+
<form class="add-filter" hx-get="/partials/main" hx-target="#app-main" hx-swap="outerHTML" hx-trigger="change">
|
|
47
|
+
{% for p in filters.projects %}<input type="hidden" name="project" value="{{ p }}">{% endfor %}
|
|
48
|
+
{% if filters.date_from %}<input type="hidden" name="date_from" value="{{ filters.date_from }}">{% endif %}
|
|
49
|
+
{% if filters.date_to %}<input type="hidden" name="date_to" value="{{ filters.date_to }}">{% endif %}
|
|
50
|
+
{% if filters.q %}<input type="hidden" name="q" value="{{ filters.q }}">{% endif %}
|
|
51
|
+
<input type="hidden" name="sort" value="{{ filters.sort }}">
|
|
52
|
+
<input type="hidden" name="view" value="{{ filters.view }}">
|
|
53
|
+
{% if filters.calendar_month %}<input type="hidden" name="calendar_month" value="{{ filters.calendar_month }}">{% endif %}
|
|
54
|
+
{% if filters.calendar_year %}<input type="hidden" name="calendar_year" value="{{ filters.calendar_year }}">{% endif %}
|
|
55
|
+
<input type="hidden" name="show_closed" value="{% if filters.show_closed %}1{% else %}0{% endif %}" data-show-closed-hidden>
|
|
56
|
+
<span class="add-label">Old tasks</span>
|
|
57
|
+
<div class="add-row add-row-tight">
|
|
58
|
+
<label class="toggle-label">
|
|
59
|
+
<input type="checkbox" class="hide-old-toggle" {% if filters.show_closed %}checked{% endif %}>
|
|
60
|
+
Show old stuff
|
|
61
|
+
</label>
|
|
62
|
+
<input type="number" name="stale_days" min="1" max="365" value="{{ filters.stale_days }}" class="stale-days-input">
|
|
63
|
+
<span class="muted">days</span>
|
|
64
|
+
</div>
|
|
65
|
+
</form>
|
|
66
|
+
|
|
67
|
+
{% if projects %}
|
|
68
|
+
<form class="add-filter" hx-get="/partials/main" hx-target="#app-main" hx-swap="outerHTML" hx-trigger="change">
|
|
69
|
+
{% if filters.date_from %}<input type="hidden" name="date_from" value="{{ filters.date_from }}">{% endif %}
|
|
70
|
+
{% if filters.date_to %}<input type="hidden" name="date_to" value="{{ filters.date_to }}">{% endif %}
|
|
71
|
+
{% if filters.q %}<input type="hidden" name="q" value="{{ filters.q }}">{% endif %}
|
|
72
|
+
<input type="hidden" name="sort" value="{{ filters.sort }}">
|
|
73
|
+
<input type="hidden" name="view" value="{{ filters.view }}">
|
|
74
|
+
{% if filters.calendar_month %}<input type="hidden" name="calendar_month" value="{{ filters.calendar_month }}">{% endif %}
|
|
75
|
+
{% if filters.calendar_year %}<input type="hidden" name="calendar_year" value="{{ filters.calendar_year }}">{% endif %}
|
|
76
|
+
{% if filters.show_closed %}<input type="hidden" name="show_closed" value="1">{% endif %}
|
|
77
|
+
<input type="hidden" name="stale_days" value="{{ filters.stale_days }}">
|
|
78
|
+
<span class="add-label">Projects</span>
|
|
79
|
+
<div class="project-checks">
|
|
80
|
+
{% for p in projects %}
|
|
81
|
+
<label class="project-check">
|
|
82
|
+
<input type="checkbox" name="project" value="{{ p.name }}"{% if p.name in filters.projects %} checked{% endif %}>
|
|
83
|
+
<span class="swatch" style="background: {{ p.color }}"></span>{{ p.name }}
|
|
84
|
+
</label>
|
|
85
|
+
{% endfor %}
|
|
86
|
+
</div>
|
|
87
|
+
</form>
|
|
88
|
+
{% endif %}
|
|
89
|
+
|
|
90
|
+
<form class="add-filter" hx-get="/partials/main" hx-target="#app-main" hx-swap="outerHTML">
|
|
91
|
+
{% for p in filters.projects %}<input type="hidden" name="project" value="{{ p }}">{% endfor %}
|
|
92
|
+
{% if filters.q %}<input type="hidden" name="q" value="{{ filters.q }}">{% endif %}
|
|
93
|
+
<input type="hidden" name="sort" value="{{ filters.sort }}">
|
|
94
|
+
<input type="hidden" name="view" value="{{ filters.view }}">
|
|
95
|
+
{% if filters.calendar_month %}<input type="hidden" name="calendar_month" value="{{ filters.calendar_month }}">{% endif %}
|
|
96
|
+
{% if filters.calendar_year %}<input type="hidden" name="calendar_year" value="{{ filters.calendar_year }}">{% endif %}
|
|
97
|
+
{% if filters.show_closed %}<input type="hidden" name="show_closed" value="1">{% endif %}
|
|
98
|
+
<input type="hidden" name="stale_days" value="{{ filters.stale_days }}">
|
|
99
|
+
<span class="add-label">Date range</span>
|
|
100
|
+
<div class="add-row">
|
|
101
|
+
<input type="date" name="date_from" value="{{ filters.date_from }}">
|
|
102
|
+
<input type="date" name="date_to" value="{{ filters.date_to }}">
|
|
103
|
+
<button type="submit">Apply</button>
|
|
104
|
+
</div>
|
|
105
|
+
</form>
|
|
106
|
+
</div>
|
|
107
|
+
</details>
|
|
108
|
+
|
|
109
|
+
<div class="pills">
|
|
110
|
+
{% for pill in filters.pills %}
|
|
111
|
+
<button class="pill" {% if pill.color %}style="background: {{ pill.color }}22; border-color: {{ pill.color }}; color: {{ pill.color }}"{% endif %} hx-get="/partials/main?{{ pill.remove }}" hx-target="#app-main" hx-swap="outerHTML">{{ pill.label }}<span class="x">×</span></button>
|
|
112
|
+
{% else %}
|
|
113
|
+
<span class="muted no-filters">No filters applied</span>
|
|
114
|
+
{% endfor %}
|
|
115
|
+
{% if filters.pills %}
|
|
116
|
+
<button class="pill clear" hx-get="/partials/main?view={{ filters.view }}" hx-target="#app-main" hx-swap="outerHTML">Clear all</button>
|
|
117
|
+
{% endif %}
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
<span class="spacer"></span>
|
|
121
|
+
<a class="btn-link" href="/export/csv{% if filters.query %}?{{ filters.query }}{% endif %}">Export CSV</a>
|
|
122
|
+
<a class="btn-link" href="/export/json{% if filters.query %}?{{ filters.query }}{% endif %}">Export JSON</a>
|
|
123
|
+
|
|
124
|
+
<div class="git-chip{% if not git.tracked %} untracked{% endif %}" title="{% if git.tracked %}Branch {{ git.branch }}{% if git.upstream %} → {{ git.upstream }}{% endif %}{% else %}{{ git.message or 'This workspace is not inside a git repository' }}{% endif %}">
|
|
125
|
+
<svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true"><path d="M12 3v12m0 0a3 3 0 1 0 0 6 3 3 0 0 0 0-6zm0-12a3 3 0 1 0 0 6 3 3 0 0 0 0-6zm6 6a3 3 0 1 0 0 6 3 3 0 0 0 0-6zm0 0c0 3-2 4-6 4" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
|
126
|
+
{% if git.tracked %}
|
|
127
|
+
<span class="git-branch">{{ git.branch or 'detached' }}</span>
|
|
128
|
+
{% if git.upstream %}
|
|
129
|
+
{% if git.ahead %}<span class="git-badge ahead" title="{{ git.ahead }} commit(s) ahead of {{ git.upstream }}">↑{{ git.ahead }}</span>{% endif %}
|
|
130
|
+
{% if git.behind %}<span class="git-badge behind" title="{{ git.behind }} commit(s) behind {{ git.upstream }}">↓{{ git.behind }}</span>{% endif %}
|
|
131
|
+
{% if not git.ahead and not git.behind and not git.dirty %}<span class="git-badge ok" title="Up to date with {{ git.upstream }}">✓ synced</span>{% endif %}
|
|
132
|
+
{% else %}
|
|
133
|
+
<span class="git-badge warn" title="No upstream remote configured">no remote</span>
|
|
134
|
+
{% endif %}
|
|
135
|
+
{% if git.dirty %}<span class="git-badge dirty" title="{{ git.dirty }} uncommitted change(s)">●{{ git.dirty }}</span>{% endif %}
|
|
136
|
+
{% if git_lfs.available %}
|
|
137
|
+
{% if git_lfs.enabled and git_lfs.tracking_assets %}
|
|
138
|
+
<span class="git-badge ok" title="Git LFS is enabled and tracking assets">LFS</span>
|
|
139
|
+
{% elif git_lfs.enabled %}
|
|
140
|
+
<span class="git-badge warn" title="Git LFS is enabled, but assets are not tracked yet">LFS partial</span>
|
|
141
|
+
{% else %}
|
|
142
|
+
<span class="git-badge warn" title="Git LFS is available but not enabled">LFS off</span>
|
|
143
|
+
{% endif %}
|
|
144
|
+
{% endif %}
|
|
145
|
+
<form hx-post="/git/sync" hx-target="#app-main" hx-swap="outerHTML" class="git-sync-form">
|
|
146
|
+
{% for p in filters.projects %}<input type="hidden" name="f_project" value="{{ p }}">{% endfor %}
|
|
147
|
+
<input type="hidden" name="f_from" value="{{ filters.date_from }}">
|
|
148
|
+
<input type="hidden" name="f_to" value="{{ filters.date_to }}">
|
|
149
|
+
<input type="hidden" name="f_q" value="{{ filters.q }}">
|
|
150
|
+
<input type="hidden" name="f_view" value="{{ filters.view }}">
|
|
151
|
+
<input type="hidden" name="f_sort" value="{{ filters.sort }}">
|
|
152
|
+
<input type="hidden" name="f_milestone" value="{{ filters.milestone }}">
|
|
153
|
+
<input type="hidden" name="f_show_closed" value="{% if filters.show_closed %}1{% endif %}">
|
|
154
|
+
<input type="hidden" name="f_stale_days" value="{{ filters.stale_days }}">
|
|
155
|
+
<input type="hidden" name="f_calendar_month" value="{{ filters.calendar_month }}">
|
|
156
|
+
<input type="hidden" name="f_calendar_year" value="{{ filters.calendar_year }}">
|
|
157
|
+
<button type="submit" class="git-sync-btn" title="Commit local changes, pull, and push">Sync</button>
|
|
158
|
+
</form>
|
|
159
|
+
{% if git_lfs.available and not git_lfs.enabled %}
|
|
160
|
+
<form hx-post="/git/lfs/init" hx-target="#app-main" hx-swap="outerHTML" class="git-sync-form">
|
|
161
|
+
{% for p in filters.projects %}<input type="hidden" name="f_project" value="{{ p }}">{% endfor %}
|
|
162
|
+
<input type="hidden" name="f_from" value="{{ filters.date_from }}">
|
|
163
|
+
<input type="hidden" name="f_to" value="{{ filters.date_to }}">
|
|
164
|
+
<input type="hidden" name="f_q" value="{{ filters.q }}">
|
|
165
|
+
<input type="hidden" name="f_view" value="{{ filters.view }}">
|
|
166
|
+
<input type="hidden" name="f_sort" value="{{ filters.sort }}">
|
|
167
|
+
<input type="hidden" name="f_milestone" value="{{ filters.milestone }}">
|
|
168
|
+
<input type="hidden" name="f_show_closed" value="{% if filters.show_closed %}1{% endif %}">
|
|
169
|
+
<input type="hidden" name="f_stale_days" value="{{ filters.stale_days }}">
|
|
170
|
+
<input type="hidden" name="f_calendar_month" value="{{ filters.calendar_month }}">
|
|
171
|
+
<input type="hidden" name="f_calendar_year" value="{{ filters.calendar_year }}">
|
|
172
|
+
<button type="submit" class="git-sync-btn secondary" title="Enable git-lfs and track assets">Enable LFS</button>
|
|
173
|
+
</form>
|
|
174
|
+
{% endif %}
|
|
175
|
+
{% else %}
|
|
176
|
+
<span class="git-branch">No git repo</span>
|
|
177
|
+
{% endif %}
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
|
|
181
|
+
{% if git_message %}
|
|
182
|
+
<div class="git-toast" role="status">
|
|
183
|
+
<span class="git-toast-msg">{{ git_message }}</span>
|
|
184
|
+
<button type="button" class="git-toast-close" aria-label="Dismiss" onclick="this.closest('.git-toast').remove()">×</button>
|
|
185
|
+
</div>
|
|
186
|
+
{% endif %}
|
|
187
|
+
|
|
188
|
+
<div class="view-bar">
|
|
189
|
+
<div class="view-switch">
|
|
190
|
+
<button class="{% if filters.view == 'list' %}active{% endif %}" hx-get="/partials/main?view=list{% if filters.query %}&{{ filters.query }}{% endif %}" hx-target="#app-main" hx-swap="outerHTML">Task List</button>
|
|
191
|
+
<button class="{% if filters.view == 'board' %}active{% endif %}" hx-get="/partials/main?view=board{% if filters.query %}&{{ filters.query }}{% endif %}" hx-target="#app-main" hx-swap="outerHTML">Task Board</button>
|
|
192
|
+
<button class="{% if filters.view == 'gantt' %}active{% endif %}" hx-get="/partials/main?view=gantt{% if filters.query %}&{{ filters.query }}{% endif %}" hx-target="#app-main" hx-swap="outerHTML">Gantt</button>
|
|
193
|
+
<button class="{% if filters.view == 'calendar' %}active{% endif %}" hx-get="/partials/main?view=calendar{% if filters.query %}&{{ filters.query }}{% endif %}" hx-target="#app-main" hx-swap="outerHTML">Calendar</button>
|
|
194
|
+
<button class="{% if filters.view == 'milestones' %}active{% endif %}" hx-get="/partials/main?view=milestones{% if filters.query %}&{{ filters.query }}{% endif %}" hx-target="#app-main" hx-swap="outerHTML">Milestones</button>
|
|
195
|
+
<button class="{% if filters.view == 'projects' %}active{% endif %}" hx-get="/partials/main?view=projects{% if filters.query %}&{{ filters.query }}{% endif %}" hx-target="#app-main" hx-swap="outerHTML">Projects</button>
|
|
196
|
+
</div>
|
|
197
|
+
{% if filters.view == 'calendar' %}
|
|
198
|
+
<div class="calendar-nav">
|
|
199
|
+
<a class="calendar-nav-btn year" hx-get="/partials/main?{{ filters.calendar_year_prev_query }}" hx-target="#app-main" hx-swap="outerHTML">«</a>
|
|
200
|
+
<a class="calendar-nav-btn" hx-get="/partials/main?{{ filters.calendar_prev_query }}" hx-target="#app-main" hx-swap="outerHTML">‹</a>
|
|
201
|
+
<form class="calendar-jump" hx-get="/partials/main" hx-target="#app-main" hx-swap="outerHTML" hx-trigger="change">
|
|
202
|
+
{% for p in filters.projects %}<input type="hidden" name="project" value="{{ p }}">{% endfor %}
|
|
203
|
+
{% if filters.date_from %}<input type="hidden" name="date_from" value="{{ filters.date_from }}">{% endif %}
|
|
204
|
+
{% if filters.date_to %}<input type="hidden" name="date_to" value="{{ filters.date_to }}">{% endif %}
|
|
205
|
+
{% if filters.q %}<input type="hidden" name="q" value="{{ filters.q }}">{% endif %}
|
|
206
|
+
<input type="hidden" name="view" value="calendar">
|
|
207
|
+
<input type="hidden" name="sort" value="{{ filters.sort }}">
|
|
208
|
+
{% if filters.show_closed %}<input type="hidden" name="show_closed" value="1">{% endif %}
|
|
209
|
+
<input type="hidden" name="stale_days" value="{{ filters.stale_days }}">
|
|
210
|
+
<label class="calendar-jump-field">
|
|
211
|
+
<span>Month</span>
|
|
212
|
+
<select name="calendar_month">
|
|
213
|
+
{% for month in calendar.month_options %}
|
|
214
|
+
<option value="{{ month.value }}"{% if month.value == filters.calendar_month %} selected{% endif %}>{{ month.label }}</option>
|
|
215
|
+
{% endfor %}
|
|
216
|
+
</select>
|
|
217
|
+
</label>
|
|
218
|
+
<label class="calendar-jump-field">
|
|
219
|
+
<span>Year</span>
|
|
220
|
+
<input type="number" name="calendar_year" value="{{ filters.calendar_year }}" min="1900" max="3000">
|
|
221
|
+
</label>
|
|
222
|
+
<button type="submit">Jump</button>
|
|
223
|
+
</form>
|
|
224
|
+
<a class="calendar-nav-btn" hx-get="/partials/main?{{ filters.calendar_next_query }}" hx-target="#app-main" hx-swap="outerHTML">›</a>
|
|
225
|
+
<a class="calendar-nav-btn year" hx-get="/partials/main?{{ filters.calendar_year_next_query }}" hx-target="#app-main" hx-swap="outerHTML">»</a>
|
|
226
|
+
</div>
|
|
227
|
+
{% endif %}
|
|
228
|
+
<form class="new-task-form" hx-post="/tasks/create" hx-target="#app-main" hx-swap="outerHTML">
|
|
229
|
+
<input name="title" placeholder="New task title">
|
|
230
|
+
{% for p in filters.projects %}<input type="hidden" name="f_project" value="{{ p }}">{% endfor %}
|
|
231
|
+
<input type="hidden" name="f_from" value="{{ filters.date_from }}">
|
|
232
|
+
<input type="hidden" name="f_to" value="{{ filters.date_to }}">
|
|
233
|
+
<input type="hidden" name="f_q" value="{{ filters.q }}">
|
|
234
|
+
<input type="hidden" name="f_view" value="{{ filters.view }}">
|
|
235
|
+
<input type="hidden" name="f_milestone" value="{{ filters.milestone }}">
|
|
236
|
+
<input type="hidden" name="f_show_closed" value="{% if filters.show_closed %}1{% endif %}">
|
|
237
|
+
<input type="hidden" name="f_stale_days" value="{{ filters.stale_days }}">
|
|
238
|
+
<input type="hidden" name="f_calendar_month" value="{{ filters.calendar_month }}">
|
|
239
|
+
<input type="hidden" name="f_calendar_year" value="{{ filters.calendar_year }}">
|
|
240
|
+
<button type="submit">Create Task</button>
|
|
241
|
+
</form>
|
|
242
|
+
</div>
|
|
243
|
+
|
|
244
|
+
{% if selected_milestone and filters.view != 'milestones' %}
|
|
245
|
+
{% include "partials/milestone_banner.html" %}
|
|
246
|
+
{% endif %}
|
|
247
|
+
|
|
248
|
+
{% if filters.view == 'list' %}
|
|
249
|
+
{% include "partials/task_list.html" %}
|
|
250
|
+
{% elif filters.view == 'gantt' %}
|
|
251
|
+
<section class="panel">{% include "partials/timeline.html" %}</section>
|
|
252
|
+
{% elif filters.view == 'calendar' %}
|
|
253
|
+
<section class="panel">{% include "partials/calendar.html" %}</section>
|
|
254
|
+
{% elif filters.view == 'milestones' %}
|
|
255
|
+
{% include "partials/milestones.html" %}
|
|
256
|
+
{% elif filters.view == 'projects' %}
|
|
257
|
+
{% include "partials/projects.html" %}
|
|
258
|
+
{% else %}
|
|
259
|
+
{% include "partials/board.html" %}
|
|
260
|
+
{% endif %}
|
|
261
|
+
</section>
|
|
262
|
+
|
|
263
|
+
<button
|
|
264
|
+
type="button"
|
|
265
|
+
class="pane-resizer"
|
|
266
|
+
data-pane-resizer
|
|
267
|
+
aria-label="Resize details pane"
|
|
268
|
+
title="Drag to resize details pane. Double-click to reset."
|
|
269
|
+
aria-orientation="vertical"
|
|
270
|
+
></button>
|
|
271
|
+
<aside id="task-panel" class="side-panel">
|
|
272
|
+
{% if selected_task %}
|
|
273
|
+
{% include "partials/task_panel.html" %}
|
|
274
|
+
{% elif selected_milestone %}
|
|
275
|
+
{% include "partials/milestone_panel.html" %}
|
|
276
|
+
{% else %}
|
|
277
|
+
<div class="empty-panel">
|
|
278
|
+
<h2>Select a task</h2>
|
|
279
|
+
<p>Details will appear when a task is selected.</p>
|
|
280
|
+
</div>
|
|
281
|
+
{% endif %}
|
|
282
|
+
</aside>
|
|
283
|
+
</main>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<section class="milestone-banner" style="--ms-color: {{ selected_milestone.color or '#3567e0' }}">
|
|
2
|
+
<div class="mb-head">
|
|
3
|
+
<div class="mb-title">
|
|
4
|
+
<span class="mb-flag" aria-hidden="true">◆</span>
|
|
5
|
+
<div>
|
|
6
|
+
<h2>{{ selected_milestone.title }}</h2>
|
|
7
|
+
<div class="mb-meta">
|
|
8
|
+
<span class="ms-status {{ selected_milestone.status }}">{{ selected_milestone.status }}</span>
|
|
9
|
+
{% for proj in selected_milestone.projects %}
|
|
10
|
+
<span class="project" style="background: {{ project_colors.get(proj, '#2e6fd8') }}22; color: {{ project_colors.get(proj, '#2e6fd8') }}">{{ proj }}</span>
|
|
11
|
+
{% endfor %}
|
|
12
|
+
{% if selected_milestone.target_date %}<span class="mb-target">🎯 {{ selected_milestone.target_date }}</span>{% endif %}
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
<div class="mb-actions">
|
|
17
|
+
<button class="btn-link" hx-get="/milestones/{{ selected_milestone.id }}/panel?view={{ filters.view }}{% if filters.show_closed %}&show_closed=1{% endif %}&stale_days={{ filters.stale_days }}" hx-target="#task-panel" hx-swap="innerHTML">Open details</button>
|
|
18
|
+
<button class="pill clear" hx-get="/partials/main?view={{ filters.view }}{% if filters.show_closed %}&show_closed=1{% endif %}&stale_days={{ filters.stale_days }}" hx-target="#app-main" hx-swap="outerHTML">Clear milestone</button>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
<div class="mb-rollup">
|
|
22
|
+
<div class="mb-progress" title="{{ rollup.progress }}% complete">
|
|
23
|
+
<div style="width: {{ rollup.progress }}%; background: {{ selected_milestone.color or '#3567e0' }}"></div>
|
|
24
|
+
</div>
|
|
25
|
+
<div class="mb-stats">
|
|
26
|
+
<span><strong>{{ rollup.total }}</strong> tasks</span>
|
|
27
|
+
<span class="done"><strong>{{ rollup.done }}</strong> done</span>
|
|
28
|
+
<span class="working"><strong>{{ rollup.working }}</strong> working</span>
|
|
29
|
+
<span class="blocked"><strong>{{ rollup.blocked }}</strong> blocked</span>
|
|
30
|
+
<span><strong>{{ rollup.progress }}%</strong> complete</span>
|
|
31
|
+
{% if rollup.next_due %}<span>next due {{ rollup.next_due }}</span>{% endif %}
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
</section>
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
<div class="panel-scroll">
|
|
2
|
+
<div class="panel-heading sticky">
|
|
3
|
+
<div>
|
|
4
|
+
<span class="ms-status {{ selected_milestone.status }}">{{ selected_milestone.status }}</span>
|
|
5
|
+
<h2>{{ selected_milestone.title }}</h2>
|
|
6
|
+
</div>
|
|
7
|
+
<button class="btn-link" hx-get="/partials/main?milestone={{ selected_milestone.id }}&view={{ filters.view if filters.view != 'milestones' else 'board' }}{% if filters.show_closed %}&show_closed=1{% endif %}&stale_days={{ filters.stale_days }}" hx-target="#app-main" hx-swap="outerHTML" title="Filter the board to this milestone">Filter tasks</button>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<form hx-post="/milestones/{{ selected_milestone.id }}/save" hx-target="#app-main" hx-swap="outerHTML" class="task-form">
|
|
11
|
+
<input type="hidden" name="f_view" value="{{ filters.view }}">
|
|
12
|
+
<input type="hidden" name="f_show_closed" value="{% if filters.show_closed %}1{% endif %}">
|
|
13
|
+
<input type="hidden" name="f_stale_days" value="{{ filters.stale_days }}">
|
|
14
|
+
<label>Title<input name="title" value="{{ selected_milestone.title }}" required></label>
|
|
15
|
+
<div class="form-grid">
|
|
16
|
+
<label>Status
|
|
17
|
+
<select name="status">
|
|
18
|
+
{% for s in ['planned', 'active', 'done'] %}<option value="{{ s }}" {% if selected_milestone.status == s %}selected{% endif %}>{{ s }}</option>{% endfor %}
|
|
19
|
+
</select>
|
|
20
|
+
</label>
|
|
21
|
+
<label>Color
|
|
22
|
+
<input type="color" name="color" value="{{ selected_milestone.color or '#3567e0' }}" class="ms-color-input">
|
|
23
|
+
</label>
|
|
24
|
+
</div>
|
|
25
|
+
<label class="field-label">Projects <span class="hint">A milestone can span multiple projects.</span></label>
|
|
26
|
+
<div class="ms-projects" data-ms-projects>
|
|
27
|
+
<div class="ms-proj-chips">
|
|
28
|
+
{% for name in selected_milestone.projects %}
|
|
29
|
+
<span class="ms-proj-chip" data-name="{{ name }}">
|
|
30
|
+
<span class="swatch" style="background: {{ project_colors.get(name, '#2e6fd8') }}"></span>{{ name }}
|
|
31
|
+
<input type="hidden" name="projects" value="{{ name }}">
|
|
32
|
+
<button type="button" class="ms-proj-x" data-ms-proj-remove aria-label="Remove project">×</button>
|
|
33
|
+
</span>
|
|
34
|
+
{% endfor %}
|
|
35
|
+
</div>
|
|
36
|
+
{% if projects %}
|
|
37
|
+
<select class="ms-proj-select" data-ms-proj-select>
|
|
38
|
+
<option value="">Add a project…</option>
|
|
39
|
+
{% for p in projects if p.name not in selected_milestone.projects %}<option value="{{ p.name }}" data-color="{{ p.color }}">{{ p.name }}</option>{% endfor %}
|
|
40
|
+
</select>
|
|
41
|
+
{% else %}
|
|
42
|
+
<span class="muted">No projects yet. Create one in the Projects view.</span>
|
|
43
|
+
{% endif %}
|
|
44
|
+
</div>
|
|
45
|
+
<label>Summary<textarea name="summary" rows="2">{{ selected_milestone.summary }}</textarea></label>
|
|
46
|
+
<label>Description<textarea name="description" rows="5">{{ selected_milestone.description }}</textarea></label>
|
|
47
|
+
<div class="form-grid">
|
|
48
|
+
<label>Start date<input type="date" name="start_date" value="{{ selected_milestone.start_date or '' }}"></label>
|
|
49
|
+
<label>Target date<input type="date" name="target_date" value="{{ selected_milestone.target_date or '' }}"></label>
|
|
50
|
+
</div>
|
|
51
|
+
<button type="submit">Save Milestone</button>
|
|
52
|
+
</form>
|
|
53
|
+
|
|
54
|
+
<section class="subsection">
|
|
55
|
+
<h3>Tasks {% if rollup %}<span class="notes-count-badge">{{ rollup.total }}</span>{% endif %}</h3>
|
|
56
|
+
|
|
57
|
+
<div class="ms-add" data-ms-add data-mid="{{ selected_milestone.id }}" data-view="{{ filters.view }}">
|
|
58
|
+
<div class="ms-add-search">
|
|
59
|
+
<input type="text" class="ms-add-input" placeholder="Add a task — search by name…" autocomplete="off">
|
|
60
|
+
<div class="ms-add-menu" hidden>
|
|
61
|
+
{% for t in task_index %}
|
|
62
|
+
<button type="button" class="ms-add-option" data-id="{{ t.id }}" data-search="{{ (t.title ~ ' ' ~ t.id ~ ' ' ~ t.project)|lower }}" hx-post="/milestones/{{ selected_milestone.id }}/tasks/{{ t.id }}/add?f_view={{ filters.view }}{% if filters.show_closed %}&f_show_closed=1{% endif %}&f_stale_days={{ filters.stale_days }}" hx-target="#app-main" hx-swap="outerHTML"{% if t.id in selected_milestone.task_ids %} hidden{% endif %}>
|
|
63
|
+
<span class="dep-option-title">{{ t.title }}</span>
|
|
64
|
+
<span class="dep-option-meta">
|
|
65
|
+
<span class="dep-status {{ t.status }}">{{ t.status }}</span>
|
|
66
|
+
{% if t.project %}<span class="dep-proj">{{ t.project }}</span>{% endif %}
|
|
67
|
+
{% if t.due_date %}<span class="dep-due">due {{ t.due_date }}</span>{% endif %}
|
|
68
|
+
<span class="dep-id">{{ t.id }}</span>
|
|
69
|
+
</span>
|
|
70
|
+
</button>
|
|
71
|
+
{% endfor %}
|
|
72
|
+
<p class="dep-empty muted" hidden>No matching tasks.</p>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<div class="ms-task-box">
|
|
78
|
+
<ul class="ms-task-list">
|
|
79
|
+
{% for t in rollup.tasks %}
|
|
80
|
+
<li class="ms-task {{ t.status }}">
|
|
81
|
+
<button type="button" class="ms-task-open" hx-get="/tasks/{{ t.id }}/panel?view={{ filters.view }}&milestone={{ selected_milestone.id }}{% if filters.show_closed %}&show_closed=1{% endif %}&stale_days={{ filters.stale_days }}" hx-target="#task-panel" hx-swap="innerHTML">
|
|
82
|
+
<span class="ms-task-title">{{ t.title }}</span>
|
|
83
|
+
<span class="ms-task-meta">
|
|
84
|
+
<span class="status-pill {{ t.status }}">{{ t.status }}</span>
|
|
85
|
+
{% if t.project %}<span class="project" style="background: {{ project_colors.get(t.project, '#2e6fd8') }}22; color: {{ project_colors.get(t.project, '#2e6fd8') }}">{{ t.project }}</span>{% endif %}
|
|
86
|
+
{% if t.due_date %}<span class="muted">due {{ t.due_date }}</span>{% endif %}
|
|
87
|
+
</span>
|
|
88
|
+
</button>
|
|
89
|
+
<button type="button" class="ms-task-remove" hx-post="/milestones/{{ selected_milestone.id }}/tasks/{{ t.id }}/remove?f_view={{ filters.view }}{% if filters.show_closed %}&f_show_closed=1{% endif %}&f_stale_days={{ filters.stale_days }}" hx-target="#app-main" hx-swap="outerHTML" title="Remove from milestone" aria-label="Remove from milestone">×</button>
|
|
90
|
+
</li>
|
|
91
|
+
{% else %}
|
|
92
|
+
<li class="muted ms-empty">No tasks yet. Search above to add one, or use “+ New task” below.</li>
|
|
93
|
+
{% endfor %}
|
|
94
|
+
</ul>
|
|
95
|
+
<form hx-post="/milestones/{{ selected_milestone.id }}/tasks/new" hx-target="#app-main" hx-swap="outerHTML" class="ms-new-task-bar">
|
|
96
|
+
<input type="hidden" name="f_view" value="{{ filters.view }}">
|
|
97
|
+
<input type="hidden" name="f_show_closed" value="{% if filters.show_closed %}1{% endif %}">
|
|
98
|
+
<input type="hidden" name="f_stale_days" value="{{ filters.stale_days }}">
|
|
99
|
+
<button type="submit" class="ms-new-task-btn" title="Quickly add a new task to this milestone">+ New task</button>
|
|
100
|
+
</form>
|
|
101
|
+
</div>
|
|
102
|
+
{% if rollup and rollup.missing %}
|
|
103
|
+
<p class="muted ms-missing">⚠ {{ rollup.missing|length }} referenced task(s) no longer exist.</p>
|
|
104
|
+
{% endif %}
|
|
105
|
+
</section>
|
|
106
|
+
|
|
107
|
+
<section class="subsection notes-section">
|
|
108
|
+
<div class="notes-head">
|
|
109
|
+
<h3>Notes {% if selected_milestone.notes %}<span class="notes-count-badge">{{ selected_milestone.notes|length }}</span>{% endif %}</h3>
|
|
110
|
+
</div>
|
|
111
|
+
<div class="notes-list">
|
|
112
|
+
{% for note in selected_milestone.notes|reverse %}
|
|
113
|
+
<article class="note">
|
|
114
|
+
<small>{{ note.created_at }}</small>
|
|
115
|
+
<div class="markdown">{{ note.body|markdown|safe }}</div>
|
|
116
|
+
</article>
|
|
117
|
+
{% else %}
|
|
118
|
+
<p class="muted">No notes yet. Add your first one below.</p>
|
|
119
|
+
{% endfor %}
|
|
120
|
+
</div>
|
|
121
|
+
<form hx-post="/milestones/{{ selected_milestone.id }}/note" hx-target="#task-panel" hx-swap="innerHTML" class="inline-form note-add">
|
|
122
|
+
<input type="hidden" name="f_view" value="{{ filters.view }}">
|
|
123
|
+
<input type="hidden" name="f_show_closed" value="{% if filters.show_closed %}1{% endif %}">
|
|
124
|
+
<input type="hidden" name="f_stale_days" value="{{ filters.stale_days }}">
|
|
125
|
+
<label class="field-label">Add a note</label>
|
|
126
|
+
<textarea name="body" rows="3" placeholder="Write a note using Markdown…"></textarea>
|
|
127
|
+
<button type="submit">Add Note</button>
|
|
128
|
+
</form>
|
|
129
|
+
</section>
|
|
130
|
+
|
|
131
|
+
<section class="subsection">
|
|
132
|
+
<h3>Attachments</h3>
|
|
133
|
+
<ul class="attachment-list">
|
|
134
|
+
{% for att in selected_milestone.attachments %}
|
|
135
|
+
<li class="attachment-item">
|
|
136
|
+
{% if att.kind == 'image' %}
|
|
137
|
+
<a href="/{{ att.path }}" target="_blank" class="attachment-thumb"><img src="/{{ att.path }}" alt="{{ att.filename }}"></a>
|
|
138
|
+
{% else %}
|
|
139
|
+
<span class="attachment-icon" aria-hidden="true">📄</span>
|
|
140
|
+
{% endif %}
|
|
141
|
+
<div class="attachment-info">
|
|
142
|
+
<a href="/{{ att.path }}" target="_blank" class="attachment-name">{{ att.filename }}</a>
|
|
143
|
+
{% if att.description %}<p class="attachment-desc">{{ att.description }}</p>{% endif %}
|
|
144
|
+
<div class="attachment-meta">
|
|
145
|
+
{% if att.uploaded_at %}<span title="Uploaded">{{ att.uploaded_at[:10] }}{% if att.uploaded_at[11:16] %} {{ att.uploaded_at[11:16] }}{% endif %}</span>{% endif %}
|
|
146
|
+
<a href="/{{ att.path }}" download class="attachment-dl">Download</a>
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
</li>
|
|
150
|
+
{% else %}
|
|
151
|
+
<li class="muted">No attachments yet.</li>
|
|
152
|
+
{% endfor %}
|
|
153
|
+
</ul>
|
|
154
|
+
<form hx-post="/milestones/{{ selected_milestone.id }}/attachment" hx-target="#task-panel" hx-swap="innerHTML" enctype="multipart/form-data" class="attachment-form">
|
|
155
|
+
<input type="hidden" name="f_view" value="{{ filters.view }}">
|
|
156
|
+
<input type="hidden" name="f_show_closed" value="{% if filters.show_closed %}1{% endif %}">
|
|
157
|
+
<input type="hidden" name="f_stale_days" value="{{ filters.stale_days }}">
|
|
158
|
+
<input type="file" name="attachment" required>
|
|
159
|
+
<input type="text" name="description" placeholder="Description (optional)">
|
|
160
|
+
<button type="submit">Upload</button>
|
|
161
|
+
</form>
|
|
162
|
+
</section>
|
|
163
|
+
|
|
164
|
+
<section class="subsection">
|
|
165
|
+
<h3>Milestone Burndown Chart</h3>
|
|
166
|
+
<canvas id="milestone-burndown-{{ selected_milestone.id }}" class="burndown-canvas" height="180"></canvas>
|
|
167
|
+
<script>
|
|
168
|
+
(function () {
|
|
169
|
+
var canvas = document.getElementById('milestone-burndown-{{ selected_milestone.id }}');
|
|
170
|
+
if (!canvas || typeof Chart === 'undefined') return;
|
|
171
|
+
window.taskunityCharts = window.taskunityCharts || {};
|
|
172
|
+
if (window.taskunityCharts[canvas.id]) window.taskunityCharts[canvas.id].destroy();
|
|
173
|
+
fetch('/milestones/{{ selected_milestone.id }}/burndown.json')
|
|
174
|
+
.then(function (response) { return response.json(); })
|
|
175
|
+
.then(function (payload) {
|
|
176
|
+
var points = (payload.points || []).map(function (point) {
|
|
177
|
+
// Convert ISO string to ms timestamp so Chart.js time scale parses it reliably
|
|
178
|
+
return { x: new Date(point.x || new Date()).getTime(), y: point.y, label: point.label };
|
|
179
|
+
});
|
|
180
|
+
window.taskunityCharts[canvas.id] = new Chart(canvas, {
|
|
181
|
+
type: 'line',
|
|
182
|
+
data: {
|
|
183
|
+
datasets: [{
|
|
184
|
+
label: 'Remaining work',
|
|
185
|
+
data: points,
|
|
186
|
+
borderColor: '#2f9e5f',
|
|
187
|
+
backgroundColor: 'rgba(47, 158, 95, 0.12)',
|
|
188
|
+
fill: true,
|
|
189
|
+
tension: 0.25,
|
|
190
|
+
stepped: 'before'
|
|
191
|
+
}]
|
|
192
|
+
},
|
|
193
|
+
options: {
|
|
194
|
+
parsing: false,
|
|
195
|
+
plugins: {
|
|
196
|
+
legend: { display: false },
|
|
197
|
+
tooltip: {
|
|
198
|
+
callbacks: {
|
|
199
|
+
label: function (ctx) {
|
|
200
|
+
return (ctx.raw && ctx.raw.label ? ctx.raw.label + ' • ' : '') + 'Remaining ' + ctx.raw.y + '%';
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
scales: {
|
|
206
|
+
x: { type: 'time', time: { unit: 'day', displayFormats: { day: 'MMM d' }, tooltipFormat: 'MMM d, yyyy' } },
|
|
207
|
+
y: { beginAtZero: true, max: 100, title: { display: true, text: 'Remaining %' } }
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
})
|
|
212
|
+
.catch(function () {});
|
|
213
|
+
})();
|
|
214
|
+
</script>
|
|
215
|
+
</section>
|
|
216
|
+
|
|
217
|
+
<form action="/milestones/{{ selected_milestone.id }}/delete" method="post" onsubmit="return confirm('Delete milestone {{ selected_milestone.title }}? Tasks are not deleted.')" class="danger-zone">
|
|
218
|
+
<button type="submit">Delete Milestone</button>
|
|
219
|
+
</form>
|
|
220
|
+
|
|
221
|
+
<p class="task-id-footer">Milestone ID: <span class="mono">{{ selected_milestone.id }}</span></p>
|
|
222
|
+
</div>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<section class="panel">
|
|
2
|
+
<div class="panel-heading">
|
|
3
|
+
<h2>Milestones</h2>
|
|
4
|
+
<span>{{ milestones|length }} milestone{{ '' if milestones|length == 1 else 's' }}</span>
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
{% if milestones %}
|
|
8
|
+
<div class="milestone-cards">
|
|
9
|
+
{% for m in milestones %}
|
|
10
|
+
{% set r = milestone_rollups.get(m.id) %}
|
|
11
|
+
<div class="milestone-card {{ m.status }}" style="--ms-color: {{ m.color or '#3567e0' }}">
|
|
12
|
+
<button class="milestone-open" hx-get="/partials/main?milestone={{ m.id }}&view=board{% if filters.show_closed %}&show_closed=1{% endif %}&stale_days={{ filters.stale_days }}" hx-target="#app-main" hx-swap="outerHTML" title="Filter tasks to this milestone">
|
|
13
|
+
<div class="mc-head">
|
|
14
|
+
<span class="mc-flag" aria-hidden="true">◆</span>
|
|
15
|
+
<strong>{{ m.title }}</strong>
|
|
16
|
+
<span class="ms-status {{ m.status }}">{{ m.status }}</span>
|
|
17
|
+
</div>
|
|
18
|
+
{% if m.summary %}<p class="mc-summary">{{ m.summary }}</p>{% endif %}
|
|
19
|
+
<div class="mc-projects">
|
|
20
|
+
{% for proj in m.projects %}
|
|
21
|
+
<span class="project" style="background: {{ project_colors.get(proj, '#2e6fd8') }}22; color: {{ project_colors.get(proj, '#2e6fd8') }}">{{ proj }}</span>
|
|
22
|
+
{% else %}
|
|
23
|
+
<span class="muted">No projects assigned</span>
|
|
24
|
+
{% endfor %}
|
|
25
|
+
</div>
|
|
26
|
+
<div class="mc-progress" title="{{ r.progress }}% complete">
|
|
27
|
+
<div style="width: {{ r.progress }}%; background: {{ m.color or '#3567e0' }}"></div>
|
|
28
|
+
</div>
|
|
29
|
+
<div class="mc-stats">
|
|
30
|
+
<span>{{ r.total }} task{{ '' if r.total == 1 else 's' }}</span>
|
|
31
|
+
<span>{{ r.done }} done</span>
|
|
32
|
+
<span>{{ r.progress }}%</span>
|
|
33
|
+
{% if m.target_date %}<span class="mc-target">🎯 {{ m.target_date }}</span>{% endif %}
|
|
34
|
+
</div>
|
|
35
|
+
</button>
|
|
36
|
+
<div class="mc-foot">
|
|
37
|
+
<button class="btn-link" hx-get="/milestones/{{ m.id }}/panel?view=milestones{% if filters.show_closed %}&show_closed=1{% endif %}&stale_days={{ filters.stale_days }}" hx-target="#task-panel" hx-swap="innerHTML">Details</button>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
{% endfor %}
|
|
41
|
+
</div>
|
|
42
|
+
{% else %}
|
|
43
|
+
<p class="muted">No milestones yet. Create one below to group tasks across projects.</p>
|
|
44
|
+
{% endif %}
|
|
45
|
+
|
|
46
|
+
<form hx-post="/milestones/create" hx-target="#app-main" hx-swap="outerHTML" class="new-milestone-form">
|
|
47
|
+
<input type="hidden" name="f_show_closed" value="{% if filters.show_closed %}1{% endif %}">
|
|
48
|
+
<input type="hidden" name="f_stale_days" value="{{ filters.stale_days }}">
|
|
49
|
+
<h3>New milestone</h3>
|
|
50
|
+
<div class="add-row">
|
|
51
|
+
<input name="title" placeholder="e.g. Beta launch" required>
|
|
52
|
+
<button type="submit">Create Milestone</button>
|
|
53
|
+
</div>
|
|
54
|
+
</form>
|
|
55
|
+
</section>
|