feat(portal-shell): 管理域 §9.4 B5 全量迁移(24 + 21 补充批次共 44 页 + 42 features)

按 ARCHITECTURE.md §9.4 规划口径 + admin-NeedTodo.md §四补充批次完成管理域全量页面迁移:

【§9.4 规划 24 页(B5)】
- users(2) + roles(1) + permissions(1) + audit-logs(4) + invitation-codes(1)
- school(6: redirect/schools/classes/departments/academic-year/grades)
- announcements(1) + files(1) + ai-settings(1) + system(1) + viewports(1)
- students(1) + teachers(1) + organization(1) + plugins(1, config-service)
- 仪表盘已存在(/shell/admin/page.tsx)

【§四补充批次 21 页】
- course-plans(4) + elective(4) + questions(1) + lesson-plans(2) + error-book(1)
- scheduling(3: auto/changes/rules) + attendance(1) + curriculum-map(1)
- announcements 详情/编辑(2) + roles/[id] 详情(1) + users/import(1)

【实现要点】
- 全部使用 ListPageShell + loading/error/empty 三态规范(§11.3 DoD)
- 走 lib/api hooks;未就绪契约走 MSW + @contract-pending 注释(§11.4)
- 文案走 useTranslations(zh-CN + en 两份同步更新)
- 42 个 features/<domain>/transformations.ts 纯函数 + 配套 vitest 单测
- catch 块统一 notify.error;无空 catch;lint:tokens 通过
- 路由全部登记到 route-permissions.ts(39 EXACT + 8 PREFIX)

【验收】
- tsc --noEmit: 0 errors
- ESLint src: 0 errors (4 generated-files warnings, pre-existing)
- lint:tokens: 0 errors
- vitest: 1639/1639 passed (含 23 admin 测试文件 671 用例)
- check:routes: PASS (143 routes, 4 ghost entries pre-existing)
- check:pages: PASS (146 pages)
- check:codegen: PASS
- arch:scan: 24 modules, 8262 symbols

关联:ARCHITECTURE.md §9.4 / §10 P5 / §11.3 DoD / §11.6
This commit is contained in:
SpecialX
2026-07-24 23:07:20 +08:00
parent 5a9f652943
commit 062d9e9582
394 changed files with 60468 additions and 118 deletions

View File

@@ -206,7 +206,7 @@ describe("E2E: 三层安全边界", () => {
it("teacher 有 QUESTION_READ → 访问题库放行OR 语义)", () => {
const result = checkRoutePermission(
"/shell/teacher/question-bank",
"/shell/teacher/questions",
TEACHER_USER.bitmap,
TEACHER_USER.role,
);
@@ -250,7 +250,7 @@ describe("E2E: 三层安全边界", () => {
it("student 有 ELECTIVE_SELECT → 访问选修课选择放行OR 语义)", () => {
const result = checkRoutePermission(
"/shell/student/electives",
"/shell/student/elective",
STUDENT_USER.bitmap,
STUDENT_USER.role,
);
@@ -263,7 +263,7 @@ describe("E2E: 三层安全边界", () => {
"DASHBOARD_READ",
]);
const result = checkRoutePermission(
"/shell/student/electives",
"/shell/student/elective",
noElective,
"student",
);
@@ -313,7 +313,7 @@ describe("E2E: 三层安全边界", () => {
const teacherRoutes = [
"/shell/teacher",
"/shell/teacher/lesson-plans",
"/shell/teacher/question-bank",
"/shell/teacher/questions",
];
const results = batchCheckRoutePermission(
teacherRoutes,

View File

@@ -0,0 +1,38 @@
"use client";
/**
* AI 配置路由错误边界ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment error.tsx捕获子树未处理异常。
*/
import { useEffect } from "react";
import { Button } from "@/shared/components/ui/button";
import { useTranslations } from "next-intl";
export default function AiSettingsError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}): React.ReactElement {
const t = useTranslations("admin.aiSettings");
useEffect(() => {
console.error("[portal-shell] ai-settings route error:", error);
}, [error]);
return (
<div className="flex flex-col items-center justify-center gap-4 rounded-xl border border-destructive/30 bg-destructive/5 p-10">
<h2 className="text-lg font-semibold text-destructive">
{t("error.title")}
</h2>
<p className="text-sm text-muted-foreground">
{error.message || t("error.unknown")}
</p>
<Button onClick={reset} variant="outline">
{t("error.retry")}
</Button>
</div>
);
}

View File

@@ -0,0 +1,9 @@
import { ListPageSkeleton } from "@/shared/components/page-templates";
/**
* AI 配置路由段加载骨架ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment loading.tsx自动包裹页面渲染期间。
*/
export default function AiSettingsLoading(): React.ReactElement {
return <ListPageSkeleton rows={5} />;
}

View File

@@ -0,0 +1,24 @@
import { Suspense } from "react";
import { AiSettingsClient } from "@/features/admin/ai-settings/ai-settings-client";
import { ListPageSkeleton } from "@/shared/components/page-templates";
/**
* AI Provider 配置与用量仪表盘ARCHITECTURE.md §7.3 列表页 / §9.4 / §10 P5
*
* Server Component 入口:仅负责 Suspense 边界包裹useSearchParams 要求)。
* 业务逻辑在 AiSettingsClientclient component中。
*
* 数据契约:
* - aiProviders(scope) ❌ schema 未就绪 → MSW 兜底(@contract-pending
* - aiUsageDashboard(range) ❌ schema 未就绪 → MSW 兜底(@contract-pending
*
* 关联ARCHITECTURE.md §5.3 / §5.4 / §7.3 / §9.4 / §10 P5 / §11.3 / §11.4
*/
export default function AiSettingsPage(): React.ReactElement {
return (
<Suspense fallback={<ListPageSkeleton rows={5} />}>
<AiSettingsClient />
</Suspense>
);
}

View File

@@ -0,0 +1,38 @@
"use client";
/**
* 公告编辑路由错误边界ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment error.tsx捕获子树未处理异常。
*/
import { useEffect } from "react";
import { Button } from "@/shared/components/ui/button";
import { useTranslations } from "next-intl";
export default function AnnouncementEditError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}): React.ReactElement {
const t = useTranslations("admin.announcements");
useEffect(() => {
console.error("[portal-shell] announcement edit route error:", error);
}, [error]);
return (
<div className="flex flex-col items-center justify-center gap-4 rounded-xl border border-destructive/30 bg-destructive/5 p-10">
<h2 className="text-lg font-semibold text-destructive">
{t("error.title")}
</h2>
<p className="text-sm text-muted-foreground">
{error.message || t("error.unknown")}
</p>
<Button onClick={reset} variant="outline">
{t("error.retry")}
</Button>
</div>
);
}

View File

@@ -0,0 +1,9 @@
import { FormPageSkeleton } from "@/shared/components/page-templates";
/**
* 公告编辑路由段加载骨架ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment loading.tsx自动包裹页面渲染期间。
*/
export default function AnnouncementEditLoading(): React.ReactElement {
return <FormPageSkeleton />;
}

View File

@@ -0,0 +1,22 @@
import { Suspense } from "react";
import { AnnouncementEditClient } from "@/features/admin/announcements/announcement-edit-client";
import { FormPageSkeleton } from "@/shared/components/page-templates";
/**
* 公告编辑表单页ARCHITECTURE.md §7.3 表单页 / §9.4 / §10 P5
*
* Server Component 入口:仅负责 Suspense 边界包裹useParams 要求)。
* 业务逻辑在 AnnouncementEditClientclient component中。
*
* 数据契约adminAnnouncement(id) + updateAnnouncement(id, input) ❌ schema 无 → MSW 兜底(@contract-pending
*
* 关联ARCHITECTURE.md §5.3 / §5.4 / §7.3 / §9.4 / §10 P5 / §11.3 / §11.4
*/
export default function AnnouncementEditPage(): React.ReactElement {
return (
<Suspense fallback={<FormPageSkeleton />}>
<AnnouncementEditClient />
</Suspense>
);
}

View File

@@ -0,0 +1,38 @@
"use client";
/**
* 公告详情路由错误边界ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment error.tsx捕获子树未处理异常。
*/
import { useEffect } from "react";
import { Button } from "@/shared/components/ui/button";
import { useTranslations } from "next-intl";
export default function AnnouncementDetailError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}): React.ReactElement {
const t = useTranslations("admin.announcements");
useEffect(() => {
console.error("[portal-shell] announcement detail route error:", error);
}, [error]);
return (
<div className="flex flex-col items-center justify-center gap-4 rounded-xl border border-destructive/30 bg-destructive/5 p-10">
<h2 className="text-lg font-semibold text-destructive">
{t("error.title")}
</h2>
<p className="text-sm text-muted-foreground">
{error.message || t("error.unknown")}
</p>
<Button onClick={reset} variant="outline">
{t("error.retry")}
</Button>
</div>
);
}

View File

@@ -0,0 +1,9 @@
import { DetailPageSkeleton } from "@/shared/components/page-templates";
/**
* 公告详情路由段加载骨架ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment loading.tsx自动包裹页面渲染期间。
*/
export default function AnnouncementDetailLoading(): React.ReactElement {
return <DetailPageSkeleton />;
}

View File

@@ -0,0 +1,22 @@
import { Suspense } from "react";
import { AnnouncementDetailClient } from "@/features/admin/announcements/announcement-detail-client";
import { DetailPageSkeleton } from "@/shared/components/page-templates";
/**
* 公告详情页ARCHITECTURE.md §7.3 详情页 / §9.4 / §10 P5
*
* Server Component 入口:仅负责 Suspense 边界包裹。
* 业务逻辑在 AnnouncementDetailClientclient component中。
*
* 数据契约adminAnnouncement(id: ID!) ❌ schema 无 → MSW 兜底(@contract-pending
*
* 关联ARCHITECTURE.md §5.5 / §7.3 / §9.4 / §10 P5 / §11.3
*/
export default function AnnouncementDetailPage(): React.ReactElement {
return (
<Suspense fallback={<DetailPageSkeleton />}>
<AnnouncementDetailClient />
</Suspense>
);
}

View File

@@ -0,0 +1,38 @@
"use client";
/**
* 公告路由错误边界ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment error.tsx捕获子树未处理异常。
*/
import { useEffect } from "react";
import { Button } from "@/shared/components/ui/button";
import { useTranslations } from "next-intl";
export default function AnnouncementsError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}): React.ReactElement {
const t = useTranslations("admin.announcements");
useEffect(() => {
console.error("[portal-shell] announcements route error:", error);
}, [error]);
return (
<div className="flex flex-col items-center justify-center gap-4 rounded-xl border border-destructive/30 bg-destructive/5 p-10">
<h2 className="text-lg font-semibold text-destructive">
{t("error.title")}
</h2>
<p className="text-sm text-muted-foreground">
{error.message || t("error.unknown")}
</p>
<Button onClick={reset} variant="outline">
{t("error.retry")}
</Button>
</div>
);
}

View File

@@ -0,0 +1,9 @@
import { ListPageSkeleton } from "@/shared/components/page-templates";
/**
* 公告路由段加载骨架ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment loading.tsx自动包裹页面渲染期间。
*/
export default function AnnouncementsLoading(): React.ReactElement {
return <ListPageSkeleton rows={5} />;
}

View File

@@ -0,0 +1,22 @@
import { Suspense } from "react";
import { AnnouncementsListClient } from "@/features/admin/announcements/announcements-list-client";
import { ListPageSkeleton } from "@/shared/components/page-templates";
/**
* 公告管理列表页ARCHITECTURE.md §7.3 列表页 / §9.4 / §10 P5
*
* Server Component 入口:仅负责 Suspense 边界包裹useSearchParams 要求)。
* 业务逻辑在 AnnouncementsListClientclient component中。
*
* 数据契约adminAnnouncements(status) ❌ schema 无 → MSW 兜底(@contract-pending
*
* 关联ARCHITECTURE.md §5.3 / §5.4 / §7.3 / §9.4 / §10 P5 / §11.3 / §11.4
*/
export default function AnnouncementsListPage(): React.ReactElement {
return (
<Suspense fallback={<ListPageSkeleton rows={5} />}>
<AnnouncementsListClient />
</Suspense>
);
}

View File

@@ -0,0 +1,36 @@
"use client";
/**
* 考勤管理路由错误边界ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment error.tsx捕获子树未处理异常。
*/
import { useEffect } from "react";
import { Button } from "@/shared/components/ui/button";
import { useTranslations } from "next-intl";
export default function AdminAttendanceError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}): React.ReactElement {
const t = useTranslations("admin.attendance.error");
useEffect(() => {
console.error("[portal-shell] admin attendance route error:", error);
}, [error]);
return (
<div className="flex flex-col items-center justify-center gap-4 rounded-xl border border-destructive/30 bg-destructive/5 p-10">
<h2 className="text-lg font-semibold text-destructive">{t("title")}</h2>
<p className="text-sm text-muted-foreground">
{error.message || t("unknown")}
</p>
<Button onClick={reset} variant="outline">
{t("retry")}
</Button>
</div>
);
}

View File

@@ -0,0 +1,9 @@
import { ListPageSkeleton } from "@/shared/components/page-templates";
/**
* 考勤管理路由段加载骨架ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment loading.tsx自动包裹页面渲染期间。
*/
export default function AdminAttendanceLoading(): React.ReactElement {
return <ListPageSkeleton rows={5} />;
}

View File

@@ -0,0 +1,23 @@
import { Suspense } from "react";
import { AdminAttendanceClient } from "@/features/admin/attendance/admin-attendance-client";
import { ListPageSkeleton } from "@/shared/components/page-templates";
/**
* 考勤管理页ARCHITECTURE.md §7.3 列表页 / §9.4 / §10 P5 / admin-NeedTodo §四)
*
* Server Component 入口:仅负责 Suspense 边界包裹useSearchParams 要求)。
* 业务逻辑在 AdminAttendanceClientclient component中。
*
* 数据契约adminAttendanceStats / attendanceGradeCorrelation / adminClasses
* ❌ schema 无 → MSW 兜底(@contract-pending
*
* 关联ARCHITECTURE.md §5.3 / §5.4 / §7.3 / §9.4 / §10 P5 / §11.3 / §11.4
*/
export default function AdminAttendancePage(): React.ReactElement {
return (
<Suspense fallback={<ListPageSkeleton rows={5} />}>
<AdminAttendanceClient />
</Suspense>
);
}

View File

@@ -0,0 +1,39 @@
"use client";
/**
* 数据变更日志路由错误边界ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment error.tsx捕获子树未处理异常。
*/
import { useEffect } from "react";
import { Button } from "@/shared/components/ui/button";
import { useTranslations } from "next-intl";
export default function DataChangesError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}): React.ReactElement {
const t = useTranslations("admin.auditLogs.error");
useEffect(() => {
console.error(
"[portal-shell] admin audit-logs/data-changes route error:",
error,
);
}, [error]);
return (
<div className="flex flex-col items-center justify-center gap-4 rounded-xl border border-destructive/30 bg-destructive/5 p-10">
<h2 className="text-lg font-semibold text-destructive">{t("title")}</h2>
<p className="text-sm text-muted-foreground">
{error.message || t("unknown")}
</p>
<Button onClick={reset} variant="outline">
{t("retry")}
</Button>
</div>
);
}

