Skip to content

Conversation

@Kartikey15dem
Copy link

@Kartikey15dem Kartikey15dem commented Jan 24, 2026

Fixes - Jira-#I632

Description

This PR improves the Client List behavior by setting Active as the default status filter and ensuring that applied filters persist across data refreshes.

Behavior Changes

Before After
Before After

Changes Implemented

  • Default Active Filter

    • Client list loads with Status = Active selected by default.
  • Filter Persistence on Refresh

    • Previously applied filters (Status and Office) are retained when:
      • Fetching data from API
      • Loading data from local database
    • Prevents filters from resetting unexpectedly.

Summary by CodeRabbit

  • New Features
    • Added client list filtering by selected status and office location
    • "Active" status is now displayed by default when viewing clients

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

Copilot AI review requested due to automatic review settings January 24, 2026 09:21
@coderabbitai
Copy link

coderabbitai bot commented Jan 24, 2026

📝 Walkthrough

Walkthrough

The pull request modifies the ClientListViewModel to add filtering functionality for client lists. It initializes selectedStatus with "Active" as default, then applies filtering by selectedStatus and selectedOffices in both direct database result handling and paging data flows, while preserving unfiltered data separately.

Changes

Cohort / File(s) Summary
Client List Filtering Logic
feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientsList/ClientListViewModel.kt
Initializes selectedStatus to "Active" by default; adds state-aware filtering by selectedStatus and selectedOffices in handleClientResultFromDb (for database results) and handleClientResult (for paging data); preserves unfiltered data in separate properties; updates imports for filtering operations.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • biplab1

Poem

🐰 Filters applied with hoppy care,
Active clients beyond compare!
States and offices sort with grace,
Data flows through the proper place!
A rabbit's work, pristine and true,
New logic blooms—your list shines too!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main changes: setting 'Active' as default status and applying filters on refresh, which matches the core implementation in the ViewModel.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR updates the Client List screen behavior to default the status filter to Active and to re-apply the currently selected Status/Office filters whenever client data is refreshed (API or DB).

Changes:

  • Set the initial selectedStatus to ["Active"] so the list defaults to Active clients.
  • Apply selectedStatus/selectedOffices filtering when new data arrives from DB and when paging data arrives from API.
  • Preserve an unfiltered paging flow (unfilteredClientsFlow) so filter changes can be re-applied without losing the original stream.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +31 to +32
import kotlin.text.contains
import kotlin.toString
Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

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

The added imports kotlin.text.contains and kotlin.toString are unused here (the file doesn't call contains(...), and toString() doesn't need an import). Please remove them to avoid lint/ktlint failures.

Suggested change
import kotlin.text.contains
import kotlin.toString

