- messaging: update message-compose, message-detail, message-draft-list, message-group-compose, message-list, message-report-block, message-template-picker, unread-message-badge, data-access - notifications: update notification-list, data-access, use-notification-stream, preferences - onboarding: update actions, data-access, use-onboarding-form - parent: update child-schedule-card, parent-export-button - proctoring: update data-access - questions: update batch-operations, create-question-dialog, import-export-buttons, question-actions, data-access - rbac: update actions, permission-catalog
122 lines
3.9 KiB
TypeScript
122 lines
3.9 KiB
TypeScript
"use client"
|
|
|
|
import { useState } from "react"
|
|
import { useTranslations } from "next-intl"
|
|
import { Trash2, X } from "lucide-react"
|
|
import { useRouter } from "next/navigation"
|
|
|
|
import { Button } from "@/shared/components/ui/button"
|
|
import {
|
|
AlertDialog,
|
|
AlertDialogAction,
|
|
AlertDialogCancel,
|
|
AlertDialogContent,
|
|
AlertDialogDescription,
|
|
AlertDialogFooter,
|
|
AlertDialogHeader,
|
|
AlertDialogTitle,
|
|
} from "@/shared/components/ui/alert-dialog"
|
|
import { deleteQuestionsBatchAction } from "../actions"
|
|
import { usePermission } from "@/shared/hooks/use-permission"
|
|
import { Permissions } from "@/shared/types/permissions"
|
|
import { notify } from "@/shared/lib/notify"
|
|
|
|
interface BatchOperationsProps {
|
|
/** 选中的题目 ID 列表 */
|
|
selectedIds: string[]
|
|
/** 清除选择回调 */
|
|
onClearSelection: () => void
|
|
}
|
|
|
|
/**
|
|
* 批量操作工具栏。
|
|
*
|
|
* 当表格有选中行时显示,提供批量删除等功能。
|
|
* 权限感知:无 QUESTION_DELETE 权限时不显示删除按钮。
|
|
*/
|
|
export function BatchOperations({ selectedIds, onClearSelection }: BatchOperationsProps): React.ReactNode {
|
|
const t = useTranslations("questions")
|
|
const router = useRouter()
|
|
const { hasPermission } = usePermission()
|
|
const [showDeleteDialog, setShowDeleteDialog] = useState(false)
|
|
const [isDeleting, setIsDeleting] = useState(false)
|
|
|
|
const canDelete = hasPermission(Permissions.QUESTION_DELETE)
|
|
|
|
if (selectedIds.length === 0) return null
|
|
|
|
const handleBatchDelete = async (): Promise<void> => {
|
|
setIsDeleting(true)
|
|
try {
|
|
const fd = new FormData()
|
|
fd.set("json", JSON.stringify({ ids: selectedIds }))
|
|
const res = await deleteQuestionsBatchAction(undefined, fd)
|
|
if (res.success) {
|
|
const deleted = res.data?.deleted ?? 0
|
|
notify.success(t("batch.deleteSuccess", { count: deleted }))
|
|
setShowDeleteDialog(false)
|
|
onClearSelection()
|
|
router.refresh()
|
|
} else {
|
|
notify.error(res.message || t("batch.deleteFailed"))
|
|
}
|
|
} catch (e) {
|
|
console.error("Failed to batch delete questions", e)
|
|
notify.error(t("batch.deleteFailed"))
|
|
} finally {
|
|
setIsDeleting(false)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<div className="flex items-center gap-2 rounded-md border bg-muted/50 px-3 py-2">
|
|
<span className="text-sm font-medium">
|
|
{t("batch.selected", { count: selectedIds.length })}
|
|
</span>
|
|
<div className="ml-auto flex items-center gap-2">
|
|
{canDelete && (
|
|
<Button
|
|
variant="destructive"
|
|
size="sm"
|
|
onClick={() => setShowDeleteDialog(true)}
|
|
disabled={isDeleting}
|
|
>
|
|
<Trash2 className="mr-2 h-4 w-4" />
|
|
{t("batch.delete")}
|
|
</Button>
|
|
)}
|
|
<Button variant="ghost" size="sm" onClick={onClearSelection}>
|
|
<X className="mr-2 h-4 w-4" />
|
|
{t("batch.clear")}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<AlertDialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
|
|
<AlertDialogContent>
|
|
<AlertDialogHeader>
|
|
<AlertDialogTitle>{t("batch.deleteConfirmTitle")}</AlertDialogTitle>
|
|
<AlertDialogDescription>
|
|
{t("batch.deleteConfirmDesc", { count: selectedIds.length })}
|
|
</AlertDialogDescription>
|
|
</AlertDialogHeader>
|
|
<AlertDialogFooter>
|
|
<AlertDialogCancel>{t("batch.cancel")}</AlertDialogCancel>
|
|
<AlertDialogAction
|
|
onClick={(e) => {
|
|
e.preventDefault()
|
|
handleBatchDelete()
|
|
}}
|
|
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
|
disabled={isDeleting}
|
|
>
|
|
{isDeleting ? t("batch.deleting") : t("batch.deleteConfirmAction")}
|
|
</AlertDialogAction>
|
|
</AlertDialogFooter>
|
|
</AlertDialogContent>
|
|
</AlertDialog>
|
|
</>
|
|
)
|
|
}
|