feat(portal-shell): restore codegen typescript-operations for data-ana domain (P1-7)

ARCHITECTURE.md §10 P1-7: dashboard domain's 6 operations strictly
match the schema, so disable skipDocumentsValidation for that output
and restore per-operation type generation.

Changes:
- codegen.yml: add dashboard-types.ts output (typescript +
  typescript-operations plugins, skipDocumentsValidation: false);
  move documents config into each generates entry
- dashboard.ts: remove 14 handwritten interfaces and 6 internal query
  type aliases; derive types via NonNullable<GetXxxQuery['xxx']> so
  the public hook API shape stays unchanged
- admin/student/teacher page.tsx: add ?? "--" / ?? 0 null guards on
  StatCard value props to match schema nullable semantics (parent page
  already uses toFixed chain, no change needed)

Acceptance (ARCHITECTURE.md §10 P1-7):
- codegen 3 outputs all SUCCESS
- tsc 0 errors / eslint 0 errors / vitest 231 passed / next build ok
- 6 operations strictly match schema with 0 errors

Refs: ARCHITECTURE.md §5.3 data layer / §10 P1-7
This commit is contained in:
SpecialX
2026-07-22 15:37:06 +08:00
parent a28a6bd6ea
commit 0beeff6329
6 changed files with 169 additions and 186 deletions

View File

@@ -1,7 +1,7 @@
"use client";
/**
* Dashboard domain API (ARCHITECTURE.md §5.5, §10 P1-2)
* Dashboard domain API (ARCHITECTURE.md §5.5, §10 P1-2 / P1-7)
*
* Real aggregation queries from data-ana subgraph, replacing the 6 fake
* widget contract queries (grades/homeworks/schedule/attendance/exams/
@@ -9,9 +9,13 @@
*
* Field naming: snake_case (as exposed by data-ana GraphQL schema).
* ARCHITECTURE.md §5.5 note: "底层字段 snake_case 需在 lib/api 层映射"
* — mapping deferred to P1-7 (codegen恢复后统一处理),当前直传 snake_case。
* — mapping deferred until backend exposes camelCase; current直传 snake_case。
*
* 关联:ARCHITECTURE.md §5.5 后端已就绪查询的立即利用 / §10 P1-2
* P1-7ARCHITECTURE.md §10手写 interface 全部删除,改用
* `__generated__/dashboard-types.ts` 由 codegen 严格校验生成的 per-operation
* 类型skipDocumentsValidation: falsedata-ana 域 schema 完全匹配)。
*
* 关联ARCHITECTURE.md §5.5 后端已就绪查询的立即利用 / §10 P1-2 / §10 P1-7
*/
import type { FetchPolicy } from "@apollo/client";
import {
@@ -23,124 +27,17 @@ import {
GET_ERROR_BOOK_STATS_DOC,
} from "@/lib/api/operations/dashboard.graphql";
import { useWidgetQuery } from "@/lib/useWidgetQuery";
import type {
GetAdminDashboardQuery,
GetErrorBookStatsQuery,
GetParentDashboardQuery,
GetStudentDashboardQuery,
GetTeacherDashboardQuery,
GetWarningsQuery,
} from "./__generated__/dashboard-types";
import type { UseQueryResult } from "./types";
// ===== 领域模型类型(对齐 combined-schema.graphql data-ana 子图 =====
export interface ClassSummary {
class_id: string;
class_name: string;
student_count: number;
average_score: number;
}
export interface StudentSummary {
student_id: string;
student_name: string;
score: number;
rank_in_class: number;
}
export interface WarningInfo {
warning_id: string;
warning_type: string;
target_id: string;
target_name: string;
threshold: number;
current_value: number;
severity: string;
occurred_at: string;
}
export interface WarningList {
warnings: WarningInfo;
total: number;
}
export interface WeakPoint {
knowledge_point_id: string;
title: string;
mastery: number;
error_count: number;
}
export interface TrendPoint {
date: string;
score: number;
}
export interface AIUsageByProvider {
provider: string;
request_count: string;
total_tokens: string;
cost_cents: string;
}
export interface AIUsageSummary {
total_requests: string;
total_tokens: string;
total_cost_cents: string;
by_provider: AIUsageByProvider;
}
export interface KnowledgePointErrorStats {
knowledge_point_id: string;
title: string;
error_count: number;
question_count: number;
error_rate: number;
}
export interface ErrorBookStats {
student_id: string;
total_error_questions: number;
total_error_count: number;
by_knowledge_point: KnowledgePointErrorStats;
recent_7d_errors: number;
}
export interface TeacherDashboard {
user_id: string;
total_classes: number;
total_students: number;
class_avg_score: number;
pending_homework_count: number;
classes: ClassSummary;
top_students: StudentSummary;
recent_warnings: WarningInfo;
}
export interface StudentDashboard {
user_id: string;
avg_score: number;
class_rank: number;
total_students: number;
weak_points: WeakPoint;
recent_trends: TrendPoint;
pending_homework: number;
}
export interface ParentDashboard {
user_id: string;
student_id: string;
child_avg_score: number;
child_class_rank: number;
total_class_students: number;
child_weak_points: WeakPoint;
child_warnings: WarningInfo;
}
export interface AdminDashboard {
user_id: string;
total_teachers: number;
total_students: number;
total_classes: number;
school_avg_score: number;
recent_warnings: WarningInfo;
ai_usage: AIUsageSummary;
}
// ===== 查询选项 =====
// ===== 查询选项UI 层类型,非 schema 类型 =====
export interface DashboardQueryOptions {
/** 是否启用查询false 时跳过) */
@@ -151,50 +48,81 @@ export interface DashboardQueryOptions {
fetchPolicy?: FetchPolicy;
}
// ===== 内部 Query 类型 =====
// ===== 数据类型(从 generated per-operation 类型提取) =====
// P1-7以下类型均由 codegen 严格校验生成,对应 dashboard.graphql.ts 中
// 6 个 operation。schema 字段全部可空(无 !),故类型为 `T | null`。
// 调用方pages需对 `data` 与字段做空值守卫。
interface TeacherDashboardQueryData {
teacherDashboard: TeacherDashboard;
}
type DashboardQueryVars = Record<string, never>;
/** 教师仪表盘聚合数据teacherDashboard 根字段) */
export type TeacherDashboard = NonNullable<
GetTeacherDashboardQuery["teacherDashboard"]
>;
interface StudentDashboardQueryData {
studentDashboard: StudentDashboard;
}
/** 学生仪表盘聚合数据studentDashboard 根字段) */
export type StudentDashboard = NonNullable<
GetStudentDashboardQuery["studentDashboard"]
>;
interface ParentDashboardQueryData {
parentDashboard: ParentDashboard;
}
/** 家长仪表盘聚合数据parentDashboard 根字段) */
export type ParentDashboard = NonNullable<
GetParentDashboardQuery["parentDashboard"]
>;
interface AdminDashboardQueryData {
adminDashboard: AdminDashboard;
}
/** 管理员仪表盘聚合数据adminDashboard 根字段) */
export type AdminDashboard = NonNullable<
GetAdminDashboardQuery["adminDashboard"]
>;
interface WarningsQueryData {
warnings: WarningList;
}
/** 预警列表warnings 根字段) */
export type WarningList = NonNullable<GetWarningsQuery["warnings"]>;
interface ErrorBookStatsQueryData {
errorBookStats: ErrorBookStats;
}
/** 错题统计errorBookStats 根字段) */
export type ErrorBookStats = NonNullable<
GetErrorBookStatsQuery["errorBookStats"]
>;
/** 班级概况TeacherDashboard.classes 子字段) */
export type ClassSummary = NonNullable<TeacherDashboard["classes"]>;
/** 学生排名TeacherDashboard.top_students 子字段) */
export type StudentSummary = NonNullable<TeacherDashboard["top_students"]>;
/** 预警信息(多处理由 parent/teacher/admin 共享引用) */
export type WarningInfo = NonNullable<TeacherDashboard["recent_warnings"]>;
/** 知识点薄弱项StudentDashboard.weak_points 子字段) */
export type WeakPoint = NonNullable<StudentDashboard["weak_points"]>;
/** 趋势点StudentDashboard.recent_trends 子字段) */
export type TrendPoint = NonNullable<StudentDashboard["recent_trends"]>;
/** AI 使用统计AdminDashboard.ai_usage 子字段) */
export type AIUsageSummary = NonNullable<AdminDashboard["ai_usage"]>;
/** AI 分提供商统计AIUsageSummary.by_provider 子字段) */
export type AIUsageByProvider = NonNullable<AIUsageSummary["by_provider"]>;
/** 知识点错误统计ErrorBookStats.by_knowledge_point 子字段) */
export type KnowledgePointErrorStats = NonNullable<
ErrorBookStats["by_knowledge_point"]
>;
// ===== Hooks =====
/**
* 查询教师仪表盘聚合数据(替换原 grades/homeworks/schedule 等假契约查询)。
*
* 关联ARCHITECTURE.md §5.5 / §10 P1-2
* 关联ARCHITECTURE.md §5.5 / §10 P1-2 / §10 P1-7
*/
export function useTeacherDashboard(
options?: DashboardQueryOptions,
): UseQueryResult<TeacherDashboard> {
const result = useWidgetQuery<TeacherDashboardQueryData, DashboardQueryVars>(
GET_TEACHER_DASHBOARD_DOC,
{},
options,
);
): UseQueryResult<TeacherDashboard | null> {
const result = useWidgetQuery<
GetTeacherDashboardQuery,
Record<string, never>
>(GET_TEACHER_DASHBOARD_DOC, {}, options);
return {
data: result.data?.teacherDashboard,
data: result.data?.teacherDashboard ?? null,
loading: result.loading,
error: result.error,
refetch: result.refetch,
@@ -204,18 +132,17 @@ export function useTeacherDashboard(
/**
* 查询学生仪表盘聚合数据。
*
* 关联ARCHITECTURE.md §5.5 / §10 P1-2
* 关联ARCHITECTURE.md §5.5 / §10 P1-2 / §10 P1-7
*/
export function useStudentDashboard(
options?: DashboardQueryOptions,
): UseQueryResult<StudentDashboard> {
const result = useWidgetQuery<StudentDashboardQueryData, DashboardQueryVars>(
GET_STUDENT_DASHBOARD_DOC,
{},
options,
);
): UseQueryResult<StudentDashboard | null> {
const result = useWidgetQuery<
GetStudentDashboardQuery,
Record<string, never>
>(GET_STUDENT_DASHBOARD_DOC, {}, options);
return {
data: result.data?.studentDashboard,
data: result.data?.studentDashboard ?? null,
loading: result.loading,
error: result.error,
refetch: result.refetch,
@@ -225,18 +152,18 @@ export function useStudentDashboard(
/**
* 查询家长仪表盘聚合数据。
*
* 关联ARCHITECTURE.md §5.5 / §10 P1-2
* 关联ARCHITECTURE.md §5.5 / §10 P1-2 / §10 P1-7
*/
export function useParentDashboard(
options?: DashboardQueryOptions,
): UseQueryResult<ParentDashboard> {
const result = useWidgetQuery<ParentDashboardQueryData, DashboardQueryVars>(
): UseQueryResult<ParentDashboard | null> {
const result = useWidgetQuery<GetParentDashboardQuery, Record<string, never>>(
GET_PARENT_DASHBOARD_DOC,
{},
options,
);
return {
data: result.data?.parentDashboard,
data: result.data?.parentDashboard ?? null,
loading: result.loading,
error: result.error,
refetch: result.refetch,
@@ -246,18 +173,18 @@ export function useParentDashboard(
/**
* 查询管理员仪表盘聚合数据。
*
* 关联ARCHITECTURE.md §5.5 / §10 P1-2
* 关联ARCHITECTURE.md §5.5 / §10 P1-2 / §10 P1-7
*/
export function useAdminDashboard(
options?: DashboardQueryOptions,
): UseQueryResult<AdminDashboard> {
const result = useWidgetQuery<AdminDashboardQueryData, DashboardQueryVars>(
): UseQueryResult<AdminDashboard | null> {
const result = useWidgetQuery<GetAdminDashboardQuery, Record<string, never>>(
GET_ADMIN_DASHBOARD_DOC,
{},
options,
);
return {
data: result.data?.adminDashboard,
data: result.data?.adminDashboard ?? null,
loading: result.loading,
error: result.error,
refetch: result.refetch,
@@ -267,18 +194,18 @@ export function useAdminDashboard(
/**
* 查询预警列表(跨角色共享)。
*
* 关联ARCHITECTURE.md §5.5 / §10 P1-2
* 关联ARCHITECTURE.md §5.5 / §10 P1-2 / §10 P1-7
*/
export function useWarnings(
options?: DashboardQueryOptions,
): UseQueryResult<WarningList> {
const result = useWidgetQuery<WarningsQueryData, DashboardQueryVars>(
): UseQueryResult<WarningList | null> {
const result = useWidgetQuery<GetWarningsQuery, Record<string, never>>(
GET_WARNINGS_DOC,
{},
options,
);
return {
data: result.data?.warnings,
data: result.data?.warnings ?? null,
loading: result.loading,
error: result.error,
refetch: result.refetch,
@@ -288,18 +215,18 @@ export function useWarnings(
/**
* 查询错题统计(学生仪表盘子区块)。
*
* 关联ARCHITECTURE.md §5.5 / §10 P1-2
* 关联ARCHITECTURE.md §5.5 / §10 P1-2 / §10 P1-7
*/
export function useErrorBookStats(
options?: DashboardQueryOptions,
): UseQueryResult<ErrorBookStats> {
const result = useWidgetQuery<ErrorBookStatsQueryData, DashboardQueryVars>(
): UseQueryResult<ErrorBookStats | null> {
const result = useWidgetQuery<GetErrorBookStatsQuery, Record<string, never>>(
GET_ERROR_BOOK_STATS_DOC,
{},
options,
);
return {
data: result.data?.errorBookStats,
data: result.data?.errorBookStats ?? null,
loading: result.loading,
error: result.error,
refetch: result.refetch,