feat(lesson-preparation): add AI evaluation, analytics, attachments, calendar, comments, review, substitutes, formative, and version diff
- Add actions-ai-evaluation, actions-analytics, actions-attachments, actions-calendar, actions-comments, actions-formative, actions-questions, actions-review, actions-substitutes - Add corresponding data-access layers for each new action module - Add calendar-view, curriculum-map-view, version-diff-viewer components - Add editor-slice, selection-slice, version-slice hooks for state management - Add document-diff and scope-check lib utilities - Add default-question-service and external-questions-bridge services
This commit is contained in:
@@ -6,6 +6,7 @@ import { Tag } from "lucide-react";
|
||||
import type { BlackboardBlockData } from "../../types";
|
||||
import { isBlackboardLayout } from "../../lib/type-guards";
|
||||
import { KnowledgePointPicker } from "../knowledge-point-picker";
|
||||
import { LessonPlanErrorBoundary } from "../lesson-plan-error-boundary";
|
||||
|
||||
interface Props {
|
||||
data: BlackboardBlockData;
|
||||
@@ -72,13 +73,15 @@ export function BlackboardBlock({ data, textbookId, chapterId, onUpdate }: Props
|
||||
</button>
|
||||
</div>
|
||||
{showKpPicker && (
|
||||
<KnowledgePointPicker
|
||||
textbookId={textbookId}
|
||||
chapterId={chapterId}
|
||||
selectedIds={data.knowledgePointIds}
|
||||
onChange={(ids) => onUpdate({ ...data, knowledgePointIds: ids })}
|
||||
onClose={() => setShowKpPicker(false)}
|
||||
/>
|
||||
<LessonPlanErrorBoundary>
|
||||
<KnowledgePointPicker
|
||||
textbookId={textbookId}
|
||||
chapterId={chapterId}
|
||||
selectedIds={data.knowledgePointIds}
|
||||
onChange={(ids) => onUpdate({ ...data, knowledgePointIds: ids })}
|
||||
onClose={() => setShowKpPicker(false)}
|
||||
/>
|
||||
</LessonPlanErrorBoundary>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useLessonPlanEditor } from "../../hooks/use-lesson-plan-editor";
|
||||
import { QuestionBankPicker } from "../question-bank-picker";
|
||||
import { InlineQuestionEditor } from "../inline-question-editor";
|
||||
import { PublishHomeworkDialog } from "../publish-homework-dialog";
|
||||
import { LessonPlanErrorBoundary } from "../lesson-plan-error-boundary";
|
||||
import { Button } from "@/shared/components/ui/button";
|
||||
import { Plus, Trash2 } from "lucide-react";
|
||||
import type {
|
||||
@@ -120,12 +122,13 @@ export function ExerciseBlock({ blockId, data, classes, textbookId, chapterId }:
|
||||
<span className="bg-tertiary-container/20 text-tertiary px-2 py-1 rounded">
|
||||
{t("status.publishedAsHomework")}
|
||||
</span>
|
||||
<a
|
||||
{/* V4 P1-5 修复:原生 <a> 替换为 next/link 的 <Link> */}
|
||||
<Link
|
||||
href="/teacher/homework"
|
||||
className="text-primary underline"
|
||||
>
|
||||
{t("action.viewHomework")}
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
) : (
|
||||
data.purpose === "after_class_homework" &&
|
||||
@@ -140,31 +143,37 @@ export function ExerciseBlock({ blockId, data, classes, textbookId, chapterId }:
|
||||
)}
|
||||
</div>
|
||||
{showBank && (
|
||||
<QuestionBankPicker
|
||||
existingIds={data.items.map((i) => i.questionId)}
|
||||
onPick={addItems}
|
||||
onClose={() => setShowBank(false)}
|
||||
/>
|
||||
<LessonPlanErrorBoundary>
|
||||
<QuestionBankPicker
|
||||
existingIds={data.items.map((i) => i.questionId)}
|
||||
onPick={addItems}
|
||||
onClose={() => setShowBank(false)}
|
||||
/>
|
||||
</LessonPlanErrorBoundary>
|
||||
)}
|
||||
{showInline && (
|
||||
<InlineQuestionEditor
|
||||
textbookId={textbookId}
|
||||
chapterId={chapterId}
|
||||
onAdd={(item) => {
|
||||
addItems([item]);
|
||||
setShowInline(false);
|
||||
}}
|
||||
onClose={() => setShowInline(false)}
|
||||
/>
|
||||
<LessonPlanErrorBoundary>
|
||||
<InlineQuestionEditor
|
||||
textbookId={textbookId}
|
||||
chapterId={chapterId}
|
||||
onAdd={(item) => {
|
||||
addItems([item]);
|
||||
setShowInline(false);
|
||||
}}
|
||||
onClose={() => setShowInline(false)}
|
||||
/>
|
||||
</LessonPlanErrorBoundary>
|
||||
)}
|
||||
{showPublish && (
|
||||
<PublishHomeworkDialog
|
||||
planId={planId}
|
||||
blockId={blockId}
|
||||
classes={classes}
|
||||
onClose={() => setShowPublish(false)}
|
||||
onPublished={() => router.refresh()}
|
||||
/>
|
||||
<LessonPlanErrorBoundary>
|
||||
<PublishHomeworkDialog
|
||||
planId={planId}
|
||||
blockId={blockId}
|
||||
classes={classes}
|
||||
onClose={() => setShowPublish(false)}
|
||||
onPublished={() => router.refresh()}
|
||||
/>
|
||||
</LessonPlanErrorBoundary>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -43,7 +43,7 @@ export function HomeworkBlock({ data, onUpdate }: Props) {
|
||||
{t("homework.hint")}
|
||||
</div>
|
||||
{data.assignments.map((item, idx) => (
|
||||
<div key={idx} className="flex items-start gap-2">
|
||||
<div key={`homework-${idx}`} className="flex items-start gap-2">
|
||||
<select
|
||||
value={item.type}
|
||||
onChange={(e) => {
|
||||
|
||||
@@ -43,7 +43,7 @@ export function KeyPointBlock({ data, onUpdate }: Props) {
|
||||
{t("keyPoint.hint")}
|
||||
</div>
|
||||
{data.keyPoints.map((item, idx) => (
|
||||
<div key={idx} className="flex items-start gap-2">
|
||||
<div key={`key-point-${idx}`} className="flex items-start gap-2">
|
||||
<select
|
||||
value={item.type}
|
||||
onChange={(e) => {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Plus, Trash2, Tag } from "lucide-react";
|
||||
import type { NewTeachingBlockData, NewTeachingPoint } from "../../types";
|
||||
import { Button } from "@/shared/components/ui/button";
|
||||
import { KnowledgePointPicker } from "../knowledge-point-picker";
|
||||
import { LessonPlanErrorBoundary } from "../lesson-plan-error-boundary";
|
||||
|
||||
interface Props {
|
||||
data: NewTeachingBlockData;
|
||||
@@ -48,7 +49,7 @@ export function NewTeachingBlock({ data, textbookId, chapterId, onUpdate }: Prop
|
||||
{t("newTeaching.hint")}
|
||||
</div>
|
||||
{data.teachingPoints.map((point, idx) => (
|
||||
<div key={idx} className="border border-outline-variant rounded p-2 space-y-2">
|
||||
<div key={`teaching-${idx}`} className="border border-outline-variant rounded p-2 space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-xs font-medium">
|
||||
{t("newTeaching.pointIndex", { index: idx + 1 })}
|
||||
@@ -106,13 +107,15 @@ export function NewTeachingBlock({ data, textbookId, chapterId, onUpdate }: Prop
|
||||
{t("newTeaching.addPoint")}
|
||||
</Button>
|
||||
{pickerFor !== null && (
|
||||
<KnowledgePointPicker
|
||||
textbookId={textbookId}
|
||||
chapterId={chapterId}
|
||||
selectedIds={data.teachingPoints[pickerFor]?.knowledgePointIds ?? []}
|
||||
onChange={(ids) => updatePoint(pickerFor, { knowledgePointIds: ids })}
|
||||
onClose={() => setPickerFor(null)}
|
||||
/>
|
||||
<LessonPlanErrorBoundary>
|
||||
<KnowledgePointPicker
|
||||
textbookId={textbookId}
|
||||
chapterId={chapterId}
|
||||
selectedIds={data.teachingPoints[pickerFor]?.knowledgePointIds ?? []}
|
||||
onChange={(ids) => updatePoint(pickerFor, { knowledgePointIds: ids })}
|
||||
onClose={() => setPickerFor(null)}
|
||||
/>
|
||||
</LessonPlanErrorBoundary>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -46,7 +46,7 @@ export function ObjectiveBlock({ data, onUpdate }: Props) {
|
||||
{t("objective.hint")}
|
||||
</div>
|
||||
{data.objectives.map((item, idx) => (
|
||||
<div key={idx} className="flex items-start gap-2">
|
||||
<div key={`objective-${idx}`} className="flex items-start gap-2">
|
||||
<select
|
||||
value={item.dimension}
|
||||
onChange={(e) => {
|
||||
|
||||
@@ -43,7 +43,7 @@ export function ReflectionBlock({ data, onUpdate }: Props) {
|
||||
{t("reflection.hint")}
|
||||
</div>
|
||||
{data.reflection.map((item, idx) => (
|
||||
<div key={idx} className="flex items-start gap-2">
|
||||
<div key={`reflection-${idx}`} className="flex items-start gap-2">
|
||||
<select
|
||||
value={item.aspect}
|
||||
onChange={(e) => {
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useEffect, useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import type { RichTextBlockData } from "../../types";
|
||||
import { KnowledgePointPicker } from "../knowledge-point-picker";
|
||||
import { LessonPlanErrorBoundary } from "../lesson-plan-error-boundary";
|
||||
import { Tag } from "lucide-react";
|
||||
|
||||
interface Props {
|
||||
@@ -70,13 +71,15 @@ export function RichTextBlock({
|
||||
</button>
|
||||
</div>
|
||||
{showKpPicker && (
|
||||
<KnowledgePointPicker
|
||||
textbookId={textbookId}
|
||||
chapterId={chapterId}
|
||||
selectedIds={data.knowledgePointIds}
|
||||
onChange={(ids) => onUpdate({ ...data, knowledgePointIds: ids })}
|
||||
onClose={() => setShowKpPicker(false)}
|
||||
/>
|
||||
<LessonPlanErrorBoundary>
|
||||
<KnowledgePointPicker
|
||||
textbookId={textbookId}
|
||||
chapterId={chapterId}
|
||||
selectedIds={data.knowledgePointIds}
|
||||
onChange={(ids) => onUpdate({ ...data, knowledgePointIds: ids })}
|
||||
onClose={() => setShowKpPicker(false)}
|
||||
/>
|
||||
</LessonPlanErrorBoundary>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -38,7 +38,7 @@ export function SummaryBlock({ data, onUpdate }: Props) {
|
||||
{t("summary.hint")}
|
||||
</div>
|
||||
{data.summaryPoints.map((point, idx) => (
|
||||
<div key={idx} className="flex items-start gap-2">
|
||||
<div key={`summary-${idx}`} className="flex items-start gap-2">
|
||||
<span className="text-xs text-on-surface-variant mt-1">{idx + 1}.</span>
|
||||
<input
|
||||
type="text"
|
||||
|
||||
Reference in New Issue
Block a user