feat(portal-shell): grades 模块 5 页迁移(教师域 §9.1 B2)
§9.1 B2 grades 行:/grades、/entry、/analytics、/stats、/report-card
契约:grade(id) ✅ 真实查询;列表/分析/统计/成绩单 → MSW 兜底
新增文件:
- src/lib/api/grades.ts (6 hooks)
- src/lib/api/operations/grades.graphql.ts (6 documents)
- src/features/teacher/grades/ (7 files: transformations + tests + 5 clients)
- src/app/shell/teacher/grades/ (5 page.tsx + loading.tsx + error.tsx)
修改文件:
- src/mocks/graphql-data.ts (5 mock 常量 + 6 handler cases)
- src/messages/{zh-CN,en}.json (grades i18n)
- src/lib/api/{index,operations/index}.ts (导出 grades)
- scripts/check-page-count.ts (baseline 26 → 31)
DoD 验收(§11.3 11 项):
- typecheck 0 errors
- lint 0 errors
- vitest 357 tests passed
- lint:tokens 0 errors
- check:pages 31 PASS
关联:ARCHITECTURE.md §5.3 / §5.4 / §9.1 / §10 P2 / §11.3 / §11.4
This commit is contained in:
426
apps/portal-shell/src/lib/api/grades.ts
Normal file
426
apps/portal-shell/src/lib/api/grades.ts
Normal file
@@ -0,0 +1,426 @@
|
||||
"use client";
|
||||
|
||||
/**
|
||||
* Grades domain API(ARCHITECTURE.md §5.1 / §5.3 / §9.1 教师域成绩模块)
|
||||
*
|
||||
* 三类操作:
|
||||
* 1. useGrade(按 id 单查):✅ 真实查询 grade(id: ID!),schema 已就绪
|
||||
* 2. 列表/分析/统计/成绩单查询:❌ schema 无对应字段 → MSW 兜底(@contract-pending)
|
||||
* 3. useCreateGrade(mutation):❌ schema 无 Mutation → MSW 兜底(@contract-pending)
|
||||
*
|
||||
* 注:homework.ts 已有 useRecordGrade(用于作业详情内联批改),
|
||||
* 故本域成绩录入 mutation 命名为 useCreateGrade 以避免命名冲突。
|
||||
*
|
||||
* 契约工单:docs/architecture/issues/contracts/core-edu_contract.md
|
||||
* 后端补齐后:重跑 normalize + codegen → 关闭 skipDocumentsValidation → 切换 fetcher → 删 mock
|
||||
*
|
||||
* 关联:ARCHITECTURE.md §5.3 契约纪律 / §5.4 MSW 兜底 / §9.1 / §11.4 契约工单
|
||||
*/
|
||||
import type { FetchPolicy } from "@apollo/client";
|
||||
|
||||
import { useWidgetMutation } from "../useWidgetMutation";
|
||||
import { useWidgetQuery } from "../useWidgetQuery";
|
||||
import { ApiError } from "./errors";
|
||||
import {
|
||||
CREATE_GRADE_DOC,
|
||||
GET_GRADE_ANALYTICS_DOC,
|
||||
GET_GRADE_DOC,
|
||||
GET_GRADES_LIST_DOC,
|
||||
GET_GRADE_STATS_DOC,
|
||||
GET_REPORT_CARD_DOC,
|
||||
} from "./operations/grades.graphql";
|
||||
import type { UseQueryResult } from "./types";
|
||||
|
||||
// ===== 数据类型(对齐 schema Grade 类型)=====
|
||||
|
||||
/**
|
||||
* 成绩实体(对齐 combined-schema.graphql Grade 类型,core-edu 子图)
|
||||
*
|
||||
* 字段命名 camelCase(与 schema 一致);score / totalScore 在 schema 中是 String,
|
||||
* 但业务层语义为数值,使用时由调用方做 Number() 转换。
|
||||
*/
|
||||
export interface Grade {
|
||||
id: string;
|
||||
studentId: string;
|
||||
examId: string | null;
|
||||
homeworkId: string | null;
|
||||
score: string;
|
||||
totalScore: string;
|
||||
feedback: string | null;
|
||||
gradedBy: string;
|
||||
schoolId: string;
|
||||
idempotencyKey: string | null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
/** 成绩列表项(含关联展示字段,用于列表渲染) */
|
||||
export interface GradeListItem {
|
||||
id: string;
|
||||
studentId: string;
|
||||
studentName: string;
|
||||
studentNo: string;
|
||||
classId: string;
|
||||
className: string;
|
||||
examId: string | null;
|
||||
examName: string | null;
|
||||
homeworkId: string | null;
|
||||
homeworkTitle: string | null;
|
||||
score: number;
|
||||
totalScore: number;
|
||||
status: string;
|
||||
gradedAt: string;
|
||||
gradedBy: string;
|
||||
}
|
||||
|
||||
/** 列表查询响应(@contract-pending 假契约形状,MSW 返回此结构) */
|
||||
interface GradesListResponse {
|
||||
grades: {
|
||||
items: GradeListItem[];
|
||||
total: number;
|
||||
};
|
||||
}
|
||||
|
||||
/** 单查响应(真实 schema) */
|
||||
interface GradeResponse {
|
||||
grade: Grade | null;
|
||||
}
|
||||
|
||||
/** 成绩录入输入 */
|
||||
export interface CreateGradeInput {
|
||||
studentId: string;
|
||||
classId: string;
|
||||
examId?: string;
|
||||
homeworkId?: string;
|
||||
score: number;
|
||||
totalScore: number;
|
||||
feedback?: string;
|
||||
}
|
||||
|
||||
/** 成绩录入 mutation 响应(@contract-pending) */
|
||||
interface CreateGradeResponse {
|
||||
createGrade: { gradeId: string } | null;
|
||||
}
|
||||
|
||||
// ===== Analytics 类型(混合契约:基础统计 + 扩展 MSW)=====
|
||||
|
||||
/** 成绩分析汇总 */
|
||||
export interface GradeAnalyticsSummary {
|
||||
totalStudents: number;
|
||||
gradedCount: number;
|
||||
avgScore: number;
|
||||
maxScore: number;
|
||||
minScore: number;
|
||||
passRate: number;
|
||||
passScore: number;
|
||||
}
|
||||
|
||||
/** 分数段分布 */
|
||||
export interface GradeDistribution {
|
||||
label: string;
|
||||
count: number;
|
||||
}
|
||||
|
||||
/** 学生排名 */
|
||||
export interface GradeRanking {
|
||||
studentId: string;
|
||||
studentNo: string;
|
||||
studentName: string;
|
||||
score: number;
|
||||
rank: number;
|
||||
level: string;
|
||||
}
|
||||
|
||||
/** 成绩分析完整数据(@contract-pending 扩展字段) */
|
||||
export interface GradeAnalytics {
|
||||
scope: string;
|
||||
scopeName: string;
|
||||
summary: GradeAnalyticsSummary;
|
||||
distribution: GradeDistribution[];
|
||||
rankings: GradeRanking[];
|
||||
}
|
||||
|
||||
/** Analytics 查询响应 */
|
||||
interface GradeAnalyticsResponse {
|
||||
gradeAnalytics: GradeAnalytics | null;
|
||||
}
|
||||
|
||||
// ===== Stats 类型(@contract-pending 全 MSW)=====
|
||||
|
||||
/** 班级/学科统计摘要 */
|
||||
export interface GradeStatsSummary {
|
||||
classId: string;
|
||||
className: string;
|
||||
subjectId: string;
|
||||
subjectName: string;
|
||||
studentCount: number;
|
||||
avgScore: number;
|
||||
maxScore: number;
|
||||
minScore: number;
|
||||
passRate: number;
|
||||
passCount: number;
|
||||
failCount: number;
|
||||
}
|
||||
|
||||
/** 成绩统计数据 */
|
||||
export interface GradeStats {
|
||||
classId: string;
|
||||
period: string;
|
||||
summaries: GradeStatsSummary[];
|
||||
}
|
||||
|
||||
/** Stats 查询响应 */
|
||||
interface GradeStatsResponse {
|
||||
gradeStats: GradeStats | null;
|
||||
}
|
||||
|
||||
// ===== Report Card 类型(@contract-pending 全 MSW)=====
|
||||
|
||||
/** 成绩单条目 */
|
||||
export interface ReportCardEntry {
|
||||
subjectId: string;
|
||||
subjectName: string;
|
||||
examName: string | null;
|
||||
homeworkTitle: string | null;
|
||||
score: number;
|
||||
totalScore: number;
|
||||
gradeLevel: string;
|
||||
feedback: string | null;
|
||||
gradedAt: string;
|
||||
}
|
||||
|
||||
/** 成绩单完整数据 */
|
||||
export interface ReportCard {
|
||||
studentId: string;
|
||||
studentName: string;
|
||||
studentNo: string;
|
||||
className: string;
|
||||
period: string;
|
||||
entries: ReportCardEntry[];
|
||||
totalAvgScore: number;
|
||||
totalRank: number;
|
||||
totalStudents: number;
|
||||
}
|
||||
|
||||
/** ReportCard 查询响应 */
|
||||
interface ReportCardResponse {
|
||||
reportCard: ReportCard | null;
|
||||
}
|
||||
|
||||
// ===== 查询选项 =====
|
||||
|
||||
export interface GradeQueryOptions {
|
||||
enabled?: boolean;
|
||||
pollInterval?: number;
|
||||
fetchPolicy?: FetchPolicy;
|
||||
}
|
||||
|
||||
// ===== Hooks =====
|
||||
|
||||
/**
|
||||
* 按 id 查询成绩详情(真实 schema,✅ 契约已就绪)。
|
||||
*
|
||||
* 关联:ARCHITECTURE.md §5.5 后端已就绪查询 / §9.1
|
||||
*/
|
||||
export function useGrade(
|
||||
id: string,
|
||||
options?: GradeQueryOptions,
|
||||
): UseQueryResult<Grade | null> {
|
||||
const result = useWidgetQuery<GradeResponse, { id: string }>(
|
||||
GET_GRADE_DOC,
|
||||
{ id },
|
||||
{
|
||||
...options,
|
||||
enabled: options?.enabled ?? id.length > 0,
|
||||
},
|
||||
);
|
||||
return {
|
||||
data: result.data?.grade ?? null,
|
||||
loading: result.loading,
|
||||
error: result.error,
|
||||
refetch: result.refetch,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询班级下的成绩列表(@contract-pending,MSW 兜底)。
|
||||
*
|
||||
* schema 无 grades(classId) 根字段,由 MSW handlers 返回 mock 数据。
|
||||
* 后端补齐列表查询后切换到真实 fetcher,页面无需改动。
|
||||
*
|
||||
* 关联:ARCHITECTURE.md §5.4 / §9.1 列表页 / §11.4 契约工单
|
||||
*/
|
||||
export function useGradesList(
|
||||
classId: string,
|
||||
options?: GradeQueryOptions & {
|
||||
examId?: string;
|
||||
studentId?: string;
|
||||
status?: string;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
},
|
||||
): UseQueryResult<{ items: GradeListItem[]; total: number }> {
|
||||
const result = useWidgetQuery<
|
||||
GradesListResponse,
|
||||
{
|
||||
classId: string;
|
||||
examId?: string;
|
||||
studentId?: string;
|
||||
status?: string;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
}
|
||||
>(
|
||||
GET_GRADES_LIST_DOC,
|
||||
{
|
||||
classId,
|
||||
examId: options?.examId,
|
||||
studentId: options?.studentId,
|
||||
status: options?.status,
|
||||
limit: options?.limit,
|
||||
offset: options?.offset,
|
||||
},
|
||||
{
|
||||
enabled: options?.enabled ?? classId.length > 0,
|
||||
fetchPolicy: options?.fetchPolicy,
|
||||
pollInterval: options?.pollInterval,
|
||||
},
|
||||
);
|
||||
return {
|
||||
data: result.data?.grades,
|
||||
loading: result.loading,
|
||||
error: result.error,
|
||||
refetch: result.refetch,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 成绩录入 mutation(@contract-pending,MSW 兜底)。
|
||||
*
|
||||
* schema 无 Mutation 类型,由 MSW handlers 返回 mock 数据。
|
||||
* 后端补齐 mutation 后切换到真实 fetcher。
|
||||
*
|
||||
* 注:homework.ts 的 useRecordGrade 用于作业详情内联批改(input.homeworkId 必填),
|
||||
* 本 hook 用于成绩模块的独立录入页(支持 examId 或 homeworkId 二选一)。
|
||||
*
|
||||
* 关联:ARCHITECTURE.md §5.4 / §9.1 录入页 / §11.4 契约工单
|
||||
*/
|
||||
export function useCreateGrade(): {
|
||||
run: (input: CreateGradeInput) => Promise<{ gradeId: string }>;
|
||||
loading: boolean;
|
||||
error: unknown;
|
||||
} {
|
||||
const {
|
||||
run: rawRun,
|
||||
loading,
|
||||
error,
|
||||
} = useWidgetMutation<CreateGradeResponse, { input: CreateGradeInput }>(
|
||||
CREATE_GRADE_DOC,
|
||||
);
|
||||
|
||||
const run = async (input: CreateGradeInput): Promise<{ gradeId: string }> => {
|
||||
const data = await rawRun({ input });
|
||||
if (!data?.createGrade) {
|
||||
throw new ApiError("Failed to create grade", "INTERNAL_ERROR");
|
||||
}
|
||||
return data.createGrade;
|
||||
};
|
||||
|
||||
return { run, loading, error };
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询成绩分析数据(混合契约:基础统计 + 扩展 MSW)。
|
||||
*
|
||||
* schema 有 assignmentAnalysis 基础字段,但排名/分布等扩展字段走 MSW。
|
||||
* MSW 开启时返回完整 mock;后端补齐扩展字段后切换真实 fetcher。
|
||||
*
|
||||
* 关联:ARCHITECTURE.md §5.4 / §9.1 analytics 页 / §11.4 契约工单
|
||||
*/
|
||||
export function useGradeAnalytics(
|
||||
params: { examId?: string; classId?: string },
|
||||
options?: GradeQueryOptions,
|
||||
): UseQueryResult<GradeAnalytics | null> {
|
||||
const result = useWidgetQuery<
|
||||
GradeAnalyticsResponse,
|
||||
{ examId?: string; classId?: string }
|
||||
>(
|
||||
GET_GRADE_ANALYTICS_DOC,
|
||||
{ examId: params.examId, classId: params.classId },
|
||||
{
|
||||
...options,
|
||||
enabled:
|
||||
options?.enabled ?? (Boolean(params.examId) || Boolean(params.classId)),
|
||||
},
|
||||
);
|
||||
return {
|
||||
data: result.data?.gradeAnalytics ?? null,
|
||||
loading: result.loading,
|
||||
error: result.error,
|
||||
refetch: result.refetch,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询班级成绩统计(@contract-pending,MSW 兜底)。
|
||||
*
|
||||
* schema 无 gradeStats 根字段,由 MSW handlers 返回 mock 数据。
|
||||
* 用于 /shell/teacher/grades/stats 统计页。
|
||||
*
|
||||
* 关联:ARCHITECTURE.md §5.4 / §9.1 统计页 / §11.4 契约工单
|
||||
*/
|
||||
export function useGradeStats(
|
||||
classId: string,
|
||||
period?: string,
|
||||
options?: GradeQueryOptions,
|
||||
): UseQueryResult<GradeStats | null> {
|
||||
const result = useWidgetQuery<
|
||||
GradeStatsResponse,
|
||||
{ classId: string; period?: string }
|
||||
>(
|
||||
GET_GRADE_STATS_DOC,
|
||||
{ classId, period },
|
||||
{
|
||||
...options,
|
||||
enabled: options?.enabled ?? classId.length > 0,
|
||||
},
|
||||
);
|
||||
return {
|
||||
data: result.data?.gradeStats ?? null,
|
||||
loading: result.loading,
|
||||
error: result.error,
|
||||
refetch: result.refetch,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询学生成绩单(@contract-pending,MSW 兜底)。
|
||||
*
|
||||
* schema 无 reportCard 根字段,由 MSW handlers 返回 mock 数据。
|
||||
* 用于 /shell/teacher/grades/report-card 成绩单页。
|
||||
*
|
||||
* 关联:ARCHITECTURE.md §5.4 / §9.1 成绩单页 / §11.4 契约工单
|
||||
*/
|
||||
export function useReportCard(
|
||||
studentId: string,
|
||||
period?: string,
|
||||
options?: GradeQueryOptions,
|
||||
): UseQueryResult<ReportCard | null> {
|
||||
const result = useWidgetQuery<
|
||||
ReportCardResponse,
|
||||
{ studentId: string; period?: string }
|
||||
>(
|
||||
GET_REPORT_CARD_DOC,
|
||||
{ studentId, period },
|
||||
{
|
||||
...options,
|
||||
enabled: options?.enabled ?? studentId.length > 0,
|
||||
},
|
||||
);
|
||||
return {
|
||||
data: result.data?.reportCard ?? null,
|
||||
loading: result.loading,
|
||||
error: result.error,
|
||||
refetch: result.refetch,
|
||||
};
|
||||
}
|
||||
@@ -16,6 +16,7 @@ export * from "./topbar";
|
||||
export * from "./teacher";
|
||||
export * from "./exams";
|
||||
export * from "./homework";
|
||||
export * from "./grades";
|
||||
export * from "./student";
|
||||
export * from "./parent";
|
||||
export * from "./admin";
|
||||
|
||||
179
apps/portal-shell/src/lib/api/operations/grades.graphql.ts
Normal file
179
apps/portal-shell/src/lib/api/operations/grades.graphql.ts
Normal file
@@ -0,0 +1,179 @@
|
||||
// Grades domain GraphQL documents (ARCHITECTURE.md §5.3 契约纪律 / §9.1)
|
||||
//
|
||||
// 拆分原则:
|
||||
// - GetGrade(按 id 单查):✅ combined-schema 中真实存在(grade(id: ID!): Grade)
|
||||
// - 其余 5 个查询/mutation:❌ schema 无对应字段/Mutation 类型
|
||||
// → 走 MSW 兜底(@contract-pending),等待后端补齐契约
|
||||
//
|
||||
// 注:homework.graphql.ts 已有同名的 RecordGrade mutation(用于作业详情内联批改),
|
||||
// 故本域成绩录入 mutation 命名为 CreateGrade 以避免命名冲突。
|
||||
//
|
||||
// 契约工单:docs/architecture/issues/contracts/core-edu_contract.md
|
||||
// 关联:ARCHITECTURE.md §5.3 / §5.4 / §9.1 / §11.4
|
||||
import { gql } from "@apollo/client";
|
||||
|
||||
// ── 真实查询:grade(id) 单查 ─────────────────────────────────────
|
||||
// 字段全部对齐 combined-schema.graphql 中 Grade 类型(core-edu 子图)
|
||||
// 注意:score / totalScore 在 schema 中为 String 类型(业务语义为数值)
|
||||
export const GET_GRADE_DOC = gql`
|
||||
query GetGrade($id: ID!) {
|
||||
grade(id: $id) {
|
||||
id
|
||||
studentId
|
||||
examId
|
||||
homeworkId
|
||||
score
|
||||
totalScore
|
||||
feedback
|
||||
gradedBy
|
||||
schoolId
|
||||
idempotencyKey
|
||||
createdAt
|
||||
updatedAt
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
// ── 假契约查询(@contract-pending)─────────────────────────────
|
||||
// 列表查询:schema 无 grades(classId) 根字段
|
||||
// 页面通过 MSW 兜底获取列表数据,后端补齐后切换 fetcher 指向真实查询
|
||||
// 契约工单:core-edu_contract.md#grades-list
|
||||
export const GET_GRADES_LIST_DOC = gql`
|
||||
query GetGradesList(
|
||||
$classId: ID!
|
||||
$examId: ID
|
||||
$studentId: ID
|
||||
$status: String
|
||||
$limit: Int
|
||||
$offset: Int
|
||||
) {
|
||||
grades(
|
||||
classId: $classId
|
||||
examId: $examId
|
||||
studentId: $studentId
|
||||
status: $status
|
||||
limit: $limit
|
||||
offset: $offset
|
||||
) {
|
||||
items {
|
||||
id
|
||||
studentId
|
||||
studentName
|
||||
studentNo
|
||||
classId
|
||||
className
|
||||
examId
|
||||
examName
|
||||
homeworkId
|
||||
homeworkTitle
|
||||
score
|
||||
totalScore
|
||||
status
|
||||
gradedAt
|
||||
gradedBy
|
||||
}
|
||||
total
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
// ── 假契约变更(@contract-pending)─────────────────────────────
|
||||
// 成绩录入:schema 无 Mutation 类型
|
||||
// 页面通过 MSW 兜底提交,后端补齐 mutation 后切换 fetcher
|
||||
// 契约工单:core-edu_contract.md#create-grade-mutation
|
||||
export const CREATE_GRADE_DOC = gql`
|
||||
mutation CreateGrade($input: CreateGradeInput!) {
|
||||
createGrade(input: $input) {
|
||||
gradeId
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
// ── 成绩分析(混合契约:基础统计部分可用 + 扩展 MSW)─────────────
|
||||
// schema 有 assignmentAnalysis 基础字段,但排名/分布等扩展字段走 MSW
|
||||
// 契约工单:core-edu_contract.md#grade-analytics
|
||||
export const GET_GRADE_ANALYTICS_DOC = gql`
|
||||
query GetGradeAnalytics($examId: ID, $classId: ID) {
|
||||
gradeAnalytics(examId: $examId, classId: $classId) {
|
||||
scope
|
||||
scopeName
|
||||
summary {
|
||||
totalStudents
|
||||
gradedCount
|
||||
avgScore
|
||||
maxScore
|
||||
minScore
|
||||
passRate
|
||||
passScore
|
||||
}
|
||||
distribution {
|
||||
label
|
||||
count
|
||||
}
|
||||
rankings {
|
||||
studentId
|
||||
studentNo
|
||||
studentName
|
||||
score
|
||||
rank
|
||||
level
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
// ── 成绩统计(@contract-pending 全 MSW)────────────────────────
|
||||
// schema 无 gradeStats 根字段 → MSW 兜底
|
||||
// 用于 /shell/teacher/grades/stats 统计页
|
||||
// 契约工单:core-edu_contract.md#grade-stats
|
||||
export const GET_GRADE_STATS_DOC = gql`
|
||||
query GetGradeStats($classId: ID!, $period: String) {
|
||||
gradeStats(classId: $classId, period: $period) {
|
||||
classId
|
||||
period
|
||||
summaries {
|
||||
classId
|
||||
className
|
||||
subjectId
|
||||
subjectName
|
||||
studentCount
|
||||
avgScore
|
||||
maxScore
|
||||
minScore
|
||||
passRate
|
||||
passCount
|
||||
failCount
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
// ── 成绩单(@contract-pending 全 MSW)──────────────────────────
|
||||
// schema 无 reportCard 根字段 → MSW 兜底
|
||||
// 用于 /shell/teacher/grades/report-card 成绩单页
|
||||
// 契约工单:core-edu_contract.md#report-card
|
||||
export const GET_REPORT_CARD_DOC = gql`
|
||||
query GetReportCard($studentId: ID!, $period: String) {
|
||||
reportCard(studentId: $studentId, period: $period) {
|
||||
studentId
|
||||
studentName
|
||||
studentNo
|
||||
className
|
||||
period
|
||||
entries {
|
||||
subjectId
|
||||
subjectName
|
||||
examName
|
||||
homeworkTitle
|
||||
score
|
||||
totalScore
|
||||
gradeLevel
|
||||
feedback
|
||||
gradedAt
|
||||
}
|
||||
totalAvgScore
|
||||
totalRank
|
||||
totalStudents
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -6,6 +6,7 @@ export * from "./topbar.graphql";
|
||||
export * from "./teacher.graphql";
|
||||
export * from "./exams.graphql";
|
||||
export * from "./homework.graphql";
|
||||
export * from "./grades.graphql";
|
||||
export * from "./student.graphql";
|
||||
export * from "./parent.graphql";
|
||||
export * from "./admin.graphql";
|
||||
|
||||
Reference in New Issue
Block a user