Files
property-match/src/components/layout/appShellConfig.ts
T
Benjamin SutterandClaude Opus 5 093a958dcf feat(startseite): Giorgio entfernt, Belegschaft auf die fünf Agenten
Auf ausdrücklichen Wunsch: Giorgio entfällt vollständig. Das weicht bewusst
von §6 des Auftrags ab, der ihn als Personalverwalter und Vorgesetzten
vorsah — festgehalten, damit die Abweichung später nachvollziehbar bleibt.

Entfernt: Rollendefinition `AGENT_SUPERVISOR`, sein gezeichnetes Porträt, sein
Eintrag in der Auslastung und in der Belegschaftsansicht sowie die Erwähnung
in der Produkttour. Die Personalverwaltung bleibt unverändert über den
Menüpunkt «Meine Agenten» erreichbar — sie brauchte nie eine eigene Person am
Bildschirm.

Mit Giorgio entfällt auch die einzige Kennzahl, die aus den Freigabevorgängen
kam; `computeAgentWorkload` braucht `agentWorkItems` deshalb nicht mehr.

Ordner und Komponenten heissen jetzt nach ihrem Inhalt: `components/staff/`,
`AgentStaffOverview`, `StaffBackdrop`. «Meetingraum» war seit dem Umbau auf
Personenkarten irreführend, und ein falscher Name kostet den Nächsten mehr
Zeit als die Umbenennung.

Fünf statt sechs Karten heisst Flexlayout statt Dreierraster: drei oben, zwei
mittig darunter, gleich breit und ohne Lücke. Die Karten geben ihre Höhe nicht
mehr selbst vor — mit `height: 100%` beanspruchte jede die volle Fläche und
die zweite Zeile fiel aus dem sichtbaren Bereich.

Geprüft: 433 Tests, Typecheck, Build, ESLint, Token-Schwelle (889/925).
Browser 1280–1920 px und Tablet: fünf Karten vollständig sichtbar, keine
Überlappung, Titel und «Filter & Übersicht» beim Einstieg sichtbar, alle fünf
Klicks auf der richtigen Subpage, «Meine Agenten» weiterhin erreichbar, keine
Konsolenfehler, keine 404.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 15:03:18 +02:00

158 lines
5.3 KiB
TypeScript

import { WorkspaceType } from '../../domain/enums'
import { HOME_NAV_LABEL, MY_AGENTS_LABEL, ROUTES } from '../../lib/constants'
import { AGENT_WORKSPACES } from '../../lib/agentWorkspaces'
import type { LucideIcon } from 'lucide-react'
import {
Building2,
CheckSquare,
Search,
ClipboardList,
Settings,
Users,
} from 'lucide-react'
// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------
/**
* 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 {
path: string
label: string
icon: LucideIcon
/**
* Subreiter, die sichtbar bleiben, solange sich der Nutzer innerhalb des
* Funktionsbereichs befindet (Property On, §3.3).
*/
children?: NavSubItem[]
}
export interface WorkspaceConfig {
label: string
abbreviation: string
icon: LucideIcon
firstPath: string
navItems: NavItem[]
chipColor: string
}
// ---------------------------------------------------------------------------
// Workspace configuration
// ---------------------------------------------------------------------------
export const WORKSPACE_CONFIG: Record<WorkspaceType, WorkspaceConfig> = {
[WorkspaceType.SUPPLY]: {
label: 'Verwaltung',
abbreviation: 'VW',
icon: Building2,
firstPath: ROUTES.SUPPLY.PROPERTIES,
chipColor: '#152642',
navItems: [
// Runde 5, §2.1/§2.2: «Übersicht» ist entfernt, «Meine Objekte» heisst im
// Menü «Startseite». Die Belegschaft und die Objektverwaltung liegen auf
// derselben Seite — zwei Einträge für eine Seite wären ein toter Link.
{ path: ROUTES.SUPPLY.PROPERTIES, label: HOME_NAV_LABEL, icon: Building2 },
{
path: ROUTES.SUPPLY.TEAM,
label: MY_AGENTS_LABEL,
icon: Users,
// 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,
})),
},
],
},
/**
* Die Flächensuche ist in diesem Branch entfernt. Der Eintrag bleibt nur
* bestehen, weil `WorkspaceType` als Domänen-Enum unangetastet bleibt und
* dieser Record vollständig sein muss — sichtbar ist er nirgends, denn
* `WORKSPACE_ORDER` führt ihn nicht mehr und es existiert keine Route.
*/
[WorkspaceType.DEMAND]: {
label: 'Suche',
abbreviation: 'SU',
icon: Search,
firstPath: ROUTES.SUPPLY.PROPERTIES,
chipColor: '#1a7a4a',
navItems: [],
},
[WorkspaceType.OPERATIONS]: {
label: 'Operations',
abbreviation: 'OP',
icon: Settings,
firstPath: '/ops/review-queue',
chipColor: '#6b21a8',
navItems: [
{ path: '/ops/review-queue', label: 'Review Queue', icon: CheckSquare },
{ path: '/ops/audit', label: 'Audit Log', icon: ClipboardList },
],
},
}
// Ordered list for rendering workspace tabs
export const WORKSPACE_ORDER: WorkspaceType[] = [
WorkspaceType.SUPPLY,
]
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
export function getWorkspaceFromPath(pathname: string): WorkspaceType | null {
if (pathname.startsWith('/supply')) return WorkspaceType.SUPPLY
return null
}
export function getPageNameFromPath(pathname: string): string {
for (const ws of Object.values(WORKSPACE_CONFIG)) {
for (const item of ws.navItems) {
if (item.path === pathname) return item.label
for (const child of item.children ?? []) {
if (child.path === pathname) return child.label
}
}
}
// 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». 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, ' ')
}
export function getUserInitials(name: string): string {
return name
.split(' ')
.map((n) => n[0])
.join('')
.toUpperCase()
.slice(0, 2)
}