CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
Fable-Review auf Code-Dopplung/interne Links (siehe Analyse). P0-Funde: - Dangling Prosa-Verweise auf die per PR #367 geloeschten *-kanban/-Ordner in SKALIERUNGSKONZEPT.md, STORAGE-KONZEPT.md, MIGRATIONSKONZEPT.md, SICHERHEITSKONZEPT.md (7x "siehe X-kanban/README.md"), PRODUKTARCHITEKTUR.md um Hinweis ergaenzt, dass Boards jetzt in main-Historie/Gitea-PRs liegen. - dms/internal/searchsync/searchsync_test.go:19: falscher Pfad im Kommentar ("internal/bulkedit"/"internal/jobqueue" statt "dms/internal/..."). - web/shl/components/ModuleNavShell.tsx: resolveHrefClientOnly war inhaltlich identisch zu resolveModuleHref in web/portal/app/ModuleGrid.tsx (zwei unabhaengig gepflegte Kopien derselben Hostname-Ersetzung). Als resolveModuleHref aus shl exportiert, ModuleGrid.tsx nutzt jetzt die gemeinsame Funktion, Test-Import entsprechend umgestellt. - web/portal/lib/modules.test.ts: KNOWN_REAL_PERMISSIONS-Whitelist hatte kennzahlen.read (migrations/0019_kennzahlen_policy_rule.up.sql, real existierend seit ANA-02/PR #362) nie nachgetragen, Test lief seitdem unbemerkt im Fehler. Ergaenzt. go test/vitest: web/portal (15/15) und web/shl (61/61) gruen auf Testhost 131. Verbleibender P0-Punkt (14 Web-Apps ohne ModuleNavShell) braucht Produktentscheidung -- mehrere Registry-Keys (ai/mail/workflow/connect) sind bereits durch eine jeweils ANDERE App belegt als die ohne Navigation (z.B. mail-search hat "mail", mail-postfach nicht), daher keine mechanische Zuordnung moeglich ohne zu raten. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
98 lines
2.7 KiB
TypeScript
98 lines
2.7 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import { useI18n, resolveModuleHref } from "@nexarch/shl";
|
|
import type { ModuleEntry } from "../lib/modules";
|
|
import { getSession } from "../lib/http";
|
|
|
|
interface VisibleModulesResponse {
|
|
modules?: ModuleEntry[];
|
|
error?: string;
|
|
}
|
|
|
|
export async function fetchVisibleModules(
|
|
tenant: string,
|
|
role: string,
|
|
fetchImpl: typeof fetch = fetch
|
|
): Promise<ModuleEntry[]> {
|
|
const res = await fetchImpl(
|
|
`/api/visible-modules?tenant=${encodeURIComponent(tenant)}&role=${encodeURIComponent(role)}`
|
|
);
|
|
if (!res.ok) return [];
|
|
const body = (await res.json()) as VisibleModulesResponse;
|
|
return body.modules ?? [];
|
|
}
|
|
|
|
type State =
|
|
| { status: "loading" }
|
|
| { status: "error"; message: string }
|
|
| { status: "ready"; modules: ModuleEntry[] };
|
|
|
|
export function ModuleGrid() {
|
|
const { t } = useI18n();
|
|
const [state, setState] = useState<State>({ status: "loading" });
|
|
|
|
useEffect(() => {
|
|
let cancelled = false;
|
|
(async () => {
|
|
try {
|
|
const session = getSession();
|
|
await session.token();
|
|
const identity = session.identity();
|
|
if (!identity) {
|
|
if (!cancelled) setState({ status: "error", message: t("portal.noIdentity", "Anmeldung erforderlich.") });
|
|
return;
|
|
}
|
|
const modules = await fetchVisibleModules(identity.tenant, identity.role);
|
|
if (!cancelled) setState({ status: "ready", modules });
|
|
} catch (err) {
|
|
if (!cancelled) {
|
|
setState({ status: "error", message: (err as Error).message });
|
|
}
|
|
}
|
|
})();
|
|
return () => {
|
|
cancelled = true;
|
|
};
|
|
}, [t]);
|
|
|
|
if (state.status === "loading") return <p>{t("portal.loading", "Lade Übersicht …")}</p>;
|
|
if (state.status === "error") return <p role="alert">{state.message}</p>;
|
|
if (state.modules.length === 0) {
|
|
return <p>{t("portal.noModules", "Für Ihr Konto sind derzeit keine Module freigeschaltet.")}</p>;
|
|
}
|
|
|
|
return (
|
|
<ul
|
|
style={{
|
|
listStyle: "none",
|
|
margin: 0,
|
|
padding: 0,
|
|
display: "grid",
|
|
gridTemplateColumns: "repeat(auto-fill, minmax(240px, 1fr))",
|
|
gap: 16,
|
|
}}
|
|
>
|
|
{state.modules.map((mod) => (
|
|
<li key={mod.key}>
|
|
<a
|
|
href={resolveModuleHref(mod.href)}
|
|
style={{
|
|
display: "block",
|
|
minHeight: 44,
|
|
padding: 16,
|
|
border: "1px solid var(--shl-color-border, #d7dbe0)",
|
|
borderRadius: 8,
|
|
textDecoration: "none",
|
|
color: "inherit",
|
|
}}
|
|
>
|
|
<strong>{mod.label}</strong>
|
|
<p style={{ margin: "4px 0 0" }}>{mod.description}</p>
|
|
</a>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
);
|
|
}
|