fix: restore navigation tree structure and missing routes after merge damage
Restores functionality lost during merge of feat/real-estate into int (00f2158, 13 Feb): MandateNavigation.tsx: - Restored 3-section collapsible navigation: Meine Sicht, dynamic mandates (flat), Administration - Reverted to flat instance structure (Mandate > Instance with feature icon > Views) - Original commit:4231727(10 Feb, enhanced generic navigation tree) App.tsx: - Restored FileProvider wrapper around MainLayout - Restored Billing routes (/billing/transactions) - Restored Admin routes: /admin/billing, /admin/automation-events - Restored Admin index redirect (/admin -> /admin/access) - Re-added missing imports: FileProvider, BillingDataView, BillingAdmin, AdminAutomationEventsPage Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
6272d21942
commit
30d23ac7df
2 changed files with 95 additions and 73 deletions
17
src/App.tsx
17
src/App.tsx
|
|
@ -30,15 +30,17 @@ import { ProtectedRoute } from './providers/auth/ProtectedRoute';
|
||||||
import { LanguageProvider } from './providers/language/LanguageContext';
|
import { LanguageProvider } from './providers/language/LanguageContext';
|
||||||
import { ToastProvider } from './contexts/ToastContext';
|
import { ToastProvider } from './contexts/ToastContext';
|
||||||
import { WorkflowSelectionProvider } from './contexts/WorkflowSelectionContext';
|
import { WorkflowSelectionProvider } from './contexts/WorkflowSelectionContext';
|
||||||
|
import { FileProvider } from './contexts/FileContext';
|
||||||
import { MainLayout } from './layouts/MainLayout';
|
import { MainLayout } from './layouts/MainLayout';
|
||||||
import { FeatureLayout } from './layouts/FeatureLayout';
|
import { FeatureLayout } from './layouts/FeatureLayout';
|
||||||
import { DashboardPage } from './pages/Dashboard';
|
import { DashboardPage } from './pages/Dashboard';
|
||||||
import { SettingsPage } from './pages/Settings';
|
import { SettingsPage } from './pages/Settings';
|
||||||
import { GDPRPage } from './pages/GDPR';
|
import { GDPRPage } from './pages/GDPR';
|
||||||
import { FeatureViewPage } from './pages/FeatureView';
|
import { FeatureViewPage } from './pages/FeatureView';
|
||||||
import { AdminMandatesPage, AdminUsersPage, AdminUserMandatesPage, AdminFeatureAccessPage, AdminInvitationsPage, AdminFeatureRolesPage, AdminFeatureInstanceUsersPage, AdminMandateRolesPage, AdminMandateRolePermissionsPage, AdminUserAccessOverviewPage, AccessManagementHub } from './pages/admin';
|
import { AccessManagementHub, AdminMandatesPage, AdminUsersPage, AdminUserMandatesPage, AdminFeatureAccessPage, AdminInvitationsPage, AdminMandateRolesPage, AdminFeatureRolesPage, AdminFeatureInstanceUsersPage, AdminMandateRolePermissionsPage, AdminUserAccessOverviewPage, AdminAutomationEventsPage } from './pages/admin';
|
||||||
import { PlaygroundPage, WorkflowsPage, AutomationsPage } from './pages/workflows';
|
import { PlaygroundPage, WorkflowsPage, AutomationsPage } from './pages/workflows';
|
||||||
import { PromptsPage, FilesPage, ConnectionsPage } from './pages/basedata';
|
import { PromptsPage, FilesPage, ConnectionsPage } from './pages/basedata';
|
||||||
|
import { BillingDataView, BillingAdmin } from './pages/billing';
|
||||||
function App() {
|
function App() {
|
||||||
// Load saved theme preference and set app name on app mount
|
// Load saved theme preference and set app name on app mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -83,7 +85,9 @@ function App() {
|
||||||
{/* ================================================== */}
|
{/* ================================================== */}
|
||||||
<Route path="/" element={
|
<Route path="/" element={
|
||||||
<ProtectedRoute>
|
<ProtectedRoute>
|
||||||
|
<FileProvider>
|
||||||
<MainLayout />
|
<MainLayout />
|
||||||
|
</FileProvider>
|
||||||
</ProtectedRoute>
|
</ProtectedRoute>
|
||||||
}>
|
}>
|
||||||
{/* Dashboard (Root) */}
|
{/* Dashboard (Root) */}
|
||||||
|
|
@ -111,6 +115,14 @@ function App() {
|
||||||
<Route path="connections" element={<ConnectionsPage />} />
|
<Route path="connections" element={<ConnectionsPage />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
|
{/* ============================================== */}
|
||||||
|
{/* BILLING ROUTES */}
|
||||||
|
{/* ============================================== */}
|
||||||
|
<Route path="billing">
|
||||||
|
<Route index element={<Navigate to="/billing/transactions" replace />} />
|
||||||
|
<Route path="transactions" element={<BillingDataView />} />
|
||||||
|
</Route>
|
||||||
|
|
||||||
{/* Legacy top-level routes – redirect to dashboard (migrated to feature-instance routes) */}
|
{/* Legacy top-level routes – redirect to dashboard (migrated to feature-instance routes) */}
|
||||||
<Route path="chatbot" element={<Navigate to="/" replace />} />
|
<Route path="chatbot" element={<Navigate to="/" replace />} />
|
||||||
<Route path="pek" element={<Navigate to="/" replace />} />
|
<Route path="pek" element={<Navigate to="/" replace />} />
|
||||||
|
|
@ -160,6 +172,7 @@ function App() {
|
||||||
{/* ADMIN ROUTES (nur SysAdmin) */}
|
{/* ADMIN ROUTES (nur SysAdmin) */}
|
||||||
{/* ============================================== */}
|
{/* ============================================== */}
|
||||||
<Route path="admin">
|
<Route path="admin">
|
||||||
|
<Route index element={<Navigate to="/admin/access" replace />} />
|
||||||
<Route path="mandates" element={<AdminMandatesPage />} />
|
<Route path="mandates" element={<AdminMandatesPage />} />
|
||||||
<Route path="users" element={<AdminUsersPage />} />
|
<Route path="users" element={<AdminUsersPage />} />
|
||||||
<Route path="user-mandates" element={<AdminUserMandatesPage />} />
|
<Route path="user-mandates" element={<AdminUserMandatesPage />} />
|
||||||
|
|
@ -171,6 +184,8 @@ function App() {
|
||||||
<Route path="mandate-roles" element={<AdminMandateRolesPage />} />
|
<Route path="mandate-roles" element={<AdminMandateRolesPage />} />
|
||||||
<Route path="mandate-role-permissions" element={<AdminMandateRolePermissionsPage />} />
|
<Route path="mandate-role-permissions" element={<AdminMandateRolePermissionsPage />} />
|
||||||
<Route path="user-access-overview" element={<AdminUserAccessOverviewPage />} />
|
<Route path="user-access-overview" element={<AdminUserAccessOverviewPage />} />
|
||||||
|
<Route path="billing" element={<BillingAdmin />} />
|
||||||
|
<Route path="automation-events" element={<AdminAutomationEventsPage />} />
|
||||||
</Route>
|
</Route>
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,26 +8,24 @@
|
||||||
* Backend liefert Blocks-Struktur mit Static und Dynamic Blocks.
|
* Backend liefert Blocks-Struktur mit Static und Dynamic Blocks.
|
||||||
* UI mappt uiComponent zu Icons via pageRegistry.
|
* UI mappt uiComponent zu Icons via pageRegistry.
|
||||||
*
|
*
|
||||||
* Struktur (gemäss Navigation-API-Konzept):
|
* TREE STRUCTURE (alles collapsible):
|
||||||
* - SYSTEM (static block, order: 10)
|
* ▼ Meine Sicht
|
||||||
* - MEINE FEATURES (dynamic block, order: 15)
|
* - Übersicht, Einstellungen, Prompts, Dateien, Verbindungen, Billing
|
||||||
* - Mandant 1
|
* ─────────────
|
||||||
* - Feature A
|
* ▼ Mandant 1
|
||||||
* - Instanz 1 (mit Views)
|
* - 🎯 Instanz 1 (Feature-Icon + Instanz-Name)
|
||||||
* - WORKFLOWS (static block, order: 20)
|
* - 💼 Instanz 2 (Feature-Icon + Instanz-Name)
|
||||||
* - BASISDATEN (static block, order: 30)
|
* ─────────────
|
||||||
* - MIGRATE TO FEATURES (static block, order: 40)
|
* ▶ Administration
|
||||||
* - ADMINISTRATION (static block, order: 200)
|
* - Users, Mandates, Roles, ...
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { useNavigation } from '../../hooks/useNavigation';
|
import { useNavigation } from '../../hooks/useNavigation';
|
||||||
import type {
|
import type {
|
||||||
StaticBlock,
|
|
||||||
DynamicBlock,
|
DynamicBlock,
|
||||||
NavigationItem,
|
NavigationItem,
|
||||||
NavigationMandate,
|
NavigationMandate,
|
||||||
MandateFeature,
|
|
||||||
FeatureInstance,
|
FeatureInstance,
|
||||||
FeatureView
|
FeatureView
|
||||||
} from '../../hooks/useNavigation';
|
} from '../../hooks/useNavigation';
|
||||||
|
|
@ -53,13 +51,20 @@ function navigationItemToTreeNode(item: NavigationItem): TreeNodeItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a StaticBlock to TreeItem (section)
|
* Convert a list of NavigationItems into a collapsible TreeNodeItem container.
|
||||||
|
* Used for grouping static items under "Meine Sicht" and "Administration".
|
||||||
*/
|
*/
|
||||||
function staticBlockToTreeItem(block: StaticBlock): TreeItem {
|
function _staticItemsToTreeNode(
|
||||||
|
id: string,
|
||||||
|
label: string,
|
||||||
|
items: NavigationItem[],
|
||||||
|
defaultExpanded: boolean = true,
|
||||||
|
): TreeNodeItem {
|
||||||
return {
|
return {
|
||||||
type: 'section',
|
id,
|
||||||
title: block.title,
|
label,
|
||||||
children: block.items.map(navigationItemToTreeNode),
|
children: items.map(navigationItemToTreeNode),
|
||||||
|
defaultExpanded,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -75,59 +80,52 @@ function featureViewToTreeNode(view: FeatureView): TreeNodeItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a FeatureInstance to TreeNodeItem
|
* Convert a FeatureInstance to TreeNodeItem (with feature icon)
|
||||||
|
* Instance node gets path to first view so clicking the instance name navigates to dashboard.
|
||||||
|
* Shows the feature icon next to the instance name for visual distinction.
|
||||||
*/
|
*/
|
||||||
function featureInstanceToTreeNode(instance: FeatureInstance): TreeNodeItem {
|
function featureInstanceToTreeNode(instance: FeatureInstance, featureUiComponent: string): TreeNodeItem {
|
||||||
|
const children = instance.views.map(featureViewToTreeNode);
|
||||||
return {
|
return {
|
||||||
id: instance.id,
|
id: instance.id,
|
||||||
label: instance.uiLabel,
|
label: instance.uiLabel,
|
||||||
|
icon: getPageIcon(featureUiComponent), // Use feature icon for instance
|
||||||
path: instance.views.length > 0 ? instance.views[0].uiPath : undefined,
|
path: instance.views.length > 0 ? instance.views[0].uiPath : undefined,
|
||||||
children: instance.views.map(featureViewToTreeNode),
|
children,
|
||||||
defaultExpanded: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert a MandateFeature to TreeNodeItem
|
|
||||||
*/
|
|
||||||
function mandateFeatureToTreeNode(feature: MandateFeature): TreeNodeItem | null {
|
|
||||||
if (feature.instances.length === 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: feature.uiComponent,
|
|
||||||
label: feature.uiLabel,
|
|
||||||
icon: getPageIcon(feature.uiComponent),
|
|
||||||
badge: feature.instances.length,
|
|
||||||
path: feature.instances.length > 0 && feature.instances[0].views.length > 0
|
|
||||||
? feature.instances[0].views[0].uiPath
|
|
||||||
: undefined,
|
|
||||||
children: feature.instances.map(featureInstanceToTreeNode),
|
|
||||||
defaultExpanded: false,
|
defaultExpanded: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a NavigationMandate to TreeNodeItem
|
* Convert a NavigationMandate to TreeNodeItem
|
||||||
|
*
|
||||||
|
* FLAT STRUCTURE: Instances are listed directly under mandate (no feature grouping).
|
||||||
|
* Each instance shows the feature's icon for visual distinction.
|
||||||
|
*
|
||||||
|
* Before: Mandate → Feature → Instance → Views
|
||||||
|
* Now: Mandate → Instance (with feature icon) → Views
|
||||||
*/
|
*/
|
||||||
function navigationMandateToTreeNode(mandate: NavigationMandate): TreeNodeItem | null {
|
function navigationMandateToTreeNode(mandate: NavigationMandate): TreeNodeItem | null {
|
||||||
if (mandate.features.length === 0) {
|
if (mandate.features.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const children = mandate.features
|
// Flatten: collect all instances from all features directly under mandate
|
||||||
.map(mandateFeatureToTreeNode)
|
const instanceNodes: TreeNodeItem[] = [];
|
||||||
.filter((node): node is TreeNodeItem => node !== null);
|
for (const feature of mandate.features) {
|
||||||
|
for (const instance of feature.instances) {
|
||||||
|
instanceNodes.push(featureInstanceToTreeNode(instance, feature.uiComponent));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (children.length === 0) {
|
if (instanceNodes.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: mandate.id,
|
id: mandate.id,
|
||||||
label: mandate.uiLabel,
|
label: mandate.uiLabel,
|
||||||
children,
|
children: instanceNodes,
|
||||||
defaultExpanded: true,
|
defaultExpanded: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -174,40 +172,49 @@ export const MandateNavigation: React.FC = () => {
|
||||||
const { blocks, loading } = useNavigation('de');
|
const { blocks, loading } = useNavigation('de');
|
||||||
|
|
||||||
// Build navigation items from blocks
|
// Build navigation items from blocks
|
||||||
|
// Groups static items into collapsible containers:
|
||||||
|
// - "Meine Sicht": all non-admin static items (Übersicht, Einstellungen, Prompts, etc.)
|
||||||
|
// - "Administration": all admin static items
|
||||||
|
// - Dynamic block (mandates) renders between them
|
||||||
const navigationItems: TreeItem[] = useMemo(() => {
|
const navigationItems: TreeItem[] = useMemo(() => {
|
||||||
const items: TreeItem[] = [];
|
const items: TreeItem[] = [];
|
||||||
|
|
||||||
// Process blocks in order (already sorted by backend)
|
// Collect static items by category
|
||||||
|
const meineSichtItems: NavigationItem[] = [];
|
||||||
|
let adminItems: NavigationItem[] = [];
|
||||||
|
|
||||||
for (const block of blocks) {
|
for (const block of blocks) {
|
||||||
if (block.type === 'static') {
|
if (block.type === 'static') {
|
||||||
// Static block: system, workflows, basedata, migrate, admin
|
if (block.id === 'admin') {
|
||||||
if (block.items.length > 0) {
|
adminItems = [...block.items];
|
||||||
// Add separator before admin block
|
} else if (block.items.length > 0) {
|
||||||
if (block.id === 'admin') {
|
meineSichtItems.push(...block.items);
|
||||||
items.push({ type: 'separator' });
|
|
||||||
}
|
|
||||||
items.push(staticBlockToTreeItem(block));
|
|
||||||
}
|
}
|
||||||
} else if (block.type === 'dynamic') {
|
|
||||||
// Dynamic block: features/mandates
|
|
||||||
// Add separator before dynamic block
|
|
||||||
items.push({ type: 'separator' });
|
|
||||||
|
|
||||||
const mandateNodes = dynamicBlockToTreeNodes(block);
|
|
||||||
if (mandateNodes.length > 0) {
|
|
||||||
items.push(...mandateNodes);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add separator after dynamic block (before next static blocks)
|
|
||||||
items.push({ type: 'separator' });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove trailing separator if present
|
// "Meine Sicht" - collapsible container for user-facing pages
|
||||||
while (items.length > 0 && (items[items.length - 1] as TreeItem & { type?: string }).type === 'separator') {
|
if (meineSichtItems.length > 0) {
|
||||||
items.pop();
|
items.push(_staticItemsToTreeNode('meine-sicht', 'Meine Sicht', meineSichtItems, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dynamic block: mandates with feature instances
|
||||||
|
for (const block of blocks) {
|
||||||
|
if (block.type === 'dynamic') {
|
||||||
|
const mandateNodes = dynamicBlockToTreeNodes(block);
|
||||||
|
if (mandateNodes.length > 0) {
|
||||||
|
if (items.length > 0) items.push({ type: 'separator' });
|
||||||
|
items.push(...mandateNodes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// "Administration" - collapsible container for admin pages
|
||||||
|
if (adminItems.length > 0) {
|
||||||
|
if (items.length > 0) items.push({ type: 'separator' });
|
||||||
|
items.push(_staticItemsToTreeNode('administration', 'Administration', adminItems, false));
|
||||||
|
}
|
||||||
|
|
||||||
return items;
|
return items;
|
||||||
}, [blocks]);
|
}, [blocks]);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue