From b8b8ef180a55f361d7e4dda4fd709e14c7a68b8c Mon Sep 17 00:00:00 2001 From: Agent Zero Date: Sun, 23 Aug 2026 23:44:43 +0200 Subject: [PATCH] fix(c8): shared TeamPanel component (arch-062) + curated icon map in SortableMenuItem (arch-063 OOM fix) --- frontend/src/components/layout/AISidebar.tsx | 59 +----------- .../src/components/layout/MessageSidebar.tsx | 63 +------------ .../components/layout/SortableMenuItem.tsx | 59 +++++++++++- frontend/src/components/shared/TeamPanel.tsx | 92 +++++++++++++++++++ 4 files changed, 151 insertions(+), 122 deletions(-) create mode 100644 frontend/src/components/shared/TeamPanel.tsx diff --git a/frontend/src/components/layout/AISidebar.tsx b/frontend/src/components/layout/AISidebar.tsx index 45ef3e6..a3ef5c8 100644 --- a/frontend/src/components/layout/AISidebar.tsx +++ b/frontend/src/components/layout/AISidebar.tsx @@ -9,6 +9,7 @@ import { getContributedTabs } from './sidebarTabs'; import { useTranslation } from 'react-i18next'; import { useUsers, useGroups } from '@/api/hooks'; import { Avatar } from '@/components/ui/Avatar'; +import { SharedTeamPanel as TeamPanel } from '@/components/shared/TeamPanel'; import { Bell, Bot, ChevronLeft, ChevronRight, Lightbulb, MessageSquare, Users, X } from 'lucide-react'; const robotIcon = (className: string) => ( @@ -42,64 +43,6 @@ interface TabDef { testId: string; } -function TeamPanel() { - const { data: usersData, isLoading: usersLoading } = useUsers(); - const { data: groupsData, isLoading: groupsLoading } = useGroups(); - const users: any[] = usersData?.items || []; - const groups: any[] = groupsData?.items || []; - - return ( -
-
-

Mitarbeiter

- {usersLoading ? ( -

Laden...

- ) : users.length === 0 ? ( -

Keine Mitarbeiter

- ) : ( -
- {users.map((u) => ( -
-
- - -
-
-

{u.name}

-

{u.email}

-
- {u.role} -
- ))} -
- )} -
-
-

Gruppen

- {groupsLoading ? ( -

Laden...

- ) : groups.length === 0 ? ( -

Keine Gruppen

- ) : ( -
- {groups.map((g) => ( -
-
-
-
-

{g.name}

- {g.description &&

{g.description}

} -
-
- ))} -
- )} -
-
- ); -} - function ChatPanel() { return (
diff --git a/frontend/src/components/layout/MessageSidebar.tsx b/frontend/src/components/layout/MessageSidebar.tsx index 0ce7536..074e92e 100644 --- a/frontend/src/components/layout/MessageSidebar.tsx +++ b/frontend/src/components/layout/MessageSidebar.tsx @@ -6,6 +6,7 @@ import { useCommStore } from '@/store/commStore'; import { useTranslation } from 'react-i18next'; import { useUsers, useGroups } from '@/api/hooks'; import { Avatar } from '@/components/ui/Avatar'; +import { SharedTeamPanel } from '@/components/shared/TeamPanel'; import { listConversations, getMessages, @@ -67,66 +68,8 @@ interface QuickAccessDef { // ─── Team Panel (reused from AISidebar) ─── -function TeamPanel({ onStartDirectChat }: { onStartDirectChat: (userId: string, userName: string) => void }) { - const { data: usersData, isLoading: usersLoading } = useUsers(); - const { data: groupsData, isLoading: groupsLoading } = useGroups(); - const users: any[] = usersData?.items || []; - const groups: any[] = groupsData?.items || []; - - return ( -
-
-

Mitarbeiter

- {usersLoading ? ( -

Laden...

- ) : users.length === 0 ? ( -

Keine Mitarbeiter

- ) : ( -
- {users.map((u) => ( - - ))} -
- )} -
-
-

Gruppen

- {groupsLoading ? ( -

Laden...

- ) : groups.length === 0 ? ( -

Keine Gruppen

- ) : ( -
- {groups.map((g) => ( -
-
-
-
-

{g.name}

- {g.description &&

{g.description}

} -
-
- ))} -
- )} -
-
- ); +function TeamPanel(props: { onStartDirectChat: (userId: string, userName: string) => void }) { + return ; } // ─── Conversation List Item ─── diff --git a/frontend/src/components/layout/SortableMenuItem.tsx b/frontend/src/components/layout/SortableMenuItem.tsx index 4e82b92..eb86ff2 100644 --- a/frontend/src/components/layout/SortableMenuItem.tsx +++ b/frontend/src/components/layout/SortableMenuItem.tsx @@ -1,10 +1,61 @@ import React from 'react'; import { useSortable } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; -import { GripVertical, FolderOpen } from 'lucide-react'; -import * as LucideIcons from 'lucide-react'; +import { + Activity, + ArrowUpDown, + BarChart3, + Bell, + BookOpen, + Bot, + Calendar, + CheckSquare, + Copy, + FileText, + FolderOpen, + Link as LinkIcon, + Mail, + MessageSquare, + Settings, + Shield, + Sparkles, + Tag, + GripVertical, + Trash2, + Users, + UsersRound, + Workflow, +} from 'lucide-react'; import clsx from 'clsx'; +// Curated icon map — avoids `import * as LucideIcons` which loads ALL icons +// and causes OOM in tests (ARCH-063). +const ICON_MAP: Record> = { + Activity, + ArrowUpDown, + BarChart: BarChart3, + BarChart3, + Bell, + BookOpen, + Bot, + Calendar, + CheckSquare, + Copy, + FolderOpen, + Link: LinkIcon, + Mail, + MessageSquare, + Settings, + Shield, + Sparkles, + Tag, + Trash: Trash2, + Trash2, + Users, + UsersRound, + Workflow, +}; + interface SortableMenuItemProps { id: string; label: string; @@ -13,8 +64,8 @@ interface SortableMenuItemProps { } function getIcon(name: string): React.ReactNode { - const Icon = (LucideIcons as any)[name]; - return Icon ? : ; + const Icon = ICON_MAP[name]; + return Icon ? : ; } export function SortableMenuItem({ id, label, icon, isGroup }: SortableMenuItemProps) { diff --git a/frontend/src/components/shared/TeamPanel.tsx b/frontend/src/components/shared/TeamPanel.tsx new file mode 100644 index 0000000..02b58f8 --- /dev/null +++ b/frontend/src/components/shared/TeamPanel.tsx @@ -0,0 +1,92 @@ +/** + * SharedTeamPanel — single source of truth for the team list panel used by + * AISidebar and MessageSidebar (ARCH-062). + * + * Pass `onStartDirectChat` to make user rows clickable (MessageSidebar); + * omit it for a read-only list (AISidebar). + */ + +import React from 'react'; +import { Users } from 'lucide-react'; +import { useUsers, useGroups } from '@/api/hooks'; +import { Avatar } from '@/components/ui/Avatar'; + +interface SharedTeamPanelProps { + onStartDirectChat?: (userId: string, userName: string) => void; +} + +export function SharedTeamPanel({ onStartDirectChat }: SharedTeamPanelProps) { + const { data: usersData, isLoading: usersLoading } = useUsers(); + const { data: groupsData, isLoading: groupsLoading } = useGroups(); + const users: any[] = usersData?.items || []; + const groups: any[] = groupsData?.items || []; + + return ( +
+
+

Mitarbeiter

+ {usersLoading ? ( +

Laden...

+ ) : users.length === 0 ? ( +

Keine Mitarbeiter

+ ) : ( +
+ {users.map((u) => + onStartDirectChat ? ( + + ) : ( +
+
+ + +
+
+

{u.name}

+

{u.email}

+
+ {u.role} +
+ ) + )} +
+ )} +
+
+

Gruppen

+ {groupsLoading ? ( +

Laden...

+ ) : groups.length === 0 ? ( +

Keine Gruppen

+ ) : ( +
+ {groups.map((g) => ( +
+
+
+
+

{g.name}

+ {g.description &&

{g.description}

} +
+
+ ))} +
+ )} +
+
+ ); +}