Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
209 changes: 178 additions & 31 deletions src/components/VoteForm.jsx
Original file line number Diff line number Diff line change
@@ -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 <div className="vote-form">Loading poll data...</div>;
}
Expand Down Expand Up @@ -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;
});
}
};

Expand All @@ -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`, {

Expand All @@ -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",
Expand All @@ -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 (
<form onSubmit={handleSubmit} className="vote-form">
<h4>
Expand All @@ -170,8 +302,9 @@ const VoteForm = ({ poll, readOnly = false }) => {
return (
<div
key={option.id}
className={`ranking-item ${draggedItem === index ? "dragging" : ""
} ${isDeleted ? "deleted" : ""}`}
className={`ranking-item ${
draggedItem === index ? "dragging" : ""
} ${isDeleted ? "deleted" : ""}`}
draggable={!readOnly && !isDeleted && !submitted}
onDragStart={(e) => !isDeleted && handleDragStart(e, index)}
onDragOver={(e) => !isDeleted && handleDragOver(e, index)}
Expand Down Expand Up @@ -238,17 +371,31 @@ const VoteForm = ({ poll, readOnly = false }) => {
</div>

{error && (
<div
className="submit-error"
style={{ color: "red" }}
>
<div className="submit-error" style={{ color: "red" }}>
{error}
</div>
)}

<button type="submit" disabled={readOnly || submitting || submitted}>
{allDeleted && (
<p style={{ color: "red" }}>
You have deleted all options. Please restore at least one option to
proceed.
</p>
)}

<button
type="submit"
disabled={readOnly || submitting || submitted || allDeleted}
>
Submit Vote
</button>

<button
onClick={handleSaveDraft}
disabled={movedOptionIds.size === 0 || submitted || allDeleted}
>
Save Draft
</button>
</form>
);
};
Expand Down