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,122 @@
|
||||
import { Box, IconButton, TextField, Typography } from '@mui/material'
|
||||
import { SendHorizonal } from 'lucide-react'
|
||||
import { AgentAvatar } from './AgentAvatar'
|
||||
import { AGENT_CHAT_MARKER, useAgentChat } from './useAgentChat'
|
||||
import { AGENT_CHAT_PROMPT_SHORT } from '../../lib/constants'
|
||||
import { DS_BORDER, DS_TEXT } from '../../lib/ds'
|
||||
import { useAgentChatStore } from '../../stores/agentChatStore'
|
||||
|
||||
/**
|
||||
* Kompakter Chatzustand im obersten globalen Balken (Runde 5, §5).
|
||||
*
|
||||
* Er erscheint, sobald der ausführliche Chatkopf der Agentenseite nach oben
|
||||
* weggescrollt ist, und zeigt nebeneinander Porträt, Name, Frage und
|
||||
* Eingabefeld. Er liegt im Balken zwischen Seitenmenü und Mandant/Konto und
|
||||
* nicht als Schwebeleiste über der Seite — so kann er Tabellen, Filter und
|
||||
* Seitennavigation gar nicht erst verdecken (§10).
|
||||
*
|
||||
* Ohne eigenen Eingabezustand: Text, Fokus und Agent kommen aus
|
||||
* `agentChatStore`, damit der Wechsel zwischen beiden Zuständen mitten im
|
||||
* Tippen nichts verliert.
|
||||
*/
|
||||
export function AgentChatStickyBar() {
|
||||
const compact = useAgentChatStore(s => s.compact)
|
||||
const agent = useAgentChatStore(s => s.agent)
|
||||
|
||||
// Der Inhalt ist eine eigene Komponente, damit `useAgentChat` erst dann
|
||||
// montiert wird, wenn der kompakte Zustand tatsächlich sichtbar ist. Läge der
|
||||
// Aufruf hier, liefe sein Fokuseffekt schon beim Start der Anwendung ins Leere
|
||||
// und der Fokus bliebe beim Scrollen im weggescrollten Feld hängen.
|
||||
if (!agent || !compact) return null
|
||||
return <StickyChat />
|
||||
}
|
||||
|
||||
function StickyChat() {
|
||||
const { agent, draft, inputRef, setDraft, send, handleKeyDown, onFocus, onBlur } = useAgentChat()
|
||||
if (!agent) return null
|
||||
|
||||
return (
|
||||
<Box
|
||||
{...{ [AGENT_CHAT_MARKER]: '' }}
|
||||
sx={{
|
||||
flex: 1,
|
||||
minWidth: 0,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 1.25,
|
||||
// Abstand zu Mandant, Benachrichtigungen und Konto auf der rechten Seite.
|
||||
mr: { xs: 1, sm: 2 },
|
||||
maxWidth: 720,
|
||||
}}
|
||||
>
|
||||
<AgentAvatar agent={agent} size={34} loading="eager" />
|
||||
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: 700,
|
||||
fontSize: '0.875rem',
|
||||
color: DS_TEXT.primary,
|
||||
whiteSpace: 'nowrap',
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
{agent.name}
|
||||
</Typography>
|
||||
|
||||
{/*
|
||||
Bei engen Breiten verschwindet zuerst der Hilfstext, danach schrumpft das
|
||||
Eingabefeld (§10). Porträt und Name bleiben in jeder Breite stehen —
|
||||
ohne sie wüsste niemand mehr, mit wem er schreibt.
|
||||
*/}
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: '0.8125rem',
|
||||
color: DS_TEXT.secondary,
|
||||
whiteSpace: 'nowrap',
|
||||
flexShrink: 0,
|
||||
display: { xs: 'none', lg: 'block' },
|
||||
}}
|
||||
>
|
||||
{AGENT_CHAT_PROMPT_SHORT}
|
||||
</Typography>
|
||||
|
||||
<TextField
|
||||
size="small"
|
||||
value={draft}
|
||||
onChange={e => setDraft(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
onFocus={onFocus}
|
||||
onBlur={onBlur}
|
||||
inputRef={inputRef}
|
||||
placeholder={`Nachricht an ${agent.name}`}
|
||||
aria-label={`Nachricht an ${agent.name}`}
|
||||
sx={{
|
||||
flex: 1,
|
||||
minWidth: 120,
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: 5,
|
||||
bgcolor: 'white',
|
||||
fontSize: '0.8125rem',
|
||||
pr: 0.5,
|
||||
'& fieldset': { borderColor: DS_BORDER.default },
|
||||
},
|
||||
}}
|
||||
slotProps={{
|
||||
input: {
|
||||
endAdornment: (
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={send}
|
||||
disabled={!draft.trim()}
|
||||
aria-label={`Nachricht an ${agent.name} senden`}
|
||||
sx={{ color: DS_TEXT.primary }}
|
||||
>
|
||||
<SendHorizonal size={15} />
|
||||
</IconButton>
|
||||
),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user