feat(agenten): Runde 4 — «Meine Agenten» mit fünf digitalen Mitarbeitenden

Setzt das Umsetzungsbriefing Runde 4 im bestehenden Frontend um.

Informationsarchitektur
- «Teamübersicht» heisst «Meine Agenten»; ihr bisheriger Inhalt (Kreisgrafik,
  Auswertung, Verbindungszusammenfassung) ist entfallen.
- Personalverwaltung, Bearbeitungsverlauf und Kanäle & Systeme sind jetzt
  Reiter derselben Seite (?section=), gestaltet wie die Auswahl im
  Bearbeitungsverlauf. Es wird nur der gewählte Bereich gerendert.
- Die alten Unterseitenpfade leiten um, damit Lesezeichen nicht brechen.

Agentenbestand
- Reto und Lea vollständig entfernt — aus Navigation, Dossiers, Protokoll,
  Verbindungen, Vorgängen und Porträtbestand.
- Retos Aufgaben liegen bei Bruno: Nachbereitung, WhatsApp-Anruf,
  Protokoll und Kundennotiz, Ablage im CRM.
- Livia ist «Exposé Master»: Lageberichte, Inserate, Angebotsbroschüren.
- Sidebar führt Ferdi, Bruno, Livia, Nora, Sina mit Porträt und Funktion.

Agentenseiten
- Ein gemeinsamer AgentWorkspaceHero auf allen fünf Seiten.
- Ferdi: Auswertungskarten, Priorität und Typfarben entfallen; Fälligkeit nur
  bei fünf Tagen oder weniger rot; Objektlinks nach «Meine Objekte»; neu die
  Terminplanung im verbundenen Kalender mit typgerechtem PDF-Ausschnitt.
- Sina: reduzierte, filter- und sortierbare Objektübersicht; Detailansicht
  direkt editierbar, leere Pflichtfelder rot umrandet.
- Nora: Signale ohne Prozentsätze und Konfidenzstufen, nur belegbare Angaben;
  Mehrfachauswahl leitet Objekte an Livia weiter.
- Livia: aktive und archivierte Leads, Arbeitsbereich gleitet an den oberen
  Rand; dreistufiger Exposé-Prozess Hochladen → Exposé → Export.
- Bruno: neue Seite mit Auftragsliste und Vor-/Nachbereitungs-Drawer;
  Glocke warnt bei Besichtigung unter 24 Stunden ohne Bericht.

Datenschicht
- Neu: Kalender, Exposé-Leads, Exposé-Entwürfe, Besichtigungsaufträge —
  je Domain, Provider, Service und Hook.
- IAIService um generateExposeText erweitert; der Entwurf nutzt ausschliesslich
  erfasste Objektdaten und meldet Lücken, statt sie zu füllen.
- Alle Objektverweise zeigen auf reale Einträge aus «Meine Objekte»; neue
  Detailroute /supply/properties/:propertyId.

Offen: Chat, Kalender, CRM und DMS sind Frontend-Simulation ohne Anbindung.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin SutterandClaude Opus 5 committed 2026-08-05 14:43:16 +02:00
1 parent cc5582b7fd
commit d200d4e930
352 files changed
+8069 -8512

No files matched your search

