auf Webseiten Modell umgestellt

This commit is contained in:
ecki
2026-04-18 14:23:24 +02:00
parent 5510d58e5a
commit 6cfbdba0a4
9 changed files with 890 additions and 30 deletions
+129 -1
View File
@@ -3,6 +3,7 @@ const state = {
events: [],
notifications: [],
providerStatuses: [],
watchSources: [],
};
const watchItemsEl = document.querySelector("#watch-items");
@@ -92,6 +93,12 @@ function renderStats() {
}
function prettifyProviderName(value) {
if (value?.startsWith("source:")) {
const sourceId = Number(value.split(":")[1]);
const source = state.watchSources.find((entry) => entry.id === sourceId);
return source?.label || "Direkte Quelle";
}
const names = {
ticketmaster: "Ticketmaster",
bandsintown: "Bandsintown",
@@ -206,12 +213,71 @@ function renderWatchItems() {
</div>
</div>
<p>${escapeHtml(item.notes || "Keine Notiz hinterlegt.")}</p>
${renderSourceList(item.id)}
<form class="source-form" data-watch-id="${item.id}">
<input
name="label"
type="text"
placeholder="Quelle, z. B. Kuenstlerseite"
/>
<input
name="url"
type="url"
placeholder="https://..."
required
/>
<button type="submit" class="action-button success">Quelle hinzufuegen</button>
</form>
</article>
`
)
.join("");
}
function renderSourceList(watchItemId) {
const sources = state.watchSources.filter((source) => source.watch_item_id === watchItemId);
if (!sources.length) {
return '<div class="source-list muted">Noch keine direkten Quellen hinterlegt.</div>';
}
return `
<div class="source-list">
${sources
.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">
<span class="pill ${
source.last_status === "ok"
? "success"
: source.last_status === "error"
? "danger"
: "warning"
}">${escapeHtml(source.last_status)}</span>
<span class="muted">${escapeHtml(source.last_message || "Noch nicht gescannt.")}</span>
</div>
</div>
<div class="action-row">
<button class="action-button" data-action="toggle-source" data-id="${source.id}">
${source.is_active ? "Pausieren" : "Aktivieren"}
</button>
<button class="action-button danger" data-action="delete-source" data-id="${source.id}">
Loeschen
</button>
</div>
</div>
`
)
.join("")}
</div>
`;
}
function getWatchNameById(id) {
return state.watchItems.find((item) => item.id === id)?.name || `Watch #${id}`;
}
@@ -270,6 +336,13 @@ function renderEvents() {
>
${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">
@@ -328,17 +401,19 @@ function updateSyncStatus(message) {
}
async function loadData() {
const [watchItems, events, notifications, providerStatuses] = await Promise.all([
const [watchItems, events, notifications, providerStatuses, watchSources] = await Promise.all([
apiFetch("/watch-items"),
apiFetch("/events"),
apiFetch("/notifications"),
apiFetch("/provider-statuses"),
apiFetch("/watch-sources"),
]);
state.watchItems = watchItems;
state.events = events;
state.notifications = notifications;
state.providerStatuses = providerStatuses;
state.watchSources = watchSources;
renderStats();
renderWatchItems();
@@ -433,7 +508,60 @@ document.addEventListener("click", async (event) => {
});
await loadData();
showToast("Ticketstatus aktualisiert.");
return;
}
if (action === "delete-event") {
await apiFetch(`/events/${id}`, { method: "DELETE" });
await loadData();
showToast("Event geloescht.");
return;
}
if (action === "delete-source") {
await apiFetch(`/watch-sources/${id}`, { method: "DELETE" });
await loadData();
showToast("Quelle geloescht.");
return;
}
if (action === "toggle-source") {
const source = state.watchSources.find((entry) => entry.id === Number(id));
await apiFetch(`/watch-sources/${id}`, {
method: "PATCH",
body: JSON.stringify({ is_active: !source.is_active }),
});
await loadData();
showToast("Quellenstatus aktualisiert.");
}
} catch (error) {
showToast(error.message);
}
});
document.addEventListener("submit", async (event) => {
const form = event.target.closest(".source-form");
if (!form) {
return;
}
event.preventDefault();
const watchId = form.dataset.watchId;
const formData = new FormData(form);
const payload = {
label: formData.get("label")?.toString().trim() || null,
url: formData.get("url")?.toString().trim(),
parser_type: "auto",
};
try {
await apiFetch(`/watch-items/${watchId}/sources`, {
method: "POST",
body: JSON.stringify(payload),
});
form.reset();
await loadData();
showToast("Quelle hinzugefuegt.");
} catch (error) {
showToast(error.message);
}
+32
View File
@@ -381,6 +381,37 @@ button:hover,
line-height: 1.55;
}
.source-list {
display: grid;
gap: 10px;
margin-top: 16px;
}
.source-row {
display: flex;
align-items: start;
justify-content: space-between;
gap: 16px;
padding: 14px;
border-radius: var(--radius-sm);
background: rgba(46, 39, 30, 0.05);
}
.source-row a {
display: block;
max-width: 46ch;
margin: 4px 0 8px;
overflow-wrap: anywhere;
color: var(--primary-dark);
}
.source-form {
display: grid;
grid-template-columns: 0.7fr 1.4fr auto;
gap: 10px;
margin-top: 14px;
}
.action-button {
min-height: 38px;
padding: 0 14px;
@@ -467,6 +498,7 @@ button:hover,
}
.watch-form,
.source-form,
.status-panel {
grid-template-columns: 1fr;
}