Portal: Fortschritt unter Projekte, neuer Tab Funktionen mit News-Editor
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6dac6aa073
commit
5a09d060a1
7 files changed
+937
-24
No files matched your search
+51
-15
@@ -210,7 +210,7 @@ function renderProjectCard(project, features) {
|
||||
/* --------------------------------------------------------- Projektdetails --- */
|
||||
// Die Daten kommen fertig fuer Kunden aufbereitet vom Server (customerProjectView.js): keine
|
||||
// Commit-Kuerzel, Kontonamen oder KI-Hinweise, Status und Arbeitsarten auf Deutsch.
|
||||
// Dieselben Bausteine nutzt der Tab "Fortschritt" (loadCustomerActivity).
|
||||
// Dieselben Bausteine nutzt der Abschnitt "Was wir zuletzt erledigt haben" unter Projekte (loadCustomerActivity).
|
||||
|
||||
const PD_ICONS = {
|
||||
update: '<path d="M4 20h4L19 9l-4-4L4 16z"/><path d="M13 7l4 4"/>',
|
||||
@@ -690,23 +690,34 @@ async function loadCustomerInvoices() {
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------- Aktivität --- */
|
||||
// Tab "Fortschritt": je Auftrag eine Karte mit Status, Fortschrittsleiste und Arbeiten
|
||||
// (Bausteine der Projektdetails; Texte kommen aufbereitet vom Server)
|
||||
// Abschnitt "Was wir zuletzt erledigt haben" unter Projekte: je Auftrag eine Karte mit Status, Fortschrittsleiste
|
||||
// und Arbeiten (Bausteine der Projektdetails; Texte kommen aufbereitet vom Server). Zuerst die drei neuesten,
|
||||
// "Alle anzeigen" zeigt den Rest.
|
||||
const ACTIVITY_SHOWN = 3
|
||||
async function loadCustomerActivity() {
|
||||
const body = document.getElementById('activity-body')
|
||||
if (!body) return
|
||||
try {
|
||||
const { tickets } = await api('/api/activity')
|
||||
if (!tickets || !tickets.length) {
|
||||
const { tickets: all } = await api('/api/activity')
|
||||
if (!all || !all.length) {
|
||||
body.innerHTML = '<p class="muted">Es wurden noch keine Arbeiten dokumentiert.</p>'
|
||||
return
|
||||
}
|
||||
body.innerHTML = tickets.map((t) => `
|
||||
const tickets = [...all].sort((a, b) => String(b.createdAt || '').localeCompare(String(a.createdAt || '')))
|
||||
const card = (t) => `
|
||||
<article class="pd-order pd-order-card pd-tone-${escapeAttr(t.status.tone)}">
|
||||
${renderOrder(t)}
|
||||
${renderWorkEntries(t.entries || [], 'Noch keine Arbeiten dokumentiert.')}
|
||||
</article>`).join('')
|
||||
initPdToggles(body)
|
||||
</article>`
|
||||
const draw = (showAll) => {
|
||||
body.innerHTML = tickets.slice(0, showAll ? tickets.length : ACTIVITY_SHOWN).map(card).join('') +
|
||||
(tickets.length > ACTIVITY_SHOWN
|
||||
? `<button type="button" class="kb-btn-outline kb-activity-all" data-activity-all>${showAll ? 'Weniger anzeigen' : `Alle anzeigen (${tickets.length})`}</button>`
|
||||
: '')
|
||||
initPdToggles(body)
|
||||
body.querySelector('[data-activity-all]')?.addEventListener('click', () => draw(!showAll))
|
||||
}
|
||||
draw(false)
|
||||
} catch (err) {
|
||||
body.innerHTML = `<p class="muted">Aktivität konnte nicht geladen werden: ${escapeHtml(err.message)}</p>`
|
||||
}
|
||||
@@ -1948,14 +1959,32 @@ function initPasswordView() {
|
||||
// Ansichten im Inhaltsbereich; Hash fuer Direktlinks (z. B. aus Mails: #vertraege, #gutschrift).
|
||||
// Die alten Hashes #wartung und #abo fuehren zu #vertraege (HASH_ALIASES).
|
||||
// "projekt" (Projektdetails als eigene Seite) gibt es ebenfalls nur in der App-Ansicht.
|
||||
// "passwort" oeffnet das Konto-Menue (Profilbild).
|
||||
const VIEW_HASHES = { projects: '', activity: 'fortschritt', invoices: 'rechnungen', chat: 'chat', vertraege: 'vertraege', stunden: 'stunden', 'bestellen-hosting': 'bestellen-hosting', 'bestellen-wartung': 'bestellen-wartung', wechsel: 'wechsel', takeover: 'gutschrift', projekt: 'projekt', passwort: 'passwort' }
|
||||
// "passwort" oeffnet das Konto-Menue (Profilbild). #fortschritt fuehrt zu Projekte und scrollt zum Abschnitt
|
||||
// "Was wir zuletzt erledigt haben" (#pj-activity); hinter dem Hash darf ein Query stehen (#news?bestellt=1, Rueckkehr von Stripe).
|
||||
const VIEW_HASHES = { projects: '', funktionen: 'funktionen', news: 'news', invoices: 'rechnungen', chat: 'chat', vertraege: 'vertraege', stunden: 'stunden', 'bestellen-hosting': 'bestellen-hosting', 'bestellen-wartung': 'bestellen-wartung', wechsel: 'wechsel', takeover: 'gutschrift', projekt: 'projekt', passwort: 'passwort' }
|
||||
const HASH_ALIASES = { wartung: 'vertraege', abo: 'vertraege' }
|
||||
const viewLoaders = { activity: loadCustomerActivity, chat: initChat }
|
||||
const viewLoaders = { chat: initChat }
|
||||
// Funktionen und News laden bei jedem Oeffnen neu (Zustand aendert sich durch Bestellung und Freischaltung); Modul erst dann laden
|
||||
const viewRefreshers = {
|
||||
funktionen: () => import('/funktionen.js').then((m) => m.initFunktionen(document.getElementById('funktionen-body'))),
|
||||
news: () => import('/funktionen.js').then((m) => m.initNews(document.getElementById('news-body'))),
|
||||
}
|
||||
const loadedViews = new Set()
|
||||
|
||||
function currentHash() {
|
||||
return window.location.hash.replace(/^#/, '').split('?')[0]
|
||||
}
|
||||
|
||||
// Direktlink #fortschritt: nach dem Laden der Projekte zum Abschnitt scrollen
|
||||
let activityScrollPending = false
|
||||
function scrollToActivity() {
|
||||
if (!activityScrollPending) return
|
||||
document.getElementById('pj-activity')?.scrollIntoView({ block: 'start' })
|
||||
}
|
||||
|
||||
function viewFromHash() {
|
||||
const hash = window.location.hash.replace(/^#/, '')
|
||||
const hash = currentHash()
|
||||
if (hash === 'fortschritt') return 'projects'
|
||||
if (HASH_ALIASES[hash]) return HASH_ALIASES[hash]
|
||||
return Object.keys(VIEW_HASHES).find((v) => VIEW_HASHES[v] && VIEW_HASHES[v] === hash) || ''
|
||||
}
|
||||
@@ -1969,6 +1998,10 @@ function showView(name, { updateHash = true } = {}) {
|
||||
loadedViews.add(name)
|
||||
viewLoaders[name]()
|
||||
}
|
||||
viewRefreshers[name]?.().catch((err) => {
|
||||
const box = document.getElementById(`${name}-body`)
|
||||
if (box) box.innerHTML = `<p class="muted">${escapeHtml(err.message || 'Konnte nicht geladen werden.')}</p>`
|
||||
})
|
||||
if (name === 'chat') {
|
||||
api('/api/chat/read', { method: 'POST' }).catch(() => {})
|
||||
updateChatBadge(0)
|
||||
@@ -2073,10 +2106,12 @@ async function initDashboardPage() {
|
||||
if (aboParam) window.history.replaceState(null, '', `/dashboard.html${window.location.hash}`)
|
||||
loadContracts({ afterCheckout: aboParam === 'success' })
|
||||
loadCustomerInvoices()
|
||||
activityScrollPending = currentHash() === 'fortschritt'
|
||||
loadCustomerActivity().then(scrollToActivity)
|
||||
|
||||
const initialView = viewFromHash()
|
||||
// Alte Hashes (#wartung, #abo) gleich auf den neuen (#vertraege) umschreiben
|
||||
if (initialView) showView(initialView, { updateHash: Boolean(HASH_ALIASES[window.location.hash.replace(/^#/, '')]) })
|
||||
if (initialView) showView(initialView, { updateHash: Boolean(HASH_ALIASES[currentHash()]) })
|
||||
|
||||
const [{ projects }, { features }] = await Promise.all([
|
||||
api('/api/projects'),
|
||||
@@ -2098,6 +2133,7 @@ async function initDashboardPage() {
|
||||
} else {
|
||||
empty.classList.remove('hidden')
|
||||
}
|
||||
scrollToActivity()
|
||||
|
||||
// Chat-Badge global pollen (auch wenn der Chat nicht offen ist)
|
||||
pollChatBadge()
|
||||
@@ -2121,8 +2157,8 @@ const appLayout = window.matchMedia('(max-width: 880px), (pointer: coarse) and (
|
||||
const coarsePointer = window.matchMedia('(pointer: coarse)')
|
||||
// Unterseite -> Tab, zu dem sie gehoert (und Text neben dem Zurueck-Pfeil).
|
||||
// "passwort" setzt onAppViewShown: die Ansicht, aus der das Konto-Menue geoeffnet wurde.
|
||||
const APP_PARENT = { stunden: 'vertraege', 'bestellen-hosting': 'vertraege', 'bestellen-wartung': 'vertraege', wechsel: 'vertraege', takeover: 'vertraege', projekt: 'projects' }
|
||||
const APP_PARENT_LABEL = { vertraege: 'Verträge', projects: 'Projekte', activity: 'Fortschritt', invoices: 'Rechnungen', chat: 'Chat' }
|
||||
const APP_PARENT = { news: 'funktionen', stunden: 'vertraege', 'bestellen-hosting': 'vertraege', 'bestellen-wartung': 'vertraege', wechsel: 'vertraege', takeover: 'vertraege', projekt: 'projects' }
|
||||
const APP_PARENT_LABEL = { vertraege: 'Verträge', projects: 'Projekte', funktionen: 'Funktionen', invoices: 'Rechnungen', chat: 'Chat' }
|
||||
const appShell = { view: 'projects', scroll: {}, fromHistory: false, sideCards: [], installPrompt: null }
|
||||
const TEXT_INPUT = 'textarea, input:not([type=checkbox]):not([type=radio]):not([type=file]):not([type=date]):not([type=button]):not([type=submit]):not([type=range]):not([type=color])'
|
||||
|
||||
|
||||
Reference in new issue
Block a user