Skip to content

feat: implement lead status sorting#633

Open
kreeti22 wants to merge 1 commit into
Kuldeeep18:mainfrom
kreeti22:main
Open

feat: implement lead status sorting#633
kreeti22 wants to merge 1 commit into
Kuldeeep18:mainfrom
kreeti22:main

Conversation

@kreeti22

@kreeti22 kreeti22 commented Jul 8, 2026

Copy link
Copy Markdown

Pull Request

🔗 Related Issue

Closes # issue #557


📝 Summary of Changes

Added a status sorting toggle to the lead table to improve workflow efficiency


🏷️ Type of Change

  • 🐛 Bug fix
  • [x ] ✨ New feature
  • ♻️ Refactor
  • 📝 Documentation update
  • [x ] 🎨 UI / Style change
  • 🔧 Chore

🧪 Testing

Steps to test:
1.
2.
3.


📸 Screenshots (if applicable)


✅ Checklist

  • No merge conflicts
  • Changes follow the project guidelines
  • Documentation updated (if applicable)
  • Related issue linked
  • Changes tested locally (if applicable)

Summary by CodeRabbit

  • New Features

    • Added sorting for leads by status in the leads table.
    • Users can now click the Status column to toggle ascending/descending order.
    • Lead lists now support sorting by several visible fields, including contact, company, status, and date created.
  • Bug Fixes

    • Lead status is now consistently shown and sorted using the related campaign status data.
    • The table displays a loading state while updated results are being fetched.

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The backend LeadViewSet now supports ordering via DRF's OrderingFilter, whitelisting fields including a new annotated status field derived from related campaignlead__status. The frontend leads page adds a clickable Status header that toggles sort direction, fetches sorted leads, and re-renders the table.

Changes

Status Sorting Feature

Layer / File(s) Summary
Backend status annotation and ordering
backend/leads/views.py
Imports F and filters, enables OrderingFilter with whitelisted ordering_fields (including status, name/email/company, created_at), and annotates the queryset's status from campaignlead__status.
Frontend sortable status header
frontend/leads.html
Status column header becomes a clickable control with a sort icon; a click handler toggles statusSortDir, fetches leads with an ordering parameter, shows a loading spinner, and re-renders lead rows.

Estimated code review effort: 2 (Simple) | ~10 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant LeadsPage
  participant LeadViewSet
  User->>LeadsPage: click sort-status-btn
  LeadsPage->>LeadsPage: toggle statusSortDir, show spinner
  LeadsPage->>LeadViewSet: fetchLeadsWithFilters(activeFilters, ordering)
  LeadViewSet-->>LeadsPage: sorted leads (status annotation)
  LeadsPage->>LeadsPage: renderLeadRows(allLeads)
Loading

Possibly related issues

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding lead status sorting in the lead table and API.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
backend/leads/views.py (1)

86-88: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Annotate status only when ordering by status

The annotation is applied unconditionally on every list request, adding a JOIN to the campaignlead table even when the client isn't sorting by status. Consider applying it only when ordering=status is requested to avoid unnecessary JOINs on the hot path.

♻️ Suggested conditional annotation
        # Annotate the queryset with the status from the related campaignlead
-        qs = qs.annotate(status=F('campaignlead__status'))
+        ordering = self.request.query_params.get('ordering', '')
+        if 'status' in ordering:
+            qs = qs.annotate(status=F('campaignlead__status'))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/leads/views.py` around lines 86 - 88, The queryset in the leads list
view is always annotated with campaignlead status, which forces an unnecessary
join on every request. Update the list/query construction in the relevant view
method that uses qs.annotate(status=F('campaignlead__status')) so the annotation
is applied only when the requested ordering is by status (for example, when
ordering=status is present), and leave the queryset unmodified for other
orderings.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontend/leads.html`:
- Around line 906-912: The fetchLeadsWithFilters error path in the sorting flow
only logs to the console, leaving the loading spinner in place and giving no
recovery behavior. Update the catch block around renderLeadRows so it restores
the table to a usable state on failure—either by re-rendering the previous leads
already held in allLeads or by clearing/replacing the spinner with an
error/empty state—while keeping the existing console.error for debugging.
- Around line 367-369: The Status table header in the leads view is only
mouse-clickable, so make the sortable `<th>` accessible by adding keyboard focus
and semantics in the status header markup and wiring keyboard activation in the
existing sort handler area. Move the shared sort behavior into a
`toggleStatusSort` function, then call it from both the current click handler
and a new keydown handler that responds to Enter and Space so keyboard and
screen reader users can trigger sorting.