View File

@@ -0,0 +1,9 @@
import { ListPageSkeleton } from "@/shared/components/page-templates";
/**
* 数据变更日志路由段加载骨架ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment loading.tsx自动包裹页面渲染期间。
*/
export default function DataChangesLoading(): React.ReactElement {
return <ListPageSkeleton rows={5} />;
}

View File

@@ -0,0 +1,22 @@
import { Suspense } from "react";
import { DataChangesClient } from "@/features/admin/audit-logs/data-changes-client";
import { ListPageSkeleton } from "@/shared/components/page-templates";
/**
* 数据变更日志页ARCHITECTURE.md §7.3 列表页 / §9.4 / §10 P5
*
* Server Component 入口:仅负责 Suspense 边界包裹useSearchParams 要求)。
* 业务逻辑在 DataChangesClientclient component中。
*
* 数据契约dataChangeLogs(filter, pagination) / dataChangeTableOptions / dataChangeStats ❌ schema 无 → MSW 兜底(@contract-pending
*
* 关联ARCHITECTURE.md §5.3 / §5.4 / §7.3 / §9.4 / §10 P5 / §11.3 / §11.4
*/
export default function DataChangesPage(): React.ReactElement {
return (
<Suspense fallback={<ListPageSkeleton rows={5} />}>
<DataChangesClient />
</Suspense>
);
}

View File

@@ -0,0 +1,36 @@
"use client";
/**
* 审计日志路由错误边界ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment error.tsx捕获子树未处理异常。
*/
import { useEffect } from "react";
import { Button } from "@/shared/components/ui/button";
import { useTranslations } from "next-intl";
export default function AuditLogsError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}): React.ReactElement {
const t = useTranslations("admin.auditLogs.error");
useEffect(() => {
console.error("[portal-shell] admin audit-logs route error:", error);
}, [error]);
return (
<div className="flex flex-col items-center justify-center gap-4 rounded-xl border border-destructive/30 bg-destructive/5 p-10">
<h2 className="text-lg font-semibold text-destructive">{t("title")}</h2>
<p className="text-sm text-muted-foreground">
{error.message || t("unknown")}
</p>
<Button onClick={reset} variant="outline">
{t("retry")}
</Button>
</div>
);
}

View File

@@ -0,0 +1,9 @@
import { ListPageSkeleton } from "@/shared/components/page-templates";
/**
* 审计日志路由段加载骨架ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment loading.tsx自动包裹页面渲染期间。
*/
export default function AuditLogsLoading(): React.ReactElement {
return <ListPageSkeleton rows={5} />;
}

View File

@@ -0,0 +1,39 @@
"use client";
/**
* 登录日志路由错误边界ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment error.tsx捕获子树未处理异常。
*/
import { useEffect } from "react";
import { Button } from "@/shared/components/ui/button";
import { useTranslations } from "next-intl";
export default function LoginLogsError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}): React.ReactElement {
const t = useTranslations("admin.auditLogs.error");
useEffect(() => {
console.error(
"[portal-shell] admin audit-logs/login-logs route error:",
error,
);
}, [error]);
return (
<div className="flex flex-col items-center justify-center gap-4 rounded-xl border border-destructive/30 bg-destructive/5 p-10">
<h2 className="text-lg font-semibold text-destructive">{t("title")}</h2>
<p className="text-sm text-muted-foreground">
{error.message || t("unknown")}
</p>
<Button onClick={reset} variant="outline">
{t("retry")}
</Button>
</div>
);
}

View File

@@ -0,0 +1,9 @@
import { ListPageSkeleton } from "@/shared/components/page-templates";
/**
* 登录日志路由段加载骨架ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment loading.tsx自动包裹页面渲染期间。
*/
export default function LoginLogsLoading(): React.ReactElement {
return <ListPageSkeleton rows={5} />;
}

