Skip to content

Conversation

@coodos
Copy link
Contributor

@coodos coodos commented Nov 24, 2025

Description of change

Issue Number

Type of change

  • Breaking (any change that would cause existing functionality to not work as expected)
  • New (a change which implements a new feature)
  • Update (a change which updates existing functionality)
  • Fix (a change which fixes an issue)
  • Docs (changes to the documentation)
  • Chore (refactoring, build scripts or anything else that isn't user-facing)

How the change has been tested

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

Release Notes

  • New Features

    • Sort preferences now automatically save between sessions.
    • Poll results now display total eligible points and points voted in eReputation mode.
  • Bug Fixes

    • Tied and Winner badges no longer appear in polls with zero votes.
    • Vote counts correctly display as "Points" instead of "votes" in eReputation mode.
    • Poll sort order now respects user selection instead of forcing active polls first.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 24, 2025

Walkthrough

This PR extends eReputation voting by adding pointsVoted and totalEligiblePoints fields to poll results, updates result rendering to conditionally display "Points" instead of "votes" in eReputation mode, implements localStorage-based sort preference persistence, and removes active poll pre-sorting bias from the API layer.

Changes

Cohort / File(s) Summary
Frontend Result Display
platforms/eVoting/src/app/(app)/[id]/page.tsx
Added conditional guards for Tied/Winner badges requiring totalVotes > 0; updated final results rendering for public polls to display additional points summary in turnout line when in eReputation mode; changed non-winning option vote counts to display as "Points" in eReputation mode; aligned badge gating logic across private and public poll paths.
Frontend Poll Listing
platforms/eVoting/src/app/(app)/page.tsx
Implemented lazy initialization of sort preferences from localStorage with defaults to "deadline" and "asc"; added persistence of sortDirection when toggled and both sortField and sortDirection when field is changed; guarded localStorage access for browser-only execution.
Backend Result Data
platforms/evoting-api/src/services/VoteService.ts
Added computation and conditional attachment of totalEligiblePoints and pointsVoted fields across normal, point-based, and eReputation-weighted voting branches; calculated fields reflect eligible points (summed reputation scores, optionally multiplied by 100) and points voted (weighted vote totals or point totals depending on mode).
Backend Result Interface
platforms/eVoting/src/lib/pollApi.ts
Extended PollResults interface with two optional fields: pointsVoted (number) and totalEligiblePoints (number).
Backend Poll Querying
platforms/evoting-api/src/services/PollService.ts
Removed pre-sort bias that forced active polls before inactive ones; sorting now directly applies user-selected sortField and sortDirection without activity-based initial ordering.

Sequence Diagram(s)

sequenceDiagram
    participant Frontend as Frontend (page.tsx)
    participant API as Backend (VoteService)
    participant Storage as Browser Storage

    Frontend->>Storage: Read sortField & sortDirection on mount
    Storage-->>Frontend: Return preferences (or defaults)
    
    Frontend->>Frontend: Initialize sort state
    
    User->>Frontend: Toggle sort direction
    Frontend->>Storage: Persist sortDirection
    
    User->>Frontend: Select new sort field
    Frontend->>Storage: Persist sortField & reset direction
    
    User->>Frontend: Request poll results
    Frontend->>API: GET poll results with sort params
    API->>API: Compute totalEligiblePoints & pointsVoted
    API-->>Frontend: Return PollResults {<br/>  ...existing fields,<br/>  pointsVoted,<br/>  totalEligiblePoints<br/>}
    
    Frontend->>Frontend: Render results<br/>(show "Points" if eReputation mode)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • VoteService.ts: Review the computation logic for totalEligiblePoints and pointsVoted across all voting branches (normal, point-based, weighted eReputation variants) to ensure consistency and correctness in all scenarios.
  • [id]/page.tsx: Verify conditional rendering logic for "Points" vs "votes" labels and that all badge guards (Tied/Winner requiring totalVotes > 0) function correctly across private and public poll result paths.
  • PollService.ts: Confirm that removal of active poll pre-sorting does not unintentionally alter user-perceived sort order for existing queries.

Possibly related PRs

  • PR #459: Directly related through eReputation-weighted voting enhancements; this PR adds pointsVoted/totalEligiblePoints fields and result rendering that depend on weighted calculation updates introduced there.
  • PR #336: Related via shared poll results UI and API surfaces; both PRs modify PollResults interface and final-results rendering for point/eReputation modes.
  • PR #309: Related at code level through modifications to PollService.ts and sort/poll-listing logic.

Suggested labels

evault-refactor

Suggested reviewers

  • xPathin
  • sosweetham
  • ananyayaya129

Poem

🐰 Points and votes now dance with grace,
eReputation finds its place,
Sorting persists through localStorage dreams,
While badges guard with totalVotes it seems,
The poll results bloom, eReputation-gleams! ✨

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings, 1 inconclusive)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is largely empty—it only contains the template structure with all sections unfilled, including missing Issue Number, Type of change selection, and How the change has been tested details. Fill out all required sections: specify the Issue Number, select the Type of change (Fix applies here), describe how changes were tested, and check off applicable items in the Change checklist.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'fix: evoting reported issues' is vague and generic, using non-descriptive phrasing that doesn't convey meaningful information about the specific changes despite multiple file modifications. Use a more specific title that highlights the primary change, such as 'fix: add eReputation points tracking and sort persistence in eVoting' to better reflect the actual modifications.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/eVoting-ui-fixes-ms-4.2

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coodos coodos marked this pull request as ready for review November 24, 2025 20:39
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
platforms/evoting-api/src/services/VoteService.ts (1)

