macros.html

  1{% macro task_section(project_name, section, all_labels, redirect_url) %}
  2  <form method="get" action="/projects/{{ project_name }}" class="hstack gap-2 mt-2 mb-4">
  3    <div data-field>
  4      <label for="{{ section.sort_ctx.prefix }}filter-priority">Priority</label>
  5      <select id="{{ section.sort_ctx.prefix }}filter-priority" name="{{ section.sort_ctx.prefix }}priority" aria-label="Filter by priority">
  6        <option value="">All</option>
  7        <option value="high"{% if section.filter_priority.as_deref() == Some("high") %} selected{% endif %}>High</option>
  8        <option value="medium"{% if section.filter_priority.as_deref() == Some("medium") %} selected{% endif %}>Medium</option>
  9        <option value="low"{% if section.filter_priority.as_deref() == Some("low") %} selected{% endif %}>Low</option>
 10      </select>
 11    </div>
 12    <div data-field>
 13      <label for="{{ section.sort_ctx.prefix }}filter-effort">Effort</label>
 14      <select id="{{ section.sort_ctx.prefix }}filter-effort" name="{{ section.sort_ctx.prefix }}effort" aria-label="Filter by effort">
 15        <option value="">All</option>
 16        <option value="low"{% if section.filter_effort.as_deref() == Some("low") %} selected{% endif %}>Low</option>
 17        <option value="medium"{% if section.filter_effort.as_deref() == Some("medium") %} selected{% endif %}>Medium</option>
 18        <option value="high"{% if section.filter_effort.as_deref() == Some("high") %} selected{% endif %}>High</option>
 19      </select>
 20    </div>
 21    <div data-field>
 22      <label for="{{ section.sort_ctx.prefix }}filter-label">Label</label>
 23      <select id="{{ section.sort_ctx.prefix }}filter-label" name="{{ section.sort_ctx.prefix }}label" aria-label="Filter by label">
 24        <option value="">All</option>
 25        {% for l in all_labels %}
 26        <option value="{{ l }}"{% if section.filter_label.as_deref() == Some(l.as_str()) %} selected{% endif %}>{{ l }}</option>
 27        {% endfor %}
 28      </select>
 29    </div>
 30    <div data-field>
 31      <label for="{{ section.sort_ctx.prefix }}filter-type">Type</label>
 32      <select id="{{ section.sort_ctx.prefix }}filter-type" name="{{ section.sort_ctx.prefix }}type" aria-label="Filter by type">
 33        <option value="">All</option>
 34        <option value="task"{% if section.filter_type.as_deref() == Some("task") %} selected{% endif %}>Task</option>
 35        <option value="bug"{% if section.filter_type.as_deref() == Some("bug") %} selected{% endif %}>Bug</option>
 36        <option value="feature"{% if section.filter_type.as_deref() == Some("feature") %} selected{% endif %}>Feature</option>
 37      </select>
 38    </div>
 39    <div data-field>
 40      <label for="{{ section.sort_ctx.prefix }}filter-search">Search</label>
 41      <input type="text" id="{{ section.sort_ctx.prefix }}filter-search" name="{{ section.sort_ctx.prefix }}q" value="{{ section.filter_search }}" placeholder="Search titles…" aria-label="Search tasks by title">
 42    </div>
 43    {# Preserve other sections' state as hidden fields when this form submits #}
 44    {% let preserve = section.sort_ctx.preserve_qs %}
 45    {% if !preserve.is_empty() %}
 46    {% for pair in preserve.split('&') %}
 47    {% let kv = pair.splitn(2, '=').collect::<Vec<_>>() %}
 48    {% if kv.len() == 2 %}
 49    <input type="hidden" name="{{ kv[0] }}" value="{{ kv[1] }}">
 50    {% endif %}
 51    {% endfor %}
 52    {% endif %}
 53    <input type="hidden" name="{{ section.sort_ctx.prefix }}sort" value="{{ section.sort_ctx.field }}">
 54    <input type="hidden" name="{{ section.sort_ctx.prefix }}order" value="{{ section.sort_ctx.order }}">
 55    <button type="submit" class="outline">Filter</button>
 56  </form>
 57
 58  {% if section.tasks.is_empty() %}
 59  <p class="text-light">No tasks match the current filters.</p>
 60  {% else %}
 61  <div class="table">
 62    <table>
 63      <caption class="sr-only">{{ section.label }} tasks for project</caption>
 64      <thead>
 65        <tr>
 66          <th scope="col"><a href="{{ section.sort_ctx.column_href("id") }}">ID{{ section.sort_ctx.arrow("id") }}</a></th>
 67          <th scope="col"><a href="{{ section.sort_ctx.column_href("type") }}">Type{{ section.sort_ctx.arrow("type") }}</a></th>
 68          <th scope="col"><a href="{{ section.sort_ctx.column_href("priority") }}">Priority{{ section.sort_ctx.arrow("priority") }}</a></th>
 69          <th scope="col"><a href="{{ section.sort_ctx.column_href("effort") }}">Effort{{ section.sort_ctx.arrow("effort") }}</a></th>
 70          <th scope="col"><a href="{{ section.sort_ctx.column_href("title") }}">Title{{ section.sort_ctx.arrow("title") }}</a></th>
 71          <th scope="col">Labels</th>
 72          <th scope="col"><a href="{{ section.sort_ctx.column_href("created") }}">Created{{ section.sort_ctx.arrow("created") }}</a></th>
 73          <th scope="col">Change status</th>
 74        </tr>
 75      </thead>
 76      <tbody>
 77        {% for t in section.tasks %}
 78        <tr>
 79          <td><a href="/projects/{{ project_name }}/tasks/{{ t.full_id }}"><code>{{ t.short_id }}</code></a></td>
 80          <td>{{ t.task_type }}</td>
 81          <td>{{ t.priority }}</td>
 82          <td>{{ t.effort }}</td>
 83          <td>{{ t.title }}</td>
 84          <td>{% for l in t.labels %}{% if !loop.first %}, {% endif %}{{ l }}{% endfor %}</td>
 85          <td><time datetime="{{ t.created_at }}">{{ t.created_at_display }}</time></td>
 86          <td>
 87            <ot-dropdown>
 88              <button popovertarget="{{ section.sort_ctx.prefix }}status-{{ t.short_id }}" class="outline small" aria-label="Change status of {{ t.short_id }}, currently {{ t.status_display }}">
 89                {{ t.status_display }}
 90                <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>
 91              </button>
 92              <menu popover id="{{ section.sort_ctx.prefix }}status-{{ t.short_id }}">
 93                <button role="menuitemradio" aria-checked="{{ t.status == "open" }}" {% if t.status == "open" %}disabled{% else %}form="{{ section.sort_ctx.prefix }}set-open-{{ t.short_id }}"{% endif %}>Open</button>
 94                <button role="menuitemradio" aria-checked="{{ t.status == "in_progress" }}" {% if t.status == "in_progress" %}disabled{% else %}form="{{ section.sort_ctx.prefix }}set-in-progress-{{ t.short_id }}"{% endif %}>In progress</button>
 95                <button role="menuitemradio" aria-checked="{{ t.status == "closed" }}" {% if t.status == "closed" %}disabled{% else %}form="{{ section.sort_ctx.prefix }}set-closed-{{ t.short_id }}"{% endif %}>Closed</button>
 96              </menu>
 97            </ot-dropdown>
 98            {% if t.status != "open" %}
 99            <form id="{{ section.sort_ctx.prefix }}set-open-{{ t.short_id }}" method="post" action="/projects/{{ project_name }}/tasks/{{ t.full_id }}" hidden>
100              <input type="hidden" name="status" value="open">
101              <input type="hidden" name="redirect" value="{{ redirect_url }}">
102            </form>
103            {% endif %}
104            {% if t.status != "in_progress" %}
105            <form id="{{ section.sort_ctx.prefix }}set-in-progress-{{ t.short_id }}" method="post" action="/projects/{{ project_name }}/tasks/{{ t.full_id }}" hidden>
106              <input type="hidden" name="status" value="in_progress">
107              <input type="hidden" name="redirect" value="{{ redirect_url }}">
108            </form>
109            {% endif %}
110            {% if t.status != "closed" %}
111            <form id="{{ section.sort_ctx.prefix }}set-closed-{{ t.short_id }}" method="post" action="/projects/{{ project_name }}/tasks/{{ t.full_id }}" hidden>
112              <input type="hidden" name="status" value="closed">
113              <input type="hidden" name="redirect" value="{{ redirect_url }}">
114            </form>
115            {% endif %}
116          </td>
117        </tr>
118        {% endfor %}
119      </tbody>
120    </table>
121  </div>
122
123  {% if section.total_pages > 1 %}
124  <nav aria-label="{{ section.label }} task list pagination" class="mt-4">
125    <menu class="buttons">
126      {% if section.page > 1 %}
127      {% let prev = section.page - 1 %}
128      <li><a href="{{ section.pagination_href(prev) }}" class="button outline small">← Previous</a></li>
129      {% endif %}
130      {% for p in section.pagination_pages %}
131      <li>
132        {% if *p == section.page %}
133        <a href="{{ section.pagination_href(p) }}" class="button small" aria-current="page">{{ p }}</a>
134        {% else %}
135        <a href="{{ section.pagination_href(p) }}" class="button outline small">{{ p }}</a>
136        {% endif %}
137      </li>
138      {% endfor %}
139      {% if section.page < section.total_pages %}
140      {% let next = section.page + 1 %}
141      <li><a href="{{ section.pagination_href(next) }}" class="button outline small">Next →</a></li>
142      {% endif %}
143    </menu>
144  </nav>
145  {% endif %}
146  {% endif %}
147{% endmacro %}
148
149{% macro task_form_dialog(dialog_id, heading, form_action, submit_label, mode, all_projects, active_project, prefix, title, description, task_type, priority, effort, parent, labels) %}
150<dialog id="{{ dialog_id }}" closedby="any">
151  <form method="post" action="{{ form_action }}" {% if mode == "create" %}id="form-new-task"{% endif %}>
152    <header>
153      <h3>{{ heading }}</h3>
154    </header>
155    <div class="vstack">
156      {% if mode == "create" %}
157      <div data-field>
158        <label for="{{ prefix }}-project">Project</label>
159        <select id="{{ prefix }}-project" aria-label="Project" required>
160          <option value=""{% if active_project.is_none() %} selected{% endif %}>Select project…</option>
161          {% for p in all_projects %}
162          <option value="{{ p }}"{% if active_project.as_deref() == Some(p.as_str()) %} selected{% endif %}>{{ p }}</option>
163          {% endfor %}
164        </select>
165      </div>
166      {% endif %}
167      <label data-field>
168        Title
169        <input type="text" id="{{ prefix }}-title" name="title" value="{{ title }}" required>
170      </label>
171      <label data-field>
172        Description
173        <textarea id="{{ prefix }}-desc" name="description" rows="{% if mode == "edit" %}6{% else %}3{% endif %}">{{ description }}</textarea>
174      </label>
175      <div class="hstack gap-2">
176        <div data-field>
177          <label for="{{ prefix }}-type">Type</label>
178          <select id="{{ prefix }}-type" name="task_type">
179            <option value="task"{% if task_type == "task" %} selected{% endif %}>Task</option>
180            <option value="bug"{% if task_type == "bug" %} selected{% endif %}>Bug</option>
181            <option value="feature"{% if task_type == "feature" %} selected{% endif %}>Feature</option>
182          </select>
183        </div>
184        <div data-field>
185          <label for="{{ prefix }}-priority">Priority</label>
186          <select id="{{ prefix }}-priority" name="priority">
187            <option value="high"{% if priority == "high" %} selected{% endif %}>High</option>
188            <option value="medium"{% if priority == "medium" %} selected{% endif %}>Medium</option>
189            <option value="low"{% if priority == "low" %} selected{% endif %}>Low</option>
190          </select>
191        </div>
192        <div data-field>
193          <label for="{{ prefix }}-effort">Effort</label>
194          <select id="{{ prefix }}-effort" name="effort">
195            <option value="low"{% if effort == "low" %} selected{% endif %}>Low</option>
196            <option value="medium"{% if effort == "medium" %} selected{% endif %}>Medium</option>
197            <option value="high"{% if effort == "high" %} selected{% endif %}>High</option>
198          </select>
199        </div>
200      </div>
201      {% if mode == "create" %}
202      <label data-field>
203        Labels <small class="text-light">(comma-separated)</small>
204        <input type="text" id="{{ prefix }}-labels" name="labels" value="{{ labels }}" placeholder="frontend, urgent">
205      </label>
206      {% endif %}
207      <label data-field>
208        Parent task ID <small class="text-light">{% if mode == "edit" %}(leave empty to clear){% else %}(optional){% endif %}</small>
209        <input type="text" id="{{ prefix }}-parent" name="parent" value="{{ parent }}" placeholder="td-XXXXXXX" aria-label="Parent task ID">
210      </label>
211      {% if mode == "edit" %}
212      <input type="hidden" name="_parent_present" value="1">
213      {% endif %}
214    </div>
215    <footer>
216      <button type="button" commandfor="{{ dialog_id }}" command="close" class="outline">Cancel</button>
217      <button type="submit">{{ submit_label }}</button>
218    </footer>
219  </form>
220</dialog>
221{% endmacro %}