View File

@@ -0,0 +1,22 @@
import { Suspense } from "react";
import { LoginLogsClient } from "@/features/admin/audit-logs/login-logs-client";
import { ListPageSkeleton } from "@/shared/components/page-templates";
/**
* 登录日志页ARCHITECTURE.md §7.3 列表页 / §9.4 / §10 P5
*
* Server Component 入口:仅负责 Suspense 边界包裹useSearchParams 要求)。
* 业务逻辑在 LoginLogsClientclient component中。
*
* 数据契约loginLogs(filter, pagination) ❌ schema 无 → MSW 兜底(@contract-pending
*
* 关联ARCHITECTURE.md §5.3 / §5.4 / §7.3 / §9.4 / §10 P5 / §11.3 / §11.4
*/
export default function LoginLogsPage(): React.ReactElement {
return (
<Suspense fallback={<ListPageSkeleton rows={5} />}>
<LoginLogsClient />
</Suspense>
);
}

View File

@@ -0,0 +1,39 @@
"use client";
/**
* 审计概览路由错误边界ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment error.tsx捕获子树未处理异常。
*/
import { useEffect } from "react";
import { Button } from "@/shared/components/ui/button";
import { useTranslations } from "next-intl";
export default function AuditOverviewError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}): React.ReactElement {
const t = useTranslations("admin.auditLogs.error");
useEffect(() => {
console.error(
"[portal-shell] admin audit-logs/overview route error:",
error,
);
}, [error]);
return (
<div className="flex flex-col items-center justify-center gap-4 rounded-xl border border-destructive/30 bg-destructive/5 p-10">
<h2 className="text-lg font-semibold text-destructive">{t("title")}</h2>
<p className="text-sm text-muted-foreground">
{error.message || t("unknown")}
</p>
<Button onClick={reset} variant="outline">
{t("retry")}
</Button>
</div>
);
}

View File

@@ -0,0 +1,9 @@
import { DetailPageSkeleton } from "@/shared/components/page-templates";
/**
* 审计概览路由段加载骨架ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment loading.tsx自动包裹页面渲染期间。
*/
export default function AuditOverviewLoading(): React.ReactElement {
return <DetailPageSkeleton />;
}

View File

@@ -0,0 +1,22 @@
import { Suspense } from "react";
import { AuditOverviewClient } from "@/features/admin/audit-logs/audit-overview-client";
import { DetailPageSkeleton } from "@/shared/components/page-templates";
/**
* 审计概览页ARCHITECTURE.md §7.3 / §9.4 / §10 P5
*
* Server Component 入口:仅负责 Suspense 边界包裹。
* 业务逻辑在 AuditOverviewClientclient component中。
*
* 数据契约auditOverviewStats / auditTrend / dataChangeActionStats ❌ schema 无 → MSW 兜底(@contract-pending
*
* 关联ARCHITECTURE.md §5.3 / §5.4 / §7.3 / §9.4 / §10 P5 / §11.3 / §11.4
*/
export default function AuditOverviewPage(): React.ReactElement {
return (
<Suspense fallback={<DetailPageSkeleton />}>
<AuditOverviewClient />
</Suspense>
);
}

View File

@@ -0,0 +1,22 @@
import { Suspense } from "react";
import { AuditLogsListClient } from "@/features/admin/audit-logs/audit-logs-list-client";
import { ListPageSkeleton } from "@/shared/components/page-templates";
/**
* 审计日志列表页ARCHITECTURE.md §7.3 列表页 / §9.4 / §10 P5
*
* Server Component 入口:仅负责 Suspense 边界包裹useSearchParams 要求)。
* 业务逻辑在 AuditLogsListClientclient component中。
*
* 数据契约auditLogs(filter, pagination) ❌ schema 无 → MSW 兜底(@contract-pending
*
* 关联ARCHITECTURE.md §5.3 / §5.4 / §7.3 / §9.4 / §10 P5 / §11.3 / §11.4
*/
export default function AuditLogsListPage(): React.ReactElement {
return (
<Suspense fallback={<ListPageSkeleton rows={5} />}>
<AuditLogsListClient />
</Suspense>
);
}

View File

@@ -0,0 +1,38 @@
"use client";
/**
* 管理端课程计划编辑路由错误边界ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment error.tsx捕获子树未处理异常。
*/
import { useEffect } from "react";
import { Button } from "@/shared/components/ui/button";
import { useTranslations } from "next-intl";
export default function CoursePlanEditError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}): React.ReactElement {
const t = useTranslations("admin.coursePlans");
useEffect(() => {
console.error("[portal-shell] admin course-plan edit route error:", error);
}, [error]);
return (
<div className="flex flex-col items-center justify-center gap-4 rounded-xl border border-destructive/30 bg-destructive/5 p-10">
<h2 className="text-lg font-semibold text-destructive">
{t("error.title")}
</h2>
<p className="text-sm text-muted-foreground">
{error.message || t("error.unknown")}
</p>
<Button onClick={reset} variant="outline">
{t("error.retry")}
</Button>
</div>
);
}

View File

@@ -0,0 +1,9 @@
import { FormPageSkeleton } from "@/shared/components/page-templates";
/**
* 管理端课程计划编辑路由段加载骨架ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment loading.tsx自动包裹页面渲染期间。
*/
export default function CoursePlanEditLoading(): React.ReactElement {
return <FormPageSkeleton />;
}

View File

@@ -0,0 +1,21 @@
import { Suspense } from "react";
import { CoursePlanEditClient } from "@/features/admin/course-plans/course-plan-edit-client";
import { FormPageSkeleton } from "@/shared/components/page-templates";
/**
* 管理端课程计划编辑表单页ARCHITECTURE.md §7.3 表单页 / §9.4 / §10 P5
*
* Server Component 入口:仅负责 Suspense 边界包裹useParams 要求)。
*
* 数据契约adminCoursePlan(id) + updateCoursePlan(input) ❌ schema 无 → MSW 兜底(@contract-pending
*
* 关联ARCHITECTURE.md §5.3 / §5.4 / §7.3 / §9.4 / §10 P5 / §11.3 / §11.4
*/
export default function CoursePlanEditPage(): React.ReactElement {
return (
<Suspense fallback={<FormPageSkeleton />}>
<CoursePlanEditClient />
</Suspense>
);
}

View File

@@ -0,0 +1,41 @@
"use client";
/**
* 管理端课程计划详情路由错误边界ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment error.tsx捕获子树未处理异常。
*/
import { useEffect } from "react";
import { Button } from "@/shared/components/ui/button";
import { useTranslations } from "next-intl";
export default function CoursePlanDetailError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}): React.ReactElement {
const t = useTranslations("admin.coursePlans");
useEffect(() => {
console.error(
"[portal-shell] admin course-plan detail route error:",
error,
);
}, [error]);
return (
<div className="flex flex-col items-center justify-center gap-4 rounded-xl border border-destructive/30 bg-destructive/5 p-10">
<h2 className="text-lg font-semibold text-destructive">
{t("error.title")}
</h2>
<p className="text-sm text-muted-foreground">
{error.message || t("error.unknown")}
</p>
<Button onClick={reset} variant="outline">
{t("error.retry")}
</Button>
</div>
);
}

View File

@@ -0,0 +1,12 @@
import { DetailPageSkeleton } from "@/shared/components/page-templates";
/**
* 管理端课程计划详情路由段加载骨架ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment loading.tsx自动包裹页面渲染期间。
*
* 子页面(编辑)的 Skeleton 由 server page 的 <Suspense> 兜底,
* 本文件仅在 /shell/admin/course-plans/[id] 期间显示。
*/
export default function CoursePlanDetailLoading(): React.ReactElement {
return <DetailPageSkeleton />;
}

View File

@@ -0,0 +1,21 @@
import { Suspense } from "react";
import { CoursePlanDetailClient } from "@/features/admin/course-plans/course-plan-detail-client";
import { DetailPageSkeleton } from "@/shared/components/page-templates";
/**
* 管理端课程计划详情页ARCHITECTURE.md §7.3 详情页 / §9.4 / §10 P5
*
* Server Component 入口:仅负责 Suspense 边界包裹。
*
* 数据契约adminCoursePlan(id: ID!) ❌ schema 无 → MSW 兜底(@contract-pending
*
* 关联ARCHITECTURE.md §5.3 / §5.4 / §7.3 / §9.4 / §10 P5 / §11.3 / §11.4
*/
export default function CoursePlanDetailPage(): React.ReactElement {
return (
<Suspense fallback={<DetailPageSkeleton />}>
<CoursePlanDetailClient />
</Suspense>
);
}

View File

@@ -0,0 +1,41 @@
"use client";
/**
* 管理端课程计划新建路由错误边界ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment error.tsx捕获子树未处理异常。
*/
import { useEffect } from "react";
import { Button } from "@/shared/components/ui/button";
import { useTranslations } from "next-intl";
export default function CoursePlanCreateError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}): React.ReactElement {
const t = useTranslations("admin.coursePlans");
useEffect(() => {
console.error(
"[portal-shell] admin course-plan create route error:",
error,
);
}, [error]);
return (
<div className="flex flex-col items-center justify-center gap-4 rounded-xl border border-destructive/30 bg-destructive/5 p-10">
<h2 className="text-lg font-semibold text-destructive">
{t("error.title")}
</h2>
<p className="text-sm text-muted-foreground">
{error.message || t("error.unknown")}
</p>
<Button onClick={reset} variant="outline">
{t("error.retry")}
</Button>
</div>
);
}