274-297: Guard against missing reputationResults.results when computing totals

These new aggregates:

if (isWeighted && reputationResults) {
  totalEligiblePoints = reputationResults.results.reduce((sum, r) => sum + r.score, 0);
  pointsVoted = totalWeightedVotes;
}

and

if (reputationResults) {
  totalEligiblePoints = reputationResults.results.reduce((sum, r) => sum + r.score, 0) * 100;
  pointsVoted = totalWeightedPoints;
}

assume reputationResults.results is always a defined array. Elsewhere (getReputationScore) you explicitly guard against !reputationResults.results, returning a default weight instead, which suggests this field can legitimately be absent.

To keep getPollResults from throwing in that case, consider mirroring that guard:

if (isWeighted && reputationResults && Array.isArray(reputationResults.results)) {
  totalEligiblePoints = reputationResults.results.reduce((sum, r) => sum + r.score, 0);
  pointsVoted = totalWeightedVotes;
}

and similarly for the points‑mode branch. If results is missing, you can simply omit these optional fields (leave them undefined) and the frontend will behave as before.

Also applies to: 355-363

🧹 Nitpick comments (5)
platforms/evoting-api/src/services/PollService.ts (1)

86-127: Hoist now out of the sort comparator for determinism and perf

Inside the filteredPolls.sort(...) callback, const now = new Date(); is evaluated on every comparison. In practice this is fine, but it does extra allocations and can theoretically flip aIsActive/bIsActive mid‑sort if deadlines are very close.

Consider hoisting now:

const now = new Date();
const sortedPolls = filteredPolls.sort((a, b) => {
  const aIsActive = !a.deadline || new Date(a.deadline) > now;
  const bIsActive = !b.deadline || new Date(b.deadline) > now;
  // ...
});

This keeps comparisons cheaper and time‑consistent during a single sort call.

platforms/eVoting/src/app/(app)/page.tsx (1)

20-31: Client sort‑state persistence is sound; consider validating stored sortDirection

The lazy useState initializers with typeof window !== "undefined" guards and the write‑backs in handleSort correctly persist sort preferences and are safe for Next.js client components.

One small hardening you might consider: localStorage.getItem("evoting_sortDirection") is blindly cast to "asc" | "desc". If some other script/user sets an unexpected value, it will flow into state. A tiny runtime guard would keep things robust:

const stored = localStorage.getItem("evoting_sortDirection");
return stored === "asc" || stored === "desc" ? stored : "asc";

Same idea could be applied to sortField if you expect external tampering.

Also applies to: 86-101

platforms/eVoting/src/app/(app)/[id]/page.tsx (2)

407-437: Zero‑vote results still visually highlight all options as winners

You now correctly gate the “Tied”/“Winner” badges on blindVoteResults.totalVotes > 0, but isWinner is still computed as:

const isWinner = result.voteCount === Math.max(...blindVoteResults.optionResults.map(r => r.voteCount));

When totalVotes is 0, every voteCount is 0, so every row gets winner styling (green card) even though badges are hidden. If you want a fully neutral look for no‑vote polls, consider tying isWinner to the same condition:

const hasVotes = blindVoteResults.totalVotes > 0;
const maxVotes = Math.max(...blindVoteResults.optionResults.map(r => r.voteCount));
const isWinner = hasVotes && result.voteCount === maxVotes;

Same pattern could be applied to the public‑results path where isWinner is used with resultsData.totalVotes.

Also applies to: 424-433


469-487: Ereputation points summary & “Points” label align with backend; just watch numeric formatting

The new turnout suffix:

(resultsData.mode === "ereputation" || selectedPoll?.votingWeight === "ereputation")
  && resultsData.pointsVoted !== undefined
  && resultsData.totalEligiblePoints !== undefined

is consistent with how VoteService.getPollResults now sets:

  • mode: "ereputation" for weighted normal and point polls.
  • pointsVoted as the weighted total.
  • totalEligiblePoints as the sum of reputation scores (×100 for point mode).

Similarly, in the non‑points branch:

displayValue = resultsData.mode === "ereputation"
  ? `${voteCount} Points`
  : `${voteCount} votes`;

correctly re‑labels weighted tallies as “Points” to avoid calling fractional weights “votes”.

If you expect fractional weights (e.g. non‑integer scores), you might want to apply toFixed/rounding here as you do for percentages, otherwise long decimals could leak into the UI:

const formatted = Number.isInteger(voteCount) ? voteCount : voteCount.toFixed(2);
displayValue = resultsData.mode === "ereputation"
  ? `${formatted} Points`
  : `${formatted} votes`;

Purely a cosmetic refinement; current logic is functionally correct.

Also applies to: 483-485, 524-533, 528-529, 548-557

platforms/evoting-api/src/services/VoteService.ts (1)

334-375: Fix field naming to align across all modes: rename totalWeightedPoints to totalWeightedVotes

The verification confirms the review comment is valid. The frontend PollResults interface defines only totalWeightedVotes?: number, not totalWeightedPoints. However, both point-mode branches (weighted at line 88 and non-weighted at line 123) return totalWeightedPoints, which diverges from the normal mode's totalWeightedVotes and misaligns with the frontend type definition.

While this doesn't cause a TypeScript error (the interface field is optional and structural typing allows extra properties), it creates unnecessary confusion and inconsistency across voting modes. Rename both occurrences of totalWeightedPoints to totalWeightedVotes in the point-mode return objects (lines 88 and 123 context):

// Line 88 context (weighted points):
totalWeightedVotes: totalWeightedPoints,  // Rename from totalWeightedPoints

// Line 123 context (non-weighted points):
totalWeightedVotes: totalPoints,  // Rename from totalWeightedPoints: totalPoints

This aligns all modes with the frontend type definition and maintains consistency.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7952d68 and 94c685b.

📒 Files selected for processing (5)
  • platforms/eVoting/src/app/(app)/[id]/page.tsx (4 hunks)
  • platforms/eVoting/src/app/(app)/page.tsx (2 hunks)
  • platforms/eVoting/src/lib/pollApi.ts (1 hunks)
  • platforms/evoting-api/src/services/PollService.ts (1 hunks)
  • platforms/evoting-api/src/services/VoteService.ts (3 hunks)
🔇 Additional comments (1)
platforms/eVoting/src/lib/pollApi.ts (1)

114-126: PollResults extension looks consistent with backend shape

Adding pointsVoted? and totalEligiblePoints? as optional fields on PollResults aligns with the new data returned by VoteService.getPollResults and preserves backwards compatibility for existing callers that ignore them. No issues from a typing or evolution standpoint.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants