From 142aa6e9d6fb74be963efd4700cfa6c268e58572 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 16 Oct 2026 16:26:23 +0000 Subject: [PATCH] =?UTF-8?q?Kundenansicht=20neu=20P1:=20wei=C3=9Fe=20Seite,?= =?UTF-8?q?=20Manifest=20und=20leere=20Ansichten=20f=C3=BCr=20P2=20und=20P?= =?UTF-8?q?3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5.5 --- public/neu/ansicht-bestellen.js | 7 ++ public/neu/ansicht-chat.js | 7 ++ public/neu/ansicht-funktionen.js | 7 ++ public/neu/ansicht-gutschrift.js | 7 ++ public/neu/ansicht-passwort.js | 7 ++ public/neu/ansicht-vertraege.js | 7 ++ public/neu/ansicht-website.js | 7 ++ public/neu/ansicht-wechsel.js | 7 ++ public/neu/ansichten.css | 2 + public/neu/dashboard.html | 100 ++++++++++++++++++++++------ public/neu/funktionen.css | 2 + public/neu/manifest.webmanifest | 24 +++++++ public/neu/module.css | 2 + test/knHuelleSeite.test.js | 39 +++++++++++ test/kundenansichtDashboard.test.js | 5 +- 15 files changed, 205 insertions(+), 25 deletions(-) create mode 100644 public/neu/ansicht-bestellen.js create mode 100644 public/neu/ansicht-chat.js create mode 100644 public/neu/ansicht-funktionen.js create mode 100644 public/neu/ansicht-gutschrift.js create mode 100644 public/neu/ansicht-passwort.js create mode 100644 public/neu/ansicht-vertraege.js create mode 100644 public/neu/ansicht-website.js create mode 100644 public/neu/ansicht-wechsel.js create mode 100644 public/neu/ansichten.css create mode 100644 public/neu/funktionen.css create mode 100644 public/neu/manifest.webmanifest create mode 100644 public/neu/module.css create mode 100644 test/knHuelleSeite.test.js diff --git a/public/neu/ansicht-bestellen.js b/public/neu/ansicht-bestellen.js new file mode 100644 index 0000000..96884ac --- /dev/null +++ b/public/neu/ansicht-bestellen.js @@ -0,0 +1,7 @@ +/* Kundenansicht neu: Platzhalter, den das nächste Paket füllt (Stub aus P1). */ + +import { h } from './dom.js' + +export async function zeigeBestellen(wurzel, { route, me, nav }) { + wurzel.replaceChildren(h('p', { class: 'kn-leer' }, 'Abo bestellen (folgt)')) +} diff --git a/public/neu/ansicht-chat.js b/public/neu/ansicht-chat.js new file mode 100644 index 0000000..ed44130 --- /dev/null +++ b/public/neu/ansicht-chat.js @@ -0,0 +1,7 @@ +/* Kundenansicht neu: Platzhalter, den das nächste Paket füllt (Stub aus P1). */ + +import { h } from './dom.js' + +export async function zeigeChat(wurzel, { route, me, nav }) { + wurzel.replaceChildren(h('p', { class: 'kn-leer' }, 'Chat (folgt)')) +} diff --git a/public/neu/ansicht-funktionen.js b/public/neu/ansicht-funktionen.js new file mode 100644 index 0000000..b03272c --- /dev/null +++ b/public/neu/ansicht-funktionen.js @@ -0,0 +1,7 @@ +/* Kundenansicht neu: Platzhalter, den das nächste Paket füllt (Stub aus P1). */ + +import { h } from './dom.js' + +export async function zeigeFunktionen(wurzel, { route, me, nav }) { + wurzel.replaceChildren(h('p', { class: 'kn-leer' }, 'Funktionen (folgt)')) +} diff --git a/public/neu/ansicht-gutschrift.js b/public/neu/ansicht-gutschrift.js new file mode 100644 index 0000000..daeb94e --- /dev/null +++ b/public/neu/ansicht-gutschrift.js @@ -0,0 +1,7 @@ +/* Kundenansicht neu: Platzhalter, den das nächste Paket füllt (Stub aus P1). */ + +import { h } from './dom.js' + +export async function zeigeGutschrift(wurzel, { route, me, nav }) { + wurzel.replaceChildren(h('p', { class: 'kn-leer' }, 'Übernahmegutschrift (folgt)')) +} diff --git a/public/neu/ansicht-passwort.js b/public/neu/ansicht-passwort.js new file mode 100644 index 0000000..86e5909 --- /dev/null +++ b/public/neu/ansicht-passwort.js @@ -0,0 +1,7 @@ +/* Kundenansicht neu: Platzhalter, den das nächste Paket füllt (Stub aus P1). */ + +import { h } from './dom.js' + +export async function zeigePasswort(wurzel, { route, me, nav }) { + wurzel.replaceChildren(h('p', { class: 'kn-leer' }, 'Passwort ändern (folgt)')) +} diff --git a/public/neu/ansicht-vertraege.js b/public/neu/ansicht-vertraege.js new file mode 100644 index 0000000..db9335a --- /dev/null +++ b/public/neu/ansicht-vertraege.js @@ -0,0 +1,7 @@ +/* Kundenansicht neu: Platzhalter, den das nächste Paket füllt (Stub aus P1). */ + +import { h } from './dom.js' + +export async function zeigeVertraege(wurzel, { route, me, nav }) { + wurzel.replaceChildren(h('p', { class: 'kn-leer' }, 'Verträge (folgt)')) +} diff --git a/public/neu/ansicht-website.js b/public/neu/ansicht-website.js new file mode 100644 index 0000000..26d5f5e --- /dev/null +++ b/public/neu/ansicht-website.js @@ -0,0 +1,7 @@ +/* Kundenansicht neu: Platzhalter, den das nächste Paket füllt (Stub aus P1). */ + +import { h } from './dom.js' + +export async function zeigeWebsite(wurzel, { route, me, nav }) { + wurzel.replaceChildren(h('p', { class: 'kn-leer' }, 'Website (folgt)')) +} diff --git a/public/neu/ansicht-wechsel.js b/public/neu/ansicht-wechsel.js new file mode 100644 index 0000000..272f6ed --- /dev/null +++ b/public/neu/ansicht-wechsel.js @@ -0,0 +1,7 @@ +/* Kundenansicht neu: Platzhalter, den das nächste Paket füllt (Stub aus P1). */ + +import { h } from './dom.js' + +export async function zeigeWechsel(wurzel, { route, me, nav }) { + wurzel.replaceChildren(h('p', { class: 'kn-leer' }, 'Abo wechseln (folgt)')) +} diff --git a/public/neu/ansichten.css b/public/neu/ansichten.css new file mode 100644 index 0000000..d64179c --- /dev/null +++ b/public/neu/ansichten.css @@ -0,0 +1,2 @@ +/* Kundenansicht neu: Stile der Ansichten Website, Chat, Verträge und der Etagen Passwort, Gutschrift, Wechsel, Bestellen. + Besitzer: P2 (danach hängt P7 einen Abschnitt für die Bestellseiten an). Hier steht bis dahin nichts. */ diff --git a/public/neu/dashboard.html b/public/neu/dashboard.html index b9b3707..77f85e4 100644 --- a/public/neu/dashboard.html +++ b/public/neu/dashboard.html @@ -2,31 +2,87 @@ - + Kundenbereich – WEBklar - + + + + + + + + + + + + + + + + + -
-

