feat(runde-5): Startseite mit Agenten-Meetingraum, sticky Chat und Noras Kanäle

Hauptnavigation und Startseite (§2)
- Hauptseite «Übersicht» samt Route, Menüeintrag, Hook, Service und den nur
  dort verwendeten Widgets entfernt; /supply/dashboard leitet auf die
  Startseite um, damit Lesezeichen nicht ins Leere laufen.
- «Meine Objekte» heisst im Menü «Startseite»; die frühere Überschrift
  «Objektverwaltung» heisst jetzt «Meine Objekte».
- Die Startseite scrollt als Ganzes. Die Höhe des Meetingraums rechnet gegen
  die Viewport-Höhe, damit Titel und Balken «Filter & Übersicht» beim Einstieg
  auf jeder Monitorgrösse sichtbar bleiben (geprüft 1280–1920 px).

Agenten-Meetingraum (§3)
- Zentralperspektive aus clip-path-Flächen, ohne 3D-Engine und ohne
  Animationsbibliothek: rechts geschlossene Wand, links und gegenüber
  boden- bis deckenhohe Glasfronten mit Blick auf Rhein, Basler Münster,
  Altstadt und Wettsteinbrücke.
- Sitzordnung, Zielrouten, Grössen und Plakettenversatz liegen in einer
  zentralen Konfiguration (meetingRoomSeats.ts).
- Neuer Agent Giorgio, Personalverwalter, sitzt vis-à-vis und führt in
  «Meine Agenten». Er steht bewusst nicht in AGENT_WORKSPACES: er hat keine
  Arbeitsseite, sondern ist die Adminfläche selbst.
- Auf schmalen Schirmen tritt eine vereinfachte Kachelanordnung an die Stelle
  der Perspektive; ausgeblendet wird kein Agent.

Sticky Chat bei allen Agenten (§5, §9–§11)
- Scrollt der Chatkopf unter den globalen Balken, übernimmt dort eine kompakte
  Fassung mit Porträt, Name, Frage und Eingabefeld.
- Ein gemeinsamer Zustand (agentChatStore) statt zweier Chat-Instanzen:
  Eingabetext und Fokus überstehen den Wechsel in beide Richtungen.
- Noras Seite scrollte bisher gar nicht — Chatkopf ausserhalb des
  Scrollbereichs, zwei getrennt scrollende Spalten darunter. Behoben über den
  Seitenaufbau, nicht über eine Nora-Sonderlösung.

Nora: CRM-Kanal und gemeinsame Lead-Liste (§12, §13)
- Dritter Kanal «CRM» mit eigenem Domänentyp, Mockdaten, Provider, Service und
  Hook nach dem bestehenden Schichtenmuster.
- Die Reiter «KI-Signale»/«Netzwerk» sind einer gemeinsamen Liste gewichen.
  Jede Zeile trägt ihre Kanalherkunft als Badge; die Herkunft steht im
  Datenmodell (LeadChannel) und wird nicht aus der Darstellung geraten.
- Der frühere Reiterbalken ist der Filter «Kanaltyp» mit Alle/KI Signal/
  Netzwerk/CRM, Standard «Alle», mit Empty State und Rücksetzung.

Produkttour und Profilmenü (§8)
- Tour auf die neue Struktur umgeschrieben: Startseite, Meetingraum, Agenten,
  sticky Chat, Leadkanäle. «Übersicht» kommt nicht mehr vor, «Meine Objekte»
  bezeichnet nur noch den Objektbereich.
- Bereich «Demo-Modus» mit «Verwaltung» und «Bürosuche» entfernt.

Geprüft: 413 Tests, Typecheck, Build, ESLint, Token-Schwelle (889/925).
Im Browser: Navigation, Agentenklicks aller sechs Plätze, Kanalfilter und
Detailschubladen, Sticky Chat bei vier Agenten, 1280–1920 px sowie
Tablet- und Mobilbreite, keine 404 für Bilder oder Routen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin SutterandClaude Opus 5 committed 2026-08-06 12:44:22 +02:00
1 parent d200d4e930
commit 9ed21e33a7
59 files changed
+2545 -1309

No files matched your search

+57
View File
@@ -0,0 +1,57 @@
import type { MarktHinweis } from './marktHinweis'
import type { CrmLead } from './crmLead'
import type { FutureSignal } from './futureSignal'
import type { Property } from './property'
/**
* Kanalherkunft eines Leads (Runde 5, §12).
*
* Ausdrücklich Teil des Datenmodells und nicht der Darstellung: der Kanal
* entscheidet über Badge, Filter und Detailansicht. Aus der Darstellung
* zurückgeraten — «hat eine Signal-ID, also KI» — bräche beim ersten neuen
* Kanal, und niemand fände die Stelle.
*/
export const LeadChannel = {
KI_SIGNAL: 'KI_SIGNAL',
NETZWERK: 'NETZWERK',
CRM: 'CRM',
} as const
export type LeadChannel = typeof LeadChannel[keyof typeof LeadChannel]
/** Reihenfolge im Filterbalken — verbindlich (§13). */
export const LEAD_CHANNEL_ORDER: LeadChannel[] = [
LeadChannel.KI_SIGNAL,
LeadChannel.NETZWERK,
LeadChannel.CRM,
]
/** Zusätzliche Filterstellung «Alle»; sie ist kein Kanal, sondern deren Abwesenheit. */
export const LEAD_CHANNEL_ALL = 'ALL'
export type LeadChannelFilter = LeadChannel | typeof LEAD_CHANNEL_ALL
/**
* Anzeigefelder, die jeder Lead unabhängig vom Kanal besitzt. Ohne sie müsste
* jede Zeile der gemeinsamen Liste den Kanal abfragen, bevor sie überhaupt
* einen Titel anzeigen könnte.
*/
interface UnifiedLeadBase {
id: string
channel: LeadChannel
/** Wer — Firma oder Ort, wenn keine Firma bekannt ist. */
title: string
/** Worum es geht. */
subtitle: string
/** Ort und eine kanaltypische Zusatzangabe. */
meta: string
/** ISO-Zeitstempel; einzige gemeinsame Grundlage für die Sortierung. */
receivedAt: string
}
export type UnifiedLead =
| (UnifiedLeadBase & {
channel: typeof LeadChannel.KI_SIGNAL
signal: FutureSignal
matchingProperties: Property[]
})
| (UnifiedLeadBase & { channel: typeof LeadChannel.NETZWERK; hinweis: MarktHinweis })
| (UnifiedLeadBase & { channel: typeof LeadChannel.CRM; crmLead: CrmLead })