Fix: Admin-Seite Mobile-kompatibel (Tabellen scrollbar, Filter wrap, Touch-Targets)

This commit is contained in:
rene 2026-04-17 23:25:50 +02:00
parent 92620c2c52
commit bfdf6ebfae
4 changed files with 208 additions and 81 deletions

View file

@ -4657,3 +4657,136 @@ textarea.form-control {
}
.photo-editor-empty { font-size: 5rem; color: var(--c-text-secondary); }
.photo-editor-controls { width: 100%; }
/* ------------------------------------------------------------
Admin-Seite Mobile-Responsive Styles
------------------------------------------------------------ */
/* Tab-Leiste mit Abstand nach unten */
.adm-tabs {
margin-bottom: var(--space-5);
}
/* Filter-Zeile (Suche + Select / Suche + Checkbox) */
.adm-filter-row {
display: flex;
gap: var(--space-2);
margin-bottom: var(--space-4);
flex-wrap: wrap;
}
.adm-filter-input {
flex: 1 1 160px;
min-width: 0;
padding: var(--space-2) var(--space-3);
border: 1.5px solid var(--c-border);
border-radius: var(--radius-md);
font-size: var(--text-sm);
font-family: inherit;
background: var(--c-surface);
color: var(--c-text);
}
.adm-filter-select {
flex: 0 0 auto;
padding: var(--space-2) var(--space-3);
border: 1.5px solid var(--c-border);
border-radius: var(--radius-md);
font-size: var(--text-sm);
font-family: inherit;
background: var(--c-surface);
color: var(--c-text);
max-width: 140px;
}
.adm-filter-label {
display: flex;
align-items: center;
gap: var(--space-2);
font-size: var(--text-sm);
color: var(--c-text-secondary);
white-space: nowrap;
flex-shrink: 0;
}
/* Forum-Unternavigation scrollbar auf Mobile */
.adm-subnav {
display: flex;
gap: var(--space-2);
margin-bottom: var(--space-4);
overflow-x: auto;
flex-wrap: nowrap;
scrollbar-width: none;
-webkit-overflow-scrolling: touch;
}
.adm-subnav::-webkit-scrollbar { display: none; }
.adm-subnav .btn { flex-shrink: 0; }
/* Tabellen: scrollbarer Container + Card ohne overflow:hidden */
.adm-table-card {
overflow: visible; /* Card-Schatten bleibt, overflow wird im Scroll-Container geregelt */
}
.adm-table-scroll {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
border-radius: var(--radius-lg); /* für abgerundete Ecken bei overflow */
}
.adm-table {
width: 100%;
border-collapse: collapse;
font-size: var(--text-sm);
min-width: 480px; /* verhindert Quetschen unter diesem Wert */
}
.adm-th {
padding: var(--space-3) var(--space-4);
font-weight: var(--weight-semibold);
color: var(--c-text-secondary);
white-space: nowrap;
}
.adm-td {
padding: var(--space-3) var(--space-4);
}
/* Job-ID unter dem Job-Namen: kürzen wenn zu lang */
.adm-job-id {
font-size: var(--text-xs);
color: var(--c-text-muted);
font-weight: normal;
max-width: 180px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Trigger-Spalte in Jobs-Tabelle */
.adm-td-trigger {
color: var(--c-text-muted);
font-size: var(--text-xs);
max-width: 160px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Monospace-Badge (Audit-Log Aktion) */
.adm-badge-mono {
font-size: var(--text-xs);
padding: 2px 7px;
border-radius: 3px;
background: var(--c-surface-2);
color: var(--c-text-secondary);
font-family: monospace;
white-space: nowrap;
}
/* Icon-only Buttons in Tabellen: 44px Touch-Target */
.adm-icon-btn {
min-width: 44px;
min-height: 44px;
padding: 0;
}
/* Auf sehr kleinen Screens: Select volle Breite */
@media (max-width: 360px) {
.adm-filter-select {
max-width: 100%;
flex-basis: 100%;
}
}

View file

@ -3,7 +3,7 @@
Router, State-Management, Navigation, Initialisierung.
============================================================ */
const APP_VER = '124'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const APP_VER = '126'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const App = (() => {

View file

@ -44,7 +44,7 @@ window.Page_admin = (() => {
<div style="max-width:720px;margin:0 auto;padding:var(--space-4)">
<!-- Tabs -->
<div class="by-tabs" style="margin-bottom:var(--space-5)" id="adm-tabs">
<div class="by-tabs adm-tabs" id="adm-tabs">
${TABS.map(t => `
<button class="by-tab${t.id === _tab ? ' active' : ''}" data-tab="${t.id}">
${UI.icon(t.icon)} ${t.label}
@ -144,14 +144,10 @@ window.Page_admin = (() => {
// ------------------------------------------------------------------
async function _renderUsers(el) {
el.innerHTML = `
<div style="display:flex;gap:var(--space-2);margin-bottom:var(--space-4)">
<div class="adm-filter-row">
<input id="adm-user-q" type="search" placeholder="Name oder E-Mail…"
style="flex:1;padding:var(--space-2) var(--space-3);border:1.5px solid var(--c-border);
border-radius:var(--radius-md);font-size:var(--text-sm);font-family:inherit;
background:var(--c-surface);color:var(--c-text)">
<select id="adm-user-rolle" style="padding:var(--space-2) var(--space-3);
border:1.5px solid var(--c-border);border-radius:var(--radius-md);
font-size:var(--text-sm);font-family:inherit;background:var(--c-surface);color:var(--c-text)">
class="adm-filter-input">
<select id="adm-user-rolle" class="adm-filter-select">
<option value="">Alle Rollen</option>
<option value="user">user</option>
<option value="moderator">moderator</option>
@ -339,7 +335,7 @@ window.Page_admin = (() => {
async function _renderForum(el) {
el.innerHTML = `
<!-- Unternavigation -->
<div style="display:flex;gap:var(--space-2);margin-bottom:var(--space-4)">
<div class="adm-subnav">
<button class="btn btn-primary btn-sm adm-forum-nav" data-view="reports" id="adm-fn-reports">
Offene Meldungen
</button>
@ -429,13 +425,10 @@ window.Page_admin = (() => {
} else {
// Threads
el.innerHTML = `
<div style="display:flex;gap:var(--space-2);margin-bottom:var(--space-3)">
<div class="adm-filter-row">
<input id="adm-thread-q" type="search" placeholder="Threads durchsuchen…"
style="flex:1;padding:var(--space-2) var(--space-3);border:1.5px solid var(--c-border);
border-radius:var(--radius-md);font-size:var(--text-sm);font-family:inherit;
background:var(--c-surface);color:var(--c-text)">
<label style="display:flex;align-items:center;gap:var(--space-2);
font-size:var(--text-sm);color:var(--c-text-secondary);white-space:nowrap">
class="adm-filter-input">
<label class="adm-filter-label">
<input type="checkbox" id="adm-show-deleted"> Gelöschte
</label>
</div>
@ -623,31 +616,32 @@ window.Page_admin = (() => {
return;
}
el.innerHTML = `
<div class="card" style="overflow:hidden">
<table style="width:100%;border-collapse:collapse;font-size:var(--text-sm)">
<div class="card adm-table-card">
<div class="adm-table-scroll">
<table class="adm-table">
<thead>
<tr style="background:var(--c-surface-2);text-align:left">
<th style="padding:var(--space-3) var(--space-4);font-weight:var(--weight-semibold);color:var(--c-text-secondary)">Job</th>
<th style="padding:var(--space-3) var(--space-4);font-weight:var(--weight-semibold);color:var(--c-text-secondary)">Nächster Lauf</th>
<th style="padding:var(--space-3) var(--space-4);font-weight:var(--weight-semibold);color:var(--c-text-secondary)">Trigger</th>
<th style="padding:var(--space-3) var(--space-4);font-weight:var(--weight-semibold);color:var(--c-text-secondary)"></th>
<th class="adm-th">Job</th>
<th class="adm-th">Nächster Lauf</th>
<th class="adm-th">Trigger</th>
<th class="adm-th"></th>
</tr>
</thead>
<tbody>
${jobs.map((j, i) => `
<tr style="${i % 2 === 1 ? 'background:var(--c-surface-2)' : ''}">
<td style="padding:var(--space-3) var(--space-4);font-weight:var(--weight-semibold);color:var(--c-text)">
<td class="adm-td" style="font-weight:var(--weight-semibold);color:var(--c-text)">
${_esc(j.name)}
<div style="font-size:var(--text-xs);color:var(--c-text-muted);font-weight:normal">${_esc(j.id)}</div>
<div class="adm-job-id">${_esc(j.id)}</div>
</td>
<td style="padding:var(--space-3) var(--space-4);color:var(--c-text-secondary)">
<td class="adm-td" style="color:var(--c-text-secondary);white-space:nowrap">
${j.next_run_time ? _formatDateTime(j.next_run_time) : '<span style="color:var(--c-text-muted)">—</span>'}
</td>
<td style="padding:var(--space-3) var(--space-4);color:var(--c-text-muted);font-size:var(--text-xs);max-width:160px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">
<td class="adm-td adm-td-trigger">
${_esc(j.trigger)}
</td>
<td style="padding:var(--space-3) var(--space-4);text-align:right">
<button class="btn btn-sm btn-ghost adm-job-trigger" data-id="${_esc(j.id)}" data-name="${_esc(j.name)}"
<td class="adm-td" style="text-align:right">
<button class="btn btn-sm btn-ghost adm-job-trigger adm-icon-btn" data-id="${_esc(j.id)}" data-name="${_esc(j.name)}"
title="Jetzt ausführen" style="color:var(--c-primary)">
${UI.icon('play')}
</button>
@ -657,6 +651,7 @@ window.Page_admin = (() => {
</tbody>
</table>
</div>
</div>
`;
el.querySelectorAll('.adm-job-trigger').forEach(btn => {
btn.addEventListener('click', async () => {
@ -704,33 +699,31 @@ window.Page_admin = (() => {
return;
}
el.innerHTML = `
<div class="card" style="overflow:hidden">
<table style="width:100%;border-collapse:collapse;font-size:var(--text-sm)">
<div class="card adm-table-card">
<div class="adm-table-scroll">
<table class="adm-table">
<thead>
<tr style="background:var(--c-surface-2);text-align:left">
<th style="padding:var(--space-3) var(--space-4);font-weight:var(--weight-semibold);color:var(--c-text-secondary)">Wann</th>
<th style="padding:var(--space-3) var(--space-4);font-weight:var(--weight-semibold);color:var(--c-text-secondary)">Admin</th>
<th style="padding:var(--space-3) var(--space-4);font-weight:var(--weight-semibold);color:var(--c-text-secondary)">Aktion</th>
<th style="padding:var(--space-3) var(--space-4);font-weight:var(--weight-semibold);color:var(--c-text-secondary)">Ziel</th>
<th class="adm-th">Wann</th>
<th class="adm-th">Admin</th>
<th class="adm-th">Aktion</th>
<th class="adm-th">Ziel</th>
</tr>
</thead>
<tbody>
${rows.map((r, i) => `
<tr style="${i % 2 === 1 ? 'background:var(--c-surface-2)' : ''}">
<td style="padding:var(--space-2) var(--space-4);color:var(--c-text-muted);white-space:nowrap;font-size:var(--text-xs)">
<td class="adm-td" style="color:var(--c-text-muted);white-space:nowrap;font-size:var(--text-xs)">
${_formatDateTime(r.created_at)}
</td>
<td style="padding:var(--space-2) var(--space-4);color:var(--c-text)">
<td class="adm-td" style="color:var(--c-text);white-space:nowrap">
${_esc(r.admin_name || '—')}
</td>
<td style="padding:var(--space-2) var(--space-4)">
<span style="font-size:var(--text-xs);padding:2px 7px;border-radius:3px;
background:var(--c-surface-2);color:var(--c-text-secondary);font-family:monospace">
${_esc(r.action)}
</span>
${r.detail ? `<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-top:2px">${_esc(r.detail)}</div>` : ''}
<td class="adm-td">
<span class="adm-badge-mono">${_esc(r.action)}</span>
${r.detail ? `<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-top:2px;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${_esc(r.detail)}</div>` : ''}
</td>
<td style="padding:var(--space-2) var(--space-4);color:var(--c-text-secondary);font-size:var(--text-xs)">
<td class="adm-td" style="color:var(--c-text-secondary);font-size:var(--text-xs);white-space:nowrap">
${_esc(r.target || '—')}
</td>
</tr>
@ -738,6 +731,7 @@ window.Page_admin = (() => {
</tbody>
</table>
</div>
</div>
`;
}

View file

@ -3,7 +3,7 @@
Offline-Cache + Push Notifications + Tile-Cache
============================================================ */
const CACHE_VERSION = 'by-v152';
const CACHE_VERSION = 'by-v153';
const CACHE_STATIC = `${CACHE_VERSION}-static`;
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten