Files
NextEdu/src/modules/questions/components/batch-operations.tsx
SpecialX 783b8f5484 feat(modules-comm): update messaging, notifications, onboarding, parent, proctoring, questions, rbac
- 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
2026-07-07 16:21:00 +08:00

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>
</>
)
}