Copilot uses AI. Check for mistakes.
updateState {
val data = result.data.pageItems
if (data.isEmpty()) {
it.copy(isEmpty = true, dialogState = null)
Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

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

When the DB returns an empty data list, the state update only sets isEmpty = true and leaves clients/unfilteredClients untouched. This can display stale clients (UI renders clients when non-empty) while also hiding actions due to isEmpty. Clear the client lists in this branch (and likely reset related fields like unfilteredClients).

Suggested change
it.copy(isEmpty = true, dialogState = null)
it.copy(
isEmpty = true,
clients = emptyList(),
unfilteredClients = emptyList(),
dialogState = null,
)

Copilot uses AI. Check for mistakes.
}
it.copy(
clients = filteredData,
unfilteredClients = data,
Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

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

In the non-empty DB success branch, isEmpty is never reset to false. If a previous load set isEmpty = true, the screen can remain in the empty-state mode (e.g., hiding ClientActions) even though clients now has data. Set isEmpty = false when updating state with non-empty results (and consider basing it on filteredData.isEmpty() if that's the intended UX).

Suggested change
unfilteredClients = data,
unfilteredClients = data,
isEmpty = filteredData.isEmpty(),

Copilot uses AI. Check for mistakes.
dialogState = null,
clientsFlow = filteredFlow,
unfilteredClientsFlow = result,
dialogState = null,
Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

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

handleClientResult updates clientsFlow/unfilteredClientsFlow but doesn't reset isEmpty. If isEmpty was previously set to true (e.g., from a DB load), the UI can stay in the empty-state mode while paging data is shown. Consider explicitly setting isEmpty = false here when assigning a non-null flow.

Suggested change
dialogState = null,
dialogState = null,
isEmpty = false,

Copilot uses AI. Check for mistakes.
Comment on lines +161 to +167
val filteredData = data.filter { client ->
val statusMatch = it.selectedStatus.isEmpty() ||
client.status?.value in it.selectedStatus
val officeMatch = it.selectedOffices.isEmpty() ||
(client.officeName ?: "Null") in it.selectedOffices
statusMatch && officeMatch
}
Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

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

The same status/office filter predicate is duplicated in multiple places (handleClientResultFromDb, handleClientResult, and the local keep(...) in handleFilterClick). To reduce the chance of future inconsistencies, consider extracting this into a single helper (e.g., a function that returns a (ClientEntity) -> Boolean based on selected filters) and reuse it for list + paging filtering.

Copilot uses AI. Check for mistakes.
Copy link

@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: 1

🤖 Fix all issues with AI agents
In
`@feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientsList/ClientListViewModel.kt`:
- Around line 155-176: When handling the DataState.Success branch, stop using
the magic string "Null" for client.officeName and instead treat null explicitly:
compute officeMatch as true when selectedOffices is empty, otherwise compare
client.officeName directly (nullable) against the selectedOffices set or handle
a dedicated sentinel for unassigned offices (e.g., a null entry or explicit
"UNASSIGNED" constant) so filtering is explicit and not coupled to the literal
"Null"; also when data.isEmpty() set isEmpty = true AND clear stale results by
setting clients = emptyList() and unfilteredClients = emptyList() (and
dialogState = null) so stale data is not preserved. Ensure these changes are
applied inside updateState where selectedStatus, selectedOffices, clients,
unfilteredClients, dialogState and isEmpty are updated.
🧹 Nitpick comments (2)
feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientsList/ClientListViewModel.kt (2)

31-32: Remove unnecessary imports.

kotlin.text.contains and kotlin.toString are standard Kotlin functions that don't require explicit imports. The code uses the in operator (which compiles to contains) and toString() is always available on any object.

Suggested fix
-import kotlin.text.contains
-import kotlin.toString

161-167: Consider extracting duplicated filter logic.

The same filtering predicate (status match + office match) is duplicated in three locations:

  • handleClientResultFromDb (lines 161-166)
  • handleClientResult (lines 182-187)
  • handleFilterClick (lines 272-276)

Extract to a reusable helper for consistency and maintainability.

Suggested extraction
private fun ClientEntity.matchesFilters(
    selectedStatus: List<String>,
    selectedOffices: List<String>,
): Boolean {
    val statusMatch = selectedStatus.isEmpty() || status?.value in selectedStatus
    val officeMatch = selectedOffices.isEmpty() || (officeName ?: "Null") in selectedOffices
    return statusMatch && officeMatch
}

Then use it as:

val filteredData = data.filter { client ->
    client.matchesFilters(it.selectedStatus, it.selectedOffices)
}

Also applies to: 182-188, 272-277

Comment on lines +155 to 176
is DataState.Success -> {
updateState {
val data = result.data.pageItems
if (data.isEmpty()) {
it.copy(isEmpty = true, dialogState = null)
} else {
val filteredData = data.filter { client ->
val statusMatch = it.selectedStatus.isEmpty() ||
client.status?.value in it.selectedStatus
val officeMatch = it.selectedOffices.isEmpty() ||
(client.officeName ?: "Null") in it.selectedOffices
statusMatch && officeMatch
}
it.copy(
clients = filteredData,
unfilteredClients = data,
dialogState = null
)
}
}
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential issues with null handling and empty state.

  1. Magic string "Null": Using "Null" as a fallback for null officeName (line 165) creates a hidden coupling. If users can filter by offices, they'd need to select a literal "Null" string to match clients with no office assigned. Consider whether this is intentional UX or if null offices should be handled differently.

  2. Empty state doesn't clear clients: When data.isEmpty() (line 158), only isEmpty = true is set without clearing clients or unfilteredClients, potentially leaving stale data.

Suggested fix for empty state
             if (data.isEmpty()) {
-                        it.copy(isEmpty = true, dialogState = null)
+                        it.copy(
+                            isEmpty = true,
+                            clients = emptyList(),
+                            unfilteredClients = emptyList(),
+                            dialogState = null
+                        )
                     } else {
📝 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
is DataState.Success -> {
updateState {
val data = result.data.pageItems
if (data.isEmpty()) {
it.copy(isEmpty = true, dialogState = null)
} else {
val filteredData = data.filter { client ->
val statusMatch = it.selectedStatus.isEmpty() ||
client.status?.value in it.selectedStatus
val officeMatch = it.selectedOffices.isEmpty() ||
(client.officeName ?: "Null") in it.selectedOffices
statusMatch && officeMatch
}
it.copy(
clients = filteredData,
unfilteredClients = data,
dialogState = null
)
}
}
}
}
is DataState.Success -> {
updateState {
val data = result.data.pageItems
if (data.isEmpty()) {
it.copy(
isEmpty = true,
clients = emptyList(),
unfilteredClients = emptyList(),
dialogState = null
)
} else {
val filteredData = data.filter { client ->
val statusMatch = it.selectedStatus.isEmpty() ||
client.status?.value in it.selectedStatus
val officeMatch = it.selectedOffices.isEmpty() ||
(client.officeName ?: "Null") in it.selectedOffices
statusMatch && officeMatch
}
it.copy(
clients = filteredData,
unfilteredClients = data,
dialogState = null
)
}
}
}
}
🤖 Prompt for AI Agents
In
`@feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientsList/ClientListViewModel.kt`
around lines 155 - 176, When handling the DataState.Success branch, stop using
the magic string "Null" for client.officeName and instead treat null explicitly:
compute officeMatch as true when selectedOffices is empty, otherwise compare
client.officeName directly (nullable) against the selectedOffices set or handle
a dedicated sentinel for unassigned offices (e.g., a null entry or explicit
"UNASSIGNED" constant) so filtering is explicit and not coupled to the literal
"Null"; also when data.isEmpty() set isEmpty = true AND clear stale results by
setting clients = emptyList() and unfilteredClients = emptyList() (and
dialogState = null) so stale data is not preserved. Ensure these changes are
applied inside updateState where selectedStatus, selectedOffices, clients,
unfilteredClients, dialogState and isEmpty are updated.

@niyajali
Copy link
Collaborator

@Kartikey15dem If the data is filtered solely by "Active" status, the user may neglect to filter other statuses, thereby failing to execute the necessary action on that data. Additionally, the Jira ticket mandates sorting the data by "Active" status, not filtering it.

@Kartikey15dem
Copy link
Author

@Kartikey15dem If the data is filtered solely by "Active" status, the user may neglect to filter other statuses, thereby failing to execute the necessary action on that data. Additionally, the Jira ticket mandates sorting the data by "Active" status, not filtering it.
@niyajali I opened the pull request but I found some other issues so I am still working on it.

@niyajali
Copy link
Collaborator

Sort And Group Client List by Status and Initially display the Active client.

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