+28 -19
View File
@@ -1,5 +1,6 @@
import { WorkspaceType } from '../../domain/enums'
import { ROUTES } from '../../lib/constants'
import { MY_AGENTS_LABEL, ROUTES } from '../../lib/constants'
import { AGENT_WORKSPACES } from '../../lib/agentWorkspaces'
import type { LucideIcon } from 'lucide-react'
import {
LayoutDashboard,
@@ -7,8 +8,6 @@ import {
CheckSquare,
Search,
ClipboardList,
Radar,
BellRing,
Settings,
Users,
} from 'lucide-react'
@@ -18,13 +17,21 @@ import {
// ---------------------------------------------------------------------------
/**
* Eingerückter Subreiter unterhalb eines Hauptreiters. Bewusst ohne Icon: die
* Einrückung und der schmalere Schriftgrad machen die Hierarchie deutlich,
* ein zweites Icon-Raster würde die Sidebar unruhig machen.
* Eingerückter Subreiter unterhalb eines Hauptreiters.
*
* Ohne `agentId` bleibt der Eintrag eine reine Textzeile — Einrückung und
* schmalerer Schriftgrad machen die Hierarchie deutlich. Mit `agentId` zeigt
* die Sidebar stattdessen Porträt, Name und Funktion: die fünf digitalen
* Mitarbeitenden sind Personen, keine Menüpunkte (Runde 4, §2.2). Bewusst kein
* Autonomiegrad und kein technisches Kurzlabel.
*/
export interface NavSubItem {
path: string
label: string
/** Agenten-ID für Porträt und Funktionszeile. */
agentId?: string
/** Funktionsbezeichnung unter dem Namen. Nur zusammen mit `agentId`. */
role?: string
}
export interface NavItem {
@@ -63,18 +70,17 @@ export const WORKSPACE_CONFIG: Record<WorkspaceType, WorkspaceConfig> = {
{ path: '/supply/properties', label: 'Meine Objekte', icon: Building2 },
{
path: ROUTES.SUPPLY.TEAM,
label: 'Teamübersicht',
label: MY_AGENTS_LABEL,
icon: Users,
children: [
{ path: ROUTES.SUPPLY.TEAM_PERSONNEL, label: 'Personalverwaltung' },
{ path: ROUTES.SUPPLY.TEAM_HISTORY, label: 'Bearbeitungsverlauf' },
{ path: ROUTES.SUPPLY.TEAM_CONNECTIONS, label: 'Kanäle & Systeme' },
],
// Die drei Verwaltungsbereiche sind Reiter der Hauptseite geworden;
// hier stehen ab Runde 4 die fünf digitalen Mitarbeitenden selbst.
children: AGENT_WORKSPACES.map(a => ({
path: a.path,
label: a.name,
agentId: a.id,
role: a.role,
})),
},
{ path: '/supply/reminder-manager', label: 'Reminder Manager', icon: BellRing },
{ path: '/supply/data-quality', label: 'Datenpflege', icon: CheckSquare },
{ path: '/supply/market-intelligence', label: 'Marktchancen', icon: Radar },
{ path: '/supply/my-listings', label: 'Inserate', icon: ClipboardList },
],
},
/**
@@ -129,9 +135,12 @@ export function getPageNameFromPath(pathname: string): string {
}
// Property On: Deep-Links auf Personalblatt und Verlaufsreiter. Ohne diese Fälle
// fiele der Titel in den Segment-Fallback und ergäbe «Personalverwaltung» statt
// des Mitarbeiternamens bzw. «Erledigte auftraege».
if (/^\/supply\/team\/personalverwaltung\/.+/.test(pathname)) return 'Personalblatt'
if (/^\/supply\/team\/bearbeitungsverlauf\/.+/.test(pathname)) return 'Bearbeitungsverlauf'
// des Mitarbeiternamens bzw. «Erledigte auftraege». Die Basispfade der drei
// Verwaltungsbereiche leiten auf `/supply/team?section=…` um, führen aber bis
// zum Abschluss der Umleitung noch kurz durch den Seitentitel.
if (/^\/supply\/team\/personalverwaltung(\/.+)?$/.test(pathname)) return MY_AGENTS_LABEL
if (/^\/supply\/team\/bearbeitungsverlauf(\/.+)?$/.test(pathname)) return MY_AGENTS_LABEL
if (/^\/supply\/team\/kanaele-systeme$/.test(pathname)) return MY_AGENTS_LABEL
if (/^\/supply\/properties\/.+/.test(pathname)) return 'Objekt Detail'
const segment = pathname.split('/').filter(Boolean).pop() ?? ''
return segment.charAt(0).toUpperCase() + segment.slice(1).replace(/-/g, ' ')