View File

@@ -0,0 +1,9 @@
import { FormPageSkeleton } from "@/shared/components/page-templates";
/**
* 管理端课程计划新建路由段加载骨架ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment loading.tsx自动包裹页面渲染期间。
*/
export default function CoursePlanCreateLoading(): React.ReactElement {
return <FormPageSkeleton />;
}

View File

@@ -0,0 +1,21 @@
import { Suspense } from "react";
import { CoursePlanCreateClient } from "@/features/admin/course-plans/course-plan-create-client";
import { FormPageSkeleton } from "@/shared/components/page-templates";
/**
* 管理端课程计划新建表单页ARCHITECTURE.md §7.3 表单页 / §9.4 / §10 P5
*
* Server Component 入口:仅负责 Suspense 边界包裹。
*
* 数据契约mutation createCoursePlan(input) ❌ schema 无 Mutation 类型 → MSW 兜底(@contract-pending
*
* 关联ARCHITECTURE.md §5.3 / §5.4 / §7.3 / §9.4 / §10 P5 / §11.3 / §11.4
*/
export default function CoursePlanCreatePage(): React.ReactElement {
return (
<Suspense fallback={<FormPageSkeleton />}>
<CoursePlanCreateClient />
</Suspense>
);
}

View File

@@ -0,0 +1,38 @@
"use client";
/**
* 管理端课程计划路由错误边界ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment error.tsx捕获子树未处理异常。
*/
import { useEffect } from "react";
import { Button } from "@/shared/components/ui/button";
import { useTranslations } from "next-intl";
export default function CoursePlansError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}): React.ReactElement {
const t = useTranslations("admin.coursePlans");
useEffect(() => {
console.error("[portal-shell] admin course-plans route error:", error);
}, [error]);
return (
<div className="flex flex-col items-center justify-center gap-4 rounded-xl border border-destructive/30 bg-destructive/5 p-10">
<h2 className="text-lg font-semibold text-destructive">
{t("error.title")}
</h2>
<p className="text-sm text-muted-foreground">
{error.message || t("error.unknown")}
</p>
<Button onClick={reset} variant="outline">
{t("error.retry")}
</Button>
</div>
);
}

View File

@@ -0,0 +1,12 @@
import { ListPageSkeleton } from "@/shared/components/page-templates";
/**
* 管理端课程计划路由段加载骨架ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment loading.tsx自动包裹页面渲染期间。
*
* 子页面(详情/编辑/新建)的 Skeleton 由各自 server page 的 <Suspense> 兜底,
* 本文件仅在 /shell/admin/course-plans 列表/重定向期间显示。
*/
export default function CoursePlansLoading(): React.ReactElement {
return <ListPageSkeleton rows={5} />;
}

View File

@@ -0,0 +1,22 @@
import { Suspense } from "react";
import { CoursePlansListClient } from "@/features/admin/course-plans/course-plans-list-client";
import { ListPageSkeleton } from "@/shared/components/page-templates";
/**
* 管理端课程计划列表页ARCHITECTURE.md §7.3 列表页 / §9.4 / §10 P5
*
* Server Component 入口:仅负责 Suspense 边界包裹useSearchParams 要求)。
* 业务逻辑在 CoursePlansListClientclient component中。
*
* 数据契约adminCoursePlans(status) ❌ schema 无 → MSW 兜底(@contract-pending
*
* 关联ARCHITECTURE.md §5.3 / §5.4 / §7.3 / §9.4 / §10 P5 / §11.3 / §11.4
*/
export default function CoursePlansListPage(): React.ReactElement {
return (
<Suspense fallback={<ListPageSkeleton rows={5} />}>
<CoursePlansListClient />
</Suspense>
);
}

View File

@@ -0,0 +1,38 @@
"use client";
/**
* 课程地图路由错误边界ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment error.tsx捕获子树未处理异常。
*/
import { useEffect } from "react";
import { Button } from "@/shared/components/ui/button";
import { useTranslations } from "next-intl";
export default function CurriculumMapError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}): React.ReactElement {
const t = useTranslations("admin.curriculumMap");
useEffect(() => {
console.error("[portal-shell] admin curriculum-map route error:", error);
}, [error]);
return (
<div className="flex flex-col items-center justify-center gap-4 rounded-xl border border-destructive/30 bg-destructive/5 p-10">
<h2 className="text-lg font-semibold text-destructive">
{t("error.title")}
</h2>
<p className="text-sm text-muted-foreground">
{error.message || t("error.unknown")}
</p>
<Button onClick={reset} variant="outline">
{t("error.retry")}
</Button>
</div>
);
}

View File

@@ -0,0 +1,9 @@
import { ListPageSkeleton } from "@/shared/components/page-templates";
/**
* 课程地图路由段加载骨架ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment loading.tsx自动包裹页面渲染期间。
*/
export default function CurriculumMapLoading(): React.ReactElement {
return <ListPageSkeleton rows={5} />;
}

View File

@@ -0,0 +1,22 @@
import { Suspense } from "react";
import { CurriculumMapClient } from "@/features/admin/curriculum-map/curriculum-map-client";
import { ListPageSkeleton } from "@/shared/components/page-templates";
/**
* 课程地图页ARCHITECTURE.md §7.3 列表页 / §9.4 / §10 P5 / admin-NeedTodo §四)
*
* Server Component 入口:仅负责 Suspense 边界包裹useSearchParams 要求)。
* 业务逻辑在 CurriculumMapClientclient component中。
*
* 数据契约standardsCoverageHeatmap / globalLessonPlanStats ❌ schema 无 → MSW 兜底(@contract-pending
*
* 关联ARCHITECTURE.md §5.3 / §5.4 / §7.3 / §9.4 / §10 P5 / §11.3 / §11.4
*/
export default function CurriculumMapPage(): React.ReactElement {
return (
<Suspense fallback={<ListPageSkeleton rows={5} />}>
<CurriculumMapClient />
</Suspense>
);
}

View File

@@ -0,0 +1,38 @@
"use client";
/**
* 选修课编辑路由错误边界ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment error.tsx捕获子树未处理异常。
*/
import { useEffect } from "react";
import { Button } from "@/shared/components/ui/button";
import { useTranslations } from "next-intl";
export default function ElectiveEditError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}): React.ReactElement {
const t = useTranslations("admin.elective");
useEffect(() => {
console.error("[portal-shell] admin elective edit route error:", error);
}, [error]);
return (
<div className="flex flex-col items-center justify-center gap-4 rounded-xl border border-destructive/30 bg-destructive/5 p-10">
<h2 className="text-lg font-semibold text-destructive">
{t("error.title")}
</h2>
<p className="text-sm text-muted-foreground">
{error.message || t("error.unknown")}
</p>
<Button onClick={reset} variant="outline">
{t("error.retry")}
</Button>
</div>
);
}

View File

@@ -0,0 +1,9 @@
import { FormPageSkeleton } from "@/shared/components/page-templates";
/**
* 选修课编辑路由段加载骨架ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment loading.tsx自动包裹页面渲染期间。
*/
export default function ElectiveEditLoading(): React.ReactElement {
return <FormPageSkeleton />;
}

View File

@@ -0,0 +1,22 @@
import { Suspense } from "react";
import { ElectiveEditClient } from "@/features/admin/elective/elective-edit-client";
import { FormPageSkeleton } from "@/shared/components/page-templates";
/**
* 选修课编辑表单页ARCHITECTURE.md §7.3 表单页 / §9.4 / §10 P5 / admin-NeedTodo §四)
*
* Server Component 入口:仅负责 Suspense 边界包裹useParams 要求)。
* 业务逻辑在 ElectiveEditClientclient component中。
*
* 数据契约adminElective(id) + updateElective(id, input) ❌ schema 无 → MSW 兜底(@contract-pending
*
* 关联ARCHITECTURE.md §5.3 / §5.4 / §7.3 / §9.4 / §10 P5 / §11.3 / §11.4
*/
export default function ElectiveEditPage(): React.ReactElement {
return (
<Suspense fallback={<FormPageSkeleton />}>
<ElectiveEditClient />
</Suspense>
);
}

View File

@@ -0,0 +1,38 @@
"use client";
/**
* 选修课详情路由错误边界ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment error.tsx捕获子树未处理异常。
*/
import { useEffect } from "react";
import { Button } from "@/shared/components/ui/button";
import { useTranslations } from "next-intl";
export default function ElectiveDetailError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}): React.ReactElement {
const t = useTranslations("admin.elective");
useEffect(() => {
console.error("[portal-shell] admin elective detail route error:", error);
}, [error]);
return (
<div className="flex flex-col items-center justify-center gap-4 rounded-xl border border-destructive/30 bg-destructive/5 p-10">
<h2 className="text-lg font-semibold text-destructive">
{t("error.title")}
</h2>
<p className="text-sm text-muted-foreground">
{error.message || t("error.unknown")}
</p>
<Button onClick={reset} variant="outline">
{t("error.retry")}
</Button>
</div>
);
}

View File

@@ -0,0 +1,9 @@
import { DetailPageSkeleton } from "@/shared/components/page-templates";
/**
* 选修课详情路由段加载骨架ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment loading.tsx自动包裹页面渲染期间。
*/
export default function ElectiveDetailLoading(): React.ReactElement {
return <DetailPageSkeleton />;
}

View File

