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 aria-current="page"><strong>{{ project_name }}</strong></li>
 12  </ol>
 13</nav>
 14
 15<h1>{{ project_name }}</h1>
 16
 17{% if stats_in_progress > 0 %}
 18<div class="row">
 19    <article class="card col-4 border-warning">
 20      <p class="text-light">In progress</p>
 21      <p><strong>{{ stats_in_progress }}</strong></p>
 22    </article>
 23    <article class="card col-4{% if stats_open > 0 %} border-success{% endif %}">
 24      <p class="text-light">Open</p>
 25      <p><strong>{{ stats_open }}</strong></p>
 26    </article>
 27    <article class="card col-4">
 28      <p class="text-light">Closed</p>
 29      <p><strong>{{ stats_closed }}</strong></p>
 30    </article>
 31</div>
 32{% else %}
 33<div class="row">
 34    <article class="card col-4{% if stats_open > 0 %} border-success{% endif %}">
 35      <p class="text-light">Open</p>
 36      <p><strong>{{ stats_open }}</strong></p>
 37    </article>
 38    <article class="card col-4">
 39      <p class="text-light">In progress</p>
 40      <p><strong>{{ stats_in_progress }}</strong></p>
 41    </article>
 42    <article class="card col-4">
 43      <p class="text-light">Closed</p>
 44      <p><strong>{{ stats_closed }}</strong></p>
 45    </article>
 46</div>
 47{% endif %}
 48
 49{% if !next_up.is_empty() %}
 50<details open class="mt-4">
 51  <summary>Next up</summary>
 52  <form method="get" action="/projects/{{ project_name }}" class="mt-2 mb-2">
 53    {% let preserve = self.section_only_qs() %}
 54    {% if !preserve.is_empty() %}
 55    {% for pair in preserve.split('&') %}
 56    {% let kv = pair.splitn(2, '=').collect::<Vec<_>>() %}
 57    {% if kv.len() == 2 %}
 58    <input type="hidden" name="{{ kv[0] }}" value="{{ kv[1] }}">
 59    {% endif %}
 60    {% endfor %}
 61    {% endif %}
 62    <label>
 63      <input type="checkbox" role="switch" name="next_mode" value="effort"{% if next_mode == "effort" %} checked{% endif %} onchange="this.form.submit()">
 64      Effort mode
 65    </label>
 66    <noscript><button type="submit" class="outline small">Apply</button></noscript>
 67  </form>
 68  <div class="table">
 69    <table>
 70      <caption class="sr-only">Top scored tasks recommended to work on next</caption>
 71      <thead>
 72        <tr>
 73          <th scope="col">#</th>
 74          <th scope="col">ID</th>
 75          <th scope="col">Score</th>
 76          <th scope="col">Title</th>
 77          <th scope="col">Labels</th>
 78          <th scope="col">Change status</th>
 79        </tr>
 80      </thead>
 81      <tbody>
 82        {% for (i, s) in next_up.iter().enumerate() %}
 83        <tr>
 84          <td>{{ i + 1 }}</td>
 85          <td><a href="/projects/{{ project_name }}/tasks/{{ s.id }}"><code>{{ s.short_id }}</code></a></td>
 86          <td title="{{ s.equation }}&#10;{{ s.unblocks_display }}">{{ s.score }}</td>
 87          <td>{{ s.title }}</td>
 88          <td>{% for l in s.labels %}{% if !loop.first %}, {% endif %}{{ l }}{% endfor %}</td>
 89          <td>
 90            <ot-dropdown>
 91              <button popovertarget="next-status-{{ s.short_id }}" class="outline small" aria-label="Change status of {{ s.short_id }}, currently {{ s.status_display }}">
 92                {{ s.status_display }}
 93                <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>
 94              </button>
 95              <menu popover id="next-status-{{ s.short_id }}">
 96                <button role="menuitemradio" aria-checked="{{ s.status == "open" }}" {% if s.status == "open" %}disabled{% else %}form="next-set-open-{{ s.short_id }}"{% endif %}>Open</button>
 97                <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>
 98                <button role="menuitemradio" aria-checked="{{ s.status == "closed" }}" {% if s.status == "closed" %}disabled{% else %}form="next-set-closed-{{ s.short_id }}"{% endif %}>Closed</button>
 99              </menu>
