feat(custom-modules): besuchte eigene Module offen halten (Keep-Alive, hoechstens 5)
iframes liegen dauerhaft in einem Behaelter im Portal-Rahmen und werden per position: fixed deckungsgleich ueber den Platzhalter der Modulseite gelegt; beim Wegnavigieren nur versteckt (visibility), nie neu eingehaengt. Name/Adresse je Modul im Sitzungsspeicher, Nachladen im Hintergrund, 404 verwirft. Abmelden und Loeschen leeren. Im Browser nachgewiesen: gleiches iframe-Element und kein neuer Seitenabruf nach Dashboard -> Modul B -> Modul A; folgt Seitenleiste ein-/ausgeklappt und Handybreite. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,12 +1,13 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useSidebarStore } from '@/lib/stores/sidebar-store';
|
||||
import { installErrorBuffer } from '@/lib/error-buffer';
|
||||
import { Header } from '@/components/layout/header';
|
||||
import { Sidebar } from '@/components/layout/sidebar';
|
||||
import { CustomModuleFrameHost } from '@/components/modules/custom-module-frame-host';
|
||||
import { ReleaseNoticeHost } from '@/components/release-notice/release-notice-host';
|
||||
import { ReminderNotifier } from '@/components/reminders/reminder-notifier';
|
||||
import { installErrorBuffer } from '@/lib/error-buffer';
|
||||
import { useSidebarStore } from '@/lib/stores/sidebar-store';
|
||||
|
||||
export function AppShell({ children }: { children: React.ReactNode }) {
|
||||
const isCollapsed = useSidebarStore((s) => s.isCollapsed);
|
||||
@@ -24,9 +25,7 @@ export function AppShell({ children }: { children: React.ReactNode }) {
|
||||
|
||||
// Desktop: sidebar pushes content via margin-left
|
||||
// Mobile: sidebar overlays, no margin needed (handled by app-shell-main class in globals.css)
|
||||
const sidebarWidth = isCollapsed
|
||||
? 'var(--sidebar-width-collapsed)'
|
||||
: 'var(--sidebar-width)';
|
||||
const sidebarWidth = isCollapsed ? 'var(--sidebar-width-collapsed)' : 'var(--sidebar-width)';
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
@@ -35,7 +34,9 @@ export function AppShell({ children }: { children: React.ReactNode }) {
|
||||
{/* Seitenrahmen seit quick-260916-bwo halbiert: p-3 (12 px) statt vorher 24 px (Stufe 6), gilt fuer alle Seiten. */}
|
||||
<main
|
||||
className="app-shell-main min-h-[calc(100vh-var(--header-height))] transition-all duration-200 p-3"
|
||||
style={mounted ? { '--current-sidebar-width': sidebarWidth } as React.CSSProperties : undefined}
|
||||
style={
|
||||
mounted ? ({ '--current-sidebar-width': sidebarWidth } as React.CSSProperties) : undefined
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</main>
|
||||
@@ -46,6 +47,10 @@ export function AppShell({ children }: { children: React.ReactNode }) {
|
||||
Benachrichtigung auf jeder Portalseite ausloest — nicht nur, wenn die
|
||||
Kachel auf dem Dashboard sichtbar ist. Rendert nichts. */}
|
||||
<ReminderNotifier />
|
||||
{/* Eigene Module offen halten (30.09.2026): die <iframe>s leben hier im
|
||||
Rahmen und ueberstehen so das Wegnavigieren; die Modulseite zeigt nur
|
||||
einen Platzhalter, ueber den der Behaelter das aktive Modul legt. */}
|
||||
<CustomModuleFrameHost />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { TesseraLogo } from '@/components/brand/tessera-logo';
|
||||
import { useSidebarStore } from '@/lib/stores/sidebar-store';
|
||||
import { useAuthStore } from '@/lib/stores/auth-store';
|
||||
import { fetchSessionState, logout } from '@/lib/auth-actions';
|
||||
import { buildNextParam } from '@/lib/safe-next';
|
||||
import { ThemeToggle } from '@/components/theme-toggle';
|
||||
import { BugReportButton } from '@/components/bug-report/bug-report-button';
|
||||
import { HEADER_ACTIONS_SLOT_ID, HEADER_CENTER_SLOT_ID } from '@/components/layout/header-slot';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { ThemeToggle } from '@/components/theme-toggle';
|
||||
import { fetchSessionState, logout } from '@/lib/auth-actions';
|
||||
import { buildNextParam } from '@/lib/safe-next';
|
||||
import { useAuthStore } from '@/lib/stores/auth-store';
|
||||
import { useCustomModuleCacheStore } from '@/lib/stores/custom-module-cache-store';
|
||||
import { resolvePageTitle, useNavStore } from '@/lib/stores/nav-store';
|
||||
import { useSidebarStore } from '@/lib/stores/sidebar-store';
|
||||
|
||||
export function Header() {
|
||||
const t = useTranslations();
|
||||
@@ -60,13 +61,8 @@ export function Header() {
|
||||
return;
|
||||
}
|
||||
redirectedRef.current = true;
|
||||
const next = buildNextParam(
|
||||
window.location.pathname,
|
||||
window.location.search,
|
||||
);
|
||||
window.location.href = next
|
||||
? `/login?next=${encodeURIComponent(next)}`
|
||||
: '/login';
|
||||
const next = buildNextParam(window.location.pathname, window.location.search);
|
||||
window.location.href = next ? `/login?next=${encodeURIComponent(next)}` : '/login';
|
||||
}
|
||||
// 'unavailable' (T-gyd-02): kein Redirect, bisheriges stilles
|
||||
// Verhalten -- ein API-Ausfall darf keine Abmelde-Schleife ausloesen.
|
||||
@@ -76,10 +72,7 @@ export function Header() {
|
||||
// Close dropdown on outside click
|
||||
useEffect(() => {
|
||||
function handleClickOutside(event: MouseEvent) {
|
||||
if (
|
||||
dropdownRef.current &&
|
||||
!dropdownRef.current.contains(event.target as Node)
|
||||
) {
|
||||
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
|
||||
setDropdownOpen(false);
|
||||
}
|
||||
}
|
||||
@@ -91,9 +84,7 @@ export function Header() {
|
||||
|
||||
const [avatarError, setAvatarError] = useState(false);
|
||||
|
||||
const userInitial = user
|
||||
? (user.displayName ?? user.username).charAt(0).toUpperCase()
|
||||
: '?';
|
||||
const userInitial = user ? (user.displayName ?? user.username).charAt(0).toUpperCase() : '?';
|
||||
|
||||
const roleLabel = user ? tHeader(`role.${user.role}`) : '';
|
||||
|
||||
@@ -101,11 +92,7 @@ export function Header() {
|
||||
// der Name der aktuellen Seite bzw. des Moduls.
|
||||
const navModules = useNavStore((s) => s.modules);
|
||||
const pageTitle = resolvePageTitle(pathname, navModules);
|
||||
const pageTitleText = pageTitle
|
||||
? 'key' in pageTitle
|
||||
? t(pageTitle.key)
|
||||
: pageTitle.text
|
||||
: null;
|
||||
const pageTitleText = pageTitle ? ('key' in pageTitle ? t(pageTitle.key) : pageTitle.text) : null;
|
||||
|
||||
return (
|
||||
<header className="sticky top-0 z-50 flex h-[var(--header-height)] items-center bg-appbar px-2 text-appbar-foreground sm:px-3 dark:shadow-[inset_0_-1px_0_oklch(1_0_0/0.07)]">
|
||||
@@ -258,7 +245,18 @@ export function Header() {
|
||||
: 'text-foreground hover:bg-muted'
|
||||
}`}
|
||||
>
|
||||
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />
|
||||
</svg>
|
||||
{tHeader('admin.label')}
|
||||
@@ -273,6 +271,9 @@ export function Header() {
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setDropdownOpen(false);
|
||||
// Offen gehaltene eigene Module und ihr Sitzungsspeicher
|
||||
// gehoeren zur Sitzung — beim Abmelden alles verwerfen.
|
||||
useCustomModuleCacheStore.getState().clear();
|
||||
logout();
|
||||
}}
|
||||
className="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm text-foreground hover:bg-muted transition-colors"
|
||||
|
||||
Reference in New Issue
Block a user