Files
leopold.stroblandClaude Opus 4.8 528d66b2b0 Add "truly done" ticket archiving with auto-unhide
- Let the user mark closed tickets as really done and hide them, so
  tickets closed prematurely by an agent stay visible until confirmed
- User-side "archived" flag stored in the DB (independent of Zammad),
  added via a schema migration on existing databases
- Hide archived tickets from the overview by default with an
  "erledigte einblenden" toggle; archive/unarchive from the ticket
  detail page and via a per-row quick action
- Poller automatically un-hides an archived ticket when new activity is
  detected, so nothing is missed

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 17:34:09 +02:00

132 lines
5.8 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends "base.html" %}
{% block title %}#{{ ticket.number }} · EPI-Support{% endblock %}
{% block content %}
<p><a class="back" href="{{ url_for('index') }}">← zurück zur Übersicht</a></p>
<section class="panel">
<div class="panel-head">
<h2>#{{ ticket.number }} {{ ticket.title }}</h2>
<div class="head-tools">
{% if ticket.archived %}
<form method="post" action="{{ url_for('unarchive_ticket', ticket_id=ticket.id) }}">
<button class="btn-ghost" type="submit">↩ Wieder einblenden</button>
</form>
{% else %}
<form method="post" action="{{ url_for('archive_ticket', ticket_id=ticket.id) }}">
<button class="btn-ghost" type="submit" title="Als wirklich erledigt ablegen und ausblenden">✓ Wirklich erledigt</button>
</form>
{% endif %}
<a class="btn-sm" href="{{ zammad_url }}/#ticket/zoom/{{ ticket.id }}"
target="_blank" rel="noopener">in Zammad öffnen ↗</a>
</div>
</div>
<dl class="meta">
<div><dt>Status</dt><dd><span class="state state-{{ ticket.state }}">{{ ticket.state }}</span></dd></div>
<div><dt>Priorität</dt><dd>{{ ticket.priority }}</dd></div>
<div><dt>Bearbeiter</dt><dd>{{ ticket.owner if ticket.owner and ticket.owner != '-' else '—' }}</dd></div>
<div><dt>Gruppe</dt><dd>{{ ticket.group }}</dd></div>
<div><dt>Artikel</dt><dd>{{ ticket.article_count }}</dd></div>
<div><dt>erstellt</dt><dd>{{ ticket.zammad_created_at|dt }}</dd></div>
<div><dt>zuletzt geändert</dt><dd>{{ ticket.zammad_updated_at|dt }}</dd></div>
<div><dt>geschlossen</dt><dd>{{ ticket.last_close_at|dt }}</dd></div>
</dl>
<form method="post" action="{{ url_for('set_state', ticket_id=ticket.id) }}" class="state-form">
<label for="state_id">Status ändern:</label>
<select id="state_id" name="state_id">
{% for s in settable_states %}
<option value="{{ s.id }}" {{ 'selected' if s.id == ticket.state_id }}>{{ s.label }}</option>
{% endfor %}
</select>
<button class="btn-sm" type="submit">setzen</button>
<span class="hint-inline">wirkt live in Zammad</span>
</form>
</section>
<section class="panel">
<div class="panel-head">
<h2>Konversation{% if conversation %} ({{ conversation|length }}){% endif %}</h2>
<form method="post" action="{{ url_for('refresh_conversation', ticket_id=ticket.id) }}">
<button class="btn-sm" type="submit">↻ Aktualisieren</button>
</form>
</div>
{% if conv_error %}
<p class="muted">Konversation konnte nicht geladen werden: {{ conv_error }}</p>
{% elif conversation %}
<div class="thread">
{% for a in conversation %}
<article class="msg msg-{{ a.sender|lower if a.sender else 'system' }}">
<header class="msg-head">
<span class="who">
<span class="role role-{{ a.sender|lower if a.sender else 'system' }}">{{ a.sender or 'System' }}</span>
{{ a.frm }}
</span>
<time>{{ a.created_at|dt }}</time>
</header>
{% if a.subject %}<div class="msg-subj">{{ a.subject }}</div>{% endif %}
<div class="msg-body">{{ a.body }}</div>
{% if a.attachments %}
{% set imgs = a.attachments|selectattr('is_image')|list %}
{% set files = a.attachments|rejectattr('is_image')|list %}
{% if imgs %}
<div class="msg-imgs">
{% for att in imgs %}
<a href="{{ url_for('attachment', ticket_id=ticket.id, article_id=a.id, att_id=att.id) }}" target="_blank" rel="noopener">
<img class="msg-img" loading="lazy" alt="{{ att.filename }}"
src="{{ url_for('attachment', ticket_id=ticket.id, article_id=a.id, att_id=att.id) }}">
</a>
{% endfor %}
</div>
{% endif %}
{% if files %}
<div class="msg-att">
{% for att in files %}
📎 {% if att.id %}<a href="{{ url_for('attachment', ticket_id=ticket.id, article_id=a.id, att_id=att.id) }}" target="_blank" rel="noopener">{{ att.filename }}</a>{% else %}{{ att.filename }}{% endif %}
{% endfor %}
</div>
{% endif %}
{% endif %}
</article>
{% endfor %}
</div>
{% else %}
<p class="muted">Keine Nachrichten vorhanden.</p>
{% endif %}
{% if ticket.state != 'closed' %}
<form method="post" action="{{ url_for('reply_preview', ticket_id=ticket.id) }}"
class="reply-form attach-form" enctype="multipart/form-data">
<textarea name="body" rows="4" required data-draft
placeholder="Antwort an den EPI-Support verfassen… (Screenshot mit Strg+V einfügen)"></textarea>
{% include "_attach.html" %}
<div class="form-actions">
<span class="hint-inline">Geht nach Bestätigung live an EPI.</span>
<button class="btn-sm" type="submit">Antworten →</button>
</div>
</form>
{% else %}
<p class="muted">Ticket ist geschlossen zum Antworten ggf. ein neues Ticket anlegen.</p>
{% endif %}
</section>
<section class="panel">
<div class="panel-head"><h2>Verlauf (von uns erfasst)</h2></div>
{% if timeline %}
<ul class="timeline">
{% for c in timeline %}
<li>
<span class="tag tag-{{ c.field }}">{{ c.label }}</span>
<div class="chg">{{ c.old_value or '' }} → <b>{{ c.new_value or '' }}</b>
{% if c.detail %}<div class="detail">{{ c.detail }}</div>{% endif %}
</div>
<time>{{ c.detected_at|dt }}</time>
</li>
{% endfor %}
</ul>
{% else %}
<p class="muted">Seit Beobachtungsbeginn keine Änderung erfasst.</p>
{% endif %}
</section>
{% endblock %}
{% block scripts %}<script src="{{ url_for('static', filename='upload.js') }}"></script>{% endblock %}