Files
property-match/src/components/assistant/AssistantContextSummary.tsx
T
Benjamin Sutter 9ed21e33a7 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>
2026-08-06 12:44:22 +02:00

80 lines
3.1 KiB
TypeScript

import { Box, Chip, Typography } from '@mui/material'
import type { AssistantContext } from '../../domain/assistant'
import { DS_ACCENT, DS_SLATE } from '../../lib/ds'
const PAGE_LABELS: Record<string, string> = {
'/supply/properties': 'Startseite',
'/supply/match-center': 'Eingehende Bedarfe',
'/supply/data-quality': 'Datenpflege',
'/supply/future-availability':'Marktchancen',
'/demand/ai-search': 'Flächensuche',
'/demand/results': 'Ergebnisse',
'/demand/compare': 'Vergleich',
'/demand/shortlists': 'Shortlists',
'/ops/review-queue': 'Review Queue',
'/ops/ai-monitoring': 'AI Monitoring',
'/ops/governance': 'Governance',
}
function resolvePageLabel(route: string): string {
for (const [path, label] of Object.entries(PAGE_LABELS)) {
if (route.startsWith(path)) return label
}
return route.split('/').filter(Boolean).pop()?.replace(/-/g, ' ') ?? 'Seite'
}
const ENTITY_LABELS: Record<string, string> = {
PROPERTY: 'Objekt', NEED: 'Gesuch', MATCH: 'Match',
SIGNAL: 'Signal', AI_OUTPUT: 'AI-Output',
}
interface Props {
context: AssistantContext
}
export function AssistantContextSummary({ context }: Props) {
const pageLabel = resolvePageLabel(context.currentRoute)
return (
<Box sx={{ px: 2, py: 1, bgcolor: DS_SLATE[50], borderBottom: '1px solid #e2e8f0' }}>
<Box sx={{ display: 'flex', gap: 0.75, flexWrap: 'wrap', alignItems: 'center' }}>
<Chip
label={pageLabel}
size="small"
sx={{ bgcolor: DS_ACCENT.cyan.bg, color: DS_ACCENT.cyan.dark, fontWeight: 600, fontSize: '0.7rem', height: 20 }}
/>
{context.selectedEntityType && context.selectedEntityId && (
<Chip
label={`${ENTITY_LABELS[context.selectedEntityType] ?? context.selectedEntityType}: ${context.selectedEntityId}`}
size="small"
sx={{ bgcolor: DS_SLATE[100], color: DS_SLATE[600], fontWeight: 500, fontSize: '0.65rem', height: 20, fontFamily: 'monospace' }}
/>
)}
{context.visibleScores?.quality !== undefined && (
<Chip
label={`Qualität: ${context.visibleScores.quality}%`}
size="small"
sx={{
bgcolor: context.visibleScores.quality >= 70 ? '#dcfce7' : '#fef3c7',
color: context.visibleScores.quality >= 70 ? '#166534' : '#92400e',
fontWeight: 600, fontSize: '0.65rem', height: 20,
}}
/>
)}
{context.visibleScores?.matchScore !== undefined && (
<Chip
label={`Match: ${context.visibleScores.matchScore}%`}
size="small"
sx={{ bgcolor: DS_ACCENT.violet.tint, color: DS_ACCENT.violet.dark, fontWeight: 600, fontSize: '0.65rem', height: 20 }}
/>
)}
</Box>
{context.visibleMissingData && context.visibleMissingData.length > 0 && (
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', mt: 0.5, fontSize: '0.65rem' }}>
Fehlende Daten: {context.visibleMissingData.slice(0, 3).join(', ')}
</Typography>
)}
</Box>
)
}