frontend_nyla/src/pages/FeatureView.tsx
2026-04-21 18:14:26 +02:00

261 lines
8.7 KiB
TypeScript

/**
* FeatureView Page
*
* Generische Feature-View-Komponente.
* Rendert den entsprechenden Content basierend auf Feature-Code und View.
*/
import React from 'react';
import { useCurrentInstance } from '../hooks/useCurrentInstance';
import { useCanViewFeatureView } from '../hooks/useInstancePermissions';
// Trustee Views
// Note: TrusteeOrganisationsView and TrusteeContractsView removed - Feature-Instanz = Organisation
// Note: TrusteePositionsView/TrusteeDocumentsView are no longer mounted directly here -
// they live as tabs inside TrusteeDataTablesView (and that file imports them).
import { TrusteeDashboardView } from './views/trustee/TrusteeDashboardView';
import { TrusteeInstanceRolesView } from './views/trustee/TrusteeInstanceRolesView';
import { TrusteeImportProcessView } from './views/trustee/TrusteeImportProcessView';
import { TrusteeAccountingSettingsView } from './views/trustee/TrusteeAccountingSettingsView';
import { TrusteeAnalyseView } from './views/trustee/TrusteeAnalyseView';
import { TrusteeAbschlussView } from './views/trustee/TrusteeAbschlussView';
import { TrusteeDataTablesView } from './views/trustee/TrusteeDataTablesView';
// Chatbot Views
import { ChatbotConversationsView } from './views/chatbot/ChatbotConversationsView';
// RealEstate Views
import { RealEstatePekView, RealEstateInstanceRolesPlaceholder } from './views/realestate';
// GraphicalEditor Views
import { GraphicalEditorPage } from './views/graphicalEditor/GraphicalEditorPage';
import { GraphicalEditorWorkflowsPage } from './views/graphicalEditor/GraphicalEditorWorkflowsPage';
import { GraphicalEditorWorkflowsTasksPage } from './views/graphicalEditor/GraphicalEditorWorkflowsTasksPage';
import { GraphicalEditorTemplatesPage } from './views/graphicalEditor/GraphicalEditorTemplatesPage';
// Workspace Views
import { WorkspacePage } from './views/workspace/WorkspacePage';
import { WorkspaceEditorPage } from './views/workspace/WorkspaceEditorPage';
import { WorkspaceSettingsPage } from './views/workspace/WorkspaceSettingsPage';
import { WorkspaceRagInsightsPage } from './views/workspace/WorkspaceRagInsightsPage';
// Teamsbot Views
import { TeamsbotDashboardView } from './views/teamsbot/TeamsbotDashboardView';
import { TeamsbotSessionView } from './views/teamsbot/TeamsbotSessionView';
import { TeamsbotSettingsView } from './views/teamsbot/TeamsbotSettingsView';
// Neutralization Views
import { NeutralizationView } from './views/neutralization';
// CommCoach Views
import { CommcoachDashboardView, CommcoachDossierView, CommcoachSettingsView } from './views/commcoach';
// Redmine Views
import { RedmineSettingsView, RedmineStatsView, RedmineBrowserView } from './views/redmine';
import styles from './FeatureView.module.css';
import { useLanguage } from '../providers/language/LanguageContext';
// =============================================================================
// PLACEHOLDER VIEWS (für nicht implementierte Features)
// =============================================================================
const PlaceholderView: React.FC<{ title: string; description: string }> = ({ title, description }) => (
<div className={styles.placeholder}>
<h2>{title}</h2>
<p>{description}</p>
</div>
);
// Chatworkflow Views
const ChatworkflowDashboard: React.FC = () => {
const { t } = useLanguage();
return (
<PlaceholderView title={t('Workflow-Dashboard')} description={t('Übersicht der Workflows')} />
);
};
const ChatworkflowRuns: React.FC = () => {
const { t } = useLanguage();
return <PlaceholderView title={t('Ausführungen')} description={t('Workflow-Ausführungen')} />;
};
const ChatworkflowFiles: React.FC = () => {
const { t } = useLanguage();
return <PlaceholderView title={t('Dateien')} description={t('Workflow-Dateien')} />;
};
// Chatbot Views
// ChatbotConversationsView is imported above
const ChatbotSettings: React.FC = () => {
const { t } = useLanguage();
return (
<PlaceholderView title={t('Chatbot-Einstellungen')} description={t('Konfiguration des Chatbots')} />
);
};
// Generic/Fallback
const NotFound: React.FC = () => {
const { t } = useLanguage();
return (
<div className={styles.notFound}>
<h2>{t('Seite nicht gefunden')}</h2>
<p>{t('Diese Ansicht existiert nicht oder')}</p>
</div>
);
};
const AccessDenied: React.FC = () => {
const { t } = useLanguage();
return (
<div className={styles.accessDenied}>
<h2>{t('Zugriff verweigert')}</h2>
<p>{t('Du hast keine Berechtigung für')}</p>
</div>
);
};
// =============================================================================
// VIEW REGISTRY
// =============================================================================
type ViewComponent = React.FC;
const VIEW_COMPONENTS: Record<string, Record<string, ViewComponent>> = {
trustee: {
dashboard: TrusteeDashboardView,
'data-tables': TrusteeDataTablesView,
'instance-roles': TrusteeInstanceRolesView,
'import-process': TrusteeImportProcessView,
settings: TrusteeAccountingSettingsView,
analyse: TrusteeAnalyseView,
abschluss: TrusteeAbschlussView,
},
chatworkflow: {
dashboard: ChatworkflowDashboard,
runs: ChatworkflowRuns,
files: ChatworkflowFiles,
},
chatbot: {
conversations: ChatbotConversationsView,
settings: ChatbotSettings,
},
realestate: {
dashboard: RealEstatePekView,
'instance-roles': RealEstateInstanceRolesPlaceholder,
},
graphicalEditor: {
editor: GraphicalEditorPage,
workflows: GraphicalEditorWorkflowsPage,
'workflows-tasks': GraphicalEditorWorkflowsTasksPage,
templates: GraphicalEditorTemplatesPage,
},
workspace: {
dashboard: WorkspacePage,
editor: WorkspaceEditorPage,
'rag-insights': WorkspaceRagInsightsPage,
settings: WorkspaceSettingsPage,
},
teamsbot: {
dashboard: TeamsbotDashboardView,
sessions: TeamsbotSessionView,
settings: TeamsbotSettingsView,
},
neutralization: {
dashboard: NeutralizationView,
playground: NeutralizationView,
},
commcoach: {
dashboard: CommcoachDashboardView,
coaching: CommcoachDossierView,
dossier: CommcoachDossierView,
settings: CommcoachSettingsView,
},
redmine: {
stats: RedmineStatsView,
browser: RedmineBrowserView,
settings: RedmineSettingsView,
},
};
// =============================================================================
// FEATURE VIEW PAGE
// =============================================================================
interface FeatureViewPageProps {
view: string;
}
export const FeatureViewPage: React.FC<FeatureViewPageProps> = ({ view }) => {
const { instance, featureCode, isValid } = useCurrentInstance();
// Berechtigungs-Check
const viewCode = `${featureCode}-${view}`;
const canView = useCanViewFeatureView(viewCode);
// DEBUG: Log permission check for chatbot
if (featureCode === 'chatbot') {
console.log('🔍 [DEBUG] FeatureView Permission Check:', {
featureCode,
view,
viewCode,
instanceId: instance?.id,
instanceLabel: instance?.instanceLabel,
isValid,
canView,
permissions: instance?.permissions,
views: instance?.permissions?.views,
viewKeys: instance?.permissions?.views ? Object.keys(instance.permissions.views) : [],
hasLegacyView: instance?.permissions?.views?.[viewCode],
hasFullObjectKey: instance?.permissions?.views?.[`ui.feature.${featureCode}.${view}`],
hasWildcard: instance?.permissions?.views?.['_all'],
});
}
// Nicht valider Kontext
if (!isValid || !featureCode || !instance) {
return <NotFound />;
}
// Keine Berechtigung
if (!canView && view !== 'not-found') {
return <AccessDenied />;
}
// Workspace dashboard is rendered persistently by WorkspaceKeepAlive at MainLayout level;
// other workspace views (e.g. settings, editor) use the standard FeatureViewPage rendering.
if (featureCode === 'workspace' && view !== 'settings' && view !== 'editor' && view !== 'rag-insights') {
return null;
}
// CommCoach coaching/dossier is rendered persistently by CommcoachKeepAlive at MainLayout level.
if (featureCode === 'commcoach' && (view === 'coaching' || view === 'dossier')) {
return null;
}
// GraphicalEditor editor is rendered persistently by GraphicalEditorKeepAlive at MainLayout level.
if (featureCode === 'graphicalEditor' && view === 'editor') {
return null;
}
// View-Komponente finden
const featureViews = VIEW_COMPONENTS[featureCode];
if (!featureViews) {
return <NotFound />;
}
const ViewComponent = featureViews[view];
if (!ViewComponent) {
return <NotFound />;
}
return (
<div className={styles.featureView}>
<main className={styles.viewContent}>
<ViewComponent />
</main>
</div>
);
};
export default FeatureViewPage;