Admin: News je Projekt aktivieren und abschalten
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a421308019
commit
6dac6aa073
6 files changed
+361
-9
No files matched your search
@@ -185,6 +185,81 @@ function renderInvoices(data) {
|
||||
</tbody></table>`
|
||||
}
|
||||
|
||||
const FEATURE_STATES = {
|
||||
off: ['Aus', 'pill-muted'],
|
||||
ordered: ['Bestellt', 'pill-amber'],
|
||||
setup: ['Einrichtung', 'pill-warn'],
|
||||
active: ['Aktiv', 'pill-ok'],
|
||||
disabled: ['Abgeschaltet', 'pill-red'],
|
||||
}
|
||||
|
||||
function featureRowHtml(p) {
|
||||
const n = p.features.news
|
||||
const [label, cls] = FEATURE_STATES[n.state] || [n.state, 'pill-muted']
|
||||
const ticket = n.ticketMissing
|
||||
? '<span class="pill pill-red">Ticket fehlt</span>'
|
||||
: n.ticketWoid ? `#${escapeHtml(n.ticketWoid)}` : '<span class="muted">–</span>'
|
||||
return `
|
||||
<tr data-feature-project="${escapeHtml(p.projectId)}">
|
||||
<td>${escapeHtml(p.projectName || '–')}</td>
|
||||
<td><span class="pill ${cls}">${escapeHtml(label)}</span>${n.adminNote ? `<div class="muted" title="${escapeHtml(n.adminNote)}">${escapeHtml(n.adminNote.slice(0, 60))}${n.adminNote.length > 60 ? '…' : ''}</div>` : ''}</td>
|
||||
<td>${ticket}</td>
|
||||
<td class="feature-actions">
|
||||
${n.state === 'active' ? '' : '<button type="button" class="mini-btn primary" data-feature-ask="activate">Aktivieren (ohne Zahlung)</button>'}
|
||||
${n.state === 'off' ? '' : n.state === 'disabled' ? '' : '<button type="button" class="mini-btn" data-feature-ask="disable">Abschalten</button>'}
|
||||
</td>
|
||||
</tr>`
|
||||
}
|
||||
|
||||
function renderFeatures(projects) {
|
||||
if (!projects.length) return '<p class="muted">Keine Projekte für diesen Kunden.</p>'
|
||||
return `<table class="admin-table"><thead><tr><th>Projekt</th><th>News</th><th>Ticket</th><th>Aktionen</th></tr></thead><tbody>
|
||||
${projects.map(featureRowHtml).join('')}
|
||||
</tbody></table><p class="feature-message muted" role="status"></p>`
|
||||
}
|
||||
|
||||
function bindFeatureRows(box, customerId) {
|
||||
const message = box.querySelector('.feature-message')
|
||||
box.querySelectorAll('[data-feature-project]').forEach((row) => {
|
||||
const cell = row.querySelector('.feature-actions')
|
||||
const original = cell.innerHTML
|
||||
const bindAsk = () => cell.querySelectorAll('[data-feature-ask]').forEach((btn) =>
|
||||
btn.addEventListener('click', () => askFeature(btn.dataset.featureAsk)))
|
||||
const askFeature = (action) => {
|
||||
const text = action === 'activate'
|
||||
? 'News ohne Zahlung aktivieren?'
|
||||
: 'News abschalten? Die Website zeigt dann keine Beiträge mehr.'
|
||||
cell.innerHTML = `<span>${text}</span>
|
||||
<input class="code-input" data-feature-note placeholder="Notiz (optional)" maxlength="120">
|
||||
<button type="button" class="mini-btn primary" data-feature-yes>Ja</button>
|
||||
<button type="button" class="mini-btn" data-feature-no>Abbrechen</button>`
|
||||
cell.querySelector('[data-feature-no]').addEventListener('click', () => {
|
||||
cell.innerHTML = original
|
||||
bindAsk()
|
||||
})
|
||||
cell.querySelector('[data-feature-yes]').addEventListener('click', async () => {
|
||||
const note = cell.querySelector('[data-feature-note]').value.trim()
|
||||
cell.querySelectorAll('button').forEach((b) => { b.disabled = true })
|
||||
try {
|
||||
const { projects } = await api(`/api/portal-admin/funktionen/${encodeURIComponent(customerId)}/news`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ projectId: row.dataset.featureProject, action, note }),
|
||||
})
|
||||
row.outerHTML = featureRowHtml(projects[0])
|
||||
bindFeatureRows(box, customerId)
|
||||
box.querySelector('.feature-message').textContent = 'Gespeichert.'
|
||||
} catch (err) {
|
||||
cell.innerHTML = original
|
||||
bindAsk()
|
||||
message.textContent = err.message
|
||||
message.className = 'feature-message error'
|
||||
}
|
||||
})
|
||||
}
|
||||
bindAsk()
|
||||
})
|
||||
}
|
||||
|
||||
async function toggleCustomerDetail(customerId, kind, cell) {
|
||||
const existing = document.getElementById(`detail-${customerId}`)
|
||||
if (existing) {
|
||||
@@ -206,6 +281,10 @@ async function toggleCustomerDetail(customerId, kind, cell) {
|
||||
if (kind === 'activity') {
|
||||
const { tickets } = await api(`/api/portal-admin/customers/${encodeURIComponent(customerId)}/activity`)
|
||||
tr.innerHTML = `<td colspan="7"><h4>Tickets & Arbeiten</h4>${renderActivity(tickets)}</td>`
|
||||
} else if (kind === 'features') {
|
||||
const { projects } = await api(`/api/portal-admin/funktionen/${encodeURIComponent(customerId)}`)
|
||||
tr.innerHTML = `<td colspan="7"><h4>Funktionen</h4>${renderFeatures(projects)}</td>`
|
||||
bindFeatureRows(tr, customerId)
|
||||
} else {
|
||||
const data = await api(`/api/portal-admin/customers/${encodeURIComponent(customerId)}/invoices`)
|
||||
tr.innerHTML = `<td colspan="7"><h4>Rechnungen</h4>${renderInvoices(data)}</td>`
|
||||
@@ -246,6 +325,7 @@ async function loadCustomers(search = '') {
|
||||
title="Bedarf im Gespräch aufnehmen und Preis kalkulieren">Rechnungsanalyse</a>` : ''}
|
||||
<button class="mini-btn${isLead(c) ? '' : ' primary'}" data-view-as="${escapeHtml(c.id)}">Als Kunde ansehen</button>
|
||||
<button class="mini-btn" data-activity="${escapeHtml(c.id)}">Arbeiten</button>
|
||||
<button class="mini-btn" data-features="${escapeHtml(c.id)}">Funktionen</button>
|
||||
<button class="mini-btn" data-invoices="${escapeHtml(c.id)}"${c.invoiceNinjaLinked ? '' : ' title="Nicht mit InvoiceNinja verknüpft"'}>Rechnungen</button>
|
||||
<button class="mini-btn" data-offer-for="${escapeHtml(c.id)}" title="Individuelles Abo-Angebot anlegen">Angebot</button>
|
||||
</div>
|
||||
@@ -258,6 +338,8 @@ async function loadCustomers(search = '') {
|
||||
btn.addEventListener('click', () => viewAsCustomer(btn.dataset.viewAs)))
|
||||
tbody.querySelectorAll('[data-activity]').forEach((btn) =>
|
||||
btn.addEventListener('click', () => toggleCustomerDetail(btn.dataset.activity, 'activity')))
|
||||
tbody.querySelectorAll('[data-features]').forEach((btn) =>
|
||||
btn.addEventListener('click', () => toggleCustomerDetail(btn.dataset.features, 'features')))
|
||||
tbody.querySelectorAll('[data-invoices]').forEach((btn) =>
|
||||
btn.addEventListener('click', () => toggleCustomerDetail(btn.dataset.invoices, 'invoices')))
|
||||
tbody.querySelectorAll('[data-offer-for]').forEach((btn) =>
|
||||
|
||||
Reference in new issue
Block a user