Files
NextEdu/src/modules/error-book/components/review-buttons.tsx
SpecialX d7017f0e30 feat(modules-classes): update classes, course-plans, dashboard, diagnostic, elective, error-book
- classes: update actions-shared, admin-classes-view, class-detail, class-invitation-manager,

  grade-classes-view, my-classes-grid, schedule dialogs, students-table,

  data-access-admin, data-access-students, data-access-teacher, data-access

- course-plans: update course-plan-detail, course-plan-form, course-plan-item-editor,

  template-picker-dialog, data-access

- dashboard: update dashboard-time-range-filter, use-dashboard-preferences,

  use-dashboard-realtime

- diagnostic: update class-diagnostic-view, report-list, data-access

- elective: update elective-course-form, elective-course-list, student-selection-view,

  data-access-operations, data-access-selections, data-access

- error-book: update add-error-book-dialog, error-book-detail-dialog, review-buttons
2026-07-07 16:19:08 +08:00

103 lines
2.9 KiB
TypeScript

"use client"
import { useState, useTransition } from "react"
import { RotateCcw, ThumbsUp, Check, Zap } from "lucide-react"
import { notify } from "@/shared/lib/notify"
import { useTranslations } from "next-intl"
import { Button } from "@/shared/components/ui/button"
import { reviewErrorBookItemAction } from "../actions"
import type { ErrorBookReviewResultValue } from "../types"
interface ReviewButtonsProps {
itemId: string
onReviewed?: () => void
}
type ReviewOption = {
result: ErrorBookReviewResultValue
label: string
description: string
icon: typeof RotateCcw
variant: "destructive" | "secondary" | "default" | "outline"
}
export function ReviewButtons({ itemId, onReviewed }: ReviewButtonsProps) {
const t = useTranslations("errorBook")
const [isPending, startTransition] = useTransition()
const [selected, setSelected] = useState<ErrorBookReviewResultValue | null>(null)
const reviewOptions: ReviewOption[] = [
{
result: "again",
label: t("review.again"),
description: t("review.againDesc"),
icon: RotateCcw,
variant: "destructive",
},
{
result: "hard",
label: t("review.hard"),
description: t("review.hardDesc"),
icon: Zap,
variant: "secondary",
},
{
result: "good",
label: t("review.good"),
description: t("review.goodDesc"),
icon: ThumbsUp,
variant: "default",
},
{
result: "easy",
label: t("review.easy"),
description: t("review.easyDesc"),
icon: Check,
variant: "outline",
},
]
function handleReview(result: ErrorBookReviewResultValue) {
setSelected(result)
startTransition(async () => {
const formData = new FormData()
formData.append("json", JSON.stringify({ itemId, result }))
const res = await reviewErrorBookItemAction(undefined, formData)
if (res.success) {
notify.success(res.message ?? t("messages.reviewRecorded"))
onReviewed?.()
} else {
notify.error(res.message ?? t("messages.recordFailed"))
setSelected(null)
}
})
}
return (
<div className="grid grid-cols-2 gap-2 sm:grid-cols-4">
{reviewOptions.map((opt) => {
const Icon = opt.icon
const isLoading = isPending && selected === opt.result
return (
<Button
key={opt.result}
variant={opt.variant}
size="sm"
disabled={isPending}
onClick={() => handleReview(opt.result)}
className="flex flex-col items-center gap-1 h-auto py-3"
>
<Icon className="h-4 w-4" data-icon="inline-start" />
<span className="font-medium">{opt.label}</span>
<span className="text-[10px] font-normal text-muted-foreground">
{opt.description}
</span>
{isLoading ? "..." : null}
</Button>
)
})}
</div>
)
}