03019898b0
Добавлена офлайн/EndUser-подпись «ознакомлен и принял в работу» для документов отчётов и файловые вложения к задачам через Nextcloud. Co-authored-by: Cursor <cursoragent@cursor.com>
229 lines
17 KiB
HTML
229 lines
17 KiB
HTML
{% load common_tags %}
|
||
{% load i18n %}
|
||
<div class="page-content">
|
||
|
||
<!-- Назад -->
|
||
<div style="margin-bottom:1.25rem;">
|
||
<a class="btn btn-ghost" style="padding:0.375rem 0.5rem; font-size:0.8125rem; gap:0.375rem; color:var(--color-muted-foreground); cursor:pointer; text-decoration:none;"
|
||
href="/tasks/" hx-get="/tasks/" hx-target="#main-content" hx-push-url="/tasks/" hx-indicator="#loader">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m15 18-6-6 6-6"/></svg>
|
||
{% trans "Задачи" %}
|
||
</a>
|
||
</div>
|
||
|
||
<!-- Заголовок задачі -->
|
||
<div style="display:flex; align-items:flex-start; justify-content:space-between; gap:1rem; margin-bottom:1.5rem;">
|
||
<div style="flex:1;">
|
||
<div style="display:flex; align-items:center; gap:0.625rem; margin-bottom:0.5rem; flex-wrap:wrap;">
|
||
{% if task.status == 'done' %}
|
||
<span class="badge badge-success" style="gap:0.25rem;">✓ {% trans "Выполнено" %}</span>
|
||
{% elif task.status == 'in_progress' %}
|
||
<span class="badge badge-success" style="gap:0.25rem;"><span style="width:6px; height:6px; border-radius:50%; background:hsl(152 55% 45%);"></span>{% trans "В работе" %}</span>
|
||
{% elif task.status == 'cancelled' %}
|
||
<span class="badge badge-muted">{% trans "Отменено" %}</span>
|
||
{% else %}
|
||
<span class="badge badge-warning" style="gap:0.25rem;"><span style="width:6px; height:6px; border-radius:50%; background:hsl(38 92% 50%);"></span>{% trans "Ожидает" %}</span>
|
||
{% endif %}
|
||
<span class="badge {{ task.priority_badge }}">{{ task.get_priority_display }}</span>
|
||
{% if task.due_date %}
|
||
<span style="font-size:0.75rem; color:{% if task.is_overdue %}var(--color-destructive); font-weight:600;{% else %}var(--color-muted-foreground);{% endif %}">
|
||
{% trans "срок:" %} {{ task.due_date|date:"d.m.Y" }}{% if task.is_overdue %} — {% trans "просрочено" %}{% endif %}
|
||
</span>
|
||
{% endif %}
|
||
</div>
|
||
<h1 style="font-size:1.375rem; font-weight:600; color:var(--color-foreground); line-height:1.3;">{{ task.title }}</h1>
|
||
</div>
|
||
<div style="display:flex; gap:0.5rem; flex-shrink:0;">
|
||
{% if can_edit %}
|
||
<a href="/tasks/{{ task.pk }}/edit/" hx-get="/tasks/{{ task.pk }}/edit/" hx-target="#main-content" hx-push-url="/tasks/{{ task.pk }}/edit/" hx-indicator="#loader"
|
||
class="btn btn-secondary" style="font-size:0.8125rem; text-decoration:none;">
|
||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
|
||
{% trans "Редактировать" %}
|
||
</a>
|
||
{% endif %}
|
||
{% if can_delete %}
|
||
<form hx-post="/tasks/{{ task.pk }}/delete/" hx-target="#main-content" hx-push-url="/tasks/" hx-indicator="#loader"
|
||
hx-confirm="{% trans 'Удалить задачу' %} «{{ task.title }}»?" style="margin:0;">
|
||
{% csrf_token %}
|
||
<button type="submit" class="btn btn-ghost" style="font-size:0.8125rem; color:var(--color-destructive);">{% trans "Удалить" %}</button>
|
||
</form>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Двоколонковий лейаут -->
|
||
<div style="display:grid; grid-template-columns:1fr 17rem; gap:1.25rem; align-items:start;">
|
||
|
||
<!-- Ліва колонка -->
|
||
<div style="display:flex; flex-direction:column; gap:1rem;">
|
||
|
||
<!-- Опис -->
|
||
<div style="background:var(--color-card); border-radius:0.75rem; border:1px solid var(--color-border); padding:1.25rem;">
|
||
<h3 style="font-size:0.875rem; font-weight:600; color:var(--color-foreground); margin-bottom:0.75rem;">{% trans "Описание" %}</h3>
|
||
{% if task.description %}
|
||
<div style="font-size:0.875rem; color:var(--color-muted-foreground); line-height:1.6; white-space:pre-line;">{{ task.description }}</div>
|
||
{% else %}
|
||
<div style="font-size:0.875rem; color:var(--color-muted-foreground); opacity:0.6;">{% trans "Описание не добавлено" %}</div>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<!-- Чек-лист -->
|
||
{% if checklist %}
|
||
<div style="background:var(--color-card); border-radius:0.75rem; border:1px solid var(--color-border); padding:1.25rem;">
|
||
<div style="display:flex; align-items:center; gap:0.5rem; margin-bottom:0.75rem;">
|
||
<h3 style="font-size:0.875rem; font-weight:600; color:var(--color-foreground);">{% trans "Чек-лист" %}</h3>
|
||
<span style="font-size:0.6875rem; background:var(--color-muted); color:var(--color-muted-foreground); border-radius:999px; padding:0.0625rem 0.5rem; font-weight:500;">{{ task.checklist_done }}/{{ task.checklist_total }}</span>
|
||
</div>
|
||
<div style="display:flex; flex-direction:column; gap:0.125rem;">
|
||
{% for item in checklist %}
|
||
<label style="display:flex; align-items:center; gap:0.625rem; padding:0.4375rem 0.5rem; border-radius:0.5rem; font-size:0.875rem; {% if is_participant %}cursor:pointer;{% endif %} {% if item.is_done %}color:var(--color-muted-foreground);{% else %}color:var(--color-foreground);{% endif %}"
|
||
{% if is_participant %}class="tr-hover"{% endif %}>
|
||
<input type="checkbox" {% if item.is_done %}checked{% endif %} {% if not is_participant %}disabled{% endif %}
|
||
{% if is_participant %}hx-post="/tasks/{{ task.pk }}/" hx-target="#main-content" hx-indicator="#loader"
|
||
hx-vals='{"action": "checklist_toggle", "item_id": "{{ item.pk }}"}'{% endif %}
|
||
style="width:1rem; height:1rem; flex-shrink:0;">
|
||
<span style="{% if item.is_done %}text-decoration:line-through;{% endif %}">{{ item.label }}</span>
|
||
{% if item.is_done and item.done_by %}
|
||
<span style="margin-left:auto; font-size:0.6875rem; color:var(--color-muted-foreground); white-space:nowrap;">{{ item.done_by.short_name }} · {{ item.done_at|date:"d.m H:i" }}</span>
|
||
{% endif %}
|
||
</label>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
|
||
<!-- Вложения -->
|
||
<div style="background:var(--color-card); border-radius:0.75rem; border:1px solid var(--color-border); padding:1.25rem;">
|
||
<div style="display:flex; align-items:center; gap:0.5rem; margin-bottom:0.75rem;">
|
||
<h3 style="font-size:0.875rem; font-weight:600; color:var(--color-foreground);">{% trans "Вложения" %}</h3>
|
||
<span style="font-size:0.6875rem; background:var(--color-muted); color:var(--color-muted-foreground); border-radius:999px; padding:0.0625rem 0.5rem; font-weight:500;">{{ attachments|length }}</span>
|
||
</div>
|
||
<div id="task-attachments-slot">
|
||
{% include "partials/task-attachments.html" %}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Коментарі -->
|
||
<div style="background:var(--color-card); border-radius:0.75rem; border:1px solid var(--color-border); overflow:hidden;">
|
||
<div style="padding:1rem 1.25rem; border-bottom:1px solid var(--color-border); display:flex; align-items:center; gap:0.5rem;">
|
||
<h3 style="font-size:0.875rem; font-weight:600; color:var(--color-foreground);">{% trans "Комментарии" %}</h3>
|
||
<span style="font-size:0.6875rem; background:var(--color-muted); color:var(--color-muted-foreground); border-radius:999px; padding:0.0625rem 0.5rem; font-weight:500;">{{ comments|length }}</span>
|
||
</div>
|
||
|
||
{% for comment in comments %}
|
||
<div style="padding:1rem 1.25rem; border-bottom:1px solid var(--color-border);">
|
||
<div style="display:flex; gap:0.75rem;">
|
||
<div style="width:2rem; height:2rem; border-radius:50%; background:hsl(174 55% 42% / 0.15); color:hsl(174 55% 35%); display:flex; align-items:center; justify-content:center; font-size:0.6875rem; font-weight:600; flex-shrink:0;">{{ comment.author|initials }}</div>
|
||
<div style="flex:1; min-width:0;">
|
||
<div style="display:flex; align-items:baseline; gap:0.5rem; margin-bottom:0.375rem;">
|
||
<span style="font-size:0.8125rem; font-weight:600; color:var(--color-foreground);">{{ comment.author.get_full_name|default:"—" }}</span>
|
||
<span style="font-size:0.75rem; color:var(--color-muted-foreground);">{{ comment.created_at|date:"d.m.Y, H:i" }}</span>
|
||
</div>
|
||
<p style="font-size:0.875rem; color:var(--color-muted-foreground); line-height:1.5; white-space:pre-line;">{{ comment.text }}</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% empty %}
|
||
<div style="padding:1.25rem; text-align:center; color:var(--color-muted-foreground); font-size:0.8125rem;">{% trans "Комментариев пока нет" %}</div>
|
||
{% endfor %}
|
||
|
||
<!-- Форма коментаря -->
|
||
<form hx-post="/tasks/{{ task.pk }}/" hx-target="#main-content" hx-indicator="#loader"
|
||
style="padding:1rem 1.25rem; border-top:1px solid var(--color-border); display:flex; gap:0.75rem; align-items:flex-start; margin:0;">
|
||
{% csrf_token %}
|
||
<input type="hidden" name="action" value="comment">
|
||
<div style="width:2rem; height:2rem; border-radius:50%; background:var(--color-accent); color:var(--color-accent-foreground); display:flex; align-items:center; justify-content:center; font-size:0.625rem; font-weight:700; flex-shrink:0;">{{ request.user|initials }}</div>
|
||
<textarea name="text" required placeholder="{% trans 'Написать комментарий...' %}"
|
||
style="flex:1; min-height:2.5rem; resize:vertical; background:var(--color-muted); border:1px solid var(--color-border); border-radius:var(--radius); padding:0.5rem 0.75rem; font-size:0.875rem; color:var(--color-foreground); font-family:var(--font-sans); outline:none; box-sizing:border-box;"></textarea>
|
||
<button type="submit" class="btn btn-primary" style="flex-shrink:0;">{% trans "Отправить" %}</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Права колонка -->
|
||
<div style="display:flex; flex-direction:column; gap:1rem;">
|
||
|
||
{% if is_participant and task.status != 'cancelled' %}
|
||
<!-- Дії -->
|
||
<div style="background:var(--color-card); border-radius:0.75rem; border:1px solid var(--color-border); padding:1rem 1.25rem; display:flex; flex-direction:column; gap:0.5rem;">
|
||
<h3 style="font-size:0.8125rem; font-weight:600; color:var(--color-foreground); margin-bottom:0.25rem;">{% trans "Действия" %}</h3>
|
||
{% if task.status == 'pending' %}
|
||
<form hx-post="/tasks/{{ task.pk }}/" hx-target="#main-content" hx-indicator="#loader" style="margin:0;">
|
||
{% csrf_token %}
|
||
<input type="hidden" name="action" value="status"><input type="hidden" name="status" value="in_progress">
|
||
<button type="submit" class="btn btn-primary" style="width:100%; font-size:0.8125rem;">{% trans "Взять в работу" %}</button>
|
||
</form>
|
||
{% endif %}
|
||
{% if task.status != 'done' %}
|
||
<form hx-post="/tasks/{{ task.pk }}/" hx-target="#main-content" hx-indicator="#loader" style="margin:0;">
|
||
{% csrf_token %}
|
||
<input type="hidden" name="action" value="status"><input type="hidden" name="status" value="done">
|
||
<button type="submit" class="btn btn-secondary" style="width:100%; font-size:0.8125rem;">✓ Позначити виконаною</button>
|
||
</form>
|
||
{% else %}
|
||
<form hx-post="/tasks/{{ task.pk }}/" hx-target="#main-content" hx-indicator="#loader" style="margin:0;">
|
||
{% csrf_token %}
|
||
<input type="hidden" name="action" value="status"><input type="hidden" name="status" value="pending">
|
||
<button type="submit" class="btn btn-secondary" style="width:100%; font-size:0.8125rem;">{% trans "Вернуть в работу" %}</button>
|
||
</form>
|
||
{% endif %}
|
||
{% if can_edit and task.status != 'done' %}
|
||
<form hx-post="/tasks/{{ task.pk }}/" hx-target="#main-content" hx-indicator="#loader" style="margin:0;">
|
||
{% csrf_token %}
|
||
<input type="hidden" name="action" value="status"><input type="hidden" name="status" value="cancelled">
|
||
<button type="submit" class="btn btn-ghost" style="width:100%; font-size:0.8125rem; color:var(--color-muted-foreground);">{% trans "Отменить задачу" %}</button>
|
||
</form>
|
||
{% endif %}
|
||
</div>
|
||
{% endif %}
|
||
|
||
<!-- Учасники -->
|
||
<div style="background:var(--color-card); border-radius:0.75rem; border:1px solid var(--color-border); padding:1rem 1.25rem;">
|
||
<h3 style="font-size:0.8125rem; font-weight:600; color:var(--color-foreground); margin-bottom:0.75rem;">{% trans "Участники" %}</h3>
|
||
<div style="display:flex; flex-direction:column; gap:0.875rem;">
|
||
{% for role_label, role_people in role_groups %}
|
||
{% if role_people %}
|
||
<div>
|
||
<div style="font-size:0.6875rem; color:var(--color-muted-foreground); text-transform:uppercase; letter-spacing:0.04em; margin-bottom:0.5rem;">{{ role_label }}</div>
|
||
<div style="display:flex; flex-direction:column; gap:0.5rem;">
|
||
{% for emp in role_people %}
|
||
<div style="display:flex; align-items:center; gap:0.5rem;">
|
||
<div style="width:1.75rem; height:1.75rem; border-radius:50%; background:color-mix(in srgb, var(--color-primary) 10%, transparent); color:var(--color-primary); display:flex; align-items:center; justify-content:center; font-size:0.625rem; font-weight:600; flex-shrink:0;">{{ emp|initials }}</div>
|
||
<div>
|
||
<div style="font-size:0.8125rem; color:var(--color-foreground);">{{ emp.get_full_name }}</div>
|
||
{% if emp.post %}<div style="font-size:0.6875rem; color:var(--color-muted-foreground);">{{ emp.post.name }}</div>{% endif %}
|
||
</div>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
{% endfor %}
|
||
{% if not task.responsible.all and not task.co_executors.all and not task.observers.all %}
|
||
<div style="font-size:0.8125rem; color:var(--color-muted-foreground); opacity:0.6;">{% trans "Не назначено" %}</div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Інфо -->
|
||
<div style="background:var(--color-card); border-radius:0.75rem; border:1px solid var(--color-border); padding:1rem 1.25rem; display:flex; flex-direction:column; gap:0.625rem;">
|
||
<div>
|
||
<div style="font-size:0.6875rem; color:var(--color-muted-foreground); text-transform:uppercase; letter-spacing:0.04em; margin-bottom:0.125rem;">{% trans "Автор" %}</div>
|
||
<div style="font-size:0.8125rem; color:var(--color-foreground);">{{ task.creator.get_full_name|default:"—" }}</div>
|
||
</div>
|
||
<div>
|
||
<div style="font-size:0.6875rem; color:var(--color-muted-foreground); text-transform:uppercase; letter-spacing:0.04em; margin-bottom:0.125rem;">{% trans "Создано" %}</div>
|
||
<div style="font-size:0.8125rem; color:var(--color-foreground);">{{ task.created_at|date:"d.m.Y H:i" }}</div>
|
||
</div>
|
||
{% if task.completed_at %}
|
||
<div>
|
||
<div style="font-size:0.6875rem; color:var(--color-muted-foreground); text-transform:uppercase; letter-spacing:0.04em; margin-bottom:0.125rem;">{% trans "Выполнено" %}</div>
|
||
<div style="font-size:0.8125rem; color:var(--color-foreground);">{{ task.completed_at|date:"d.m.Y H:i" }}</div>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|