@@ -0,0 +1,22 @@
import { Suspense } from "react";
import { ElectiveDetailClient } from "@/features/admin/elective/elective-detail-client";
import { DetailPageSkeleton } from "@/shared/components/page-templates";
/**
* 选修课详情页ARCHITECTURE.md §7.3 详情页 / §9.4 / §10 P5 / admin-NeedTodo §四)
*
* Server Component 入口:仅负责 Suspense 边界包裹。
* 业务逻辑在 ElectiveDetailClientclient component中。
*
* 数据契约adminElective(id: ID!) ❌ schema 无 → MSW 兜底(@contract-pending
*
* 关联ARCHITECTURE.md §5.5 / §7.3 / §9.4 / §10 P5 / §11.3
*/
export default function ElectiveDetailPage(): React.ReactElement {
return (
<Suspense fallback={<DetailPageSkeleton />}>
<ElectiveDetailClient />
</Suspense>
);
}

View File

@@ -0,0 +1,38 @@
"use client";
/**
* 选修课新建路由错误边界ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment error.tsx捕获子树未处理异常。
*/
import { useEffect } from "react";
import { Button } from "@/shared/components/ui/button";
import { useTranslations } from "next-intl";
export default function ElectiveCreateError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}): React.ReactElement {
const t = useTranslations("admin.elective");
useEffect(() => {
console.error("[portal-shell] admin elective create route error:", error);
}, [error]);
return (
<div className="flex flex-col items-center justify-center gap-4 rounded-xl border border-destructive/30 bg-destructive/5 p-10">
<h2 className="text-lg font-semibold text-destructive">
{t("error.title")}
</h2>
<p className="text-sm text-muted-foreground">
{error.message || t("error.unknown")}
</p>
<Button onClick={reset} variant="outline">
{t("error.retry")}
</Button>
</div>
);
}

View File

@@ -0,0 +1,9 @@
import { FormPageSkeleton } from "@/shared/components/page-templates";
/**
* 选修课新建路由段加载骨架ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment loading.tsx自动包裹页面渲染期间。
*/
export default function ElectiveCreateLoading(): React.ReactElement {
return <FormPageSkeleton />;
}

View File

@@ -0,0 +1,22 @@
import { Suspense } from "react";
import { ElectiveCreateClient } from "@/features/admin/elective/elective-create-client";
import { FormPageSkeleton } from "@/shared/components/page-templates";
/**
* 选修课新建表单页ARCHITECTURE.md §7.3 表单页 / §9.4 / §10 P5 / admin-NeedTodo §四)
*
* Server Component 入口:仅负责 Suspense 边界包裹。
* 业务逻辑在 ElectiveCreateClientclient component中。
*
* 数据契约createElective(input) ❌ schema 无 Mutation 类型 → MSW 兜底(@contract-pending
*
* 关联ARCHITECTURE.md §5.3 / §5.4 / §7.3 / §9.4 / §10 P5 / §11.3 / §11.4
*/
export default function ElectiveCreatePage(): React.ReactElement {
return (
<Suspense fallback={<FormPageSkeleton />}>
<ElectiveCreateClient />
</Suspense>
);
}

View File

@@ -0,0 +1,38 @@
"use client";
/**
* 选修课管理路由错误边界ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment error.tsx捕获子树未处理异常。
*/
import { useEffect } from "react";
import { Button } from "@/shared/components/ui/button";
import { useTranslations } from "next-intl";
export default function ElectiveListError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}): React.ReactElement {
const t = useTranslations("admin.elective");
useEffect(() => {
console.error("[portal-shell] admin elective list route error:", error);
}, [error]);
return (
<div className="flex flex-col items-center justify-center gap-4 rounded-xl border border-destructive/30 bg-destructive/5 p-10">
<h2 className="text-lg font-semibold text-destructive">
{t("error.title")}
</h2>
<p className="text-sm text-muted-foreground">
{error.message || t("error.unknown")}
</p>
<Button onClick={reset} variant="outline">
{t("error.retry")}
</Button>
</div>
);
}

View File

@@ -0,0 +1,9 @@
import { ListPageSkeleton } from "@/shared/components/page-templates";
/**
* 选修课管理路由段加载骨架ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment loading.tsx自动包裹页面渲染期间。
*/
export default function ElectiveListLoading(): React.ReactElement {
return <ListPageSkeleton rows={5} />;
}

View File

@@ -0,0 +1,22 @@
import { Suspense } from "react";
import { ElectiveListClient } from "@/features/admin/elective/elective-list-client";
import { ListPageSkeleton } from "@/shared/components/page-templates";
/**
* 选修课管理列表页ARCHITECTURE.md §7.3 列表页 / §9.4 / §10 P5 / admin-NeedTodo §四)
*
* Server Component 入口:仅负责 Suspense 边界包裹useSearchParams 要求)。
* 业务逻辑在 ElectiveListClientclient component中。
*
* 数据契约adminElectives ❌ schema 无 → MSW 兜底(@contract-pending
*
* 关联ARCHITECTURE.md §5.3 / §5.4 / §7.3 / §9.4 / §10 P5 / §11.3 / §11.4
*/
export default function ElectiveListPage(): React.ReactElement {
return (
<Suspense fallback={<ListPageSkeleton rows={5} />}>
<ElectiveListClient />
</Suspense>
);
}

View File

@@ -0,0 +1,36 @@
"use client";
/**
* 错题本分析路由错误边界ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment error.tsx捕获子树未处理异常。
*/
import { useEffect } from "react";
import { Button } from "@/shared/components/ui/button";
import { useTranslations } from "next-intl";
export default function AdminErrorBookError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}): React.ReactElement {
const t = useTranslations("admin.errorBook.error");
useEffect(() => {
console.error("[portal-shell] admin error-book route error:", error);
}, [error]);
return (
<div className="flex flex-col items-center justify-center gap-4 rounded-xl border border-destructive/30 bg-destructive/5 p-10">
<h2 className="text-lg font-semibold text-destructive">{t("title")}</h2>
<p className="text-sm text-muted-foreground">
{error.message || t("unknown")}
</p>
<Button onClick={reset} variant="outline">
{t("retry")}
</Button>
</div>
);
}

View File

@@ -0,0 +1,9 @@
import { ListPageSkeleton } from "@/shared/components/page-templates";
/**
* 错题本分析路由段加载骨架ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment loading.tsx自动包裹页面渲染期间。
*/
export default function AdminErrorBookLoading(): React.ReactElement {
return <ListPageSkeleton rows={5} />;
}

View File

@@ -0,0 +1,22 @@
import { Suspense } from "react";
import { ErrorBookClient } from "@/features/admin/error-book/error-book-client";
import { ListPageSkeleton } from "@/shared/components/page-templates";
/**
* 错题本分析页ARCHITECTURE.md §7.3 列表页 / §9.4 / §10 P5 / admin-NeedTodo §四)
*
* Server Component 入口:仅负责 Suspense 边界包裹useSearchParams 要求)。
* 业务逻辑在 ErrorBookClientclient component中。
*
* 数据契约adminErrorBookStats ❌ schema 无 → MSW 兜底(@contract-pending
*
* 关联ARCHITECTURE.md §5.3 / §5.4 / §7.3 / §9.4 / §10 P5 / §11.3 / §11.4
*/
export default function AdminErrorBookPage(): React.ReactElement {
return (
<Suspense fallback={<ListPageSkeleton rows={5} />}>
<ErrorBookClient />
</Suspense>
);
}

View File

@@ -0,0 +1,38 @@
"use client";
/**
* 文件路由错误边界ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment error.tsx捕获子树未处理异常。
*/
import { useEffect } from "react";
import { Button } from "@/shared/components/ui/button";
import { useTranslations } from "next-intl";
export default function FilesError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}): React.ReactElement {
const t = useTranslations("admin.files");
useEffect(() => {
console.error("[portal-shell] files route error:", error);
}, [error]);
return (
<div className="flex flex-col items-center justify-center gap-4 rounded-xl border border-destructive/30 bg-destructive/5 p-10">
<h2 className="text-lg font-semibold text-destructive">
{t("error.title")}
</h2>
<p className="text-sm text-muted-foreground">
{error.message || t("error.unknown")}
</p>
<Button onClick={reset} variant="outline">
{t("error.retry")}
</Button>
</div>
);
}

View File

@@ -0,0 +1,9 @@
import { ListPageSkeleton } from "@/shared/components/page-templates";
/**
* 文件路由段加载骨架ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment loading.tsx自动包裹页面渲染期间。
*/
export default function FilesLoading(): React.ReactElement {
return <ListPageSkeleton rows={5} />;
}

View File

@@ -0,0 +1,22 @@
import { Suspense } from "react";
import { FilesListClient } from "@/features/admin/files/files-list-client";
import { ListPageSkeleton } from "@/shared/components/page-templates";
/**
* 文件管理列表页ARCHITECTURE.md §7.3 列表页 / §9.4 / §10 P5
*
* Server Component 入口:仅负责 Suspense 边界包裹。
* 业务逻辑在 FilesListClientclient component中。
*
* 数据契约fileAttachments(filter) + fileStats ❌ schema 无 → MSW 兜底(@contract-pending
*
* 关联ARCHITECTURE.md §5.3 / §5.4 / §7.3 / §9.4 / §10 P5 / §11.3 / §11.4
*/
export default function FilesListPage(): React.ReactElement {
return (
<Suspense fallback={<ListPageSkeleton rows={5} />}>
<FilesListClient />
</Suspense>
);
}

View File

