diff --git a/src/components/VoteForm.jsx b/src/components/VoteForm.jsx index 6cb17588..791d4174 100644 --- a/src/components/VoteForm.jsx +++ b/src/components/VoteForm.jsx @@ -1,55 +1,126 @@ import axios from "axios"; -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, use } from "react"; import { API_URL } from "../shared"; const VoteForm = ({ poll, readOnly = false }) => { const [rankings, setRankings] = useState({}); - console.log("this is rankins---->", rankings) + console.log("this is rankins---->", rankings); const [submitting, setSubmitting] = useState(false); const [orderedOptions, setOrderedOptions] = useState([]); - console.log("this is ordered options", orderedOptions) + console.log("this is ordered options", orderedOptions); const [draggedItem, setDraggedItem] = useState(null); - console.log("dragged--->", draggedItem) + console.log("dragged--->", draggedItem); const [deletedOptions, setDeletedOptions] = useState(new Set()); const [submitted, setSubmitted] = useState(false); const [error, setError] = useState(null); + const [voteId, setVoteId] = useState(null); + const [movedOptionIds, setMovedOptionIds] = useState(new Set()); + const [moveWarning, setMoveWarning] = useState(""); console.log("VoteForm rendered with poll:", poll); console.log("Poll options:", poll?.pollOptions); + const allDeleted = + orderedOptions.length > 0 && + orderedOptions.every((opt) => deletedOptions.has(opt.id)); // Check if all options are deleted + // Initialize ordered options when poll changes useEffect(() => { - if (poll?.pollOptions) { + if (poll?.pollOptions && orderedOptions.length === 0) { setOrderedOptions([...poll.pollOptions]); } }, [poll?.pollOptions]); // Update rankings whenever the order changes useEffect(() => { - let newRankings = []; - orderedOptions.forEach((option, index) => { + const newRankings = {}; + let currentRank = 1; + + orderedOptions.forEach((option) => { if (deletedOptions.has(option.id)) { - // Keep deleted options as null for algorrithm newRankings[option.id] = null; } else { - // Find the position among non-deleted options - const nonDeletedBefore = orderedOptions - .slice(0, index) - .filter((opt) => !deletedOptions.has(opt.id)).length; - - newRankings.push({ - optionId: option.id, - rank: nonDeletedBefore + 1 - }) - - // newRankings.optionId = option.id, - // newRankings.ranking = index - // newRankings.optionId = option.id + newRankings[option.id] = currentRank++; } }); + setRankings(newRankings); }, [orderedOptions, deletedOptions]); + useEffect(() => { + const fetchOrCreateVote = async () => { + if (!poll?.id || readOnly) return; + + try { + // Try to fetch vote + const res = await axios.get(`${API_URL}/api/polls/${poll.id}/vote`, { + withCredentials: true, + }); + + const voteData = res.data; + setVoteId(voteData.id); + + // Restore saved rankings if they exist + if (voteData.votingRanks) { + const restored = voteData.votingRanks.map((rank) => ({ + optionId: rank.pollOptionId, + rank: rank.rank, + })); + + const restoredMap = {}; + restored.forEach((r) => { + restoredMap[r.optionId] = r.rank; + }); + + setRankings(restoredMap); + + // Set ordered options based on restored rankings + + if (poll?.pollOptions) { + const sortedOptions = [...poll.pollOptions] + .filter( + (opt) => + restoredMap[opt.id] !== undefined && + restoredMap[opt.id] !== null + ) + .sort((a, b) => restoredMap[a.id] - restoredMap[b.id]); + + const unranked = poll.pollOptions.filter( + (opt) => restoredMap[opt.id] === undefined + ); + + setOrderedOptions([...sortedOptions, ...unranked]); + + const deleted = new Set( + poll.pollOptions + .filter((opt) => restoredMap[opt.id] === null) + .map((opt) => opt.id) + ); + setDeletedOptions(deleted); + } + } + } catch (err) { + // Vote doesn't exist, so create it + try { + const createRes = await axios.post( + `${API_URL}/api/polls/${poll.id}/vote`, + { + submitted: false, + rankings: [], + }, + { withCredentials: true } + ); + + setVoteId(createRes.data.id); + } catch (createErr) { + console.error("Failed to create vote:", createErr); + } + } + }; + + fetchOrCreateVote(); + }, [poll?.id, readOnly]); + if (!poll) { return
Loading poll data...
; } @@ -103,6 +174,13 @@ const VoteForm = ({ poll, readOnly = false }) => { setOrderedOptions(newOrderedOptions); setDraggedItem(index); + + setMovedOptionIds((prev) => { + const updated = new Set(prev); + updated.add(draggedOption.id); + setMoveWarning(""); + return updated; + }); } }; @@ -124,11 +202,33 @@ const VoteForm = ({ poll, readOnly = false }) => { const handleSubmit = async (e) => { e.preventDefault(); - setSubmitting(true); - try { + + const hasRankedAll = orderedOptions + .filter((opt) => !deletedOptions.has(opt.id)) + .every((opt) => movedOptionIds.has(opt.id)); + + + // Show popup if not all options moved + if (!hasRankedAll) { + const confirmProceed = window.confirm( + "You haven't ranked all options. Are you sure you want to submit anyway?" + ); + if (!confirmProceed) return; + } + // Convert rankings object to array format + const formattedRankings = Object.entries(rankings) + .filter(([_, rank]) => rank !== null) // filter out nulls if needed + .map(([optionId, rank]) => ({ + optionId: parseInt(optionId), + rank: rank + })); + setSubmitting(true); + setMoveWarning(""); + + try { await fetch(`${API_URL}/api/polls/${poll.id}/vote`, { @@ -137,7 +237,7 @@ const VoteForm = ({ poll, readOnly = false }) => { credentials: "include", body: JSON.stringify({ pollId: poll.id, - rankings: rankings, + rankings: formattedRankings, }), }); // await axios.post("http://localhost:8080/api/:pollId/vote", @@ -156,6 +256,38 @@ const VoteForm = ({ poll, readOnly = false }) => { } }; + const handleSaveDraft = async (e) => { + e.preventDefault(); + if (!voteId) { + setError("No voteId – cannot save draft."); + return; + } + + try { + const formattedRankings = orderedOptions + .filter((opt) => !deletedOptions.has(opt.id)) + .map((opt, index) => ({ + optionId: opt.id, + rank: index + 1, + })); + + await axios.patch( + `${API_URL}/api/polls/${poll.id}/vote/${voteId}`, + { + submitted: false, + rankings: formattedRankings, + }, + { withCredentials: true } + ); + + alert("Draft saved successfully!"); + setSubmitted(false); + } catch (err) { + console.error("Failed to save draft:", err); + setError("Failed to save draft. Please try again."); + } + }; + return (

@@ -170,8 +302,9 @@ const VoteForm = ({ poll, readOnly = false }) => { return (
!isDeleted && handleDragStart(e, index)} onDragOver={(e) => !isDeleted && handleDragOver(e, index)} @@ -238,17 +371,31 @@ const VoteForm = ({ poll, readOnly = false }) => {
{error && ( -
+
{error}
)} - + + ); };