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:
@@ -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-7(ARCHITECTURE.md §10):手写 interface 全部删除,改用
|
||||
* `__generated__/dashboard-types.ts` 由 codegen 严格校验生成的 per-operation
|
||||
* 类型(skipDocumentsValidation: false,data-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,
|
||||
|
||||
Reference in New Issue
Block a user