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
178 lines
6.2 KiB
TypeScript
178 lines
6.2 KiB
TypeScript
"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,
|
||
})}
|
||
</>
|
||
)
|
||
}
|