project.html

  1{% extends "base.html" %}
  2{% import "macros.html" as macros %}
  3
  4{% block title %}{{ project_name }} — td{% endblock %}
  5
  6{% block content %}
  7<nav aria-label="Breadcrumb">
  8  <ol class="unstyled hstack">
  9    <li><a href="/" class="unstyled">Projects</a></li>
 10    <li aria-hidden="true">/</li>
 11    <li><a href="/projects/{{ project_name }}" class="unstyled" aria-current="page"><strong>{{ project_name }}</strong></a></li>
 12  </ol>
 13</nav>
 14
 15<h1>{{ project_name }}</h1>
 16
 17<div class="row">
 18    <article class="card col-4">
 19      <p class="text-light">Open</p>
 20      <p><strong>{{ stats_open }}</strong></p>
 21    </article>
 22    <article class="card col-4">
 23      <p class="text-light">In progress</p>
 24      <p><strong>{{ stats_in_progress }}</strong></p>
 25    </article>
 26    <article class="card col-4">
 27      <p class="text-light">Closed</p>
 28      <p><strong>{{ stats_closed }}</strong></p>
 29    </article>
 30</div>
 31
 32{% if !next_up.is_empty() %}
 33<details open class="mt-4">
 34  <summary>Next up</summary>
 35  <div class="table">
 36    <table>
 37      <caption class="sr-only">Top scored tasks recommended to work on next</caption>
 38      <thead>
 39        <tr>
 40          <th scope="col">#</th>
 41          <th scope="col">ID</th>
 42          <th scope="col">Score</th>
 43          <th scope="col">Title</th>
 44          <th scope="col">Change status</th>
 45        </tr>
 46      </thead>
 47      <tbody>
 48        {% for (i, s) in next_up.iter().enumerate() %}
 49        <tr>
 50          <td>{{ i + 1 }}</td>
 51          <td><a href="/projects/{{ project_name }}/tasks/{{ s.id }}"><code>{{ s.short_id }}</code></a></td>
 52          <td>{{ s.score }}</td>
 53          <td>{{ s.title }}</td>
 54          <td>
 55            <ot-dropdown>
 56              <button popovertarget="next-status-{{ s.short_id }}" class="outline small" aria-label="Change status of {{ s.short_id }}, currently {{ s.status_display }}">
 57                {{ s.status_display }}
 58                <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>
 59              </button>
 60              <menu popover id="next-status-{{ s.short_id }}">
 61                <button role="menuitemradio" aria-checked="{{ s.status == "open" }}" {% if s.status == "open" %}disabled{% else %}form="next-set-open-{{ s.short_id }}"{% endif %}>Open</button>
 62                <button role="menuitemradio" aria-checked="{{ s.status == "in_progress" }}" {% if s.status == "in_progress" %}disabled{% else %}form="next-set-in-progress-{{ s.short_id }}"{% endif %}>In progress</button>
 63                <button role="menuitemradio" aria-checked="{{ s.status == "closed" }}" {% if s.status == "closed" %}disabled{% else %}form="next-set-closed-{{ s.short_id }}"{% endif %}>Closed</button>
 64              </menu>
 65            </ot-dropdown>
 66            {% if s.status != "open" %}
 67            <form id="next-set-open-{{ s.short_id }}" method="post" action="/projects/{{ project_name }}/tasks/{{ s.id }}" hidden>
 68              <input type="hidden" name="status" value="open">
 69              <input type="hidden" name="redirect" value="/projects/{{ project_name }}">
 70            </form>
 71            {% endif %}
 72            {% if s.status != "in_progress" %}
 73            <form id="next-set-in-progress-{{ s.short_id }}" method="post" action="/projects/{{ project_name }}/tasks/{{ s.id }}" hidden>
 74              <input type="hidden" name="status" value="in_progress">
 75              <input type="hidden" name="redirect" value="/projects/{{ project_name }}">
 76            </form>
 77            {% endif %}
 78            {% if s.status != "closed" %}
 79            <form id="next-set-closed-{{ s.short_id }}" method="post" action="/projects/{{ project_name }}/tasks/{{ s.id }}" hidden>
 80              <input type="hidden" name="status" value="closed">
 81              <input type="hidden" name="redirect" value="/projects/{{ project_name }}">
 82            </form>
 83            {% endif %}
 84          </td>
 85        </tr>
 86        {% endfor %}
 87      </tbody>
 88    </table>
 89  </div>
 90</details>
 91{% endif %}
 92
 93<details{% if next_up.is_empty() || filter_priority.is_some() || filter_effort.is_some() || filter_label.is_some() || !filter_search.is_empty() || filter_status.as_deref() != Some("open") || sort_ctx.field != "priority" || sort_ctx.order != "asc" %} open{% endif %} class="mt-4">
 94  <summary>Tasks</summary>
 95
 96  <form method="get" action="/projects/{{ project_name }}" class="hstack gap-2 mt-2 mb-4">
 97    <div data-field>
 98      <label for="filter-status">Status</label>
 99      <select id="filter-status" name="status" aria-label="Filter by status">
100        <option value="">All</option>
101        <option value="open"{% if filter_status.as_deref() == Some("open") %} selected{% endif %}>Open</option>
102        <option value="in_progress"{% if filter_status.as_deref() == Some("in_progress") %} selected{% endif %}>In progress</option>
103        <option value="closed"{% if filter_status.as_deref() == Some("closed") %} selected{% endif %}>Closed</option>
104      </select>
105    </div>
106    <div data-field>
107      <label for="filter-priority">Priority</label>
108      <select id="filter-priority" name="priority" aria-label="Filter by priority">
109        <option value="">All</option>
110        <option value="high"{% if filter_priority.as_deref() == Some("high") %} selected{% endif %}>High</option>
111        <option value="medium"{% if filter_priority.as_deref() == Some("medium") %} selected{% endif %}>Medium</option>
112        <option value="low"{% if filter_priority.as_deref() == Some("low") %} selected{% endif %}>Low</option>
113      </select>
114    </div>
115    <div data-field>
116      <label for="filter-effort">Effort</label>
117      <select id="filter-effort" name="effort" aria-label="Filter by effort">
118        <option value="">All</option>
119        <option value="low"{% if filter_effort.as_deref() == Some("low") %} selected{% endif %}>Low</option>
120        <option value="medium"{% if filter_effort.as_deref() == Some("medium") %} selected{% endif %}>Medium</option>
121        <option value="high"{% if filter_effort.as_deref() == Some("high") %} selected{% endif %}>High</option>
122      </select>
123    </div>
124    <div data-field>
125      <label for="filter-label">Label</label>
126      <select id="filter-label" name="label" aria-label="Filter by label">
127        <option value="">All</option>
128        {% for l in all_labels %}
129        <option value="{{ l }}"{% if filter_label.as_deref() == Some(l.as_str()) %} selected{% endif %}>{{ l }}</option>
130        {% endfor %}
131      </select>
132    </div>
133    <div data-field>
134      <label for="filter-search">Search</label>
135      <input type="text" id="filter-search" name="q" value="{{ filter_search }}" placeholder="Search titles…" aria-label="Search tasks by title">
136    </div>
137    <input type="hidden" name="sort" value="{{ sort_ctx.field }}">
138    <input type="hidden" name="order" value="{{ sort_ctx.order }}">
139    <button type="submit" class="outline">Filter</button>
140  </form>
141
142  {% if page_tasks.is_empty() %}
143  <p class="text-light">No tasks match the current filters.</p>
144  {% else %}
145  {% call macros::sortable_task_table(project_name, page_tasks, "Task list for project", sort_ctx) %}{% endcall %}
146
147  {% if total_pages > 1 %}
148  <nav aria-label="Task list pagination" class="mt-4">
149    <menu class="buttons">
150      {% if page > 1 %}
151      {% let prev = page - 1 %}
152      <li><a href="{{ self.pagination_href(prev) }}" class="button outline small">← Previous</a></li>
153      {% endif %}
154      {% for p in pagination_pages %}
155      <li>
156        {% if *p == page %}
157        <a href="{{ self.pagination_href(p) }}" class="button small" aria-current="page">{{ p }}</a>
158        {% else %}
159        <a href="{{ self.pagination_href(p) }}" class="button outline small">{{ p }}</a>
160        {% endif %}
161      </li>
162      {% endfor %}
163      {% if page < total_pages %}
164      {% let next = page + 1 %}
165      <li><a href="{{ self.pagination_href(next) }}" class="button outline small">Next →</a></li>
166      {% endif %}
167    </menu>
168  </nav>
169  {% endif %}
170  {% endif %}
171</details>
172{% endblock %}