diff --git a/src/components/frontend/profile/LeadershipEvaluationDialog.tsx b/src/components/frontend/profile/LeadershipEvaluationDialog.tsx new file mode 100644 index 0000000..55c3dc7 --- /dev/null +++ b/src/components/frontend/profile/LeadershipEvaluationDialog.tsx @@ -0,0 +1,218 @@ +"use client"; + +import { useState } from "react"; +import { useRouter } from "next/navigation"; +import { Loader2Icon, PencilIcon } from "lucide-react"; +import { toast } from "sonner"; +import { + getLeadershipDisplayLabel, + PERFORMANCE_LEVELS, + isPerformanceLevelId, + type PerformanceLevelId, +} from "@/lib/evaluations/levels"; +import { submitEvaluation } from "@/app/(frontend)/profile/[username]/actions"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { Label } from "@/components/ui/label"; +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Textarea } from "@/components/ui/textarea"; +import type { EvaluationMissionOption } from "./evaluation-types"; + +interface LeadershipEvaluationDialogProps { + readonly username: string; + readonly missions: readonly EvaluationMissionOption[]; +} + +function formatMissionDate(value: string): string { + return new Intl.DateTimeFormat("en-US", { + month: "short", + day: "numeric", + year: "numeric", + }).format(new Date(value)); +} + +export function LeadershipEvaluationDialog({ + username, + missions, +}: LeadershipEvaluationDialogProps) { + const router = useRouter(); + const [open, setOpen] = useState(false); + const [missionId, setMissionId] = useState(""); + const [level, setLevel] = useState(""); + const [comment, setComment] = useState(""); + const [pending, setPending] = useState(false); + const [error, setError] = useState(null); + + function reset() { + setMissionId(""); + setLevel(""); + setComment(""); + setPending(false); + setError(null); + } + + async function handleSubmit(event: React.FormEvent) { + event.preventDefault(); + + const selectedMission = missions.find((mission) => String(mission.id) === missionId); + if (!selectedMission) { + setError("Select a mission to rate."); + return; + } + if (!isPerformanceLevelId(level)) { + setError("Select a performance level."); + return; + } + + setPending(true); + setError(null); + + try { + const trimmedComment = comment.trim(); + const result = await submitEvaluation({ + username, + missionId: selectedMission.id, + level, + ...(trimmedComment ? { comment: trimmedComment } : {}), + }); + + if (!result.success) { + setError(result.error ?? "Could not save the rating."); + return; + } + + setOpen(false); + reset(); + toast.success("Leadership rating saved"); + router.refresh(); + } catch (caught) { + setError(caught instanceof Error ? caught.message : "Could not save the rating."); + } finally { + setPending(false); + } + } + + return ( + { + if (!nextOpen && pending) return; + setOpen(nextOpen); + if (!nextOpen) reset(); + }} + > + + + + + + Rate leadership + + Submit a new rating or update your existing rating for one completed mission. Your + rating is included anonymously in the leadership aggregate. + + + +
+
+ + +
+ +
+ + +
+ +
+ +