Webseitensuche verbessert UI aufgeräumt
This commit is contained in:
@@ -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>
|
||||
`
|
||||
)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user