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><a href="/projects/{{ project_name }}/tasks/{{ task.full_id }}" class="unstyled" aria-current="page"><strong>{{ task.short_id }}</strong></a></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 == "closed" %} success{% elif task.status == "in_progress" %} secondary{% 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    <ot-dropdown>
 37      <button popovertarget="status-menu" class="outline small">
 38        Change status
 39        <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>
 40      </button>
 41      <menu popover id="status-menu">
 42        <button role="menuitemradio" aria-checked="{{ task.status == "open" }}" {% if task.status == "open" %}disabled{% else %}form="set-open"{% endif %}>Open</button>
 43        <button role="menuitemradio" aria-checked="{{ task.status == "in_progress" }}" {% if task.status == "in_progress" %}disabled{% else %}form="set-in-progress"{% endif %}>In progress</button>
 44        <button role="menuitemradio" aria-checked="{{ task.status == "closed" }}" {% if task.status == "closed" %}disabled{% else %}form="set-closed"{% endif %}>Closed</button>
 45      </menu>
 46    </ot-dropdown>
 47    {% if task.status != "open" %}
 48    <form id="set-open" method="post" action="/projects/{{ project_name }}/tasks/{{ task.full_id }}" hidden>
 49      <input type="hidden" name="status" value="open">
 50    </form>
 51    {% endif %}
 52    {% if task.status != "in_progress" %}
 53    <form id="set-in-progress" method="post" action="/projects/{{ project_name }}/tasks/{{ task.full_id }}" hidden>
 54      <input type="hidden" name="status" value="in_progress">
 55    </form>
 56    {% endif %}
 57    {% if task.status != "closed" %}
 58    <form id="set-closed" method="post" action="/projects/{{ project_name }}/tasks/{{ task.full_id }}" hidden>
 59      <input type="hidden" name="status" value="closed">
 60    </form>
 61    {% endif %}
 62    <ot-dropdown>
 63      <button popovertarget="confirm-delete" class="outline small danger">Delete</button>
 64      <article class="card" popover id="confirm-delete">
 65        <header>
 66          <h4>Are you sure?</h4>
 67          <p>This will delete <strong>{{ task.short_id }}</strong>.</p>
 68        </header>
 69        <br>
 70        <footer class="hstack gap-2">
 71          <button class="outline small" popovertarget="confirm-delete">Cancel</button>
 72          <button data-variant="danger" class="small" form="delete-task" type="submit">Delete</button>
 73        </footer>
 74      </article>
 75    </ot-dropdown>
 76    <form id="delete-task" method="post" action="/projects/{{ project_name }}/tasks/{{ task.full_id }}/delete" hidden></form>
 77  </div>
 78
 79  {% if !task.labels.is_empty() %}
 80  <section class="mt-4" aria-label="Labels">
 81    <h2>Labels</h2>
 82    <div class="hstack gap-2">
 83      {% for l in task.labels %}
 84      <form method="post" action="/projects/{{ project_name }}/tasks/{{ task.full_id }}/labels" class="inline">
 85        <input type="hidden" name="action" value="rm">
 86        <input type="hidden" name="label" value="{{ l }}">
 87        <button type="submit" class="badge outline small" aria-label="Remove label {{ l }}" title="Remove">{{ l }} ×</button>
 88      </form>
 89      {% endfor %}
 90    </div>
 91  </section>
 92  {% endif %}
 93
 94  <section class="mt-4" aria-label="Add label">
 95    <form method="post" action="/projects/{{ project_name }}/tasks/{{ task.full_id }}/labels">
 96      <input type="hidden" name="action" value="add">
 97      <fieldset class="group">
 98        <input type="text" name="label" placeholder="Label…" required aria-label="New label">
 99        <button type="submit">Add label</button>
100      </fieldset>
101    </form>
102  </section>
103
104  <details class="mt-4">
105    <summary>Work log ({{ task.logs.len() }})</summary>
106    {% for log in task.logs %}
107    <div><time datetime="{{ log.timestamp }}">{{ log.timestamp_display }}</time> — {{ log.message|safe }}</div>
108    {% endfor %}
109    <form method="post" action="/projects/{{ project_name }}/tasks/{{ task.full_id }}/log" class="mt-4">
110      <label for="log-message">Add log entry</label>
111      <textarea id="log-message" name="message" rows="3" required placeholder="What happened…"></textarea>
112      <button type="submit" class="outline small mt-2">Add log</button>
113    </form>
114  </details>
115</article>
116
117<article class="card mt-4">
118  <h2>Blockers</h2>
119  {% if !blockers_open.is_empty() || !blockers_resolved.is_empty() %}
120  <ul>
121    {% for b in blockers_open %}
122    <li class="hstack items-center gap-2">
123      <a href="/projects/{{ project_name }}/tasks/{{ b.full_id }}"><code>{{ b.short_id }}</code></a> <span class="badge warning">open</span>
124      <form method="post" action="/projects/{{ project_name }}/tasks/{{ task.full_id }}/deps" class="inline">
125        <input type="hidden" name="action" value="rm">
126        <input type="hidden" name="blocker" value="{{ b.short_id }}">
127        <button type="submit" class="outline small" aria-label="Remove blocker {{ b.short_id }}">×</button>
128      </form>
129    </li>
130    {% endfor %}
131    {% for b in blockers_resolved %}
132    <li class="hstack items-center gap-2">
133      <a href="/projects/{{ project_name }}/tasks/{{ b.full_id }}"><code>{{ b.short_id }}</code></a> <span class="badge success">resolved</span>
134      <form method="post" action="/projects/{{ project_name }}/tasks/{{ task.full_id }}/deps" class="inline">
135        <input type="hidden" name="action" value="rm">
136        <input type="hidden" name="blocker" value="{{ b.short_id }}">
137        <button type="submit" class="outline small" aria-label="Remove blocker {{ b.short_id }}">×</button>
138      </form>
139    </li>
140    {% endfor %}
141  </ul>
142  {% endif %}
143  <form method="post" action="/projects/{{ project_name }}/tasks/{{ task.full_id }}/deps" class="mt-2">
144    <input type="hidden" name="action" value="add">
145    <fieldset class="group">
146      <input type="text" name="blocker" placeholder="Task ID…" required aria-label="Blocker task ID">
147      <button type="submit">Add blocker</button>
148    </fieldset>
149  </form>
150</article>
151
152{% if !subtasks.is_empty() %}
153<article class="card mt-4">
154  <h2>Subtasks</h2>
155  {% call macros::task_table(project_name, subtasks, "Subtasks") %}{% endcall %}
156</article>
157{% endif %}
158{% endblock %}