Kundenansicht neu

-

Platzhalter bis P1

-

Ansicht:

-
- + Zum Inhalt + + +
+ + +
+
+

+
+
+ + + +
+ + + + +
+ + +
+
+ + + +
+
+ + + diff --git a/public/neu/funktionen.css b/public/neu/funktionen.css new file mode 100644 index 0000000..83d0a41 --- /dev/null +++ b/public/neu/funktionen.css @@ -0,0 +1,2 @@ +/* Kundenansicht neu: Stile der Ansicht Funktionen (Meine, Entdecken) und der Einschalt-Ansicht. + Besitzer: P3. Hier steht bis dahin nichts. */ diff --git a/public/neu/manifest.webmanifest b/public/neu/manifest.webmanifest new file mode 100644 index 0000000..e9a96db --- /dev/null +++ b/public/neu/manifest.webmanifest @@ -0,0 +1,24 @@ +{ + "id": "/dashboard.html", + "name": "WEBklar Kundenbereich", + "short_name": "WEBklar", + "description": "Website, Funktionen, Chat und Verträge – der WEBklar-Kundenbereich als App.", + "lang": "de", + "dir": "ltr", + "start_url": "/dashboard.html", + "scope": "/", + "display": "standalone", + "background_color": "#ffffff", + "theme_color": "#ffffff", + "categories": ["business", "productivity"], + "icons": [ + { "src": "/img/app-icon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any" }, + { "src": "/img/app-icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any" }, + { "src": "/img/app-icon-maskable-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" } + ], + "shortcuts": [ + { "name": "Chat", "short_name": "Chat", "url": "/dashboard.html#chat", "icons": [{ "src": "/img/app-icon-192.png", "sizes": "192x192", "type": "image/png" }] }, + { "name": "Rechnungen", "short_name": "Rechnungen", "url": "/dashboard.html#rechnungen", "icons": [{ "src": "/img/app-icon-192.png", "sizes": "192x192", "type": "image/png" }] }, + { "name": "Verträge", "short_name": "Verträge", "url": "/dashboard.html#vertraege", "icons": [{ "src": "/img/app-icon-192.png", "sizes": "192x192", "type": "image/png" }] } + ] +} diff --git a/public/neu/module.css b/public/neu/module.css new file mode 100644 index 0000000..ecca2b4 --- /dev/null +++ b/public/neu/module.css @@ -0,0 +1,2 @@ +/* Kundenansicht neu: Stile der gemeinsamen Funktionsmodule (Terminplaner, Webshop, Buchungskalender, Öffnungszeiten, News, Analyse) unter .kn. + Besitzer: P4. Hier steht bis dahin nichts. */ diff --git a/test/knHuelleSeite.test.js b/test/knHuelleSeite.test.js new file mode 100644 index 0000000..0c35858 --- /dev/null +++ b/test/knHuelleSeite.test.js @@ -0,0 +1,39 @@ +import { test } from 'node:test' +import assert from 'node:assert/strict' +import { GERUEST_IDS, ANSICHTEN, ETAGE_ANSICHTEN } from '../public/neu/huelle.js' +import { lies } from './support/knHuelle.js' + +const html = lies('dashboard.html') + +test('dashboard.html: weiße PWA-Metas, Stile in fester Reihenfolge, klassisches Skript vor dem Modul, nichts Altes (P2 darf chat-composer ergänzen)', () => { + assert.match(html, //); assert.match(html, //) + assert.match(html, /apple-mobile-web-app-status-bar-style" content="default"/); assert.match(html, //) + assert.match(html, /viewport" content="width=device-width, initial-scale=1, viewport-fit=cover, interactive-widget=resizes-content"/) + const css = [...html.matchAll(/ m[1]); const ORDNUNG = ['/neu/tokens.css', '/neu/basis.css', '/neu/huelle.css', '/neu/ansichten.css', '/neu/funktionen.css', '/neu/module.css'] + assert.deepEqual(css.filter((f) => f !== '/chat-composer.css'), ORDNUNG); assert.ok(css.indexOf('/chat-composer.css') === -1 || css.indexOf('/chat-composer.css') > css.indexOf('/neu/huelle.css')) + assert.match(html, //) + const skripte = [...html.matchAll(/]*)>/g)].map((m) => m[1].trim()) + assert.deepEqual([skripte[0], skripte.at(-1)], ['src="/kundenansicht.js"', 'type="module" src="/neu/huelle.js"']); assert.ok(skripte.slice(1, -1).every((s) => s === 'src="/chat-composer.js"')) + assert.doesNotMatch(html, /login\.css|kundenbereich\.css|\/app\.js|dot-background|kb-glow|class="[^"]*\bkb-/) +}) + +test('dashboard.html: jede Gerüst-ID genau einmal, dazu die Body-IDs der Funktionsmodule und beide Navigationen', () => { + for (const id of GERUEST_IDS) assert.equal(html.split(`id="${id}"`).length - 1, 1, id) + for (const id of ['news-body', 'termine-body', 'webshop-body', 'buchungen-body', 'zeiten-body', 'analyse-body', 'funktionen-body']) assert.ok(GERUEST_IDS.includes(id), id) + for (const id of ['kn-menue', 'kn-leiste']) assert.match(html, new RegExp(`]*id="${id}")(?=[^>]*aria-label="Hauptnavigation")`)) +}) + +test('manifest.webmanifest: weiß, gleiche Identität wie die alte App, Shortcuts Chat, Rechnungen, Verträge', () => { + const m = JSON.parse(lies('manifest.webmanifest')) + assert.deepEqual([m.id, m.start_url, m.scope, m.display, m.background_color, m.theme_color], ['/dashboard.html', '/dashboard.html', '/', 'standalone', '#ffffff', '#ffffff']) + assert.deepEqual(m.shortcuts.map((s) => [s.name, s.url]), [['Chat', '/dashboard.html#chat'], ['Rechnungen', '/dashboard.html#rechnungen'], ['Verträge', '/dashboard.html#vertraege']]) + assert.deepEqual(m.icons.map((i) => i.src), ['/img/app-icon-192.png', '/img/app-icon-512.png', '/img/app-icon-maskable-512.png']) +}) + +test('Stubs: jede Ansicht der Hülle existiert unter dem Namen aus V2 (zeige + Dateiname)', async () => { + const soll = { website: 'zeigeWebsite', funktionen: 'zeigeFunktionen', chat: 'zeigeChat', vertraege: 'zeigeVertraege', passwort: 'zeigePasswort', gutschrift: 'zeigeGutschrift', wechsel: 'zeigeWechsel', bestellen: 'zeigeBestellen' } + for (const [name, fn] of Object.entries(soll)) { + assert.equal(typeof (await import(`../public/neu/ansicht-${name}.js`))[fn], 'function', fn) + assert.deepEqual(Object.values({ ...ANSICHTEN, ...ETAGE_ANSICHTEN }).find(([p]) => p === `/neu/ansicht-${name}.js`).slice(0, 2), [`/neu/ansicht-${name}.js`, fn]) + } +}) diff --git a/test/kundenansichtDashboard.test.js b/test/kundenansichtDashboard.test.js index 814e102..be20497 100644 --- a/test/kundenansichtDashboard.test.js +++ b/test/kundenansichtDashboard.test.js @@ -66,10 +66,9 @@ test('neue Seite: HTML und Cache-Control no-cache; Query bleibt erhalten', async const r = await rawGet('/dashboard.html?abo=success', { cookie: customerCookie('c1') }) assert.ok(istNeu(r)); assert.equal(r.headers.get('cache-control'), 'no-cache'); assert.match(r.headers.get('content-type'), /^text\/html/) }) -test('Platzhalter trägt beide Marker (N5) und die Hülle des Briefs', () => { +test('neue Seite trägt den Marker (N5) und die Hülle des Briefs', () => { const html = readFileSync(path.join(publicDir, 'neu', 'dashboard.html'), 'utf8') - for (const teil of ['', '', 'data-kn-platzhalter', 'id="kn-platzhalter-ansicht"', 'Kundenansicht neu', 'Platzhalter bis P1', '/api/auth/me']) assert.ok(html.includes(teil), teil) - assert.ok(!/]*\btype=["']module/.test(html), 'klassisches Skript, kein Modul') + for (const teil of ['', '', '']) assert.ok(html.includes(teil), teil) // P1 ersetzt den Platzhalter aus P0 durch die weiße Hülle }) test('Direktaufruf /neu/dashboard.html: Pilot ja, sonst 302 auf /dashboard.html', async () => { stelle('pilot', [MAIL])