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 { ToastProvider } from './contexts/ToastContext';
|
||||
import { WorkflowSelectionProvider } from './contexts/WorkflowSelectionContext';
|
||||
import { FileProvider } from './contexts/FileContext';
|
||||
import { MainLayout } from './layouts/MainLayout';
|
||||
import { FeatureLayout } from './layouts/FeatureLayout';
|
||||
import { DashboardPage } from './pages/Dashboard';
|
||||
import { SettingsPage } from './pages/Settings';
|
||||
import { GDPRPage } from './pages/GDPR';
|
||||
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 { PromptsPage, FilesPage, ConnectionsPage } from './pages/basedata';
|
||||
import { BillingDataView, BillingAdmin } from './pages/billing';
|
||||
function App() {
|
||||
// Load saved theme preference and set app name on app mount
|
||||
useEffect(() => {
|
||||
|
|
@ -83,7 +85,9 @@ function App() {
|
|||
{/* ================================================== */}
|
||||
<Route path="/" element={
|
||||
<ProtectedRoute>
|
||||
<FileProvider>
|
||||
<MainLayout />
|
||||
</FileProvider>
|
||||
</ProtectedRoute>
|
||||
}>
|
||||
{/* Dashboard (Root) */}
|
||||
|
|
@ -111,6 +115,14 @@ function App() {
|
|||
<Route path="connections" element={<ConnectionsPage />} />
|
||||
</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) */}
|
||||
<Route path="chatbot" element={<Navigate to="/" replace />} />
|
||||
<Route path="pek" element={<Navigate to="/" replace />} />
|
||||
|
|
@ -160,6 +172,7 @@ function App() {
|
|||
{/* ADMIN ROUTES (nur SysAdmin) */}
|
||||
{/* ============================================== */}
|
||||
<Route path="admin">
|
||||
<Route index element={<Navigate to="/admin/access" replace />} />
|
||||
<Route path="mandates" element={<AdminMandatesPage />} />
|
||||
<Route path="users" element={<AdminUsersPage />} />
|
||||
<Route path="user-mandates" element={<AdminUserMandatesPage />} />
|
||||
|
|
@ -171,6 +184,8 @@ function App() {
|
|||
<Route path="mandate-roles" element={<AdminMandateRolesPage />} />
|
||||
<Route path="mandate-role-permissions" element={<AdminMandateRolePermissionsPage />} />
|
||||
<Route path="user-access-overview" element={<AdminUserAccessOverviewPage />} />
|
||||
<Route path="billing" element={<BillingAdmin />} />
|
||||
<Route path="automation-events" element={<AdminAutomationEventsPage />} />
|
||||
</Route>
|
||||
</Route>
|
||||
|
||||
|
|
|
|||
|
|
@ -8,26 +8,24 @@
|
|||
* Backend liefert Blocks-Struktur mit Static und Dynamic Blocks.
|
||||
* UI mappt uiComponent zu Icons via pageRegistry.
|
||||
*
|
||||
* Struktur (gemäss Navigation-API-Konzept):
|
||||
* - SYSTEM (static block, order: 10)
|
||||
* - MEINE FEATURES (dynamic block, order: 15)
|
||||
* - Mandant 1
|
||||
* - Feature A
|
||||
* - Instanz 1 (mit Views)
|
||||
* - WORKFLOWS (static block, order: 20)
|
||||
* - BASISDATEN (static block, order: 30)
|
||||
* - MIGRATE TO FEATURES (static block, order: 40)
|
||||
* - ADMINISTRATION (static block, order: 200)
|
||||
* TREE STRUCTURE (alles collapsible):
|
||||
* ▼ Meine Sicht
|
||||
* - Übersicht, Einstellungen, Prompts, Dateien, Verbindungen, Billing
|
||||
* ─────────────
|
||||
* ▼ Mandant 1
|
||||
* - 🎯 Instanz 1 (Feature-Icon + Instanz-Name)
|
||||
* - 💼 Instanz 2 (Feature-Icon + Instanz-Name)
|
||||
* ─────────────
|
||||
* ▶ Administration
|
||||
* - Users, Mandates, Roles, ...
|
||||
*/
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import { useNavigation } from '../../hooks/useNavigation';
|
||||
import type {
|
||||
StaticBlock,
|
||||
DynamicBlock,
|
||||
NavigationItem,
|
||||
NavigationMandate,
|
||||
MandateFeature,
|
||||
FeatureInstance,
|
||||
FeatureView
|
||||
} 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 {
|
||||
type: 'section',
|
||||
title: block.title,
|
||||
children: block.items.map(navigationItemToTreeNode),
|
||||
id,
|
||||
label,
|
||||
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 {
|
||||
id: instance.id,
|
||||
label: instance.uiLabel,
|
||||
icon: getPageIcon(featureUiComponent), // Use feature icon for instance
|
||||
path: instance.views.length > 0 ? instance.views[0].uiPath : undefined,
|
||||
children: instance.views.map(featureViewToTreeNode),
|
||||
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),
|
||||
children,
|
||||
defaultExpanded: false,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
if (mandate.features.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const children = mandate.features
|
||||
.map(mandateFeatureToTreeNode)
|
||||
.filter((node): node is TreeNodeItem => node !== null);
|
||||
// Flatten: collect all instances from all features directly under mandate
|
||||
const instanceNodes: TreeNodeItem[] = [];
|
||||
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 {
|
||||
id: mandate.id,
|
||||
label: mandate.uiLabel,
|
||||
children,
|
||||
children: instanceNodes,
|
||||
defaultExpanded: true,
|
||||
};
|
||||
}
|
||||
|
|
@ -174,40 +172,49 @@ export const MandateNavigation: React.FC = () => {
|
|||
const { blocks, loading } = useNavigation('de');
|
||||
|
||||
// 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 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) {
|
||||
if (block.type === 'static') {
|
||||
// Static block: system, workflows, basedata, migrate, admin
|
||||
if (block.items.length > 0) {
|
||||
// Add separator before admin block
|
||||
if (block.id === 'admin') {
|
||||
items.push({ type: 'separator' });
|
||||
}
|
||||
items.push(staticBlockToTreeItem(block));
|
||||
if (block.id === 'admin') {
|
||||
adminItems = [...block.items];
|
||||
} else if (block.items.length > 0) {
|
||||
meineSichtItems.push(...block.items);
|
||||
}
|
||||
} 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
|
||||
while (items.length > 0 && (items[items.length - 1] as TreeItem & { type?: string }).type === 'separator') {
|
||||
items.pop();
|
||||
|
||||
// "Meine Sicht" - collapsible container for user-facing pages
|
||||
if (meineSichtItems.length > 0) {
|
||||
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;
|
||||
}, [blocks]);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue