feat: Vote panel UI cleanup; auto-close fix (#96166) (conflict resolved)

This commit is contained in:
mcbalaam
2026-06-14 02:31:45 -04:00
committed by nevimer
parent f4d6d1e38f
commit c7fe8ac18e
+198 -205
View File
@@ -216,217 +216,210 @@ const ChoicesPanel = (props) => {
const { currentVote, user } = data;
return (
<Stack.Item grow>
<Section fill scrollable title="Active Vote">
{currentVote && currentVote.countMethod === VoteSystem.VOTE_SINGLE ? (
<NoticeBox success>Select one option</NoticeBox>
) : null}
{currentVote &&
currentVote.choices.length !== 0 &&
currentVote.countMethod === VoteSystem.VOTE_SINGLE ? (
<LabeledList>
{currentVote.choices.map((choice) => (
<Box key={choice.name}>
<LabeledList.Item
label={choice.name.replace(/^\w/, (c) => c.toUpperCase())}
textAlign="right"
buttons={
<Button
tooltip={
user.isGhost && 'Ghost voting was disabled by an admin.'
}
disabled={
user.singleSelection === choice.name || user.isGhost
}
onClick={() => {
act('voteSingle', { voteOption: choice.name });
}}
>
Vote
</Button>
}
>
{user.singleSelection &&
choice.name === user.singleSelection && (
<>
{currentVote && currentVote.countMethod === VoteSystem.VOTE_SINGLE ? (
<NoticeBox success>Select one option</NoticeBox>
) : null}
{currentVote &&
currentVote.choices.length !== 0 &&
currentVote.countMethod === VoteSystem.VOTE_SINGLE ? (
<LabeledList>
{currentVote.choices.map((choice) => (
<Box key={choice.name}>
<LabeledList.Item
label={choice.name.replace(/^\w/, (c) => c.toUpperCase())}
textAlign="right"
buttons={
<Button
tooltip={
user.isGhost && 'Ghost voting was disabled by an admin.'
}
disabled={
user.singleSelection === choice.name || user.isGhost
}
onClick={() => {
act('voteSingle', { voteOption: choice.name });
}}
>
Vote
</Button>
}
>
{user.singleSelection &&
choice.name === user.singleSelection && (
<Icon align="right" mr={2} color="green" name="vote-yea" />
)}
{currentVote.displayStatistics || user.isLowerAdmin /* SKYRAT EDIT*/ ? `${choice.votes} Votes` : null}
</LabeledList.Item>
<LabeledList.Divider />
</Box>
))}
</LabeledList>
) : null}
{currentVote && currentVote.countMethod === VoteSystem.VOTE_MULTI ? (
<NoticeBox success>Select any number of options</NoticeBox>
) : null}
{currentVote &&
currentVote.choices.length !== 0 &&
currentVote.countMethod === VoteSystem.VOTE_MULTI ? (
<LabeledList>
{currentVote.choices.map((choice) => (
<Box key={choice.name}>
<LabeledList.Item
label={choice.name.replace(/^\w/, (c) => c.toUpperCase())}
textAlign="right"
buttons={
<Button
tooltip={
user.isGhost && 'Ghost voting was disabled by an admin.'
}
disabled={user.isGhost}
onClick={() => {
act('voteMulti', { voteOption: choice.name });
}}
>
Vote
</Button>
}
>
{user.multiSelection &&
// BUBBER EDIT CHANGE - Original: [user.ckey.concat(choice.name)]
user.multiSelection[`${user.ckey}_${choice.name}`] === 1 ? (
<Icon align="right" mr={2} color="blue" name="vote-yea" />
) : null}
{
user.isLowerAdmin
? `${choice.votes} Votes`
: '' /* SKYRAT EDIT*/
}
</LabeledList.Item>
<LabeledList.Divider />
</Box>
))}
</LabeledList>
) : null}
{/* BUBBER EDIT ADDITION - Ranked Choice Voting */}
{currentVote && currentVote.countMethod === VoteSystem.VOTE_RANKED ? (
<NoticeBox success>
Click options to rank them in order of preference. Click again to
remove.
</NoticeBox>
) : null}
{currentVote &&
currentVote.choices.length !== 0 &&
currentVote.countMethod === VoteSystem.VOTE_RANKED ? (
<LabeledList>
{currentVote.choices
.map((choice) => {
// Get all current ranks for this user
const userRanks: Record<string, number> = {};
let maxRank = 0;
currentVote.choices.forEach((c) => {
const rankKey = `${user.ckey}_${c.name}`;
const rank = user.multiSelection?.[rankKey] || 0;
if (rank > 0) {
userRanks[c.name] = rank;
maxRank = Math.max(maxRank, rank);
}
});
// Get this choice's current rank
const rankKey = `${user.ckey}_${choice.name}`;
const currentRank = user.multiSelection?.[rankKey] || 0;
return {
choice,
currentRank,
userRanks,
maxRank,
};
})
// Sort by rank (unranked at bottom)
.sort((a, b) => {
if (a.currentRank === 0 && b.currentRank === 0) {
// If both unranked, sort alphabetically
return a.choice.name.localeCompare(b.choice.name);
{currentVote.displayStatistics ? `${choice.votes} Votes` : null}
</LabeledList.Item>
<LabeledList.Divider />
</Box>
))}
</LabeledList>
) : null}
{currentVote && currentVote.countMethod === VoteSystem.VOTE_MULTI ? (
<NoticeBox success>Select any number of options</NoticeBox>
) : null}
{currentVote &&
currentVote.choices.length !== 0 &&
currentVote.countMethod === VoteSystem.VOTE_MULTI ? (
<LabeledList>
{currentVote.choices.map((choice) => (
<Box key={choice.name}>
<LabeledList.Item
label={choice.name.replace(/^\w/, (c) => c.toUpperCase())}
textAlign="right"
buttons={
<Button
tooltip={
user.isGhost && 'Ghost voting was disabled by an admin.'
}
disabled={user.isGhost}
onClick={() => {
act('voteMulti', { voteOption: choice.name });
}}
>
Vote
</Button>
}
if (a.currentRank === 0) return 1; // a is unranked, move to bottom
if (b.currentRank === 0) return -1; // b is unranked, move to bottom
return a.currentRank - b.currentRank; // sort by rank
})
.map(({ choice, currentRank, userRanks, maxRank }) => {
// Function to get button text
const getButtonText = () => {
if (currentRank === 0) {
return 'Vote';
}
return `Choice #${currentRank}`;
};
>
{user.multiSelection &&
user.multiSelection[user.ckey.concat(choice.name)] === 1 ? (
<Icon align="right" mr={2} color="blue" name="vote-yea" />
) : null}
{choice.votes} Votes
</LabeledList.Item>
<LabeledList.Divider />
</Box>
))}
</LabeledList>
) : null}
{/* BUBBER EDIT ADDITION - Ranked Choice Voting */}
{currentVote && currentVote.countMethod === VoteSystem.VOTE_RANKED ? (
<NoticeBox success>
Click options to rank them in order of preference. Click again to
remove.
</NoticeBox>
) : null}
{currentVote &&
currentVote.choices.length !== 0 &&
currentVote.countMethod === VoteSystem.VOTE_RANKED ? (
<LabeledList>
{currentVote.choices
.map((choice) => {
// Get all current ranks for this user
const userRanks: Record<string, number> = {};
let maxRank = 0;
currentVote.choices.forEach((c) => {
const rankKey = `${user.ckey}_${c.name}`;
const rank = user.multiSelection?.[rankKey] || 0;
if (rank > 0) {
userRanks[c.name] = rank;
maxRank = Math.max(maxRank, rank);
}
});
// Function to handle vote click
const handleVoteClick = () => {
if (currentRank > 0) {
// Remove this rank and shift others up
const newRanks: Record<string, number> = {};
Object.entries(userRanks).forEach(
([name, rank]: [string, number]) => {
if (name === choice.name) {
return; // Skip this one as we're removing it
}
if (rank > currentRank) {
newRanks[name] = rank - 1; // Shift up
} else {
newRanks[name] = rank; // Keep same
}
},
);
// Send all rank updates
Object.entries(newRanks).forEach(
([name, newRank]: [string, number]) => {
act('voteRanked', {
voteOption: name,
voteRank: newRank,
});
},
);
// Remove this rank
act('voteRanked', {
voteOption: choice.name,
voteRank: 0,
});
} else {
// Add as next rank
act('voteRanked', {
voteOption: choice.name,
voteRank: maxRank + 1,
});
}
};
// Get this choice's current rank
const rankKey = `${user.ckey}_${choice.name}`;
const currentRank = user.multiSelection?.[rankKey] || 0;
return (
<Box key={choice.name}>
<LabeledList.Item
label={choice.name.replace(/^\w/, (c) => c.toUpperCase())}
textAlign="right"
buttons={
<Button
tooltip={
user.isGhost &&
'Ghost voting was disabled by an admin.'
}
selected={currentRank > 0}
disabled={user.isGhost}
onClick={handleVoteClick}
>
{getButtonText()}
</Button>
return {
choice,
currentRank,
userRanks,
maxRank,
};
})
// Sort by rank (unranked at bottom)
.sort((a, b) => {
if (a.currentRank === 0 && b.currentRank === 0) {
// If both unranked, sort alphabetically
return a.choice.name.localeCompare(b.choice.name);
}
if (a.currentRank === 0) return 1; // a is unranked, move to bottom
if (b.currentRank === 0) return -1; // b is unranked, move to bottom
return a.currentRank - b.currentRank; // sort by rank
})
.map(({ choice, currentRank, userRanks, maxRank }) => {
// Function to get button text
const getButtonText = () => {
if (currentRank === 0) {
return 'Vote';
}
return `Choice #${currentRank}`;
};
// Function to handle vote click
const handleVoteClick = () => {
if (currentRank > 0) {
// Remove this rank and shift others up
const newRanks: Record<string, number> = {};
Object.entries(userRanks).forEach(
([name, rank]: [string, number]) => {
if (name === choice.name) {
return; // Skip this one as we're removing it
}
>
{currentVote.displayStatistics || user.isLowerAdmin
? `${choice.votes} Votes`
: null}
</LabeledList.Item>
<LabeledList.Divider />
</Box>
);
})}
</LabeledList>
) : null}
{/* BUBBER EDIT ADDITION END */}
{currentVote ? null : <NoticeBox>No vote active!</NoticeBox>}
</Section>
</Stack.Item>
if (rank > currentRank) {
newRanks[name] = rank - 1; // Shift up
} else {
newRanks[name] = rank; // Keep same
}
},
);
// Send all rank updates
Object.entries(newRanks).forEach(
([name, newRank]: [string, number]) => {
act('voteRanked', {
voteOption: name,
voteRank: newRank,
});
},
);
// Remove this rank
act('voteRanked', {
voteOption: choice.name,
voteRank: 0,
});
} else {
// Add as next rank
act('voteRanked', {
voteOption: choice.name,
voteRank: maxRank + 1,
});
}
};
return (
<Box key={choice.name}>
<LabeledList.Item
label={choice.name.replace(/^\w/, (c) => c.toUpperCase())}
textAlign="right"
buttons={
<Button
tooltip={
user.isGhost &&
'Ghost voting was disabled by an admin.'
}
selected={currentRank > 0}
disabled={user.isGhost}
onClick={handleVoteClick}
>
{getButtonText()}
</Button>
}
>
{currentVote.displayStatistics || user.isLowerAdmin
? `${choice.votes} Votes`
: null}
</LabeledList.Item>
<LabeledList.Divider />
</Box>
);
})}
</LabeledList>
) : null}
{/* BUBBER EDIT ADDITION END */}
{currentVote ? null : <NoticeBox>No vote active!</NoticeBox>}
</>
);
};