feat: Add admin flag to user and templates

Passes an `is_admin` flag to the `CaseRecordingsTemplate` and
`case_page.html`.
This flag determines whether the full case ID is displayed in the meta
section
for administrative users. It also influences the header display on the
case page
to prioritize the oneliner when available.
This commit is contained in:
2026-04-19 17:33:29 +02:00
parent b7e54db54c
commit 6369ff1680
4 changed files with 132 additions and 30 deletions
+27 -20
View File
@@ -8,9 +8,9 @@ body { font-family: sans-serif; max-width: 900px; margin: 2em auto; padding: 0 1
header { display: flex; justify-content: space-between; align-items: center; }
header form { margin: 0; }
.back { color: #4a90e2; text-decoration: none; }
.oneliner { font-size: 1.1em; color: #222; margin: 0.6em 0 1em; }
.meta { color: #666; font-family: monospace; font-size: 0.9em; }
.status-badge { display: inline-block; padding: 0.1em 0.6em; border-radius: 999px; font-size: 0.8em; font-weight: bold; color: white; }
h1 { display: flex; align-items: center; gap: 0.6em; flex-wrap: wrap; }
.meta { color: #999; font-family: monospace; font-size: 0.8em; margin: 0 0 1em; }
.status-badge { display: inline-block; padding: 0.1em 0.6em; border-radius: 999px; font-size: 0.6em; font-weight: bold; color: white; vertical-align: middle; }
.status-badge.open { background: #4a90e2; }
.status-badge.done { background: #7ed321; }
.actions { margin: 1em 0 1.5em; display: flex; align-items: center; gap: 0.4em; }
@@ -23,14 +23,17 @@ header form { margin: 0; }
.status-panel { margin: 1em 0; padding: 0.8em 1em; background: #f6f6f6; border-radius: 4px; }
.recordings-link { display: inline-block; margin: 1em 0; color: #4a90e2; text-decoration: none; font-size: 0.95em; }
.recordings-link:hover { text-decoration: underline; }
.doc-content { font-family: serif; font-size: 1.05em; line-height: 1.55; background: #f6f6f6; padding: 1em 1.2em; border-radius: 4px; }
.doc-content { position: relative; font-family: serif; font-size: 1.05em; line-height: 1.55; background: #f6f6f6; padding: 1em 1.2em; border-radius: 4px; }
.doc-content p { margin: 0.8em 0; }
.doc-content p:first-child { margin-top: 0; }
.doc-content p:last-child { margin-bottom: 0; }
.doc-content mark { background: #fff3a8; padding: 0 0.2em; border-radius: 2px; }
.copy-btn { padding: 0.5em 1em; font-size: 1em; border: 1px solid #4a90e2; background: white; color: #4a90e2; border-radius: 4px; cursor: pointer; font-weight: bold; }
.copy-btn:hover { background: #eaf3fc; }
.toolbar { display: flex; gap: 0.6em; margin: 1em 0; }
.copy-btn { position: absolute; bottom: 0.5em; right: 0.5em; padding: 0.35em; background: transparent; border: none; border-radius: 4px; cursor: pointer; color: #888; display: inline-flex; align-items: center; justify-content: center; line-height: 0; opacity: 0.45; transition: opacity 0.15s, color 0.15s; }
.copy-btn:hover { color: #4a90e2; opacity: 1; }
.copy-btn .icon-check { display: none; }
.copy-btn.copied { color: #2d8c2d; opacity: 1; }
.copy-btn.copied .icon-copy { display: none; }
.copy-btn.copied .icon-check { display: block; }
</style>
</head>
<body>
@@ -38,14 +41,14 @@ header form { margin: 0; }
<div><a class="back" href="/web/cases">&larr; Fälle</a></div>
<form method="post" action="/web/logout"><button type="submit">Logout</button></form>
</header>
<h1>Fall {{ case_id_short }} {% if has_document %}<span class="status-badge done">ausgewertet</span>{% else %}<span class="status-badge open">offen</span>{% endif %}</h1>
<h1>
{% match oneliner %}
{% when Some with (t) %}
<div class="oneliner">{{ t }}</div>
{% when None %}
<div class="oneliner" style="color:#888;font-style:italic">Noch kein Oneliner.</div>
{% when Some with (t) %}{{ t }}
{% when None %}Fall
{% endmatch %}
<div class="meta">{{ case_id }}</div>
{% if has_document %}<span class="status-badge done">ausgewertet</span>{% else %}<span class="status-badge open">offen</span>{% endif %}
</h1>
{% if is_admin %}<div class="meta">{{ case_id }}</div>{% endif %}
<div class="actions">
{% if analyzing %}
@@ -63,18 +66,20 @@ header form { margin: 0; }
{% match document_html %}
{% when Some with (html) %}
<div class="toolbar">
<button id="copy-btn" type="button" class="copy-btn" hidden>In Zwischenablage</button>
<div id="doc-content" class="doc-content">
<button id="copy-btn" type="button" class="copy-btn" title="In Zwischenablage kopieren" hidden>
<svg class="icon-copy" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>
<svg class="icon-check" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>
</button>
<div id="doc-body">{{ html|safe }}</div>
</div>
<div id="doc-content" class="doc-content">{{ html|safe }}</div>
<script>
(() => {
const btn = document.getElementById('copy-btn');
if (!btn) return;
btn.hidden = false;
const label = btn.textContent;
btn.addEventListener('click', async () => {
const text = document.getElementById('doc-content').innerText;
const text = document.getElementById('doc-body').innerText;
let ok = false;
try {
if (navigator.clipboard && window.isSecureContext) {
@@ -93,8 +98,10 @@ header form { margin: 0; }
try { ok = document.execCommand('copy'); } catch (_) {}
ta.remove();
}
btn.textContent = ok ? 'Kopiert ✓' : 'Fehlgeschlagen';
setTimeout(() => { btn.textContent = label; }, 1500);
if (ok) {
btn.classList.add('copied');
setTimeout(() => btn.classList.remove('copied'), 1500);
}
});
})();
</script>
+14 -10
View File
@@ -7,9 +7,10 @@
body { font-family: sans-serif; max-width: 900px; margin: 2em auto; padding: 0 1em; }
header { display: flex; justify-content: space-between; align-items: center; }
header form { margin: 0; }
.back { color: #4a90e2; text-decoration: none; }
.oneliner { font-size: 1.1em; color: #222; margin: 0.6em 0 1em; }
.meta { color: #666; font-family: monospace; font-size: 0.9em; }
.breadcrumb { display: flex; gap: 0.8em; flex-wrap: wrap; }
.breadcrumb a { color: #4a90e2; text-decoration: none; }
.breadcrumb a:hover { text-decoration: underline; }
.meta { color: #999; font-family: monospace; font-size: 0.8em; margin: 0 0 1em; }
.recording { margin: 1em 0; padding: 0.7em; border: 1px solid #ddd; border-radius: 4px; }
.recording.failed-row { background: #fff7f7; border-left: 3px solid #e24a4a; }
.recording-head { display: flex; align-items: center; gap: 1em; flex-wrap: wrap; }
@@ -21,16 +22,19 @@ header form { margin: 0; }
</head>
<body>
<header>
<div><a class="back" href="/web/cases/{{ case_id }}">&larr; Fall {{ case_id_short }}</a></div>
<div class="breadcrumb">
<a href="/web/cases">&larr; Fälle</a>
{% match oneliner %}
{% when Some with (t) %}
<a href="/web/cases/{{ case_id }}">&larr; {{ t }}</a>
{% when None %}
<a href="/web/cases/{{ case_id }}">&larr; Fall</a>
{% endmatch %}
</div>
<form method="post" action="/web/logout"><button type="submit">Logout</button></form>
</header>
<h1>Aufnahmen</h1>
{% match oneliner %}
{% when Some with (t) %}
<div class="oneliner">{{ t }}</div>
{% when None %}
{% endmatch %}
<div class="meta">{{ case_id }}</div>
{% if is_admin %}<div class="meta">{{ case_id }}</div>{% endif %}
<h2>Aufnahmen ({{ recordings.len() }})</h2>
{% for rec in recordings %}