@@ -0,0 +1,36 @@
"use client";
/**
* 邀请码管理路由错误边界ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment error.tsx捕获子树未处理异常。
*/
import { useEffect } from "react";
import { Button } from "@/shared/components/ui/button";
import { useTranslations } from "next-intl";
export default function InvitationCodesError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}): React.ReactElement {
const t = useTranslations("admin.invitationCodes.error");
useEffect(() => {
console.error("[portal-shell] admin invitation-codes route error:", error);
}, [error]);
return (
<div className="flex flex-col items-center justify-center gap-4 rounded-xl border border-destructive/30 bg-destructive/5 p-10">
<h2 className="text-lg font-semibold text-destructive">{t("title")}</h2>
<p className="text-sm text-muted-foreground">
{error.message || t("unknown")}
</p>
<Button onClick={reset} variant="outline">
{t("retry")}
</Button>
</div>
);
}

View File

@@ -0,0 +1,9 @@
import { ListPageSkeleton } from "@/shared/components/page-templates";
/**
* 邀请码管理路由段加载骨架ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment loading.tsx自动包裹页面渲染期间。
*/
export default function InvitationCodesLoading(): React.ReactElement {
return <ListPageSkeleton rows={5} />;
}

View File

@@ -0,0 +1,22 @@
import { Suspense } from "react";
import { InvitationCodesListClient } from "@/features/admin/invitation-codes/invitation-codes-list-client";
import { ListPageSkeleton } from "@/shared/components/page-templates";
/**
* 邀请码管理列表页ARCHITECTURE.md §7.3 列表页 / §9.4 / §10 P5
*
* Server Component 入口:仅负责 Suspense 边界包裹useSearchParams 要求)。
* 业务逻辑在 InvitationCodesListClientclient component中。
*
* 数据契约invitationCodes(status) / createInvitationCode / revokeInvitationCode ❌ schema 无 → MSW 兜底(@contract-pending
*
* 关联ARCHITECTURE.md §5.3 / §5.4 / §7.3 / §9.4 / §10 P5 / §11.3 / §11.4
*/
export default function InvitationCodesListPage(): React.ReactElement {
return (
<Suspense fallback={<ListPageSkeleton rows={5} />}>
<InvitationCodesListClient />
</Suspense>
);
}

View File

@@ -0,0 +1,38 @@
"use client";
/**
* 教案详情只读路由错误边界ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment error.tsx捕获子树未处理异常。
*/
import { useEffect } from "react";
import { useTranslations } from "next-intl";
import { Button } from "@/shared/components/ui/button";
export default function AdminLessonPlanViewError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}): React.ReactElement {
const t = useTranslations("admin.lessonPlans");
useEffect(() => {
console.error("[portal-shell] admin lesson plan view route error:", error);
}, [error]);
return (
<div className="flex flex-col items-center justify-center gap-4 rounded-xl border border-destructive/30 bg-destructive/5 p-10">
<h2 className="text-lg font-semibold text-destructive">
{t("error.title")}
</h2>
<p className="text-sm text-muted-foreground">
{error.message || t("error.unknown")}
</p>
<Button onClick={reset} variant="outline">
{t("error.retry")}
</Button>
</div>
);
}

View File

@@ -0,0 +1,9 @@
import { DetailPageSkeleton } from "@/shared/components/page-templates";
/**
* 教案详情只读路由段加载骨架ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment loading.tsx自动包裹页面渲染期间。
*/
export default function AdminLessonPlanViewLoading(): React.ReactElement {
return <DetailPageSkeleton />;
}

View File

@@ -0,0 +1,22 @@
import { Suspense } from "react";
import { AdminLessonPlanViewClient } from "@/features/admin/lesson-plans/lesson-plan-view-client";
import { DetailPageSkeleton } from "@/shared/components/page-templates";
/**
* 教案详情只读页ARCHITECTURE.md §7.3 详情页 / §9.4 / §10 P5
*
* Server Component 入口:仅负责 Suspense 边界包裹。
* 业务逻辑在 AdminLessonPlanViewClientclient component中。
*
* 数据契约adminLessonPlan(id: ID!) ❌ schema 无 → MSW 兜底(@contract-pending
*
* 关联ARCHITECTURE.md §5.5 / §7.3 / §9.4 / §10 P5 / §11.3
*/
export default function AdminLessonPlanViewPage(): React.ReactElement {
return (
<Suspense fallback={<DetailPageSkeleton />}>
<AdminLessonPlanViewClient />
</Suspense>
);
}

View File

@@ -0,0 +1,38 @@
"use client";
/**
* 教案管理路由错误边界ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment error.tsx捕获子树未处理异常。
*/
import { useEffect } from "react";
import { useTranslations } from "next-intl";
import { Button } from "@/shared/components/ui/button";
export default function AdminLessonPlansError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}): React.ReactElement {
const t = useTranslations("admin.lessonPlans");
useEffect(() => {
console.error("[portal-shell] admin lesson plans route error:", error);
}, [error]);
return (
<div className="flex flex-col items-center justify-center gap-4 rounded-xl border border-destructive/30 bg-destructive/5 p-10">
<h2 className="text-lg font-semibold text-destructive">
{t("error.title")}
</h2>
<p className="text-sm text-muted-foreground">
{error.message || t("error.unknown")}
</p>
<Button onClick={reset} variant="outline">
{t("error.retry")}
</Button>
</div>
);
}

View File

@@ -0,0 +1,9 @@
import { ListPageSkeleton } from "@/shared/components/page-templates";
/**
* 教案管理路由段加载骨架ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment loading.tsx自动包裹页面渲染期间。
*/
export default function AdminLessonPlansLoading(): React.ReactElement {
return <ListPageSkeleton rows={5} />;
}

View File

@@ -0,0 +1,22 @@
import { Suspense } from "react";
import { AdminLessonPlansListClient } from "@/features/admin/lesson-plans/lesson-plans-list-client";
import { ListPageSkeleton } from "@/shared/components/page-templates";
/**
* 教案管理列表页ARCHITECTURE.md §7.3 列表页 / §9.4 / §10 P5
*
* Server Component 入口:仅负责 Suspense 边界包裹useSearchParams 要求)。
* 业务逻辑在 AdminLessonPlansListClientclient component中。
*
* 数据契约adminLessonPlans ❌ schema 无 → MSW 兜底(@contract-pending
*
* 关联ARCHITECTURE.md §5.3 / §5.4 / §7.3 / §9.4 / §10 P5 / §11.3 / §11.4
*/
export default function AdminLessonPlansListPage(): React.ReactElement {
return (
<Suspense fallback={<ListPageSkeleton rows={5} />}>
<AdminLessonPlansListClient />
</Suspense>
);
}

View File

@@ -0,0 +1,38 @@
"use client";
/**
* 组织管理路由错误边界ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment error.tsx捕获子树未处理异常。
*/
import { useEffect } from "react";
import { Button } from "@/shared/components/ui/button";
import { useTranslations } from "next-intl";
export default function OrganizationError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}): React.ReactElement {
const t = useTranslations("admin.organization");
useEffect(() => {
console.error("[portal-shell] admin organization route error:", error);
}, [error]);
return (
<div className="flex flex-col items-center justify-center gap-4 rounded-xl border border-destructive/30 bg-destructive/5 p-10">
<h2 className="text-lg font-semibold text-destructive">
{t("error.title")}
</h2>
<p className="text-sm text-muted-foreground">
{error.message || t("error.unknown")}
</p>
<Button onClick={reset} variant="outline">
{t("error.retry")}
</Button>
</div>
);
}

View File

@@ -0,0 +1,9 @@
import { ListPageSkeleton } from "@/shared/components/page-templates";
/**
* 组织管理路由段加载骨架ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment loading.tsx自动包裹页面渲染期间。
*/
export default function OrganizationLoading(): React.ReactElement {
return <ListPageSkeleton rows={5} />;
}

View File

@@ -0,0 +1,22 @@
import { Suspense } from "react";
import { OrganizationTreeClient } from "@/features/admin/organization/organization-tree-client";
import { ListPageSkeleton } from "@/shared/components/page-templates";
/**
* 组织管理树视图页ARCHITECTURE.md §7.3 列表页 / §9.4 / §10 P5
*
* Server Component 入口:仅负责 Suspense 边界包裹useSearchParams 要求)。
* 业务逻辑在 OrganizationTreeClientclient component中。
*
* 数据契约organizationTree() ❌ schema 无 → MSW 兜底(@contract-pending
*
* 关联ARCHITECTURE.md §5.3 / §5.4 / §7.3 / §9.4 / §10 P5 / §11.3 / §11.4
*/
export default function OrganizationTreePage(): React.ReactElement {
return (
<Suspense fallback={<ListPageSkeleton rows={5} />}>
<OrganizationTreeClient />
</Suspense>
);
}

View File

@@ -0,0 +1,38 @@
"use client";
/**
* 权限目录路由错误边界ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment error.tsx捕获子树未处理异常。
*/
import { useEffect } from "react";
import { Button } from "@/shared/components/ui/button";
import { useTranslations } from "next-intl";
export default function PermissionsError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}): React.ReactElement {
const t = useTranslations("admin.permissions");
useEffect(() => {
console.error("[portal-shell] admin permissions route error:", error);
}, [error]);
return (
<div className="flex flex-col items-center justify-center gap-4 rounded-xl border border-destructive/30 bg-destructive/5 p-10">
<h2 className="text-lg font-semibold text-destructive">
{t("error.title")}
</h2>
<p className="text-sm text-muted-foreground">
{error.message || t("error.unknown")}
</p>
<Button onClick={reset} variant="outline">
{t("error.retry")}
</Button>
</div>
);
}

