From 0ad9006b94facb2959ba687915c9146aad91c739 Mon Sep 17 00:00:00 2001 From: ValueOn AG Date: Thu, 11 Jun 2026 22:55:09 +0200 Subject: [PATCH] panel fixes 3 --- src/App.tsx | 3 +- src/components/Layout/Panel.module.css | 23 +- src/components/Layout/Panel.tsx | 74 ++++--- src/components/Layout/types.ts | 10 +- src/pages/ComplianceAuditPage.tsx | 14 +- src/pages/Dashboard.tsx | 3 +- src/pages/GDPR.tsx | 4 +- src/pages/IntegrationsOverviewPage.tsx | 4 +- src/pages/RagInventoryPage.tsx | 14 +- src/pages/Settings.tsx | 20 +- src/pages/Store.tsx | 10 +- src/pages/admin/AccessManagementHub.tsx | 10 +- src/pages/admin/AdminDatabaseHealthPage.tsx | 22 +- src/pages/admin/AdminDemoConfigPage.tsx | 8 +- src/pages/admin/AdminFeatureAccessPage.tsx | 12 +- .../admin/AdminFeatureInstanceUsersPage.tsx | 15 +- src/pages/admin/AdminFeatureRolesPage.tsx | 10 +- src/pages/admin/AdminInvitationsPage.tsx | 8 +- src/pages/admin/AdminLanguagesPage.tsx | 6 +- src/pages/admin/AdminLogsPage.tsx | 6 +- .../admin/AdminMandateRolePermissionsPage.tsx | 12 +- src/pages/admin/AdminMandateRolesPage.tsx | 10 +- src/pages/admin/AdminMandatesPage.tsx | 6 +- src/pages/admin/AdminSessionsPage.tsx | 204 ++++++++++++++++++ .../admin/AdminUserAccessOverviewPage.tsx | 20 +- src/pages/admin/AdminUserMandatesPage.tsx | 8 +- src/pages/admin/AdminUsersPage.tsx | 6 +- src/pages/admin/InstanceHierarchyView.tsx | 6 +- src/pages/admin/PermissionMatrix.tsx | 6 +- src/pages/admin/index.ts | 1 + .../wizards/AdminInvitationWizardPage.tsx | 6 +- .../admin/wizards/AdminMandateWizardPage.tsx | 6 +- .../admin/wizards/FeatureInstanceWizard.tsx | 4 +- src/pages/basedata/ConnectionsPage.tsx | 7 +- src/pages/basedata/FilesPage.tsx | 8 +- src/pages/basedata/PromptsPage.tsx | 6 +- src/pages/billing/AdminSubscriptionsPage.tsx | 4 +- src/pages/billing/BillingAdmin.tsx | 14 +- src/pages/billing/BillingDataView.tsx | 10 +- src/pages/billing/BillingMandateView.tsx | 2 + src/pages/billing/BillingNav.tsx | 2 +- src/pages/billing/SubscriptionTab.tsx | 10 +- .../commcoach/CommcoachAssistantView.tsx | 4 +- .../commcoach/CommcoachDashboardView.tsx | 11 +- .../views/commcoach/CommcoachModulesView.tsx | 6 +- .../views/commcoach/CommcoachSessionView.tsx | 10 +- .../views/commcoach/CommcoachSettingsView.tsx | 10 +- .../neutralization/NeutralizationView.tsx | 14 +- .../RealEstateInstanceRolesPlaceholder.tsx | 2 +- .../views/realestate/RealEstatePekView.tsx | 7 +- .../views/realestate/pek/PekLocationInput.tsx | 2 +- src/pages/views/realestate/pek/PekMapView.tsx | 4 +- .../views/redmine/RedmineBrowserView.tsx | 16 +- .../views/redmine/RedmineSettingsView.tsx | 4 +- src/pages/views/redmine/RedmineStatsView.tsx | 6 +- .../views/redmine/RedmineTicketEditor.tsx | 8 +- .../views/teamsbot/TeamsbotAssistantView.tsx | 4 +- .../views/teamsbot/TeamsbotDashboardView.tsx | 8 +- .../views/teamsbot/TeamsbotModulesView.tsx | 6 +- .../views/teamsbot/TeamsbotSessionView.tsx | 14 +- .../views/teamsbot/TeamsbotSettingsView.tsx | 6 +- .../views/trustee/TrusteeAbschlussView.tsx | 4 +- .../trustee/TrusteeAccountingSettingsView.tsx | 13 +- .../views/trustee/TrusteeAnalyseView.tsx | 4 +- .../views/trustee/TrusteeDashboardView.tsx | 5 +- .../views/trustee/TrusteeDocumentsView.tsx | 6 +- .../trustee/TrusteeExpenseImportView.tsx | 4 +- .../trustee/TrusteeImportProcessView.tsx | 2 +- .../trustee/TrusteeInstanceRolesView.tsx | 12 +- .../views/trustee/TrusteePositionsView.tsx | 6 +- .../views/trustee/TrusteeScanUploadView.tsx | 6 +- .../trustee/dataTables/TrusteeDataTab.tsx | 6 +- .../workflowAutomation/WorkflowEditorPage.tsx | 4 +- .../WorkflowTemplatesPage.tsx | 6 +- src/pages/views/workspace/ChatStream.tsx | 2 +- src/pages/views/workspace/FilePreview.tsx | 4 +- .../views/workspace/NeutralizationPanel.tsx | 3 + src/pages/views/workspace/ToolActivityLog.tsx | 4 +- .../workspace/WorkspaceContextSidebar.tsx | 5 +- .../views/workspace/WorkspaceEditorPage.tsx | 8 +- .../workspace/WorkspaceGeneralSettings.tsx | 4 +- src/pages/views/workspace/WorkspaceInput.tsx | 5 +- .../views/workspace/WorkspacePage.module.css | 8 + src/pages/views/workspace/WorkspacePage.tsx | 21 +- .../views/workspace/WorkspaceSettingsPage.tsx | 2 +- .../workflowAutomation/tabs/RunDetailTab.tsx | 14 +- src/pages/workflowAutomation/tabs/RunsTab.tsx | 3 +- .../workflowAutomation/tabs/TasksTab.tsx | 5 +- .../workflowAutomation/tabs/WorkflowsTab.tsx | 3 +- 89 files changed, 603 insertions(+), 342 deletions(-) create mode 100644 src/pages/admin/AdminSessionsPage.tsx diff --git a/src/App.tsx b/src/App.tsx index ebbc86c..af4219c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -41,7 +41,7 @@ import { GDPRPage } from './pages/GDPR'; import StorePage from './pages/Store'; import { IntegrationsOverviewPage } from './pages/IntegrationsOverviewPage'; import { FeatureViewPage } from './pages/FeatureView'; -import { AccessManagementHub, AdminMandatesPage, AdminUsersPage, AdminUserMandatesPage, AdminFeatureAccessPage, AdminInvitationsPage, AdminMandateRolesPage, AdminFeatureRolesPage, AdminFeatureInstanceUsersPage, AdminMandateRolePermissionsPage, AdminUserAccessOverviewPage, AdminLogsPage, AdminDemoConfigPage } from './pages/admin'; +import { AccessManagementHub, AdminMandatesPage, AdminUsersPage, AdminUserMandatesPage, AdminFeatureAccessPage, AdminInvitationsPage, AdminMandateRolesPage, AdminFeatureRolesPage, AdminFeatureInstanceUsersPage, AdminMandateRolePermissionsPage, AdminUserAccessOverviewPage, AdminLogsPage, AdminDemoConfigPage, AdminSessionsPage } from './pages/admin'; import { AdminMandateWizardPage, AdminInvitationWizardPage } from './pages/admin/wizards'; import { PromptsPage, FilesPage, ConnectionsPage } from './pages/basedata'; import { BillingDataView, BillingAdmin, BillingMandateView, AdminSubscriptionsPage } from './pages/billing'; @@ -220,6 +220,7 @@ function App() { } /> } /> + } /> } /> diff --git a/src/components/Layout/Panel.module.css b/src/components/Layout/Panel.module.css index 5b9fc84..397ff48 100644 --- a/src/components/Layout/Panel.module.css +++ b/src/components/Layout/Panel.module.css @@ -39,6 +39,9 @@ padding: 8px 14px; } +/* Toolbars are chrome (filter/action bars), not collapsible content regions. + title/id are still required for identification + a11y, but no visible header + bar is rendered to avoid duplicate headers above existing toolbar content. */ .panel[data-variant="toolbar"] .header { display: none; } @@ -131,19 +134,17 @@ gap: 4px; } +/* Stable chevron position: fixed at the right edge of the header, same spot + whether collapsed or expanded. Icon swaps (down = open, right = collapsed). */ .chevron { flex-shrink: 0; - width: 0; - height: 0; - border-left: 5px solid transparent; - border-right: 5px solid transparent; - border-top: 6px solid var(--text-tertiary, #888); - transition: transform 0.2s ease; - transform-origin: 50% 40%; -} - -.panelCollapsed .chevron { - transform: rotate(-90deg); + display: inline-flex; + align-items: center; + justify-content: center; + width: 16px; + height: 16px; + font-size: 12px; + color: var(--text-tertiary, #888); } .body { diff --git a/src/components/Layout/Panel.tsx b/src/components/Layout/Panel.tsx index 2d2c3f2..0f30415 100644 --- a/src/components/Layout/Panel.tsx +++ b/src/components/Layout/Panel.tsx @@ -1,11 +1,12 @@ // Copyright (c) 2026 PowerOn AG // All rights reserved. import { type FC, useState, useEffect, useCallback } from 'react'; +import { useLocation } from 'react-router-dom'; +import { FaChevronDown, FaChevronRight } from 'react-icons/fa'; import type { PanelProps } from './types'; import styles from './Panel.module.css'; -function _loadCollapsed(key: string | undefined, fallback: boolean): boolean { - if (!key) return fallback; +function _loadCollapsed(key: string, fallback: boolean): boolean { try { const stored = localStorage.getItem(`panel-collapse:${key}`); if (stored !== null) return stored === '1'; @@ -13,8 +14,7 @@ function _loadCollapsed(key: string | undefined, fallback: boolean): boolean { return fallback; } -function _saveCollapsed(key: string | undefined, value: boolean): void { - if (!key) return; +function _saveCollapsed(key: string, value: boolean): void { try { localStorage.setItem(`panel-collapse:${key}`, value ? '1' : '0'); } catch { /* noop */ } @@ -23,59 +23,65 @@ function _saveCollapsed(key: string | undefined, value: boolean): void { export const Panel: FC = ({ variant = 'card', title, + id, subtitle, actions, - collapsible = false, + collapsible = true, defaultCollapsed = false, collapseKey, className = '', + style, fill = false, children, }) => { - const [collapsed, setCollapsed] = useState(() => _loadCollapsed(collapseKey, defaultCollapsed)); + const { pathname } = useLocation(); + const persistKey = collapseKey ?? `${pathname}:${id}`; + const [collapsed, setCollapsed] = useState(() => _loadCollapsed(persistKey, defaultCollapsed)); useEffect(() => { - _saveCollapsed(collapseKey, collapsed); - }, [collapseKey, collapsed]); + _saveCollapsed(persistKey, collapsed); + }, [persistKey, collapsed]); const _toggleCollapsed = useCallback(() => { if (collapsible) setCollapsed((prev) => !prev); }, [collapsible]); - const hasHeader = title != null; - return (
- {hasHeader && ( -
{ - if (e.key === 'Enter' || e.key === ' ') { - e.preventDefault(); - _toggleCollapsed(); - } +
{ + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + _toggleCollapsed(); } - : undefined - } - > -
- {title} - {subtitle && {subtitle}} -
- {actions &&
{actions}
} - {collapsible && } + } + : undefined + } + > +
+ {title} + {subtitle && {subtitle}}
- )} + {actions &&
e.stopPropagation()}>{actions}
} + {collapsible && ( + + {collapsed ? : } + + )} +
{children}
diff --git a/src/components/Layout/types.ts b/src/components/Layout/types.ts index 92a7ff4..b3fcba6 100644 --- a/src/components/Layout/types.ts +++ b/src/components/Layout/types.ts @@ -4,7 +4,7 @@ * Shared types for the Layout component system. */ -import type { ReactNode } from 'react'; +import type { CSSProperties, ReactNode } from 'react'; // --------------------------------------------------------------------------- // ScrollMode (from useScrollMode hook) @@ -81,13 +81,19 @@ export type PanelVariant = 'card' | 'table' | 'dashboard' | 'toolbar' | 'editor' export interface PanelProps { variant?: PanelVariant; - title?: string | ReactNode; + /** Region title (required). Rendered in the header; use t() for i18n. */ + title: string | ReactNode; + /** Stable, non-i18n region id (required). Used for collapse persistence. */ + id: string; subtitle?: string | ReactNode; actions?: ReactNode; + /** Collapse/expand toggle. Default true (opt-out for chat/editor regions). */ collapsible?: boolean; defaultCollapsed?: boolean; + /** Explicit persistence key. Defaults to `{pathname}:{id}`. */ collapseKey?: string; className?: string; + style?: CSSProperties; /** * Fill the available height of the parent flex container and let the body * own its scroll. Use when a `card` (or any non-table/editor) Panel is placed diff --git a/src/pages/ComplianceAuditPage.tsx b/src/pages/ComplianceAuditPage.tsx index ca15a1f..7aae7ad 100644 --- a/src/pages/ComplianceAuditPage.tsx +++ b/src/pages/ComplianceAuditPage.tsx @@ -475,7 +475,7 @@ export const ComplianceAuditPage: React.FC = () => { }, [detail?.neutralizationMappings]); return ( - + {detail?.neutralizationMappings && detail.neutralizationMappings.length > 0 && (
@@ -713,7 +713,7 @@ export const ComplianceAuditPage: React.FC = () => { if (!selectedMandateId) return []; const statsPanel = ( - +
{ id: 'audit-log', label: _tabLabel('audit-log', t), render: () => ( - + { render: () => ( - + { id: 'neutralization', label: _tabLabel('neutralization', t), render: () => ( - + {

- +
setUserId(e.target.value)} + onKeyDown={e => e.key === 'Enter' && loadData()} + style={{ flex: 1, padding: '0.5rem', borderRadius: '4px', border: '1px solid var(--border-color)' }} + /> + +
+ + {error &&
{error}
} + + {/* Active Sessions */} +

+ {t('Aktive Sitzungen')} ({sessions.length}) + {sessions.length > 0 && ( + + )} +

+ {sessions.length > 0 ? ( + + + + + + + + + + + + {sessions.map(s => ( + + + + + + + + ))} + +
SessionAuthorityCreatedExpires
+ {s.sessionId?.slice(0, 8)}... + {s.authority}{formatTimestamp(s.createdAt)}{formatTimestamp(s.expiresAt)} + +
+ ) : ( + !loading && userId &&

{t('Keine aktiven Sitzungen')}

+ )} + + {/* Trusted Devices */} +

+ {t('Vertrauenswürdige Geräte')} ({trustedDevices.length}) + {trustedDevices.length > 0 && ( + + )} +

+ {trustedDevices.length > 0 ? ( + + + + + + + + + + + {trustedDevices.map((d, i) => ( + + + + + + + ))} + +
DeviceIPTrusted UntilStatus
+ {d.id} + {d.ipAddress || '-'}{formatTimestamp(d.trustedUntil)} + + {d.isExpired ? 'Expired' : 'Active'} + +
+ ) : ( + !loading && userId &&

{t('Keine vertrauenswürdigen Geräte')}

+ )} +
+
+ + ); +}; diff --git a/src/pages/admin/AdminUserAccessOverviewPage.tsx b/src/pages/admin/AdminUserAccessOverviewPage.tsx index 25a5003..3c5000a 100644 --- a/src/pages/admin/AdminUserAccessOverviewPage.tsx +++ b/src/pages/admin/AdminUserAccessOverviewPage.tsx @@ -84,8 +84,6 @@ interface UserAccessOverview { resourceAccess: AccessEntry[]; } -type TabId = 'overview' | 'ui' | 'data' | 'resources'; - export const AdminUserAccessOverviewPage: React.FC = () => { const { t } = useLanguage(); @@ -588,22 +586,22 @@ export const AdminUserAccessOverviewPage: React.FC = () => { { id: 'overview', label: t('Übersicht'), - render: () => {renderOverviewTab()}, + render: () => {renderOverviewTab()}, }, { id: 'ui', label: `${t('UI-Zugriff')} (${overview.uiAccess.length})`, - render: () => {renderUiAccessTab()}, + render: () => {renderUiAccessTab()}, }, { id: 'data', label: `${t('Daten-Zugriff')} (${overview.dataAccess.length})`, - render: () => {renderDataAccessTab()}, + render: () => {renderDataAccessTab()}, }, { id: 'resources', label: `${t('Ressourcen')} (${overview.resourceAccess.length})`, - render: () => {renderResourceAccessTab()}, + render: () => {renderResourceAccessTab()}, }, ]; }, [overview, t, expandedRoles, expandedMandates]); @@ -612,7 +610,7 @@ export const AdminUserAccessOverviewPage: React.FC = () => { return ( - +
⚠️

@@ -640,7 +638,7 @@ export const AdminUserAccessOverviewPage: React.FC = () => {

- +