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:
@@ -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,
|
||||
|
||||
38
apps/portal-shell/src/app/shell/admin/ai-settings/error.tsx
Normal file
38
apps/portal-shell/src/app/shell/admin/ai-settings/error.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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} />;
|
||||
}
|
||||
24
apps/portal-shell/src/app/shell/admin/ai-settings/page.tsx
Normal file
24
apps/portal-shell/src/app/shell/admin/ai-settings/page.tsx
Normal 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 要求)。
|
||||
* 业务逻辑在 AiSettingsClient(client 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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 />;
|
||||
}
|
||||
@@ -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 要求)。
|
||||
* 业务逻辑在 AnnouncementEditClient(client 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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 />;
|
||||
}
|
||||
@@ -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 边界包裹。
|
||||
* 业务逻辑在 AnnouncementDetailClient(client 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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} />;
|
||||
}
|
||||
22
apps/portal-shell/src/app/shell/admin/announcements/page.tsx
Normal file
22
apps/portal-shell/src/app/shell/admin/announcements/page.tsx
Normal 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 要求)。
|
||||
* 业务逻辑在 AnnouncementsListClient(client 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>
|
||||
);
|
||||
}
|
||||
36
apps/portal-shell/src/app/shell/admin/attendance/error.tsx
Normal file
36
apps/portal-shell/src/app/shell/admin/attendance/error.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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} />;
|
||||
}
|
||||
23
apps/portal-shell/src/app/shell/admin/attendance/page.tsx
Normal file
23
apps/portal-shell/src/app/shell/admin/attendance/page.tsx
Normal 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 要求)。
|
||||
* 业务逻辑在 AdminAttendanceClient(client 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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} />;
|
||||
}
|
||||
@@ -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 要求)。
|
||||
* 业务逻辑在 DataChangesClient(client 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>
|
||||
);
|
||||
}
|
||||
36
apps/portal-shell/src/app/shell/admin/audit-logs/error.tsx
Normal file
36
apps/portal-shell/src/app/shell/admin/audit-logs/error.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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} />;
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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} />;
|
||||
}
|
||||
@@ -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 要求)。
|
||||
* 业务逻辑在 LoginLogsClient(client 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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 />;
|
||||
}
|
||||
@@ -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 边界包裹。
|
||||
* 业务逻辑在 AuditOverviewClient(client 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>
|
||||
);
|
||||
}
|
||||
22
apps/portal-shell/src/app/shell/admin/audit-logs/page.tsx
Normal file
22
apps/portal-shell/src/app/shell/admin/audit-logs/page.tsx
Normal 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 要求)。
|
||||
* 业务逻辑在 AuditLogsListClient(client 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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 />;
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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 />;
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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 />;
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
38
apps/portal-shell/src/app/shell/admin/course-plans/error.tsx
Normal file
38
apps/portal-shell/src/app/shell/admin/course-plans/error.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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} />;
|
||||
}
|
||||
22
apps/portal-shell/src/app/shell/admin/course-plans/page.tsx
Normal file
22
apps/portal-shell/src/app/shell/admin/course-plans/page.tsx
Normal 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 要求)。
|
||||
* 业务逻辑在 CoursePlansListClient(client 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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} />;
|
||||
}
|
||||
@@ -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 要求)。
|
||||
* 业务逻辑在 CurriculumMapClient(client 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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 />;
|
||||
}
|
||||
@@ -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 要求)。
|
||||
* 业务逻辑在 ElectiveEditClient(client 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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 />;
|
||||
}
|
||||
22
apps/portal-shell/src/app/shell/admin/elective/[id]/page.tsx
Normal file
22
apps/portal-shell/src/app/shell/admin/elective/[id]/page.tsx
Normal 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 边界包裹。
|
||||
* 业务逻辑在 ElectiveDetailClient(client 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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 />;
|
||||
}
|
||||
@@ -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 边界包裹。
|
||||
* 业务逻辑在 ElectiveCreateClient(client 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>
|
||||
);
|
||||
}
|
||||
38
apps/portal-shell/src/app/shell/admin/elective/error.tsx
Normal file
38
apps/portal-shell/src/app/shell/admin/elective/error.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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} />;
|
||||
}
|
||||
22
apps/portal-shell/src/app/shell/admin/elective/page.tsx
Normal file
22
apps/portal-shell/src/app/shell/admin/elective/page.tsx
Normal 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 要求)。
|
||||
* 业务逻辑在 ElectiveListClient(client 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>
|
||||
);
|
||||
}
|
||||
36
apps/portal-shell/src/app/shell/admin/error-book/error.tsx
Normal file
36
apps/portal-shell/src/app/shell/admin/error-book/error.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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} />;
|
||||
}
|
||||
22
apps/portal-shell/src/app/shell/admin/error-book/page.tsx
Normal file
22
apps/portal-shell/src/app/shell/admin/error-book/page.tsx
Normal 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 要求)。
|
||||
* 业务逻辑在 ErrorBookClient(client 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>
|
||||
);
|
||||
}
|
||||
38
apps/portal-shell/src/app/shell/admin/files/error.tsx
Normal file
38
apps/portal-shell/src/app/shell/admin/files/error.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
9
apps/portal-shell/src/app/shell/admin/files/loading.tsx
Normal file
9
apps/portal-shell/src/app/shell/admin/files/loading.tsx
Normal 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} />;
|
||||
}
|
||||
22
apps/portal-shell/src/app/shell/admin/files/page.tsx
Normal file
22
apps/portal-shell/src/app/shell/admin/files/page.tsx
Normal 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 边界包裹。
|
||||
* 业务逻辑在 FilesListClient(client 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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} />;
|
||||
}
|
||||
@@ -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 要求)。
|
||||
* 业务逻辑在 InvitationCodesListClient(client 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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 />;
|
||||
}
|
||||
@@ -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 边界包裹。
|
||||
* 业务逻辑在 AdminLessonPlanViewClient(client 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>
|
||||
);
|
||||
}
|
||||
38
apps/portal-shell/src/app/shell/admin/lesson-plans/error.tsx
Normal file
38
apps/portal-shell/src/app/shell/admin/lesson-plans/error.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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} />;
|
||||
}
|
||||
22
apps/portal-shell/src/app/shell/admin/lesson-plans/page.tsx
Normal file
22
apps/portal-shell/src/app/shell/admin/lesson-plans/page.tsx
Normal 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 要求)。
|
||||
* 业务逻辑在 AdminLessonPlansListClient(client 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>
|
||||
);
|
||||
}
|
||||
38
apps/portal-shell/src/app/shell/admin/organization/error.tsx
Normal file
38
apps/portal-shell/src/app/shell/admin/organization/error.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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} />;
|
||||
}
|
||||
22
apps/portal-shell/src/app/shell/admin/organization/page.tsx
Normal file
22
apps/portal-shell/src/app/shell/admin/organization/page.tsx
Normal 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 要求)。
|
||||
* 业务逻辑在 OrganizationTreeClient(client 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>
|
||||
);
|
||||
}
|
||||
38
apps/portal-shell/src/app/shell/admin/permissions/error.tsx
Normal file
38
apps/portal-shell/src/app/shell/admin/permissions/error.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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} />;
|
||||
}
|
||||
23
apps/portal-shell/src/app/shell/admin/permissions/page.tsx
Normal file
23
apps/portal-shell/src/app/shell/admin/permissions/page.tsx
Normal 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 要求)。
|
||||
* 业务逻辑在 PermissionsListClient(client 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>
|
||||
);
|
||||
}
|
||||
38
apps/portal-shell/src/app/shell/admin/plugins/error.tsx
Normal file
38
apps/portal-shell/src/app/shell/admin/plugins/error.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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} />;
|
||||
}
|
||||
24
apps/portal-shell/src/app/shell/admin/plugins/page.tsx
Normal file
24
apps/portal-shell/src/app/shell/admin/plugins/page.tsx
Normal 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 要求)。
|
||||
* 业务逻辑在 PluginsClient(client 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>
|
||||
);
|
||||
}
|
||||
38
apps/portal-shell/src/app/shell/admin/questions/error.tsx
Normal file
38
apps/portal-shell/src/app/shell/admin/questions/error.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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} />;
|
||||
}
|
||||
22
apps/portal-shell/src/app/shell/admin/questions/page.tsx
Normal file
22
apps/portal-shell/src/app/shell/admin/questions/page.tsx
Normal 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 要求)。
|
||||
* 业务逻辑在 AdminQuestionsListClient(client 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>
|
||||
);
|
||||
}
|
||||
38
apps/portal-shell/src/app/shell/admin/roles/[id]/error.tsx
Normal file
38
apps/portal-shell/src/app/shell/admin/roles/[id]/error.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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 />;
|
||||
}
|
||||
22
apps/portal-shell/src/app/shell/admin/roles/[id]/page.tsx
Normal file
22
apps/portal-shell/src/app/shell/admin/roles/[id]/page.tsx
Normal 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 边界包裹。
|
||||
* 业务逻辑在 RoleDetailClient(client 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>
|
||||
);
|
||||
}
|
||||
38
apps/portal-shell/src/app/shell/admin/roles/error.tsx
Normal file
38
apps/portal-shell/src/app/shell/admin/roles/error.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
9
apps/portal-shell/src/app/shell/admin/roles/loading.tsx
Normal file
9
apps/portal-shell/src/app/shell/admin/roles/loading.tsx
Normal 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} />;
|
||||
}
|
||||
23
apps/portal-shell/src/app/shell/admin/roles/page.tsx
Normal file
23
apps/portal-shell/src/app/shell/admin/roles/page.tsx
Normal 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 要求)。
|
||||
* 业务逻辑在 RolesListClient(client 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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} />;
|
||||
}
|
||||
@@ -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 边界包裹。
|
||||
* 业务逻辑在 AutoScheduleClient(client 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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} />;
|
||||
}
|
||||
@@ -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 边界包裹。
|
||||
* 业务逻辑在 ScheduleChangesClient(client 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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 />;
|
||||
}
|
||||
@@ -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 边界包裹。
|
||||
* 业务逻辑在 SchedulingRulesClient(client 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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} />;
|
||||
}
|
||||
@@ -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 要求)。
|
||||
* 业务逻辑在 AcademicYearClient(client 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
Reference in New Issue
Block a user