Files
NextEdu/src/modules/textbooks/components/knowledge-point-dialogs.tsx
SpecialX f3c223d914 feat(settings,questions,school,textbooks): add brand config, question components, school dialogs, textbooks hooks
settings:

- Add actions-brand, brand-config, data-access-brand for brand management

- Add admin-file-upload-card, admin-notification-config-card, admin-school-info-card, admin-security-policy-card

- Add ai-provider-delete-dialog, ai-provider-selector, brand-config-card

- Add security-recent-logins-section, security-two-factor-section

- Add config/profile-overview-config, data-access-profile-overview, lib/system-settings-utils

questions:

- Add batch-operations, import-export-buttons, knowledge-point-selector, options-editor

- Add question-bank-results-client, question-cascade-filter, question-content-renderer, utils

school:

- Add grade-delete-dialog, grade-form-dialog, grade-list-toolbar, grade-overview-cards

- Add use-grade-data hook

textbooks:

- Add textbook-form-fields component

- Add use-kp-create, use-kp-delete, use-kp-update hooks
2026-07-03 10:26:00 +08:00

178 lines
6.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client"
import type { ReactNode } from "react"
import { useTranslations } from "next-intl"
import type { KnowledgePoint } from "../types"
import { Button } from "@/shared/components/ui/button"
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/shared/components/ui/dialog"
import { Input } from "@/shared/components/ui/input"
import { Label } from "@/shared/components/ui/label"
import { Textarea } from "@/shared/components/ui/textarea"
/**
* 题目创建器的渲染接口。
*
* 通过 render prop 注入,避免 textbooks 模块直接 import questions 模块组件P0-1 解耦)。
* 页面层负责传入实际的 CreateQuestionDialog 实现。
*/
export interface QuestionCreatorRenderProps {
open: boolean
onOpenChange: (open: boolean) => void
targetKp: KnowledgePoint | null
}
export interface KnowledgePointDialogsProps {
// Create KP dialog
createDialogOpen: boolean
setCreateDialogOpen: (open: boolean) => void
selectedText: string
isCreating: boolean
onCreateKnowledgePoint: (formData: FormData) => Promise<boolean | void>
// Edit KP dialog
editKpDialogOpen: boolean
setEditKpDialogOpen: (open: boolean) => void
editingKp: KnowledgePoint | null
isUpdatingKp: boolean
onUpdateKnowledgePoint: (formData: FormData) => Promise<void>
// Question dialog通过 render prop 注入,解耦 questions 模块)
questionDialogOpen: boolean
setQuestionDialogOpen: (open: boolean) => void
targetKpForQuestion: KnowledgePoint | null
/**
* 题目创建器渲染函数。
* 由页面层注入实际的 questions 模块组件,模块内部不直接 import questions。
* 若不传则不渲染题目创建入口(学生端等无权限场景)。
*/
renderQuestionCreator?: (props: QuestionCreatorRenderProps) => ReactNode
}
export function KnowledgePointDialogs({
createDialogOpen,
setCreateDialogOpen,
selectedText,
isCreating,
onCreateKnowledgePoint,
editKpDialogOpen,
setEditKpDialogOpen,
editingKp,
isUpdatingKp,
onUpdateKnowledgePoint,
questionDialogOpen,
setQuestionDialogOpen,
targetKpForQuestion,
renderQuestionCreator,
}: KnowledgePointDialogsProps) {
const t = useTranslations("textbooks.dialog.knowledge")
return (
<>
<Dialog open={createDialogOpen} onOpenChange={setCreateDialogOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>{t("createTitle")}</DialogTitle>
<DialogDescription>{t("createDesc")}</DialogDescription>
</DialogHeader>
{/* 包装为 void 返回,避免 as 断言Promise 仍会被 React 处理 */}
<form action={(formData: FormData) => { void onCreateKnowledgePoint(formData) }}>
<div className="grid gap-4 py-4">
<div className="grid gap-2">
<Label htmlFor="name">{t("name")}</Label>
<Input id="name" name="name" defaultValue={selectedText} required />
</div>
<div className="grid gap-2">
<Label htmlFor="description">{t("description")}</Label>
<Textarea id="description" name="description" placeholder={t("descriptionPlaceholder")} />
</div>
</div>
<DialogFooter>
<Button type="button" variant="outline" onClick={() => setCreateDialogOpen(false)} disabled={isCreating}>
{t("cancel")}
</Button>
<Button type="submit" disabled={isCreating}>
{isCreating ? t("creating") : t("create")}
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
<Dialog open={editKpDialogOpen} onOpenChange={setEditKpDialogOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>{t("editTitle")}</DialogTitle>
<DialogDescription>{t("editDesc")}</DialogDescription>
</DialogHeader>
<form action={onUpdateKnowledgePoint}>
<div className="grid gap-4 py-4">
<div className="grid gap-2">
<Label htmlFor="edit-name">{t("displayName")}</Label>
<Input id="edit-name" name="name" defaultValue={editingKp?.name} required />
</div>
<div className="grid gap-2">
<Label htmlFor="edit-description">{t("description")}</Label>
<Textarea
id="edit-description"
name="description"
defaultValue={editingKp?.description || ""}
placeholder={t("descriptionPlaceholder")}
/>
</div>
<div className="space-y-2 border rounded-md p-3 bg-muted/20">
<div className="flex items-center justify-between">
<Label
htmlFor="edit-anchorText"
className="text-muted-foreground text-xs flex items-center gap-1"
>
{t("anchorText")}
</Label>
</div>
<div className="pt-2">
<Input
key={editingKp?.id}
id="edit-anchorText"
name="anchorText"
defaultValue={editingKp?.anchorText || editingKp?.name}
className="text-sm font-mono"
required
/>
{/* 任意值 text-[10px]辅助提示文案text-xs(12px) 过大 */}
<p className="text-[10px] text-muted-foreground mt-1">{t("anchorTextHint")}</p>
</div>
</div>
</div>
<DialogFooter>
<Button
type="button"
variant="outline"
onClick={() => setEditKpDialogOpen(false)}
disabled={isUpdatingKp}
>
{t("cancel")}
</Button>
<Button type="submit" disabled={isUpdatingKp}>
{isUpdatingKp ? t("saving") : t("save")}
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
{renderQuestionCreator?.({
open: questionDialogOpen,
onOpenChange: setQuestionDialogOpen,
targetKp: targetKpForQuestion,
})}
</>
)
}