---

Nitpick comments:
In `@backend/leads/views.py`:
- Around line 86-88: The queryset in the leads list view is always annotated
with campaignlead status, which forces an unnecessary join on every request.
Update the list/query construction in the relevant view method that uses
qs.annotate(status=F('campaignlead__status')) so the annotation is applied only
when the requested ordering is by status (for example, when ordering=status is
present), and leave the queryset unmodified for other orderings.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 836073bb-35fb-4be1-8f57-cc2a3232209d

📥 Commits

Reviewing files that changed from the base of the PR and between 4a33158 and 71666a6.

📒 Files selected for processing (3)
  • .gitignore
  • backend/leads/views.py
  • frontend/leads.html

Comment thread frontend/leads.html
Comment on lines +367 to +369
<th id="sort-status-btn" style="cursor: pointer;" class="user-select-none text-primary" title="Sort by Status">
Status <i class="bi bi-arrow-down-up ms-1" style="font-size: 0.8rem;"></i>
</th>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Status header is not keyboard accessible

The clickable <th> has no tabindex, role, or keyboard event handler. Keyboard and screen reader users cannot activate the sort.

♿ Proposed accessibility fix
-                                    <th id="sort-status-btn" style="cursor: pointer;" class="user-select-none text-primary" title="Sort by Status">
+                                    <th id="sort-status-btn" style="cursor: pointer;" class="user-select-none text-primary" role="button" tabindex="0" aria-label="Sort by Status" title="Sort by Status">

And add a keyboard handler alongside the click handler (around line 890):

             document.getElementById('sort-status-btn').addEventListener('click', async () => {
+                await toggleStatusSort();
+            });
+            document.getElementById('sort-status-btn').addEventListener('keydown', async (e) => {
+                if (e.key === 'Enter' || e.key === ' ') {
+                    e.preventDefault();
+                    await toggleStatusSort();
+                }
             });

Extract the sort logic into a shared toggleStatusSort function to avoid duplication.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/leads.html` around lines 367 - 369, The Status table header in the
leads view is only mouse-clickable, so make the sortable `<th>` accessible by
adding keyboard focus and semantics in the status header markup and wiring
keyboard activation in the existing sort handler area. Move the shared sort
behavior into a `toggleStatusSort` function, then call it from both the current
click handler and a new keydown handler that responds to Enter and Space so
keyboard and screen reader users can trigger sorting.

Comment thread frontend/leads.html
Comment on lines +906 to +912
try {
const leads = await fetchLeadsWithFilters(params);
allLeads = leads;
renderLeadRows(leads);
} catch (e) {
console.error('Error sorting leads by status:', e);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Error handling leaves spinner stuck

On fetch failure, only console.error is called. The spinner HTML injected at line 904 remains in the table body indefinitely with no user feedback or recovery path.

🛡️ Proposed fix to restore previous data on error
                } catch (e) {
                    console.error('Error sorting leads by status:', e);
+                    renderLeadRows(allLeads);
                }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try {
const leads = await fetchLeadsWithFilters(params);
allLeads = leads;
renderLeadRows(leads);
} catch (e) {
console.error('Error sorting leads by status:', e);
}
try {
const leads = await fetchLeadsWithFilters(params);
allLeads = leads;
renderLeadRows(leads);
} catch (e) {
console.error('Error sorting leads by status:', e);
renderLeadRows(allLeads);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/leads.html` around lines 906 - 912, The fetchLeadsWithFilters error
path in the sorting flow only logs to the console, leaving the loading spinner
in place and giving no recovery behavior. Update the catch block around
renderLeadRows so it restores the table to a usable state on failure—either by
re-rendering the previous leads already held in allLeads or by
clearing/replacing the spinner with an error/empty state—while keeping the
existing console.error for debugging.

@kreeti22

Copy link
Copy Markdown
Author

@Kuldeeep18, The implementation for lead status sorting is ready and passing all checks. Looking forward to your feedback for the final merge!

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.

1 participant