- 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
103 lines
2.9 KiB
TypeScript
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>
|
|
)
|
|
}
|