Webseitensuche verbessert UI aufgeräumt

This commit is contained in:
ecki
2026-04-21 13:22:20 +02:00
parent 6cfbdba0a4
commit 1014f822a1
10 changed files with 579 additions and 136 deletions
+91 -46
View File
@@ -47,6 +47,47 @@ function formatDate(value) {
}).format(date);
}
function formatEventDay(value) {
if (!value) {
return "unbekannt";
}
const normalizedValue =
typeof value === "string" && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?$/.test(value)
? `${value}Z`
: value;
const date = new Date(normalizedValue);
if (Number.isNaN(date.getTime())) {
return value;
}
return new Intl.DateTimeFormat("de-DE", {
day: "2-digit",
month: "short",
year: "numeric",
}).format(date);
}
function formatEventTime(value) {
if (!value) {
return "";
}
const normalizedValue =
typeof value === "string" && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?$/.test(value)
? `${value}Z`
: value;
const date = new Date(normalizedValue);
if (Number.isNaN(date.getTime())) {
return "";
}
return new Intl.DateTimeFormat("de-DE", {
hour: "2-digit",
minute: "2-digit",
}).format(date);
}
function escapeHtml(value) {
return String(value ?? "")
.replaceAll("&", "&")
@@ -212,7 +253,11 @@ function renderWatchItems() {
</button>
</div>
</div>
<p>${escapeHtml(item.notes || "Keine Notiz hinterlegt.")}</p>
${
item.notes
? `<p class="watch-note">${escapeHtml(item.notes)}</p>`
: ""
}
${renderSourceList(item.id)}
<form class="source-form" data-watch-id="${item.id}">
<input
@@ -246,12 +291,9 @@ function renderSourceList(watchItemId) {
.map(
(source) => `
<div class="source-row">
<div>
<strong>${escapeHtml(source.label || "Quelle")}</strong>
<a href="${escapeHtml(source.url)}" target="_blank" rel="noreferrer">
${escapeHtml(source.url)}
</a>
<div class="pill-row">
<div class="source-content">
<div class="source-title-row">
<strong>${escapeHtml(source.label || "Quelle")}</strong>
<span class="pill ${
source.last_status === "ok"
? "success"
@@ -259,8 +301,11 @@ function renderSourceList(watchItemId) {
? "danger"
: "warning"
}">${escapeHtml(source.last_status)}</span>
<span class="muted">${escapeHtml(source.last_message || "Noch nicht gescannt.")}</span>
</div>
<a href="${escapeHtml(source.url)}" target="_blank" rel="noreferrer">
${escapeHtml(source.url)}
</a>
<p class="source-message">${escapeHtml(source.last_message || "Noch nicht gescannt.")}</p>
</div>
<div class="action-row">
<button class="action-button" data-action="toggle-source" data-id="${source.id}">
@@ -313,48 +358,48 @@ function renderEvents() {
.map(
(event) => `
<article class="event-card">
<div class="event-header">
<div>
<div class="event-date-badge">
<strong>${escapeHtml(formatEventDay(event.event_date))}</strong>
<span>${escapeHtml(formatEventTime(event.event_date) || "Zeit offen")}</span>
</div>
<div class="event-content">
<div class="event-header">
<h3>${escapeHtml(event.title)}</h3>
<div class="pill-row">
<span class="pill">${escapeHtml(getWatchNameById(event.watch_item_id))}</span>
<span class="pill">${escapeHtml(event.city || "ohne Stadt")}</span>
<span class="pill ${getProviderClass(event.source)}">
${escapeHtml(prettifyProviderName(event.source))}
</span>
<span class="pill ${event.is_ticket_purchased ? "success" : "warning"}">
${event.is_ticket_purchased ? "Ticket markiert" : "ohne Ticket"}
</span>
</div>
<div class="event-meta">
<span>${escapeHtml(getWatchNameById(event.watch_item_id))}</span>
<span>${escapeHtml(event.city || "ohne Stadt")}</span>
<span>${escapeHtml(event.venue_name || "Venue unbekannt")}</span>
<span>${escapeHtml(prettifyProviderName(event.source))}</span>
</div>
<div class="event-footer">
<span class="pill ${event.is_ticket_purchased ? "success" : "warning"}">
${event.is_ticket_purchased ? "Ticket markiert" : "ohne Ticket"}
</span>
<div class="event-actions">
${
event.ticket_url
? `<a class="action-button event-link" href="${escapeHtml(event.ticket_url)}" target="_blank" rel="noreferrer">Tickets</a>`
: ""
}
<button
class="action-button ${event.is_ticket_purchased ? "" : "success"}"
data-action="toggle-ticket"
data-id="${event.id}"
data-value="${event.is_ticket_purchased ? "false" : "true"}"
>
${event.is_ticket_purchased ? "Ticket entfernen" : "Ticket gekauft"}
</button>
<button
class="action-button danger"
data-action="delete-event"
data-id="${event.id}"
>
Loeschen
</button>
</div>
</div>
<div class="event-actions">
<button
class="action-button ${event.is_ticket_purchased ? "" : "success"}"
data-action="toggle-ticket"
data-id="${event.id}"
data-value="${event.is_ticket_purchased ? "false" : "true"}"
>
${event.is_ticket_purchased ? "Ticket entfernen" : "Ticket gekauft"}
</button>
<button
class="action-button danger"
data-action="delete-event"
data-id="${event.id}"
>
Loeschen
</button>
</div>
</div>
<div class="event-meta">
<span><strong>Datum:</strong> ${escapeHtml(formatDate(event.event_date))}</span>
<span><strong>Venue:</strong> ${escapeHtml(event.venue_name || "unbekannt")}</span>
<span><strong>Quelle:</strong> ${escapeHtml(event.source)}</span>
</div>
${
event.ticket_url
? `<p><a class="event-link" href="${escapeHtml(event.ticket_url)}" target="_blank" rel="noreferrer">Ticketlink oeffnen</a></p>`
: ""
}
</article>
`
)
+157 -55
View File
@@ -11,10 +11,10 @@
--success: #245e3f;
--warning: #8d5a13;
--danger: #8a2f2f;
--shadow: 0 22px 70px rgba(96, 64, 24, 0.14);
--radius-lg: 28px;
--radius-md: 18px;
--radius-sm: 12px;
--shadow: 0 16px 42px rgba(72, 55, 36, 0.11);
--radius-lg: 8px;
--radius-md: 8px;
--radius-sm: 8px;
--mono: "IBM Plex Mono", monospace;
--sans: "Space Grotesk", sans-serif;
}
@@ -29,8 +29,6 @@ body {
font-family: var(--sans);
color: var(--text);
background:
radial-gradient(circle at top left, rgba(194, 77, 44, 0.18), transparent 28%),
radial-gradient(circle at 85% 18%, rgba(36, 94, 63, 0.14), transparent 22%),
linear-gradient(135deg, var(--bg), #f7f2ea 48%, var(--bg-accent));
}
@@ -49,14 +47,14 @@ body::before {
.page-shell {
width: min(1280px, calc(100vw - 32px));
margin: 0 auto;
padding: 32px 0 56px;
padding: 24px 0 44px;
}
.hero {
display: grid;
grid-template-columns: 1.6fr 1fr;
gap: 24px;
margin-bottom: 24px;
gap: 16px;
margin-bottom: 16px;
}
.hero-copy,
@@ -69,7 +67,7 @@ body::before {
}
.hero-copy {
padding: 36px;
padding: 28px;
border-radius: var(--radius-lg);
}
@@ -93,36 +91,36 @@ p {
}
h1 {
font-size: clamp(2.5rem, 5vw, 4.9rem);
line-height: 0.95;
max-width: 11ch;
font-size: clamp(2.2rem, 4vw, 3.8rem);
line-height: 1;
max-width: 14ch;
}
.hero-text {
max-width: 58ch;
margin-top: 18px;
font-size: 1.05rem;
line-height: 1.6;
margin-top: 14px;
font-size: 1rem;
line-height: 1.5;
color: var(--muted);
}
.hero-actions {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin-top: 26px;
gap: 10px;
margin-top: 20px;
}
.status-panel {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 14px;
padding: 20px;
gap: 10px;
padding: 14px;
border-radius: var(--radius-lg);
}
.stat-card {
padding: 18px;
padding: 14px;
background: rgba(255, 250, 242, 0.86);
border-radius: var(--radius-md);
border: 1px solid rgba(46, 39, 30, 0.08);
@@ -130,7 +128,7 @@ h1 {
.stat-card strong {
display: block;
font-size: 2rem;
font-size: 1.7rem;
line-height: 1.1;
}
@@ -151,12 +149,12 @@ h1 {
.dashboard-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 24px;
grid-template-columns: minmax(360px, 0.9fr) minmax(420px, 1.1fr);
gap: 16px;
}
.panel {
padding: 24px;
padding: 18px;
border-radius: var(--radius-lg);
}
@@ -168,8 +166,8 @@ h1 {
display: flex;
align-items: start;
justify-content: space-between;
gap: 18px;
margin-bottom: 18px;
gap: 14px;
margin-bottom: 14px;
}
.panel-tools {
@@ -180,8 +178,8 @@ h1 {
.watch-form {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 14px;
margin-bottom: 20px;
gap: 10px;
margin-bottom: 16px;
}
.full-width {
@@ -206,7 +204,7 @@ input,
select,
textarea {
width: 100%;
padding: 14px 16px;
padding: 10px 12px;
border-radius: var(--radius-sm);
border: 1px solid rgba(46, 39, 30, 0.16);
background: rgba(255, 255, 255, 0.88);
@@ -227,8 +225,8 @@ button,
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 48px;
padding: 0 18px;
min-height: 40px;
padding: 0 14px;
border: 0;
border-radius: 999px;
text-decoration: none;
@@ -258,11 +256,11 @@ button:hover,
.notification-list,
.provider-status-list {
display: grid;
gap: 14px;
gap: 10px;
}
.empty-state {
padding: 28px;
padding: 20px;
border-radius: var(--radius-md);
border: 1px dashed rgba(46, 39, 30, 0.18);
color: var(--muted);
@@ -292,7 +290,7 @@ button:hover,
display: flex;
align-items: start;
justify-content: space-between;
gap: 16px;
gap: 12px;
}
.pill-row,
@@ -302,14 +300,14 @@ button:hover,
.notification-meta {
display: flex;
flex-wrap: wrap;
gap: 8px;
gap: 6px;
}
.pill {
display: inline-flex;
align-items: center;
min-height: 28px;
padding: 0 10px;
min-height: 24px;
padding: 0 8px;
border-radius: 999px;
background: rgba(194, 77, 44, 0.09);
color: var(--primary-dark);
@@ -377,47 +375,74 @@ button:hover,
.event-card p,
.notification-card p,
.provider-status-card p {
margin-top: 12px;
line-height: 1.55;
margin-top: 8px;
line-height: 1.45;
}
.watch-note {
color: var(--muted);
}
.source-list {
display: grid;
gap: 10px;
margin-top: 16px;
gap: 8px;
margin-top: 12px;
}
.source-row {
display: flex;
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
align-items: start;
justify-content: space-between;
gap: 16px;
padding: 14px;
gap: 12px;
padding: 10px;
border-radius: var(--radius-sm);
background: rgba(46, 39, 30, 0.05);
}
.source-content {
min-width: 0;
}
.source-title-row {
display: flex;
align-items: center;
gap: 8px;
}
.source-row a {
display: block;
max-width: 46ch;
margin: 4px 0 8px;
max-width: 100%;
margin: 4px 0;
overflow-wrap: anywhere;
color: var(--primary-dark);
font-size: 0.9rem;
}
.source-message {
margin: 0;
color: var(--muted);
font-size: 0.9rem;
}
.source-form {
display: grid;
grid-template-columns: 0.7fr 1.4fr auto;
gap: 10px;
margin-top: 14px;
gap: 8px;
margin-top: 10px;
}
.action-button {
min-height: 38px;
padding: 0 14px;
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 32px;
padding: 0 10px;
border-radius: 999px;
background: rgba(46, 39, 30, 0.08);
color: var(--text);
text-decoration: none;
white-space: nowrap;
font-size: 0.92rem;
}
.action-button.danger {
@@ -430,16 +455,67 @@ button:hover,
color: var(--success);
}
.event-card {
display: grid;
grid-template-columns: 112px minmax(0, 1fr);
gap: 14px;
padding: 14px;
}
.event-date-badge {
display: grid;
align-content: center;
min-height: 86px;
padding: 10px;
border-radius: var(--radius-sm);
background: rgba(194, 77, 44, 0.1);
color: var(--primary-dark);
}
.event-date-badge strong {
font-size: 1rem;
line-height: 1.15;
}
.event-date-badge span {
margin-top: 4px;
color: var(--muted);
font-family: var(--mono);
font-size: 0.78rem;
}
.event-content {
min-width: 0;
}
.event-header h3 {
overflow-wrap: anywhere;
}
.event-meta,
.notification-meta {
margin-top: 14px;
margin-top: 8px;
color: var(--muted);
font-size: 0.94rem;
font-size: 0.9rem;
}
.event-meta span:not(:last-child)::after {
content: "/";
margin-left: 8px;
color: rgba(101, 89, 77, 0.52);
}
.event-footer {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
margin-top: 12px;
}
.event-link {
color: var(--primary-dark);
text-decoration-thickness: 2px;
background: rgba(194, 77, 44, 0.09);
}
.toast {
@@ -514,4 +590,30 @@ button:hover,
.notification-header {
flex-direction: column;
}
.source-row,
.event-card {
grid-template-columns: 1fr;
}
.event-date-badge {
min-height: auto;
}
.event-footer,
.source-title-row {
align-items: flex-start;
flex-direction: column;
}
.event-actions,
.action-row,
.panel-tools {
width: 100%;
}
.event-actions .action-button,
.action-row .action-button {
flex: 1 1 140px;
}
}