Fix multi-click submit bug and add draft submit indicator on juror dashboard
All checks were successful
Build and Push Docker Image / build (push) Successful in 8m24s
All checks were successful
Build and Push Docker Image / build (push) Successful in 8m24s
- Initialize slider values to midpoint so visual matches stored value (root cause: sliders appeared filled but criteriaValues was undefined) - Use mutateAsync for submit to properly await and prevent double-clicks - Add startMutation.isPending to submit button disabled state - Add error toast in evaluation-form.tsx catch block (was silent) - Show "Ready to submit" badge and "Review & Submit" button for drafts on juror dashboard Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -173,6 +173,24 @@ export default function JuryEvaluatePage({ params: paramsPromise }: PageProps) {
|
||||
}
|
||||
})
|
||||
|
||||
// Initialize numeric criteria with midpoint values so slider visual matches stored value.
|
||||
const criteriaInitializedRef = useRef(false)
|
||||
useEffect(() => {
|
||||
if (criteriaInitializedRef.current || criteria.length === 0) return
|
||||
if (existingEvaluation?.criterionScoresJson) return
|
||||
criteriaInitializedRef.current = true
|
||||
|
||||
const defaults: Record<string, number | boolean | string> = {}
|
||||
for (const c of criteria) {
|
||||
if (c.type === 'numeric') {
|
||||
defaults[c.id] = Math.ceil((c.minScore + c.maxScore) / 2)
|
||||
}
|
||||
}
|
||||
if (Object.keys(defaults).length > 0) {
|
||||
setCriteriaValues((prev) => ({ ...defaults, ...prev }))
|
||||
}
|
||||
}, [criteria, existingEvaluation?.criterionScoresJson])
|
||||
|
||||
// Build current form data for autosave
|
||||
const buildSavePayload = useCallback(() => {
|
||||
return {
|
||||
@@ -370,13 +388,17 @@ export default function JuryEvaluatePage({ params: paramsPromise }: PageProps) {
|
||||
}
|
||||
}
|
||||
|
||||
submitMutation.mutate({
|
||||
id: evaluationId,
|
||||
criterionScoresJson: scoringMode === 'criteria' ? criteriaValues : {},
|
||||
globalScore: scoringMode === 'global' ? parseInt(globalScore, 10) : computedGlobalScore,
|
||||
binaryDecision: scoringMode === 'binary' ? binaryDecision === 'accept' : true,
|
||||
feedbackText: feedbackText || 'No feedback provided',
|
||||
})
|
||||
try {
|
||||
await submitMutation.mutateAsync({
|
||||
id: evaluationId,
|
||||
criterionScoresJson: scoringMode === 'criteria' ? criteriaValues : {},
|
||||
globalScore: scoringMode === 'global' ? parseInt(globalScore, 10) : computedGlobalScore,
|
||||
binaryDecision: scoringMode === 'binary' ? binaryDecision === 'accept' : true,
|
||||
feedbackText: feedbackText || 'No feedback provided',
|
||||
})
|
||||
} catch {
|
||||
// Error toast already handled by onError callback
|
||||
}
|
||||
}
|
||||
|
||||
if (!round || !project) {
|
||||
@@ -840,7 +862,7 @@ export default function JuryEvaluatePage({ params: paramsPromise }: PageProps) {
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
disabled={submitMutation.isPending || autosaveMutation.isPending}
|
||||
disabled={submitMutation.isPending || autosaveMutation.isPending || startMutation.isPending}
|
||||
className="bg-brand-blue hover:bg-brand-blue-light"
|
||||
>
|
||||
<Send className="mr-2 h-4 w-4" />
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
Zap,
|
||||
BarChart3,
|
||||
Waves,
|
||||
Send,
|
||||
} from 'lucide-react'
|
||||
import { formatDateOnly } from '@/lib/utils'
|
||||
import { CountdownTimer } from '@/components/shared/countdown-timer'
|
||||
@@ -390,6 +391,11 @@ async function JuryDashboardContent() {
|
||||
<CheckCircle2 className="mr-1 h-3 w-3" />
|
||||
Done
|
||||
</Badge>
|
||||
) : isDraft && isVotingOpen ? (
|
||||
<Badge className="text-xs bg-amber-100 text-amber-800 border-amber-300 dark:bg-amber-950 dark:text-amber-300 dark:border-amber-700 animate-pulse">
|
||||
<Send className="mr-1 h-3 w-3" />
|
||||
Ready to submit
|
||||
</Badge>
|
||||
) : isDraft ? (
|
||||
<Badge variant="warning" className="text-xs">
|
||||
<Clock className="mr-1 h-3 w-3" />
|
||||
@@ -404,10 +410,17 @@ async function JuryDashboardContent() {
|
||||
View
|
||||
</Link>
|
||||
</Button>
|
||||
) : isVotingOpen && isDraft ? (
|
||||
<Button size="sm" asChild className="h-7 px-3 bg-amber-600 hover:bg-amber-700 text-white shadow-sm">
|
||||
<Link href={`/jury/competitions/${assignment.round.id}/projects/${assignment.project.id}/evaluate`}>
|
||||
<Send className="mr-1 h-3 w-3" />
|
||||
Review & Submit
|
||||
</Link>
|
||||
</Button>
|
||||
) : isVotingOpen ? (
|
||||
<Button size="sm" asChild className="h-7 px-3 bg-brand-blue hover:bg-brand-blue-light shadow-sm">
|
||||
<Link href={`/jury/competitions/${assignment.round.id}/projects/${assignment.project.id}/evaluate`}>
|
||||
{isDraft ? 'Continue' : 'Evaluate'}
|
||||
Evaluate
|
||||
</Link>
|
||||
</Button>
|
||||
) : (
|
||||
|
||||
@@ -303,7 +303,10 @@ export function EvaluationForm({
|
||||
|
||||
// Submit handler
|
||||
const onSubmit = async (data: EvaluationFormData) => {
|
||||
if (!currentEvaluationId) return
|
||||
if (!currentEvaluationId) {
|
||||
toast.error('Evaluation is still being created. Please wait a moment and try again.')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await submit.mutateAsync({
|
||||
@@ -325,6 +328,7 @@ export function EvaluationForm({
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Submit failed:', error)
|
||||
toast.error(error instanceof Error ? error.message : 'Failed to submit evaluation. Please try again.')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -359,7 +363,7 @@ export function EvaluationForm({
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button
|
||||
type="button"
|
||||
disabled={!isValid || submit.isPending}
|
||||
disabled={!isValid || submit.isPending || startEvaluation.isPending}
|
||||
>
|
||||
{submit.isPending ? (
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
@@ -678,7 +682,7 @@ export function EvaluationForm({
|
||||
<Button
|
||||
type="button"
|
||||
size="lg"
|
||||
disabled={!isValid || submit.isPending}
|
||||
disabled={!isValid || submit.isPending || startEvaluation.isPending}
|
||||
className="w-full sm:w-auto"
|
||||
>
|
||||
{submit.isPending ? (
|
||||
|
||||
Reference in New Issue
Block a user