[Customer Portal][FE][Web] Improve Empty State UX with Refinement-Aware Messaging , Icons and refine search logic - #436
Conversation
Adjust unit tests for CasesList by refining mocked Oxygen UI components and updating assertions. TableRow mock now forwards onClick (and ignores unused props) so click behavior can be tested reliably; mocked imports were reformatted for consistency. Update the empty-state assertion to match the new message text and reformat objectContaining expectations for readability. These changes make tests more realistic and less flaky.
Improve empty-state UX in CasesList: add EmptyIcon and SearchNoResultsIcon imports, introduce hasListRefinement prop (default false), and render a centered empty-state layout with appropriate icon and message depending on whether the list was refined (search/filters) or truly empty. Includes styling for icon sizing and spacing.
Pass hasListRefinement to the ListingTable based on getActiveFiltersCount() > 0 so the table can know when list filters are active and adjust its UI/behavior accordingly.
Reset the search input when clearing all filters and treat a non-empty trimmed search as an active filter. handleClearFilters now calls setSearchInput("") and activeFilterCount's memo adds 1 when searchInput.trim() is non-empty, with searchInput added to the dependency array so the count updates correctly.
Introduce hasListSearchOrFilters to determine whether a list has an active search term or any non-empty filter value. The function trims the search term and checks filter values for undefined, null, or empty (after stringifying), returning true if any are present. Includes JSDoc for usage and intent (used to choose refined vs default empty-state copy).
Add a hasListRefinement prop to ServiceRequestsList to indicate when the list is being refined. The prop is true if the trimmed searchTerm is non-empty or if statusFilter is not "all". This lets the list component react to active refinements (e.g. show reset/clear UI) and prevents relying on internal heuristics.
Reset the search term when clearing filters and compute a list refinement flag using hasListSearchOrFilters(searchTerm, filters). Pass the resulting hasListRefinement prop to AllCasesList so the list can react to active search/filters. Also import the helper from @utils/support.
Import hasListSearchOrFilters, clear the search term when clearing filters, compute listHasRefinement from searchTerm and filters, and pass it as hasListRefinement to ChangeRequestsList so the child can react when search or filters are active.
Import hasListSearchOrFilters and use it to determine whether the announcements list has active search or filters. Reset searchTerm when clearing filters, compute listHasRefinement, and pass it as hasListRefinement to AnnouncementList so the list can adjust its UI/behavior when refinements are present.
Import hasListSearchOrFilters and compute listHasRefinement from searchTerm and filters, then pass it as hasListRefinement to AllConversationsList. Also reset the searchTerm when clearing filters so the list fully clears refinements and pagination is reset to page 1.
Adjust AllCasesPage to better handle project loading state and list refinements. Add hasListSearchOrFilters import and compute listHasRefinement from searchTerm and filters. Include projectDetailsReady/isProjectLoading in stats loading checks and guard deployments prop until project details are ready. Clear searchTerm when clearing filters and pass isProjectContextLoading to the Filters component. Finally, forward hasListRefinement to the cases list so the UI can reflect active search/filters.
Replace the plain text fallback with a centered empty state component for when there are no change requests. Adds an EmptyIcon import and renders a Box with the icon and a simplified "No change requests." message (with layout/styling) to improve UX.
Introduce hasListRefinement prop to ServiceRequestsList to show a different empty state when filters/search are applied. Import and render SearchNoResultsIcon for the refined-empty case and keep EmptyIcon for the initial-empty case, update copy accordingly, and center icons/text with consistent styling. Default hasListRefinement to false to preserve existing behavior.
Treat the current searchTerm as an active filter in ChangeRequestsSearchBar. Previously hasActiveFilters only checked the filters object, so a non-empty search input wasn't considered active; this change adds searchTerm.trim().length > 0 to ensure the UI reflects when a search is active.
Treat the search input as an active filter by including searchTerm.trim().length > 0 in the hasActiveFilters check. This ensures whitespace-only input is ignored while preserving the existing filters check so the UI correctly reflects when a search is active.
Include a check for a non-empty trimmed searchTerm in hasActiveFilters so that entering a search is considered an active filter. This ensures the UI reflects active searches even when other filter values are empty and ignores whitespace-only input.
Introduce isProjectContextLoading prop (default false) to show loading placeholders while project context loads. Import Skeleton from the UI library and return a Paper containing Skeletons for the search bar and button when loading. Also update hasActiveFilters to consider searchTerm.trim() so an active search counts as a filter.
Treat the search term as an active filter by checking trimmed length in hasActiveFilters, and clear the search term when handleClearFilters is invoked. This ensures the UI correctly reflects search-based filtering and resets search state when filters are cleared.
Introduce a hasListRefinement prop (default false) and import EmptyIcon and SearchNoResultsIcon. Render a distinct "no results" UI when filters/search yield no items, and keep a separate "no change requests yet" empty state for initial/empty lists. Adjusts icon layout and message text accordingly.
Introduce distinct empty-state UIs for AnnouncementList: import EmptyIcon and SearchNoResultsIcon, add an optional hasListRefinement prop (default false), and render a "no results" illustration and message when filters/search are applied; otherwise render a generic "no announcements yet" illustration. Also adjust layout/styling for both empty states.
Introduce hasListRefinement prop (default false) to AllConversationsList and import EmptyIcon and SearchNoResultsIcon. When conversations are empty, show a centered SearchNoResultsIcon and a suggestion to adjust filters/search if hasListRefinement is true; otherwise show the generic EmptyIcon and updated copy ('No conversations yet.'). Minor layout adjustments to center icons and text.
Add hasListRefinement prop and conditional empty-state rendering for AllCasesList. Import EmptyIcon and SearchNoResultsIcon, default hasListRefinement to false, and render SearchNoResultsIcon + helpful message when filters/search yield no results; otherwise render EmptyIcon with "No cases yet." message. Minor layout tweaks to center the empty-state content.
Expand support utils mocks and update tests for AllCasesList. Adds mocks for formatDateTime, getStatusColor, mapSeverityToDisplay, and getAssignedEngineerLabel, and adjusts assertions for the empty-state copy. Also adds a new test covering the refined/search empty state message when the list is empty and hasListRefinement is true. No production code changes.
📝 WalkthroughWalkthroughThis PR distinguishes "no data" from "no results" across list UIs by adding a boolean Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/customer-portal/webapp/src/components/dashboard/cases-table/__tests__/CasesList.test.tsx (1)
148-164:⚠️ Potential issue | 🟠 MajorEmpty-state test is asserting the refined branch without enabling refinement.
At Line 160-164, the test expects refined copy, but the rendered component at Line 150-157 does not pass
hasListRefinement, so default behavior should be the non-refined empty state.✅ Suggested test fix
it("should render empty state when no cases", () => { render( <CasesList isLoading={false} data={{ cases: [], totalRecords: 0, offset: 0, limit: 10 } as any} page={0} rowsPerPage={10} onPageChange={mockOnPageChange} onRowsPerPageChange={mockOnRowsPerPageChange} + hasListRefinement={true} />, ); expect( screen.getByText( "No outstanding cases.Try adjusting your filters or search query", ), ).toBeInTheDocument(); });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/customer-portal/webapp/src/components/dashboard/cases-table/__tests__/CasesList.test.tsx` around lines 148 - 164, The test asserts the refined empty-state copy but doesn't enable refinement on the component; either pass the hasListRefinement prop into the rendered CasesList (e.g., hasListRefinement={true}) or change the expected string in the expect(...) to the non-refined copy; update the test in CasesList.test.tsx to use the chosen approach so the assertion matches the rendered CasesList component.
🧹 Nitpick comments (2)
apps/customer-portal/webapp/src/components/support/change-requests/ChangeRequestsSearchBar.tsx (1)
67-69: Use the shared refinement helper here to avoid logic drift.Line 67-Line 69 re-implements refinement detection while
hasListSearchOrFiltersalready centralizes this behavior. Reusing it keeps search bars and list empty-state logic consistent.♻️ Suggested refactor
+import { hasListSearchOrFilters } from "@utils/support"; ... - const hasActiveFilters = - searchTerm.trim().length > 0 || - Object.values(filters).some((value) => !!value); + const hasActiveFilters = hasListSearchOrFilters( + searchTerm, + filters as Record<string, string | number | undefined | null>, + );🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/customer-portal/webapp/src/components/support/change-requests/ChangeRequestsSearchBar.tsx` around lines 67 - 69, Replace the ad-hoc refinement detection in ChangeRequestsSearchBar (the hasActiveFilters computation) with the shared helper hasListSearchOrFilters to avoid logic drift: remove the current searchTerm.trim()/Object.values(filters) logic and call hasListSearchOrFilters(searchTerm, filters) (import the helper if not already) so the component uses the centralized refinement detection used by list empty-state logic.apps/customer-portal/webapp/src/components/security/SecurityReportAnalysis.tsx (1)
196-199: Include all active filter keys inhasActiveFilters.Line 196-Line 199 currently checks only
statusIdanddeploymentId. If other filter fields (e.g.,severityId,issueTypes) are set, the UI can misreport refinement state.♻️ Suggested refactor
- const hasActiveFilters = - searchTerm.trim().length > 0 || - !!filters.statusId || - !!filters.deploymentId; + const hasActiveFilters = hasListSearchOrFilters(searchTerm, { + statusId: filters.statusId, + deploymentId: filters.deploymentId, + severityId: filters.severityId, + issueTypes: filters.issueTypes, + });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/customer-portal/webapp/src/components/security/SecurityReportAnalysis.tsx` around lines 196 - 199, The current hasActiveFilters calculation (variable hasActiveFilters) only checks searchTerm, filters.statusId, and filters.deploymentId, so other filter fields like severityId or issueTypes are ignored; update hasActiveFilters to treat any filter property as active by checking all keys of the filters object (e.g., Object.keys/values) and returning true if any value is non-empty (for strings/numbers), truthy, or non-empty array, in addition to the existing searchTerm check; locate and update the hasActiveFilters definition in SecurityReportAnalysis.tsx to use this generic check so all filter fields (severityId, issueTypes, etc.) correctly mark the UI as refined.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@apps/customer-portal/webapp/src/components/dashboard/cases-table/CasesList.tsx`:
- Around line 127-128: Fix the missing space in the empty-state copy inside the
CasesList component: locate the JSX text "No outstanding cases.Try adjusting
your filters or search query" in CasesList.tsx and add a space after the period
so it reads "No outstanding cases. Try adjusting your filters or search query"
(update the string wherever rendered in the CasesList component).
In
`@apps/customer-portal/webapp/src/components/support/all-conversations/AllConversationsList.tsx`:
- Around line 104-105: Update the user-facing empty-state copy in the
AllConversationsList component: locate the Typography string currently reading
"No conversations found, Try adjusting your filters or search query." and
replace it with properly punctuated/capitalized copy such as "No conversations
found. Try adjusting your filters or search query." so the comma is a period and
"Try" is capitalized.
In
`@apps/customer-portal/webapp/src/components/support/announcements/AnnouncementList.tsx`:
- Line 78: In AnnouncementList.tsx update the empty-state copy string (the JSX
text "No announcements found, Try adjusting your filters or search query.") to
use sentence punctuation — replace the comma with a period so it reads "No
announcements found. Try adjusting your filters or search query." in the
AnnouncementList component's render/return.
In `@apps/customer-portal/webapp/src/pages/AllCasesPage.tsx`:
- Around line 146-149: The current isStatsLoading expression mixes "still
loading" with "project details missing" (using !projectDetailsReady) which
causes permanent loading when projectDetailsReady resolves to undefined; update
the logic that computes isStatsLoading to remove the !projectDetailsReady clause
(keep isProjectLoading || isStatsQueryLoading || (!!projectId &&
!hasStatsResponse)) and instead introduce a separate explicit check (e.g.,
isProjectMissing or projectDetailsReady === false) to drive the
empty/missing-data UI path; update any render branches that relied on
!projectDetailsReady to use that new missing-data flag so skeletons only show
while actual loading flags (isProjectLoading, isStatsQueryLoading) are true.
---
Outside diff comments:
In
`@apps/customer-portal/webapp/src/components/dashboard/cases-table/__tests__/CasesList.test.tsx`:
- Around line 148-164: The test asserts the refined empty-state copy but doesn't
enable refinement on the component; either pass the hasListRefinement prop into
the rendered CasesList (e.g., hasListRefinement={true}) or change the expected
string in the expect(...) to the non-refined copy; update the test in
CasesList.test.tsx to use the chosen approach so the assertion matches the
rendered CasesList component.
---
Nitpick comments:
In
`@apps/customer-portal/webapp/src/components/security/SecurityReportAnalysis.tsx`:
- Around line 196-199: The current hasActiveFilters calculation (variable
hasActiveFilters) only checks searchTerm, filters.statusId, and
filters.deploymentId, so other filter fields like severityId or issueTypes are
ignored; update hasActiveFilters to treat any filter property as active by
checking all keys of the filters object (e.g., Object.keys/values) and returning
true if any value is non-empty (for strings/numbers), truthy, or non-empty
array, in addition to the existing searchTerm check; locate and update the
hasActiveFilters definition in SecurityReportAnalysis.tsx to use this generic
check so all filter fields (severityId, issueTypes, etc.) correctly mark the UI
as refined.
In
`@apps/customer-portal/webapp/src/components/support/change-requests/ChangeRequestsSearchBar.tsx`:
- Around line 67-69: Replace the ad-hoc refinement detection in
ChangeRequestsSearchBar (the hasActiveFilters computation) with the shared
helper hasListSearchOrFilters to avoid logic drift: remove the current
searchTerm.trim()/Object.values(filters) logic and call
hasListSearchOrFilters(searchTerm, filters) (import the helper if not already)
so the component uses the centralized refinement detection used by list
empty-state logic.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 9ac85ebc-d7e1-4112-b4c2-21d671b41982
📒 Files selected for processing (23)
apps/customer-portal/webapp/src/components/dashboard/cases-table/CasesList.tsxapps/customer-portal/webapp/src/components/dashboard/cases-table/CasesTable.tsxapps/customer-portal/webapp/src/components/dashboard/cases-table/__tests__/CasesList.test.tsxapps/customer-portal/webapp/src/components/security/ProductVulnerabilitiesTable.tsxapps/customer-portal/webapp/src/components/security/SecurityReportAnalysis.tsxapps/customer-portal/webapp/src/components/support/all-cases/AllCasesList.tsxapps/customer-portal/webapp/src/components/support/all-cases/AllCasesSearchBar.tsxapps/customer-portal/webapp/src/components/support/all-cases/__tests__/AllCasesList.test.tsxapps/customer-portal/webapp/src/components/support/all-conversations/AllConversationsList.tsxapps/customer-portal/webapp/src/components/support/all-conversations/AllConversationsSearchBar.tsxapps/customer-portal/webapp/src/components/support/announcements/AnnouncementList.tsxapps/customer-portal/webapp/src/components/support/announcements/AnnouncementsSearchBar.tsxapps/customer-portal/webapp/src/components/support/change-requests/ChangeRequestsList.tsxapps/customer-portal/webapp/src/components/support/change-requests/ChangeRequestsSearchBar.tsxapps/customer-portal/webapp/src/components/support/service-requests/ServiceRequestsList.tsxapps/customer-portal/webapp/src/components/support/support-overview-cards/OutstandingChangeRequestsList.tsxapps/customer-portal/webapp/src/pages/AllCasesPage.tsxapps/customer-portal/webapp/src/pages/AllConversationsPage.tsxapps/customer-portal/webapp/src/pages/AnnouncementsPage.tsxapps/customer-portal/webapp/src/pages/ChangeRequestsPage.tsxapps/customer-portal/webapp/src/pages/EngagementsPage.tsxapps/customer-portal/webapp/src/pages/ServiceRequestsPage.tsxapps/customer-portal/webapp/src/utils/support.ts
Introduce a local isProjectContextLoading variable and use it in loading checks. Replace the inline isProjectContextLoading prop (previously isProjectLoading || !projectDetailsReady) with the new variable, and remove !projectDetailsReady from isStatsLoading. This centralizes the project-context loading flag and simplifies the loading logic (note: child components will now receive only isProjectLoading via the prop).
Add a missing period in the AnnouncementList empty-state copy: "No announcements found. Try adjusting your filters or search query." This is a minor text change for grammar/readability in apps/customer-portal/webapp/src/components/support/announcements/AnnouncementList.tsx with no behavior changes.
Replace a comma with a period in the empty-state copy to improve punctuation and readability in AllConversationsList.tsx: "No conversations found. Try adjusting your filters or search query."
Corrects spacing and punctuation in CasesList.tsx empty-state copy: changes "No outstanding cases.Try adjusting your filters or search query" to "No outstanding cases. Try adjusting your filters or search query." to improve readability and grammar.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
apps/customer-portal/webapp/src/components/support/announcements/AnnouncementList.tsx (1)
58-103: Consider consolidating duplicated empty-state JSX into one render path.Line 61-80 and Line 84-103 duplicate container and icon styles. A small helper/variant object will reduce drift when updating spacing, alignment, or typography later.
♻️ Suggested refactor
+ const emptyStateSx = { + display: "flex", + flexDirection: "column" as const, + alignItems: "center", + py: 6, + }; + + const emptyIconStyle = { + width: 200, + maxWidth: "100%", + height: "auto", + marginBottom: 16, + }; + if (cases.length === 0) { - if (hasListRefinement) { - return ( - <Box - sx={{ - display: "flex", - flexDirection: "column", - alignItems: "center", - py: 6, - }} - > - <SearchNoResultsIcon - style={{ - width: 200, - maxWidth: "100%", - height: "auto", - marginBottom: 16, - }} - /> - <Typography variant="body1" color="text.secondary"> - No announcements found. Try adjusting your filters or search query. - </Typography> - </Box> - ); - } + const message = hasListRefinement + ? "No announcements found. Try adjusting your filters or search query." + : "No announcements yet."; + + const icon = hasListRefinement ? ( + <SearchNoResultsIcon style={emptyIconStyle} /> + ) : ( + <EmptyIcon style={emptyIconStyle} /> + ); + return ( - <Box - sx={{ - display: "flex", - flexDirection: "column", - alignItems: "center", - py: 6, - }} - > - <EmptyIcon - style={{ - width: 200, - maxWidth: "100%", - height: "auto", - marginBottom: 16, - }} - /> + <Box sx={emptyStateSx}> + {icon} <Typography variant="body1" color="text.secondary"> - No announcements yet. + {message} </Typography> </Box> ); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/customer-portal/webapp/src/components/support/announcements/AnnouncementList.tsx` around lines 58 - 103, The empty-state JSX in AnnouncementList.tsx duplicates the same Box container and Typography styling for both conditional branches (when cases.length === 0 and hasListRefinement vs not), so extract the shared container props and icon styles into local constants (e.g., containerSx and iconStyle) inside the AnnouncementList component, choose the icon component conditionally (use SearchNoResultsIcon when hasListRefinement is true else EmptyIcon), and render a single return block that uses those shared constants and the conditional icon and message; update references to cases and hasListRefinement accordingly so behavior remains identical.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@apps/customer-portal/webapp/src/components/dashboard/cases-table/CasesList.tsx`:
- Around line 116-129: The empty-state message in CasesList.tsx contradicts how
hasListRefinement is computed (it relies on getActiveFiltersCount() in
CasesTable.tsx which only counts structured filters); either remove "or search
query" from the copy in CasesList.tsx so the text matches current logic, or
update getActiveFiltersCount() in CasesTable.tsx to include the active search
term (e.g., treat non-empty search string as a refinement) and ensure
hasListRefinement reflects that change; pick one approach and update the
corresponding function/text so the message and refinement logic are consistent.
---
Nitpick comments:
In
`@apps/customer-portal/webapp/src/components/support/announcements/AnnouncementList.tsx`:
- Around line 58-103: The empty-state JSX in AnnouncementList.tsx duplicates the
same Box container and Typography styling for both conditional branches (when
cases.length === 0 and hasListRefinement vs not), so extract the shared
container props and icon styles into local constants (e.g., containerSx and
iconStyle) inside the AnnouncementList component, choose the icon component
conditionally (use SearchNoResultsIcon when hasListRefinement is true else
EmptyIcon), and render a single return block that uses those shared constants
and the conditional icon and message; update references to cases and
hasListRefinement accordingly so behavior remains identical.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 5dcb37fc-50f7-4ca2-8519-4f19629ca982
📒 Files selected for processing (4)
apps/customer-portal/webapp/src/components/dashboard/cases-table/CasesList.tsxapps/customer-portal/webapp/src/components/support/all-conversations/AllConversationsList.tsxapps/customer-portal/webapp/src/components/support/announcements/AnnouncementList.tsxapps/customer-portal/webapp/src/pages/AllCasesPage.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/customer-portal/webapp/src/components/support/all-conversations/AllConversationsList.tsx
0713723
into
wso2-open-operations:dev-app-customer-portal-v1.0.x
Description
Closes(https://github.com/wso2-enterprise/digiops-cs/issues/1486)
This pull request improves the empty state UX for case and conversation lists by introducing more descriptive and visually distinct empty states depending on whether filters or search terms are active. It also updates the logic for detecting active filters/search across several components, and enhances test coverage to reflect these changes.
Empty State UX Improvements:
Added
EmptyIconandSearchNoResultsIconcomponents to visually differentiate between "no data" and "no results due to filters/search" states inCasesList,AllCasesList,AllConversationsList, andAnnouncementList. The displayed message and icon now depend on whether the list is empty due to refinement (filters/search) or truly empty. [1] [2] [3] [4] [5] [6] [7]Introduced a new
hasListRefinementprop to the above components and their usages, which determines which empty state to show. This prop is set based on the presence of active filters or search terms. [1] [2] [3] [4] [5] [6]Filter/Search Detection Logic:
Testing Enhancements:
CasesListandAllCasesListto verify the correct empty state is rendered depending onhasListRefinement. Also improved test mock setups for utility functions and component props. [1] [2] [3] [4]Other UI/UX Improvements:
AllCasesSearchBarwhen project context is loading, improving perceived performance and feedback. [1] [2] [3]Code Consistency and Cleanup:
Let me know if you want to see how the new empty states look or need help understanding how to use the new
hasListRefinementprop!Summary by CodeRabbit
Release Notes