task.html

  1{% extends "base.html" %}
  2{% import "macros.html" as macros %}
  3
  4{% block title %}{{ task.title }} — 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">{{ project_name }}</a></li>
 12    <li aria-hidden="true">/</li>
 13    <li aria-current="page"><strong>{{ task.short_id }}</strong></li>
 14    <li>
 15      <button class="outline small js-copy-id" hidden aria-label="Copy task ID {{ task.short_id }}" data-copy="{{ task.short_id }}"><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"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg></button>
 16    </li>
 17  </ol>
 18</nav>
 19
 20<article class="card mt-4">
 21  <header>
 22    <h1 class="task-title">{{ task.title }}</h1>
 23    <span class="badge{% if task.status == "open" %} success{% elif task.status == "in_progress" %} warning{% endif %}">{{ task.status }}</span>
 24  </header>
 25
 26  {% if !task.description.is_empty() %}
 27  <div>{{ task.description|safe }}</div>
 28  {% endif %}
 29
 30  <hr>
 31  <footer>
 32    <p class="text-light">{{ task.task_type }} · {{ task.priority }} priority · {{ task.effort }} effort<br>Created <time datetime="{{ task.created_at }}">{{ task.created_at_display }}</time> · Updated <time datetime="{{ task.updated_at }}">{{ task.updated_at_display }}</time></p>
 33  </footer>
 34
 35  <div class="hstack gap-2 mt-4">
 36    <button commandfor="dlg-edit-task" command="show-modal" class="outline small">Edit</button>
 37    <ot-dropdown>
 38      <button popovertarget="status-menu" class="outline small">
 39        Change status
 40        <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>
 41      </button>
 42      <menu popover id="status-menu">
 43        <button role="menuitemradio" aria-checked="{{ task.status == "open" }}" {% if task.status == "open" %}disabled{% else %}form="set-open"{% endif %}>Open</button>
 44        <button role="menuitemradio" aria-checked="{{ task.status == "in_progress" }}" {% if task.status == "in_progress" %}disabled{% else %}form="set-in-progress"{% endif %}>In progress</button>
 45        <button role="menuitemradio" aria-checked="{{ task.status == "closed" }}" {% if task.status == "closed" %}disabled{% else %}form="set-closed"{% endif %}>Closed</button>
 46      </menu>
 47    </ot-dropdown>
 48    {% if task.status != "open" %}
 49    <form id="set-open" method="post" action="/projects/{{ project_name }}/tasks/{{ task.full_id }}" hidden>
 50      <input type="hidden" name="status" value="open">
 51    </form>
 52    {% endif %}
 53    {% if task.status != "in_progress" %}
 54    <form id="set-in-progress" method="post" action="/projects/{{ project_name }}/tasks/{{ task.full_id }}" hidden>
 55      <input type="hidden" name="status" value="in_progress">
 56    </form>
 57    {% endif %}
 58    {% if task.status != "closed" %}
 59    <form id="set-closed" method="post" action="/projects/{{ project_name }}/tasks/{{ task.full_id }}" hidden>
 60      <input type="hidden" name="status" value="closed">
 61    </form>
 62    {% endif %}
 63    <ot-dropdown>
 64      <button popovertarget="confirm-delete" class="outline small danger">Delete</button>
 65      <article class="card" popover id="confirm-delete">
 66        <header>
 67          <h4>Are you sure?</h4>
 68          <p>This will delete <strong>{{ task.short_id }}</strong>.</p>
 69        </header>
 70        <br>
 71        <footer class="hstack gap-2">
 72          <button class="outline small" popovertarget="confirm-delete">Cancel</button>
 73          <button data-variant="danger" class="small" form="delete-task" type="submit">Delete</button>
 74        </footer>
 75      </article>
 76    </ot-dropdown>
 77    <form id="delete-task" method="post" action="/projects/{{ project_name }}/tasks/{{ task.full_id }}/delete" hidden></form>
 78  </div>
 79
 80  {% if !task.labels.is_empty() %}
 81  <section class="mt-4" aria-label="Labels">
 82    <h2>Labels</h2>
 83    <div class="hstack gap-2">
 84      {% for l in task.labels %}
 85      <form method="post" action="/projects/{{ project_name }}/tasks/{{ task.full_id }}/labels" class="inline">
 86        <input type="hidden" name="action" value="rm">
 87        <input type="hidden" name="label" value="{{ l }}">
 88        <button type="submit" class="badge outline small" aria-label="Remove label {{ l }}" title="Remove">{{ l }} ×</button>
 89      </form>
 90      {% endfor %}
 91    </div>
 92  </section>
 93  {% endif %}
 94
 95  <section class="mt-4" aria-label="Add label">
 96    <form method="post" action="/projects/{{ project_name }}/tasks/{{ task.full_id }}/labels">
 97      <input type="hidden" name="action" value="add">
 98      <fieldset class="group">
 99        <input type="text" name="label" placeholder="Label…" required aria-label="New label">
100        <button type="submit">Add label</button>
101      </fieldset>
102    </form>
103  </section>
104
105  <details class="mt-4">
106    <summary>Work log ({{ task.logs.len() }})</summary>
107    {% for log in task.logs %}
108    <article class="log-entry mt-4">
109      <p class="text-light"><small><time datetime="{{ log.timestamp }}">{{ log.timestamp_display }}</time></small></p>
110      <div>{{ log.message|safe }}</div>
111    </article>
112    {% if !loop.last %}
113    <hr>
114    {% endif %}
115    {% endfor %}
116    <form method="post" action="/projects/{{ project_name }}/tasks/{{ task.full_id }}/log" class="mt-4">
117      <label for="log-message">Add log entry</label>
118      <textarea id="log-message" name="message" rows="3" required placeholder="What happened…"></textarea>
119      <button type="submit" class="outline small mt-2">Add log</button>
120    </form>
121  </details>
122</article>
123
124<article class="card mt-4">
125  <h2>Blockers</h2>
126  {% if !blockers_open.is_empty() || !blockers_resolved.is_empty() %}
127  <ul>
128    {% for b in blockers_open %}
129    <li class="hstack items-center gap-2">
130      <a href="/projects/{{ project_name }}/tasks/{{ b.full_id }}"><code>{{ b.short_id }}</code></a> <span class="badge warning">open</span>
131      <form method="post" action="/projects/{{ project_name }}/tasks/{{ task.full_id }}/deps" class="inline">
132        <input type="hidden" name="action" value="rm">
133        <input type="hidden" name="blocker" value="{{ b.short_id }}">
134        <button type="submit" class="outline small" aria-label="Remove blocker {{ b.short_id }}">×</button>
135      </form>
136    </li>
137    {% endfor %}
138    {% for b in blockers_resolved %}
139    <li class="hstack items-center gap-2">
140      <a href="/projects/{{ project_name }}/tasks/{{ b.full_id }}"><code>{{ b.short_id }}</code></a> <span class="badge success">resolved</span>
141      <form method="post" action="/projects/{{ project_name }}/tasks/{{ task.full_id }}/deps" class="inline">
142        <input type="hidden" name="action" value="rm">
143        <input type="hidden" name="blocker" value="{{ b.short_id }}">
144        <button type="submit" class="outline small" aria-label="Remove blocker {{ b.short_id }}">×</button>
145      </form>
146    </li>
147    {% endfor %}
148  </ul>
149  {% endif %}
150  <form method="post" action="/projects/{{ project_name }}/tasks/{{ task.full_id }}/deps" class="mt-2">
151    <input type="hidden" name="action" value="add">
152    <fieldset class="group">
153      <input type="text" name="blocker" placeholder="Task ID…" required aria-label="Blocker task ID">
154      <button type="submit">Add blocker</button>
155    </fieldset>
156  </form>
157</article>
158
159{% if !subtasks.is_empty() %}
160<article class="card mt-4">
161  <h2>Subtasks</h2>
162  <div class="table">
163    <table>
164      <caption class="sr-only">Subtasks</caption>
165      <thead>
166        <tr>
167          <th scope="col">ID</th>
168          <th scope="col">Status</th>
169          <th scope="col">Type</th>
170          <th scope="col">Priority</th>
171          <th scope="col">Effort</th>
172          <th scope="col">Title</th>
173          <th scope="col">Labels</th>
174          <th scope="col">Created</th>
175        </tr>
176      </thead>
177      <tbody>
178        {% for t in subtasks %}
179        <tr>
180          <td><a href="/projects/{{ project_name }}/tasks/{{ t.full_id }}"><code>{{ t.short_id }}</code></a></td>
181          <td><span class="badge{% if t.status == "open" %} success{% elif t.status == "in_progress" %} warning{% endif %}">{{ t.status }}</span></td>
182          <td>{{ t.task_type }}</td>
183          <td>{{ t.priority }}</td>
184          <td>{{ t.effort }}</td>
185          <td>{{ t.title }}</td>
186          <td>{% for l in t.labels %}{% if !loop.first %}, {% endif %}{{ l }}{% endfor %}</td>
187          <td><time datetime="{{ t.created_at }}">{{ t.created_at_display }}</time></td>
188        </tr>
189        {% endfor %}
190      </tbody>
191    </table>
192  </div>
193</article>
194{% endif %}
195
196{% call macros::task_form_dialog("dlg-edit-task", edit_heading, edit_form_action, "Save", "edit", all_projects, active_project, "edit", task.title, task.description_raw, task.task_type, task.priority, task.effort, task.parent_id, "") %}{% endcall %}
197{% endblock %}