100            </ot-dropdown>
101            {% if s.status != "open" %}
102            <form id="next-set-open-{{ s.short_id }}" method="post" action="/projects/{{ project_name }}/tasks/{{ s.id }}" hidden>
103              <input type="hidden" name="status" value="open">
104              <input type="hidden" name="redirect" value="{{ self.mutation_redirect() }}">
105            </form>
106            {% endif %}
107            {% if s.status != "in_progress" %}
108            <form id="next-set-in-progress-{{ s.short_id }}" method="post" action="/projects/{{ project_name }}/tasks/{{ s.id }}" hidden>
109              <input type="hidden" name="status" value="in_progress">
110              <input type="hidden" name="redirect" value="{{ self.mutation_redirect() }}">
111            </form>
112            {% endif %}
113            {% if s.status != "closed" %}
114            <form id="next-set-closed-{{ s.short_id }}" method="post" action="/projects/{{ project_name }}/tasks/{{ s.id }}" hidden>
115              <input type="hidden" name="status" value="closed">
116              <input type="hidden" name="redirect" value="{{ self.mutation_redirect() }}">
117            </form>
118            {% endif %}
119          </td>
120        </tr>
121        {% endfor %}
122      </tbody>
123    </table>
124  </div>
125</details>
126{% endif %}
127
128{# Render sections: in-progress before open when it has tasks, otherwise open first #}
129{% let redirect_url = self.mutation_redirect() %}
130
131{% if in_progress.total_count > 0 %}
132{# In progress first when there are tasks #}
133<details{% if in_progress.total_count > 0 || in_progress.has_user_params %} open{% endif %} class="mt-4">
134  <summary>{{ in_progress.label }} ({% if in_progress.filtered_count != in_progress.total_count %}{{ in_progress.filtered_count }} of {% endif %}{{ in_progress.total_count }})</summary>
135  {% call macros::task_section(project_name, in_progress, all_labels, redirect_url) %}{% endcall %}
136</details>
137
138<details{% if open.total_count > 0 || open.has_user_params %} open{% endif %} class="mt-4">
139  <summary>{{ open.label }} ({% if open.filtered_count != open.total_count %}{{ open.filtered_count }} of {% endif %}{{ open.total_count }})</summary>
140  {% call macros::task_section(project_name, open, all_labels, redirect_url) %}{% endcall %}
141</details>
142{% else %}
143{# Open first when nothing is in progress #}
144<details{% if open.total_count > 0 || open.has_user_params %} open{% endif %} class="mt-4">
145  <summary>{{ open.label }} ({% if open.filtered_count != open.total_count %}{{ open.filtered_count }} of {% endif %}{{ open.total_count }})</summary>
146  {% call macros::task_section(project_name, open, all_labels, redirect_url) %}{% endcall %}
147</details>
148
149<details{% if in_progress.has_user_params %} open{% endif %} class="mt-4">
150  <summary>{{ in_progress.label }} ({{ in_progress.total_count }})</summary>
151  {% call macros::task_section(project_name, in_progress, all_labels, redirect_url) %}{% endcall %}
152</details>
153{% endif %}
154
155<details{% if closed.has_user_params %} open{% endif %} class="mt-4">
156  <summary>{{ closed.label }} ({% if closed.filtered_count != closed.total_count %}{{ closed.filtered_count }} of {% endif %}{{ closed.total_count }})</summary>
157  {% call macros::task_section(project_name, closed, all_labels, redirect_url) %}{% endcall %}
158</details>
159{% endblock %}