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:
1 parent
d200d4e930
commit
9ed21e33a7
59 files changed
+2545
-1309
No files matched your search
@@ -0,0 +1,51 @@
|
||||
import type { AssetType } from './enums'
|
||||
|
||||
/**
|
||||
* Lead aus dem angebundenen CRM (Runde 5, §12).
|
||||
*
|
||||
* Dritter Kanal neben KI-Signalen und Netzwerk-Hinweisen. Fachlich der am
|
||||
* besten belegte der drei: hier hat ein Mensch bereits mit dem Interessenten
|
||||
* gesprochen, es gibt einen Ansprechpartner, einen Bearbeitungsstand und ein
|
||||
* Datum des letzten Kontakts.
|
||||
*
|
||||
* Eigener Typ statt einer Erweiterung von `MarktHinweis`: ein CRM-Lead trägt
|
||||
* einen Verkaufstrichter und eine Zuständigkeit, ein Netzwerk-Hinweis eine
|
||||
* Sichtbarkeit und eine Quelle. Zwei Bedeutungen in einem Feld wären der
|
||||
* Anfang jeder späteren Verwechslung.
|
||||
*/
|
||||
|
||||
/** Stand im Verkaufstrichter des CRM. */
|
||||
export const CrmLeadStage = {
|
||||
NEW: 'NEW',
|
||||
QUALIFIED: 'QUALIFIED',
|
||||
VIEWING: 'VIEWING',
|
||||
NEGOTIATION: 'NEGOTIATION',
|
||||
} as const
|
||||
export type CrmLeadStage = typeof CrmLeadStage[keyof typeof CrmLeadStage]
|
||||
|
||||
export const CRM_LEAD_STAGE_LABELS: Record<CrmLeadStage, string> = {
|
||||
[CrmLeadStage.NEW]: 'Neu erfasst',
|
||||
[CrmLeadStage.QUALIFIED]: 'Qualifiziert',
|
||||
[CrmLeadStage.VIEWING]: 'Besichtigung geplant',
|
||||
[CrmLeadStage.NEGOTIATION]: 'In Verhandlung',
|
||||
}
|
||||
|
||||
export interface CrmLead {
|
||||
id: string
|
||||
companyName: string
|
||||
contactPerson: string
|
||||
contactEmail?: string
|
||||
contactPhone?: string
|
||||
locationHint: string
|
||||
assetType: AssetType
|
||||
areaSqmMin?: number
|
||||
areaSqmMax?: number
|
||||
stage: CrmLeadStage
|
||||
/** Zuständiger Bewirtschafter laut CRM. */
|
||||
ownerName: string
|
||||
/** Name des Quellsystems — die Herkunft bleibt bis zur Anzeige nachvollziehbar. */
|
||||
crmSystem: string
|
||||
/** ISO-Zeitstempel des letzten dokumentierten Kontakts. */
|
||||
lastContactAt: string
|
||||
note?: string
|
||||
}
|
||||
@@ -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 })
|
||||
Reference in new issue
Block a user