View File

@@ -0,0 +1,9 @@
import { ListPageSkeleton } from "@/shared/components/page-templates";
/**
* 权限目录路由段加载骨架ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment loading.tsx自动包裹页面渲染期间。
*/
export default function PermissionsLoading(): React.ReactElement {
return <ListPageSkeleton rows={5} />;
}

View File

@@ -0,0 +1,23 @@
import { Suspense } from "react";
import { PermissionsListClient } from "@/features/admin/permissions/permissions-list-client";
import { ListPageSkeleton } from "@/shared/components/page-templates";
/**
* 权限目录列表页ARCHITECTURE.md §7.3 列表页 / §9.4 / §10 P5
*
* Server Component 入口:仅负责 Suspense 边界包裹useSearchParams 要求)。
* 业务逻辑在 PermissionsListClientclient component中。
*
* 数据契约permissions / permissionRoleCounts ❌ schema 无 → MSW 兜底(@contract-pending
* 契约工单docs/architecture/issues/contracts/iam_contract.md#permissions
*
* 关联ARCHITECTURE.md §5.3 / §5.4 / §7.3 / §9.4 / §10 P5 / §11.3 / §11.4
*/
export default function PermissionsListPage(): React.ReactElement {
return (
<Suspense fallback={<ListPageSkeleton rows={5} />}>
<PermissionsListClient />
</Suspense>
);
}

View File

@@ -0,0 +1,38 @@
"use client";
/**
* 插件管理路由错误边界ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment error.tsx捕获子树未处理异常。
*/
import { useEffect } from "react";
import { Button } from "@/shared/components/ui/button";
import { useTranslations } from "next-intl";
export default function PluginsError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}): React.ReactElement {
const t = useTranslations("admin.plugins");
useEffect(() => {
console.error("[portal-shell] plugins route error:", error);
}, [error]);
return (
<div className="flex flex-col items-center justify-center gap-4 rounded-xl border border-destructive/30 bg-destructive/5 p-10">
<h2 className="text-lg font-semibold text-destructive">
{t("error.title")}
</h2>
<p className="text-sm text-muted-foreground">
{error.message || t("error.unknown")}
</p>
<Button onClick={reset} variant="outline">
{t("error.retry")}
</Button>
</div>
);
}

View File

@@ -0,0 +1,9 @@
import { ListPageSkeleton } from "@/shared/components/page-templates";
/**
* 插件管理路由段加载骨架ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment loading.tsx自动包裹页面渲染期间。
*/
export default function PluginsLoading(): React.ReactElement {
return <ListPageSkeleton rows={5} />;
}

View File

@@ -0,0 +1,24 @@
import { Suspense } from "react";
import { PluginsClient } from "@/features/admin/plugins/plugins-client";
import { ListPageSkeleton } from "@/shared/components/page-templates";
/**
* 插件管理ARCHITECTURE.md §7.3 列表页 / §9.4 / §10 P5 / 004 §5.4
*
* Server Component 入口:仅负责 Suspense 边界包裹useSearchParams 要求)。
* 业务逻辑在 PluginsClientclient component中。
*
* 数据契约:
* - pluginRegistry ✅ schema 已就绪config-service
* - updatePluginRegistry(pluginId, input) ✅ schema 已就绪
*
* 关联ARCHITECTURE.md §5.3 / §5.4 / §7.3 / §9.4 / §10 P5 / §11.3 / §11.4
*/
export default function PluginsPage(): React.ReactElement {
return (
<Suspense fallback={<ListPageSkeleton rows={5} />}>
<PluginsClient />
</Suspense>
);
}

View File

@@ -0,0 +1,38 @@
"use client";
/**
* 题库管理路由错误边界ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment error.tsx捕获子树未处理异常。
*/
import { useEffect } from "react";
import { useTranslations } from "next-intl";
import { Button } from "@/shared/components/ui/button";
export default function AdminQuestionsError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}): React.ReactElement {
const t = useTranslations("admin.questions");
useEffect(() => {
console.error("[portal-shell] admin questions route error:", error);
}, [error]);
return (
<div className="flex flex-col items-center justify-center gap-4 rounded-xl border border-destructive/30 bg-destructive/5 p-10">
<h2 className="text-lg font-semibold text-destructive">
{t("error.title")}
</h2>
<p className="text-sm text-muted-foreground">
{error.message || t("error.unknown")}
</p>
<Button onClick={reset} variant="outline">
{t("error.retry")}
</Button>
</div>
);
}

View File

@@ -0,0 +1,9 @@
import { ListPageSkeleton } from "@/shared/components/page-templates";
/**
* 题库管理路由段加载骨架ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment loading.tsx自动包裹页面渲染期间。
*/
export default function AdminQuestionsLoading(): React.ReactElement {
return <ListPageSkeleton rows={5} />;
}

View File

@@ -0,0 +1,22 @@
import { Suspense } from "react";
import { AdminQuestionsListClient } from "@/features/admin/questions/questions-list-client";
import { ListPageSkeleton } from "@/shared/components/page-templates";
/**
* 题库管理列表页ARCHITECTURE.md §7.3 列表页 / §9.4 / §10 P5
*
* Server Component 入口:仅负责 Suspense 边界包裹useSearchParams 要求)。
* 业务逻辑在 AdminQuestionsListClientclient component中。
*
* 数据契约adminQuestions(filter) ❌ schema 无 → MSW 兜底(@contract-pending
*
* 关联ARCHITECTURE.md §5.3 / §5.4 / §7.3 / §9.4 / §10 P5 / §11.3 / §11.4
*/
export default function AdminQuestionsListPage(): React.ReactElement {
return (
<Suspense fallback={<ListPageSkeleton rows={5} />}>
<AdminQuestionsListClient />
</Suspense>
);
}

View File

@@ -0,0 +1,38 @@
"use client";
/**
* 角色详情路由错误边界ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment error.tsx捕获子树未处理异常。
*/
import { useEffect } from "react";
import { Button } from "@/shared/components/ui/button";
import { useTranslations } from "next-intl";
export default function RoleDetailError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}): React.ReactElement {
const t = useTranslations("admin.roles");
useEffect(() => {
console.error("[portal-shell] admin role detail route error:", error);
}, [error]);
return (
<div className="flex flex-col items-center justify-center gap-4 rounded-xl border border-destructive/30 bg-destructive/5 p-10">
<h2 className="text-lg font-semibold text-destructive">
{t("error.title")}
</h2>
<p className="text-sm text-muted-foreground">
{error.message || t("error.unknown")}
</p>
<Button onClick={reset} variant="outline">
{t("error.retry")}
</Button>
</div>
);
}

View File

@@ -0,0 +1,9 @@
import { DetailPageSkeleton } from "@/shared/components/page-templates";
/**
* 角色详情路由段加载骨架ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment loading.tsx自动包裹页面渲染期间。
*/
export default function RoleDetailLoading(): React.ReactElement {
return <DetailPageSkeleton />;
}

View File

@@ -0,0 +1,22 @@
import { Suspense } from "react";
import { RoleDetailClient } from "@/features/admin/roles/role-detail-client";
import { DetailPageSkeleton } from "@/shared/components/page-templates";
/**
* 角色详情页ARCHITECTURE.md §7.3 详情页 / §9.4 / §10 P5
*
* Server Component 入口:仅负责 Suspense 边界包裹。
* 业务逻辑在 RoleDetailClientclient component中。
*
* 数据契约role(id: ID!) ❌ schema 无 → MSW 兜底(@contract-pending
*
* 关联ARCHITECTURE.md §5.5 / §7.3 / §9.4 / §10 P5 / §11.3
*/
export default function RoleDetailPage(): React.ReactElement {
return (
<Suspense fallback={<DetailPageSkeleton />}>
<RoleDetailClient />
</Suspense>
);
}

View File

@@ -0,0 +1,38 @@
"use client";
/**
* 角色管理路由错误边界ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment error.tsx捕获子树未处理异常。
*/
import { useEffect } from "react";
import { Button } from "@/shared/components/ui/button";
import { useTranslations } from "next-intl";
export default function RolesError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}): React.ReactElement {
const t = useTranslations("admin.roles");
useEffect(() => {
console.error("[portal-shell] admin roles route error:", error);
}, [error]);
return (
<div className="flex flex-col items-center justify-center gap-4 rounded-xl border border-destructive/30 bg-destructive/5 p-10">
<h2 className="text-lg font-semibold text-destructive">
{t("error.title")}
</h2>
<p className="text-sm text-muted-foreground">
{error.message || t("error.unknown")}
</p>
<Button onClick={reset} variant="outline">
{t("error.retry")}
</Button>
</div>
);
}

View File

@@ -0,0 +1,9 @@
import { ListPageSkeleton } from "@/shared/components/page-templates";
/**
* 角色管理路由段加载骨架ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment loading.tsx自动包裹页面渲染期间。
*/
export default function RolesLoading(): React.ReactElement {
return <ListPageSkeleton rows={5} />;
}

View File

@@ -0,0 +1,23 @@
import { Suspense } from "react";
import { RolesListClient } from "@/features/admin/roles/roles-list-client";
import { ListPageSkeleton } from "@/shared/components/page-templates";
/**
* 角色管理列表页ARCHITECTURE.md §7.3 列表页 / §9.4 / §10 P5
*
* Server Component 入口:仅负责 Suspense 边界包裹useSearchParams 要求)。
* 业务逻辑在 RolesListClientclient component中。
*
* 数据契约roles ❌ schema 无 → MSW 兜底(@contract-pending
* 契约工单docs/architecture/issues/contracts/iam_contract.md#roles
*
* 关联ARCHITECTURE.md §5.3 / §5.4 / §7.3 / §9.4 / §10 P5 / §11.3 / §11.4
*/
export default function RolesListPage(): React.ReactElement {
return (
<Suspense fallback={<ListPageSkeleton rows={5} />}>
<RolesListClient />
</Suspense>
);
}

