SHL-02: PageContainer-Komponente, 16 kopierte Seiten-Container ersetzt #371

Open
patrick wants to merge 1 commits from feature/shl-02-pagecontainer-dedup into main
pull from: feature/shl-02-pagecontainer-dedup
18 changed files with 68 additions and 39 deletions

No files matched your search

+3 -2
View File
@@ -3,10 +3,11 @@
import { ExtractionReviewCard } from "./ExtractionReviewCard";
import { getInvoiceExtraction } from "../lib/api";
import { EXAMPLE_INVOICE } from "../lib/examples";
import { PageContainer } from "@nexarch/shl";
export default function ExtractionReviewPage() {
return (
<main style={{ maxWidth: 800, margin: "0 auto", padding: 24 }}>
<PageContainer maxWidth={800}>
<h1>Extraktions-Korrektur</h1>
<ExtractionReviewCard
title={EXAMPLE_INVOICE.title}
@@ -21,6 +22,6 @@ export default function ExtractionReviewPage() {
console.log("Bestaetigte Werte:", values);
}}
/>
</main>
</PageContainer>
);
}
+3 -2
View File
@@ -4,6 +4,7 @@ import { useState } from "react";
import { SearchPanel } from "./SearchPanel";
import { ChatPanel, ChatTurn } from "./ChatPanel";
import { Hit } from "../lib/api";
import { PageContainer } from "@nexarch/shl";
type Tab = "search" | "chat";
@@ -17,7 +18,7 @@ export default function SearchChatPage() {
const [chatHistory, setChatHistory] = useState<ChatTurn[]>([]);
return (
<main style={{ maxWidth: 800, margin: "0 auto", padding: 24 }}>
<PageContainer maxWidth={800}>
<h1>Suche und Chat</h1>
<div role="tablist" aria-label="Ansicht" style={{ marginBottom: 16 }}>
@@ -59,6 +60,6 @@ export default function SearchChatPage() {
onHistoryChange={setChatHistory}
/>
</div>
</main>
</PageContainer>
);
}
+3 -2
View File
@@ -3,10 +3,11 @@
import { SummaryCard } from "./SummaryCard";
import { getDocumentSummary, getThreadSummary } from "../lib/api";
import { EXAMPLE_DOCUMENT, EXAMPLE_THREAD } from "../lib/examples";
import { PageContainer } from "@nexarch/shl";
export default function SummariesPage() {
return (
<main style={{ maxWidth: 800, margin: "0 auto", padding: 24 }}>
<PageContainer maxWidth={800}>
<h1>Zusammenfassungen</h1>
<SummaryCard
title={EXAMPLE_DOCUMENT.title}
@@ -20,6 +21,6 @@ export default function SummariesPage() {
sourceLabel="Thread ansehen"
load={async () => (await getThreadSummary(EXAMPLE_THREAD.id)).summary}
/>
</main>
</PageContainer>
);
}
+3 -2
View File
@@ -2,6 +2,7 @@
import { useState } from "react";
import { fetchRecords, downloadExport, type AuditRecord, type AuditFilter } from "@/lib/api";
import { PageContainer } from "@nexarch/shl";
const EMPTY_FILTER: AuditFilter = { tenant: "", actor: "", action: "", from: "", to: "" };
@@ -37,7 +38,7 @@ export default function Page() {
}
return (
<main style={{ maxWidth: 1000, margin: "0 auto", padding: "2rem 1rem" }}>
<PageContainer maxWidth={1000} padding={"2rem 1rem"}>
<h1>Audit-Log</h1>
<div style={{ display: "grid", gap: "0.5rem", gridTemplateColumns: "repeat(3, 1fr)", marginBottom: "1rem" }}>
@@ -163,6 +164,6 @@ export default function Page() {
<button onClick={() => setSelected(null)}>Schließen</button>
</section>
)}
</main>
</PageContainer>
);
}
+3 -2
View File
@@ -1,10 +1,11 @@
import { ControlSections } from "./ControlSections";
import { PageContainer } from "@nexarch/shl";
export default function Page() {
return (
<main style={{ maxWidth: 1100, margin: "0 auto", padding: 24 }}>
<PageContainer maxWidth={1100}>
<h1>NEXARCH Core-Steuerung</h1>
<ControlSections />
</main>
</PageContainer>
);
}
+3 -2
View File
@@ -1,10 +1,11 @@
import { UploadManager } from "../components/UploadManager";
import { PageContainer } from "@nexarch/shl";
export default function IndexPage() {
return (
<main style={{ maxWidth: 720, margin: "0 auto", padding: "24px 16px" }}>
<PageContainer maxWidth={720} padding={"24px 16px"}>
<h1>Dokumente hochladen</h1>
<UploadManager />
</main>
</PageContainer>
);
}
+3 -3
View File
@@ -2,7 +2,7 @@
import { Suspense } from "react";
import { useSearchParams } from "next/navigation";
import { TextField } from "@nexarch/shl";
import { TextField, PageContainer } from "@nexarch/shl";
import { ImportAssistant } from "./ImportAssistant";
function ImportPage() {
@@ -11,7 +11,7 @@ function ImportPage() {
const tenant = params.get("tenant") ?? "";
return (
<main style={{ maxWidth: 1100, margin: "0 auto", padding: 24 }}>
<PageContainer maxWidth={1100}>
<h1>Import-Assistent fuer Drittsystem-Exporte</h1>
{userId === "" || tenant === "" ? (
<form
@@ -32,7 +32,7 @@ function ImportPage() {
) : (
<ImportAssistant userId={userId} tenant={tenant} />
)}
</main>
</PageContainer>
);
}
+3 -2
View File
@@ -2,6 +2,7 @@
import { useState } from "react";
import { fetchOverview, toggleFlag, type Overview } from "@/lib/api";
import { PageContainer } from "@nexarch/shl";
const STATUS_LABEL: Record<string, string> = {
ok: "In Ordnung",
@@ -48,7 +49,7 @@ export default function Page() {
}
return (
<main style={{ maxWidth: 800, margin: "0 auto", padding: "2rem 1rem" }}>
<PageContainer maxWidth={800} padding={"2rem 1rem"}>
<h1>Lizenz- und Modulverwaltung</h1>
<div style={{ display: "flex", gap: "0.5rem", marginBottom: "1.5rem" }}>
@@ -169,6 +170,6 @@ export default function Page() {
</section>
</>
)}
</main>
</PageContainer>
);
}
+3 -3
View File
@@ -2,7 +2,7 @@
import { useState, useEffect, useCallback, useRef, type FormEvent } from "react";
import Link from "next/link";
import { TextField, useToast } from "@nexarch/shl";
import { TextField, useToast, PageContainer } from "@nexarch/shl";
import { search, facets, ApiError, type SearchHit, type AppliedFilter, type FacetsResponse } from "../lib/api";
import { splitHighlighted } from "../lib/highlight";
import { HIGHLIGHT_BG_LIGHT, HIGHLIGHT_FG_LIGHT } from "../lib/highlightColors";
@@ -185,7 +185,7 @@ export default function SearchPage() {
}, [hits, focusedIndex, focusResult, archiveFocused]);
return (
<main style={{ maxWidth: 720, margin: "40px auto", padding: "0 16px" }}>
<PageContainer maxWidth={720} margin={"40px auto"} padding={"0 16px"}>
<h1>Mail-Suche</h1>
<p style={{ color: "var(--shl-color-text-secondary, #4b5563)", fontSize: "0.875rem" }}>
Tastenkürzel: <kbd>/</kbd> Suche fokussieren, <kbd>j</kbd>/<kbd>k</kbd> nächste/vorherige Nachricht,{" "}
@@ -255,6 +255,6 @@ export default function SearchPage() {
))}
</ul>
)}
</main>
</PageContainer>
);
}
+3 -3
View File
@@ -1,14 +1,14 @@
"use client";
import { useI18n } from "@nexarch/shl";
import { useI18n, PageContainer } from "@nexarch/shl";
import "../lib/messages";
export default function IndexPage() {
const { t } = useI18n();
return (
<main style={{ maxWidth: 720, margin: "40px auto", padding: "0 16px" }}>
<PageContainer maxWidth={720} margin={"40px auto"} padding={"0 16px"}>
<h1>{t("pdf.index.title", "NEXARCH Dokumentbetrachter")}</h1>
<p>{t("pdf.index.hint", "Diese Anwendung wird über /viewer/<dokument-id> aufgerufen.")}</p>
</main>
</PageContainer>
);
}
+3 -3
View File
@@ -2,7 +2,7 @@
import { Suspense } from "react";
import { useSearchParams } from "next/navigation";
import { TextField } from "@nexarch/shl";
import { TextField, PageContainer } from "@nexarch/shl";
import { RuleEditor } from "./RuleEditor";
function RulesPage() {
@@ -10,7 +10,7 @@ function RulesPage() {
const userId = params.get("user") ?? "";
return (
<main style={{ maxWidth: 1100, margin: "0 auto", padding: 24 }}>
<PageContainer maxWidth={1100}>
<h1>Routing-Regel-Editor</h1>
{userId === "" ? (
<form
@@ -28,7 +28,7 @@ function RulesPage() {
) : (
<RuleEditor userId={userId} />
)}
</main>
</PageContainer>
);
}
+17
View File
@@ -0,0 +1,17 @@
import type { CSSProperties, ReactNode } from "react";
export interface PageContainerProps {
maxWidth?: CSSProperties["maxWidth"];
margin?: CSSProperties["margin"];
padding?: CSSProperties["padding"];
children: ReactNode;
}
// Ersetzt den in 17 Seiten unabhaengig kopierten
// `<main style={{ maxWidth, margin, padding }}>`-Container (Fable-Review
// auf Code-Dopplung). margin/padding bleiben als Props einstellbar, da
// die kopierten Varianten sich darin unterschieden (z. B. "0 auto" vs.
// "40px auto", "24px" vs. "2rem 1rem").
export function PageContainer({ maxWidth = 960, margin = "0 auto", padding = 24, children }: PageContainerProps) {
return <main style={{ maxWidth, margin, padding }}>{children}</main>;
}
+3
View File
@@ -17,6 +17,9 @@ export type { ShellProps, ModuleLink } from "./components/Shell";
export { ModuleNavShell, MODULE_REGISTRY, resolveModuleHref } from "./components/ModuleNavShell";
export type { ModuleNavShellProps, ModuleRegistryEntry } from "./components/ModuleNavShell";
export { PageContainer } from "./components/PageContainer";
export type { PageContainerProps } from "./components/PageContainer";
export { Dialog } from "./components/Dialog";
export type { DialogProps } from "./components/Dialog";
+3 -2
View File
@@ -2,6 +2,7 @@
import { useEffect, useState } from "react";
import { fetchOverview, fetchHistory, type ModuleStatus, type HistoryEntry } from "@/lib/api";
import { PageContainer } from "@nexarch/shl";
const POLL_INTERVAL_MS = 5000;
@@ -56,7 +57,7 @@ export default function Page() {
const anyDown = modules.some((m) => m.status === "down");
return (
<main style={{ maxWidth: 800, margin: "0 auto", padding: "2rem 1rem" }}>
<PageContainer maxWidth={800} padding={"2rem 1rem"}>
<h1>Systemstatus</h1>
{error && (
@@ -120,6 +121,6 @@ export default function Page() {
<button onClick={() => setSelected(null)}>Schließen</button>
</section>
)}
</main>
</PageContainer>
);
}
+3 -2
View File
@@ -1,6 +1,7 @@
"use client";
import { useState } from "react";
import { PageContainer } from "@nexarch/shl";
import {
fetchTenants,
fetchTenantDetail,
@@ -97,7 +98,7 @@ export default function Page() {
}
return (
<main style={{ maxWidth: 900, margin: "0 auto", padding: "2rem 1rem" }}>
<PageContainer maxWidth={900} padding={"2rem 1rem"}>
<h1>Mandantenverwaltung</h1>
<div style={{ display: "flex", gap: "0.5rem", marginBottom: "1rem", flexWrap: "wrap" }}>
@@ -222,6 +223,6 @@ export default function Page() {
</div>
</section>
)}
</main>
</PageContainer>
);
}
+3 -3
View File
@@ -2,7 +2,7 @@
import { Suspense } from "react";
import { useSearchParams } from "next/navigation";
import { TextField } from "@nexarch/shl";
import { TextField, PageContainer } from "@nexarch/shl";
import { ApprovalList } from "./ApprovalList";
function ApprovalsPage() {
@@ -10,7 +10,7 @@ function ApprovalsPage() {
const userId = params.get("user") ?? "";
return (
<main style={{ maxWidth: 720, margin: "0 auto", padding: 24 }}>
<PageContainer maxWidth={720}>
<h1>Meine Freigaben</h1>
{userId === "" ? (
<form
@@ -28,7 +28,7 @@ function ApprovalsPage() {
) : (
<ApprovalList userId={userId} />
)}
</main>
</PageContainer>
);
}
+3 -3
View File
@@ -2,7 +2,7 @@
import { Suspense, useState } from "react";
import { useSearchParams } from "next/navigation";
import { TextField } from "@nexarch/shl";
import { TextField, PageContainer } from "@nexarch/shl";
import { CaseOverview } from "./CaseOverview";
import { ReminderKanban } from "./ReminderKanban";
@@ -14,7 +14,7 @@ function CasesPage() {
const [tab, setTab] = useState<Tab>("cases");
return (
<main style={{ maxWidth: 1100, margin: "0 auto", padding: 24 }}>
<PageContainer maxWidth={1100}>
<h1>Akten &amp; Wiedervorlagen</h1>
{userId === "" ? (
<form
@@ -52,7 +52,7 @@ function CasesPage() {
{tab === "cases" ? <CaseOverview userId={userId} /> : <ReminderKanban userId={userId} />}
</>
)}
</main>
</PageContainer>
);
}
+3 -3
View File
@@ -2,7 +2,7 @@
import { Suspense } from "react";
import { useSearchParams } from "next/navigation";
import { TextField } from "@nexarch/shl";
import { TextField, PageContainer } from "@nexarch/shl";
import { TaskBoard } from "./TaskBoard";
function TasksPage() {
@@ -11,7 +11,7 @@ function TasksPage() {
const tenantSlug = params.get("tenant") ?? "";
return (
<main style={{ maxWidth: 1100, margin: "0 auto", padding: 24 }}>
<PageContainer maxWidth={1100}>
<h1>Aufgaben</h1>
{userId === "" || tenantSlug === "" ? (
<form
@@ -31,7 +31,7 @@ function TasksPage() {
) : (
<TaskBoard userId={userId} tenantSlug={tenantSlug} />
)}
</main>
</PageContainer>
);
}