View File

@@ -0,0 +1,36 @@
"use client";
/**
* 自动排课路由错误边界ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment error.tsx捕获子树未处理异常。
*/
import { useEffect } from "react";
import { Button } from "@/shared/components/ui/button";
import { useTranslations } from "next-intl";
export default function AdminSchedulingAutoError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}): React.ReactElement {
const t = useTranslations("admin.scheduling.error");
useEffect(() => {
console.error("[portal-shell] admin scheduling/auto route error:", error);
}, [error]);
return (
<div className="flex flex-col items-center justify-center gap-4 rounded-xl border border-destructive/30 bg-destructive/5 p-10">
<h2 className="text-lg font-semibold text-destructive">{t("title")}</h2>
<p className="text-sm text-muted-foreground">
{error.message || t("unknown")}
</p>
<Button onClick={reset} variant="outline">
{t("retry")}
</Button>
</div>
);
}

View File

@@ -0,0 +1,9 @@
import { ListPageSkeleton } from "@/shared/components/page-templates";
/**
* 自动排课路由段加载骨架ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment loading.tsx自动包裹页面渲染期间。
*/
export default function AdminSchedulingAutoLoading(): React.ReactElement {
return <ListPageSkeleton rows={5} />;
}

View File

@@ -0,0 +1,22 @@
import { Suspense } from "react";
import { AutoScheduleClient } from "@/features/admin/scheduling/auto-schedule-client";
import { ListPageSkeleton } from "@/shared/components/page-templates";
/**
* 自动排课页ARCHITECTURE.md §7.3 列表页 / §9.4 / §10 P5 / admin-NeedTodo §四)
*
* Server Component 入口:仅负责 Suspense 边界包裹。
* 业务逻辑在 AutoScheduleClientclient component中。
*
* 数据契约adminClasses / autoSchedule ❌ schema 无 → MSW 兜底(@contract-pending
*
* 关联ARCHITECTURE.md §5.3 / §5.4 / §7.3 / §9.4 / §10 P5 / §11.3 / §11.4
*/
export default function AdminSchedulingAutoPage(): React.ReactElement {
return (
<Suspense fallback={<ListPageSkeleton rows={5} />}>
<AutoScheduleClient />
</Suspense>
);
}

View File

@@ -0,0 +1,39 @@
"use client";
/**
* 排课变更审批路由错误边界ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment error.tsx捕获子树未处理异常。
*/
import { useEffect } from "react";
import { Button } from "@/shared/components/ui/button";
import { useTranslations } from "next-intl";
export default function AdminSchedulingChangesError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}): React.ReactElement {
const t = useTranslations("admin.scheduling.error");
useEffect(() => {
console.error(
"[portal-shell] admin scheduling/changes route error:",
error,
);
}, [error]);
return (
<div className="flex flex-col items-center justify-center gap-4 rounded-xl border border-destructive/30 bg-destructive/5 p-10">
<h2 className="text-lg font-semibold text-destructive">{t("title")}</h2>
<p className="text-sm text-muted-foreground">
{error.message || t("unknown")}
</p>
<Button onClick={reset} variant="outline">
{t("retry")}
</Button>
</div>
);
}

View File

@@ -0,0 +1,9 @@
import { ListPageSkeleton } from "@/shared/components/page-templates";
/**
* 排课变更审批路由段加载骨架ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment loading.tsx自动包裹页面渲染期间。
*/
export default function AdminSchedulingChangesLoading(): React.ReactElement {
return <ListPageSkeleton rows={5} />;
}

View File

@@ -0,0 +1,22 @@
import { Suspense } from "react";
import { ScheduleChangesClient } from "@/features/admin/scheduling/schedule-changes-client";
import { ListPageSkeleton } from "@/shared/components/page-templates";
/**
* 排课变更审批页ARCHITECTURE.md §7.3 列表页 / §9.4 / §10 P5 / admin-NeedTodo §四)
*
* Server Component 入口:仅负责 Suspense 边界包裹。
* 业务逻辑在 ScheduleChangesClientclient component中。
*
* 数据契约adminScheduleChanges / adminScheduleEntries / approve-reject ❌ schema 无 → MSW 兜底(@contract-pending
*
* 关联ARCHITECTURE.md §5.3 / §5.4 / §7.3 / §9.4 / §10 P5 / §11.3 / §11.4
*/
export default function AdminSchedulingChangesPage(): React.ReactElement {
return (
<Suspense fallback={<ListPageSkeleton rows={5} />}>
<ScheduleChangesClient />
</Suspense>
);
}

View File

@@ -0,0 +1,36 @@
"use client";
/**
* 排课规则配置路由错误边界ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment error.tsx捕获子树未处理异常。
*/
import { useEffect } from "react";
import { Button } from "@/shared/components/ui/button";
import { useTranslations } from "next-intl";
export default function AdminSchedulingRulesError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}): React.ReactElement {
const t = useTranslations("admin.scheduling.error");
useEffect(() => {
console.error("[portal-shell] admin scheduling/rules route error:", error);
}, [error]);
return (
<div className="flex flex-col items-center justify-center gap-4 rounded-xl border border-destructive/30 bg-destructive/5 p-10">
<h2 className="text-lg font-semibold text-destructive">{t("title")}</h2>
<p className="text-sm text-muted-foreground">
{error.message || t("unknown")}
</p>
<Button onClick={reset} variant="outline">
{t("retry")}
</Button>
</div>
);
}

View File

@@ -0,0 +1,9 @@
import { FormPageSkeleton } from "@/shared/components/page-templates";
/**
* 排课规则配置路由段加载骨架ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment loading.tsx自动包裹页面渲染期间。
*/
export default function AdminSchedulingRulesLoading(): React.ReactElement {
return <FormPageSkeleton />;
}

View File

@@ -0,0 +1,22 @@
import { Suspense } from "react";
import { SchedulingRulesClient } from "@/features/admin/scheduling/scheduling-rules-client";
import { FormPageSkeleton } from "@/shared/components/page-templates";
/**
* 排课规则配置页ARCHITECTURE.md §7.3 表单页 / §9.4 / §10 P5 / admin-NeedTodo §四)
*
* Server Component 入口:仅负责 Suspense 边界包裹。
* 业务逻辑在 SchedulingRulesClientclient component中。
*
* 数据契约schedulingRules / updateSchedulingRules ❌ schema 无 → MSW 兜底(@contract-pending
*
* 关联ARCHITECTURE.md §5.3 / §5.4 / §7.3 / §9.4 / §10 P5 / §11.3 / §11.4
*/
export default function AdminSchedulingRulesPage(): React.ReactElement {
return (
<Suspense fallback={<FormPageSkeleton />}>
<SchedulingRulesClient />
</Suspense>
);
}

View File

@@ -0,0 +1,41 @@
"use client";
/**
* 学年管理路由错误边界ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment error.tsx捕获子树未处理异常。
*/
import { useEffect } from "react";
import { Button } from "@/shared/components/ui/button";
import { useTranslations } from "next-intl";
export default function AcademicYearError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}): React.ReactElement {
const t = useTranslations("admin.school.academicYear");
useEffect(() => {
console.error(
"[portal-shell] admin school academic-year route error:",
error,
);
}, [error]);
return (
<div className="flex flex-col items-center justify-center gap-4 rounded-xl border border-destructive/30 bg-destructive/5 p-10">
<h2 className="text-lg font-semibold text-destructive">
{t("error.title")}
</h2>
<p className="text-sm text-muted-foreground">
{error.message || t("error.unknown")}
</p>
<Button onClick={reset} variant="outline">
{t("error.retry")}
</Button>
</div>
);
}

View File

@@ -0,0 +1,9 @@
import { ListPageSkeleton } from "@/shared/components/page-templates";
/**
* 学年管理路由段加载骨架ARCHITECTURE.md §7.4 三态规范 / §11.3 DoD
* Next.js Route Segment loading.tsx自动包裹页面渲染期间。
*/
export default function AcademicYearLoading(): React.ReactElement {
return <ListPageSkeleton rows={5} />;
}

View File

@@ -0,0 +1,23 @@
import { Suspense } from "react";
import { AcademicYearClient } from "@/features/admin/school/academic-year-client";
import { ListPageSkeleton } from "@/shared/components/page-templates";
/**
* 学年管理列表页ARCHITECTURE.md §7.3 列表页 / §9.4 / §10 P5
*
* Server Component 入口:仅负责 Suspense 边界包裹useSearchParams 要求)。
* 业务逻辑在 AcademicYearClientclient component中。
*
* 数据契约academicYears() / createAcademicYear / updateAcademicYear / deleteAcademicYear
* ❌ schema 无 → MSW 兜底(@contract-pending
*
* 关联ARCHITECTURE.md §5.3 / §5.4 / §7.3 / §9.4 / §10 P5 / §11.3 / §11.4
*/
export default function AcademicYearListPage(): React.ReactElement {
return (
<Suspense fallback={<ListPageSkeleton rows={5} />}>
<AcademicYearClient />
</Suspense>
);
}

Some files were not shown because too many files have changed in this diff Show More