Skip to content

[Customer Portal][Web] Add Time Card State Filter & Infinite Pagination - #287

Merged
Rashmika998 merged 11 commits into
wso2-open-operations:customer-portal-milestone-1from
dileepapeiris:feat/add-time-cards-filter
Mar 2, 2026
Merged

Rashmika998 merged 11 commits into
wso2-open-operations:customer-portal-milestone-1from
dileepapeiris:feat/add-time-cards-filter

Conversation

@dileepapeiris

@dileepapeiris dileepapeiris commented Mar 2, 2026 •

Copy link
Copy Markdown
Contributor

Description

This pull request introduces a major enhancement to the project time tracking feature by adding support for filtering time cards by state, improving infinite pagination, and updating related data structures and tests to support these changes. The most important changes are grouped below:

Feature: Time Card State Filtering

  • Added a state filter to the time tracking UI, allowing users to filter time cards by their state (e.g., Pending, Submitted, Approved). The filter options are dynamically loaded from project metadata, and the filter is integrated into the infinite query for time cards. (ProjectTimeTracking.tsx, TimeCardsDateFilter.tsx, useSearchProjectTimeCards.ts, useGetProjectFilters, projectDetailsConstants.ts, models/requests.ts, models/responses.ts) [1] [2] [3] [4] [5] [6] [7] [8] [9]

Enhancement: Infinite Pagination for Time Cards

  • Refactored the useSearchProjectTimeCards hook to use useInfiniteQuery instead of useQuery, enabling seamless loading of all time card pages and automatically fetching additional pages as needed. (useSearchProjectTimeCards.ts) [1] [2] [3] [4] [5]

Data Model Updates

  • Updated the TimeCard model so that its state property is now an object with id and label, instead of a string. This change is reflected throughout the UI and tests to support richer state information and display. (models/responses.ts, TimeTrackingCard.tsx, related tests) [1] [2] [3] [4] [5] [6]

Tests and Mocks

  • Updated and extended tests for time tracking components to accommodate the new state filter and the updated TimeCard state structure. Mocks for project filters and time card states were added to ensure comprehensive coverage. (ProjectTimeTracking.test.tsx, TimeCardsDateFilter.test.tsx, TimeTrackingCard.test.tsx) [1] [2] [3] [4] [5] [6] [7]

Constants and Types

  • Introduced a new TIME_CARD_STATE constant and associated TimeCardState type to standardize possible time card states across the codebase. (projectDetailsConstants.ts)

These changes collectively improve the user experience when viewing and filtering project time cards, provide more robust data handling, and ensure the codebase is well-tested for the new functionality.

Summary by CodeRabbit

  • New Features

    • Added state-based filtering for time cards (Pending, Submitted, Approved, Rejected, Processed, Recalled).
    • Implemented infinite pagination for improved performance when loading large datasets.
  • Tests

    • Updated test coverage to reflect new filtering and pagination functionality.

Represent TimeCard.state as an object ({id, label} | null) instead of a plain string. Update usages to read state.label (with a "--" fallback) and adapt getTimeCardStateColorPath to accept the state object and normalize state.label for color resolution. This ensures consistency with other typed fields and prevents runtime errors when rendering or computing chip colors.
Update tests to match the new TimeCard state shape: replace string states with objects ({ id, label }) in ProjectTimeTracking and TimeTrackingCard tests, and use null for missing state in the fallback case. This keeps test fixtures aligned with the component's expected prop structure.
Introduce TIME_CARD_STATE and TimeCardState type in projectDetailsConstants.ts to represent time card lifecycle values (Pending, Submitted, Approved, Rejected, Processed, Recalled). Update getTimeCardStateColorPath in projectDetails.ts to import and use the new constant, switch on explicit state values with a defensive null check, and map each state to the appropriate color token. Also add a console.error in formatProjectDateTime to log formatting failures for easier debugging.
Support filtering time cards by state across API, model, UI, and tests. Introduces an optional `state` field on TimeCardSearchRequest and UseSearchProjectTimeCards (propagated into the request filters), adds a state Select control to TimeCardsDateFilter with props `state`, `onStateChange`, and `timeCardStates`, and wires state handling in ProjectTimeTracking (including fetching `timeCardStates` via useGetProjectFilters). Tests updated/mocked to cover the new filter behavior.
Rework the TimeCardsDateFilter UI to separate Date Range and State filters, improve layout/responsiveness, and tidy up labels and icons. Removed shownCount, totalCount and isLoading props from the component and updated ProjectTimeTracking to stop passing those props. Updated unit tests to remove assertions for counts/loading and instead assert the state dropdown and labels are rendered. Files changed: ProjectTimeTracking.tsx, TimeCardsDateFilter.tsx, and its test.
Replace useQuery with useInfiniteQuery in the project time cards hook to support paginated/infinite fetching. The hook now accepts states[] instead of a single state, uses pageParam as the offset with a default limit of 10, and includes initialPageParam/getNextPageParam logic to compute the next offset. The TimeCardSearchRequest model was updated to use filters.states: string[].

The ProjectTimeTracking component was updated to pass states as an array, auto-fetch remaining pages (fetchNextPage) in a useEffect, and flatten pages into a single timeCards array; totalRecords is taken from the first page. Query keys and debug logging were updated to include the offset.
Rework the TimeCardsDateFilter component to simplify and improve the date/state filter UI. Changes include:

- Remove unused Filter icon import and usages; keep Calendar icon only.
- Align container items to center and unify spacing.
- Consolidate and restructure date inputs: add a "Filter by Date Range:" label, add explicit "From:" and "To:" labels, and replace the previous duplicated markup with two TextField date inputs using InputAdornment(Calendar).
- Add ids and htmlFor attributes for better accessibility and apply nowrap styling to labels.
- Move and inline the state filter label with the Select control for a more compact layout.

These updates improve readability, reduce duplicated markup, and enhance accessibility and visual consistency.
@coderabbitai

coderabbitai Bot commented Mar 2, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@dileepapeiris has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 4 minutes and 36 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 525dcf7 and fbc5126.

📒 Files selected for processing (5)
  • apps/customer-portal/webapp/src/components/project-details/time-tracking/ProjectTimeTracking.tsx
  • apps/customer-portal/webapp/src/components/project-details/time-tracking/TimeCardsDateFilter.tsx
  • apps/customer-portal/webapp/src/components/project-details/time-tracking/__tests__/ProjectTimeTracking.test.tsx
  • apps/customer-portal/webapp/src/components/project-details/time-tracking/__tests__/TimeCardsDateFilter.test.tsx
  • apps/customer-portal/webapp/src/utils/projectDetails.ts
📝 Walkthrough

Walkthrough

Replaces single-page time card query with an infinite query using pageParam-based pagination and state filters; changes TimeCard.state from a string to an object ({id, label} | null); adds a state dropdown filter and auto-fetch logic in the time-tracking UI; updates models, utils, and tests accordingly.

Changes

Cohort / File(s) Summary
Infinite Query Migration
apps/customer-portal/webapp/src/api/useSearchProjectTimeCards.ts
Replaced useQuery with useInfiniteQuery; queryKey now includes states; queryFn accepts { pageParam = 0, signal }; pagination body uses pagination: { limit: 10, offset: pageParam }; added initialPageParam and getNextPageParam; return type updated to UseInfiniteQueryResult<InfiniteData<...>>.
Time Tracking Components
apps/customer-portal/webapp/src/components/project-details/time-tracking/ProjectTimeTracking.tsx, apps/customer-portal/webapp/src/components/project-details/time-tracking/TimeCardsDateFilter.tsx, apps/customer-portal/webapp/src/components/project-details/time-tracking/TimeTrackingCard.tsx
ProjectTimeTracking: integrates infinite query results, flattens pages, adds auto-fetch effect (capped), and state filter wiring via useGetProjectFilters. TimeCardsDateFilter: refactors UI to include state dropdown and updates props. TimeTrackingCard: uses state?.label with fallback --.
Tests: Time Tracking
apps/customer-portal/webapp/src/components/project-details/time-tracking/__tests__/*
Updated mocks and assertions to reflect paginated pages structure and new state object shape; added useGetProjectFilters mock; adjusted tests for dropdown interactions and fallback display.
Models & Constants
apps/customer-portal/webapp/src/models/requests.ts, apps/customer-portal/webapp/src/models/responses.ts, apps/customer-portal/webapp/src/constants/projectDetailsConstants.ts
Added optional states?: string[] to TimeCardSearchRequest.filters; changed TimeCard.state to `{ id: string; label: string }
Utilities
apps/customer-portal/webapp/src/utils/projectDetails.ts
Updated getTimeCardStateColorPath signature to accept the state object and switched mapping to use state.id against TIME_CARD_STATE; added safer guards and console.error in formatProjectDateTime.

Sequence Diagram

sequenceDiagram
    participant User
    participant Component as ProjectTimeTracking<br/>Component
    participant FilterAPI as useGetProjectFilters
    participant SearchAPI as useSearchProjectTimeCards
    participant Backend as Backend API

    User->>Component: Mount / interact
    Component->>FilterAPI: request timeCardStates
    FilterAPI->>Backend: GET /filters
    Backend-->>FilterAPI: { timeCardStates: [...] }
    FilterAPI-->>Component: timeCardStates

    Component->>SearchAPI: fetch initial page (pageParam=0, states?)
    SearchAPI->>Backend: POST /search (pagination: {limit:10, offset:0}, filters.states?)
    Backend-->>SearchAPI: { timeCards: [...], totalRecords: N }
    SearchAPI-->>Component: pages[0]

    Component->>Component: flatten pages & render
    Component->>SearchAPI: (on hasNextPage) fetchNextPage (pageParam=offsetNext)
    SearchAPI->>Backend: POST /search (offset: offsetNext, states?)
    Backend-->>SearchAPI: next page
    SearchAPI-->>Component: pages[1]...
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly Related PRs

Suggested Reviewers

  • Rashmika998
  • v15a1

Poem

🐰 I hopped through pages, one by one,
From offset zero to fetched more fun.
States now wear labels, neat and bright,
Auto-fetch whispers through the night.
A little rabbit cheers: the UI's just right!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description provides a detailed explanation of features, enhancements, and changes, but does not follow the required template structure with sections like Purpose, Goals, Approach, User stories, Release note, Documentation, etc. Restructure the description to match the template: add Purpose/Resolves section, Goals, Approach, User stories, Release note, Documentation, Training, Certification, Marketing, Automation tests, Security checks, etc.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title directly and clearly describes the two main changes: adding a time card state filter and implementing infinite pagination.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

@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

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/project-details/time-tracking/__tests__/ProjectTimeTracking.test.tsx (1)

119-158: ⚠️ Potential issue | 🟠 Major

Fix mock shape for infinite-query contract—wrap timeCards in pages array.

The test mocks useSearchProjectTimeCards with a flat data object { timeCards: [...] }, but the component reads data?.pages.flatMap((page) => page.timeCards) at line 99 and data?.pages?.[0]?.totalRecords at line 103. This mismatch breaks the test: assertions for "Log 1", "Log 2", and time card count will fail because data.pages is undefined.

Update the mock to match the InfiniteData shape:

Mock structure fix
     const mockData = {
-      timeCards: [
+      pages: [
+        {
+          timeCards: [
           {
             id: "1",
             case: { label: "Log 1", number: "CS001", id: "c1" },
             totalTime: 60,
             state: { id: "approved", label: "Approved" },
             hasBillable: false,
             approvedBy: null,
             project: { id: "p1", label: "Project 1" },
             createdOn: "2025-12-10",
           },
           {
             id: "2",
             case: { label: "Log 2", number: "CS002", id: "c2" },
             totalTime: 30,
             state: { id: "submitted", label: "Submitted" },
             hasBillable: true,
             approvedBy: null,
             project: { id: "p1", label: "Project 1" },
             createdOn: "2025-12-11",
           },
-      ],
-      totalRecords: 2,
-      offset: 0,
-      limit: 10,
+          ],
+          totalRecords: 2,
+          offset: 0,
+          limit: 10,
+        },
+      ],
+      pageParams: [0],
     };

     vi.mocked(useSearchProjectTimeCards).mockReturnValue({
       data: mockData,
       isLoading: false,
       isError: false,
+      hasNextPage: false,
+      fetchNextPage: vi.fn(),
     } as any);

Also apply the same fix to the mocks at lines 192 and 208.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/customer-portal/webapp/src/components/project-details/time-tracking/__tests__/ProjectTimeTracking.test.tsx`
around lines 119 - 158, The test's mock for useSearchProjectTimeCards uses a
flat data object but the component expects React Query InfiniteData shape
(data.pages array); update the mocked return value for useSearchProjectTimeCards
so data is { pages: [{ timeCards: [...], totalRecords: 2, offset: 0, limit: 10
}] } instead of { timeCards: [...] }, and make the same change for the other
mocks mentioned (the ones around lines 192 and 208) so assertions that rely on
data.pages and pages[0].totalRecords succeed; keep useGetTimeCardsStats mock
as-is if it remains unrelated.
🧹 Nitpick comments (6)
apps/customer-portal/webapp/src/components/project-details/time-tracking/__tests__/TimeTrackingCard.test.tsx (1)

51-63: Assert state fallback explicitly in the null-state scenario.

On Line 54 you set state: null, but the test only verifies approvedBy. Add an assertion that the state label fallback is rendered to lock this behavior in.

💡 Minimal test hardening
   render(<TimeTrackingCard card={incompleteCard} />);

   expect(screen.getByText(/Approved by: --/)).toBeInTheDocument();
+  expect(screen.queryByText("Approved")).not.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/project-details/time-tracking/__tests__/TimeTrackingCard.test.tsx`
around lines 51 - 63, The test sets state: null on the TimeCard (incompleteCard)
but only asserts the approvedBy fallback; add an assertion to verify the state
label fallback is rendered by TimeTrackingCard (e.g., assert that the "State:
--" text is present). Locate the test using TimeTrackingCard, TimeCard and
mockCard and append an expectation like
expect(screen.getByText(/State:\s*--/)).toBeInTheDocument() to lock the
null-state behavior.
apps/customer-portal/webapp/src/components/project-details/time-tracking/__tests__/TimeCardsDateFilter.test.tsx (1)

49-63: Test intent and assertion don’t fully match.

The test says it validates dropdown options, but it only checks the section label. Please assert that provided state options are actually rendered/selectable.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/customer-portal/webapp/src/components/project-details/time-tracking/__tests__/TimeCardsDateFilter.test.tsx`
around lines 49 - 63, The test currently titled to check dropdown options for
TimeCardsDateFilter only asserts the label; update the test to assert that the
provided timeCardStates (mockTimeCardStates) are rendered and selectable: locate
the state select (e.g., getByRole('combobox') or getByLabelText("Filter by
State:")), assert each mockTimeCardStates item appears as an option (getByText
or within getAllByRole('option')), and simulate selection using
userEvent.selectOptions or fireEvent.change to ensure onStateChange behavior can
be triggered; keep the render call for TimeCardsDateFilter and reuse the
existing props (startDate, endDate, onStartDateChange, onEndDateChange, state,
onStateChange, timeCardStates) while adding these option-rendering and selection
assertions.
apps/customer-portal/webapp/src/components/project-details/time-tracking/TimeCardsDateFilter.tsx (1)

44-45: JSDoc is stale after prop changes.

Line 44 still says “handlers, and counts”, but count-related props were removed. Please align the doc comment with the current API.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/customer-portal/webapp/src/components/project-details/time-tracking/TimeCardsDateFilter.tsx`
around lines 44 - 45, The JSDoc for TimeCardsDateFilter is stale: update the
comment for TimeCardsDateFilterProps/TimeCardsDateFilter to reflect current
props (remove references to "counts" and "count-related props") and accurately
list the remaining props (date values and handlers) and return type; ensure the
`@param` description matches the prop shape used in the component signature
(TimeCardsDateFilterProps) so the doc no longer references removed count props.
apps/customer-portal/webapp/src/utils/projectDetails.ts (2)

40-59: Prefer stable state identifiers over labels in color mapping.

At Line 44, switching on state.label ties color logic to display text. If labels change (copy update/localization), chip colors can silently regress. Prefer using state.id for the switch and keep label for UI only.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/customer-portal/webapp/src/utils/projectDetails.ts` around lines 40 -
59, The color mapping currently switches on state.label which couples colors to
display text; change the guard and switch to use state.id instead: replace the
initial null/empty check (currently if (!state?.label)) with a check for id (if
(!state?.id) return "text.secondary") and switch on state.id in the switch
statement, keeping the same case branches using the TIME_CARD_STATE constants
(e.g., TIME_CARD_STATE.APPROVED, SUBMITTED, REJECTED, RECALLED, PENDING,
PROCESSED) and leave state.label solely for UI display.

173-173: Avoid direct console.error in shared formatting utility.

Line 173 can spam console noise from bad upstream payloads. Consider routing through the project logger (or logging once at call-site boundaries) instead of raw console calls in this low-level helper.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/customer-portal/webapp/src/utils/projectDetails.ts` at line 173, Replace
the raw console.error call in projectDetails.ts with the project logging
infrastructure: remove console.error and instead call the shared logger (e.g.,
projectLogger.error or logger.error) from the same module or import it, passing
a descriptive message and the error/context (e.g., input value) so the low-level
helper (function formatDateString / formatDate in projectDetails.ts) logs via
the project logger rather than console; alternatively if no logger is available,
remove the log and surface the error (throw or return null) so callers can log
once at their boundary.
apps/customer-portal/webapp/src/api/useSearchProjectTimeCards.ts (1)

63-89: Add abort signal support to prevent stale requests.

The queryFn at lines 63-89 should destructure the signal parameter from the context and pass it to fetchFn. Without this, cancelled queries (e.g., from rapid filter/date changes) may continue executing and waste network resources. The pattern is already implemented in useGetCaseAttachments.ts and the fetchFn supports it via RequestInit.

Example from useGetCaseAttachments.ts
queryFn: async ({ pageParam, signal }): Promise<CaseAttachmentsResponse> => {
  // ...
  const response = await fetchFn(requestUrl, { method: "GET", signal });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/customer-portal/webapp/src/api/useSearchProjectTimeCards.ts` around
lines 63 - 89, Destructure the abort Signal from the query context in the
queryFn of useSearchProjectTimeCards (i.e., change the parameter to include
signal) and pass that signal into the fetchFn call via the RequestInit options
object; update the fetchFn invocation inside queryFn (where
TimeCardSearchRequest is constructed and requestUrl is used) to include signal
so cancelled queries are aborted and no stale network requests continue.
🤖 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/project-details/time-tracking/ProjectTimeTracking.tsx`:
- Around line 91-95: The current useEffect in ProjectTimeTracking (the effect
watching data, hasNextPage, fetchNextPage) auto-fetches pages unboundedly which
can create request bursts; modify this effect to guard against aggressive
pagination by limiting consecutive auto-fetches (e.g., track a local counter or
timestamp inside the effect to fetch at most N additional pages per render or
per user action), or debounce/throttle calls to fetchNextPage so only a small
batch (configurable, e.g., 1–3 pages) is fetched automatically, and keep
hasNextPage and fetchNextPage checks; implement the guard within the same
component (ProjectTimeTracking) using a ref/state like autoFetchCountRef or
lastAutoFetchRef to enforce the limit and reset it on filter changes.

In
`@apps/customer-portal/webapp/src/components/project-details/time-tracking/TimeCardsDateFilter.tsx`:
- Around line 136-156: The "Filter by State" label (Typography) is not
programmatically associated with the Select, harming screen reader
accessibility; fix by adding an accessible label id and wiring it to the Select:
give the label element (replace or augment Typography) an id like
"state-filter-label" (or create an InputLabel with that id) and set Select's
labelId="state-filter-label" and id="state-filter-select" (or add
aria-labelledby="state-filter-label") so the Select (component Select) is
announced correctly; update usages around TimeCardsDateFilter where Typography,
FormControl and Select are declared (e.g., the Typography/InputLabel and Select
components).

---

Outside diff comments:
In
`@apps/customer-portal/webapp/src/components/project-details/time-tracking/__tests__/ProjectTimeTracking.test.tsx`:
- Around line 119-158: The test's mock for useSearchProjectTimeCards uses a flat
data object but the component expects React Query InfiniteData shape (data.pages
array); update the mocked return value for useSearchProjectTimeCards so data is
{ pages: [{ timeCards: [...], totalRecords: 2, offset: 0, limit: 10 }] } instead
of { timeCards: [...] }, and make the same change for the other mocks mentioned
(the ones around lines 192 and 208) so assertions that rely on data.pages and
pages[0].totalRecords succeed; keep useGetTimeCardsStats mock as-is if it
remains unrelated.

---

Nitpick comments:
In `@apps/customer-portal/webapp/src/api/useSearchProjectTimeCards.ts`:
- Around line 63-89: Destructure the abort Signal from the query context in the
queryFn of useSearchProjectTimeCards (i.e., change the parameter to include
signal) and pass that signal into the fetchFn call via the RequestInit options
object; update the fetchFn invocation inside queryFn (where
TimeCardSearchRequest is constructed and requestUrl is used) to include signal
so cancelled queries are aborted and no stale network requests continue.

In
`@apps/customer-portal/webapp/src/components/project-details/time-tracking/__tests__/TimeCardsDateFilter.test.tsx`:
- Around line 49-63: The test currently titled to check dropdown options for
TimeCardsDateFilter only asserts the label; update the test to assert that the
provided timeCardStates (mockTimeCardStates) are rendered and selectable: locate
the state select (e.g., getByRole('combobox') or getByLabelText("Filter by
State:")), assert each mockTimeCardStates item appears as an option (getByText
or within getAllByRole('option')), and simulate selection using
userEvent.selectOptions or fireEvent.change to ensure onStateChange behavior can
be triggered; keep the render call for TimeCardsDateFilter and reuse the
existing props (startDate, endDate, onStartDateChange, onEndDateChange, state,
onStateChange, timeCardStates) while adding these option-rendering and selection
assertions.

In
`@apps/customer-portal/webapp/src/components/project-details/time-tracking/__tests__/TimeTrackingCard.test.tsx`:
- Around line 51-63: The test sets state: null on the TimeCard (incompleteCard)
but only asserts the approvedBy fallback; add an assertion to verify the state
label fallback is rendered by TimeTrackingCard (e.g., assert that the "State:
--" text is present). Locate the test using TimeTrackingCard, TimeCard and
mockCard and append an expectation like
expect(screen.getByText(/State:\s*--/)).toBeInTheDocument() to lock the
null-state behavior.

In
`@apps/customer-portal/webapp/src/components/project-details/time-tracking/TimeCardsDateFilter.tsx`:
- Around line 44-45: The JSDoc for TimeCardsDateFilter is stale: update the
comment for TimeCardsDateFilterProps/TimeCardsDateFilter to reflect current
props (remove references to "counts" and "count-related props") and accurately
list the remaining props (date values and handlers) and return type; ensure the
`@param` description matches the prop shape used in the component signature
(TimeCardsDateFilterProps) so the doc no longer references removed count props.

In `@apps/customer-portal/webapp/src/utils/projectDetails.ts`:
- Around line 40-59: The color mapping currently switches on state.label which
couples colors to display text; change the guard and switch to use state.id
instead: replace the initial null/empty check (currently if (!state?.label))
with a check for id (if (!state?.id) return "text.secondary") and switch on
state.id in the switch statement, keeping the same case branches using the
TIME_CARD_STATE constants (e.g., TIME_CARD_STATE.APPROVED, SUBMITTED, REJECTED,
RECALLED, PENDING, PROCESSED) and leave state.label solely for UI display.
- Line 173: Replace the raw console.error call in projectDetails.ts with the
project logging infrastructure: remove console.error and instead call the shared
logger (e.g., projectLogger.error or logger.error) from the same module or
import it, passing a descriptive message and the error/context (e.g., input
value) so the low-level helper (function formatDateString / formatDate in
projectDetails.ts) logs via the project logger rather than console;
alternatively if no logger is available, remove the log and surface the error
(throw or return null) so callers can log once at their boundary.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 146b549 and 1892e38.

📒 Files selected for processing (11)
  • apps/customer-portal/webapp/src/api/useSearchProjectTimeCards.ts
  • apps/customer-portal/webapp/src/components/project-details/time-tracking/ProjectTimeTracking.tsx
  • apps/customer-portal/webapp/src/components/project-details/time-tracking/TimeCardsDateFilter.tsx
  • apps/customer-portal/webapp/src/components/project-details/time-tracking/TimeTrackingCard.tsx
  • apps/customer-portal/webapp/src/components/project-details/time-tracking/__tests__/ProjectTimeTracking.test.tsx
  • apps/customer-portal/webapp/src/components/project-details/time-tracking/__tests__/TimeCardsDateFilter.test.tsx
  • apps/customer-portal/webapp/src/components/project-details/time-tracking/__tests__/TimeTrackingCard.test.tsx
  • apps/customer-portal/webapp/src/constants/projectDetailsConstants.ts
  • apps/customer-portal/webapp/src/models/requests.ts
  • apps/customer-portal/webapp/src/models/responses.ts
  • apps/customer-portal/webapp/src/utils/projectDetails.ts

@dileepapeiris dileepapeiris self-assigned this Mar 2, 2026
@dileepapeiris dileepapeiris added Type/New Feature Represents a request or task for a new feature Type/Improvement Marks enhancements or improvements to existing features Type/Task General task that does not fit into other categories Type/UX Refers to user experience-related tasks or issues App/Customer Portal Area/Frontend Platform/Web labels Mar 2, 2026
@dileepapeiris dileepapeiris changed the title Feat/add time cards filter [Customer Portal][Web] Add Time Card State Filter & Infinite Pagination Mar 2, 2026
Add abort signal to search API requests and include it in fetch options to allow request cancellation. Limit automatic pagination fetches in ProjectTimeTracking (useRef counter, max 3) and reset the counter when filters change to avoid request bursts. Make TimeCardsDateFilter more accessible by adding InputLabel/id/labelId/aria-labelledby attributes and adjust imports; enhance tests to interact with the Select via userEvent and assert state selection. Update tests to match paginated response shape (pages/timeCards), provide hasNextPage and fetchNextPage mocks, and add an assertion for State display in TimeTrackingCard. Fix getTimeCardStateColorPath to switch on state.id (not label) and simplify the catch block in formatProjectDateTime.
Remove unused InputLabel import from TimeCardsDateFilter. Update TimeCardsDateFilter tests to use fireEvent and waitFor (remove userEvent), open the select via mouseDown, wait for options to appear, and click the option to assert selection. Fix formatProjectDateTime catch block to accept the error parameter and log a clearer message including the original date string.

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

🧹 Nitpick comments (1)
apps/customer-portal/webapp/src/components/project-details/time-tracking/__tests__/ProjectTimeTracking.test.tsx (1)

37-39: Consider asserting TimeCardsDateFilter prop wiring instead of fully stubbing it out.

The static mock hides regressions in state/onStateChange integration and auto-fetch reset behavior. A lightweight prop-capturing mock would improve coverage for the new filter flow.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/customer-portal/webapp/src/components/project-details/time-tracking/__tests__/ProjectTimeTracking.test.tsx`
around lines 37 - 39, Replace the fully static mock of TimeCardsDateFilter with
a lightweight prop-capturing mock so tests can assert prop wiring (state and
onStateChange) and confirm auto-fetch reset behavior; specifically, update the
mock for TimeCardsDateFilter to render a simple element (e.g., div with
data-testid="date-filter") that also stores or forwards received props (state,
onStateChange) so the test can simulate calling onStateChange and assert that
ProjectTimeTracking (or the tested component) responds by updating state and
triggering the expected fetch/reset logic.
🤖 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/project-details/time-tracking/ProjectTimeTracking.tsx`:
- Around line 93-99: The auto-fetch cap in the useEffect (autoFetchCountRef +
maxAutoFetches) stops loading additional pages even when hasNextPage is true;
update the logic to provide a fallback so results aren’t truncated: either (A)
implement a controlled background fetchRemainingPages routine (called from
useEffect when cap reached and hasNextPage is true) that continues calling
fetchNextPage in a throttled/async loop until hasNextPage is false (respecting
rate limits), or (B) when autoFetchCountRef.current >= maxAutoFetches and
hasNextPage is true, set a state flag (e.g., showLoadMore) to surface a user
action that calls fetchNextPage manually to load remaining pages; modify
useEffect, autoFetchCountRef, and add the fetchRemainingPages or showLoadMore
handling so large histories fully load.
- Around line 102-104: The effect that resets autoFetchCountRef.current runs on
changes to startDate, endDate, and state but not when the selected project
changes; update the dependency array of the useEffect that sets
autoFetchCountRef.current = 0 to also include projectId so the counter is reset
whenever projectId changes (look for useEffect that references
autoFetchCountRef, startDate, endDate, and state and add projectId to its
dependencies).

In
`@apps/customer-portal/webapp/src/components/project-details/time-tracking/TimeCardsDateFilter.tsx`:
- Around line 155-158: The MenuItem values are using stateOption.label which
couples filtering to display text; change the MenuItem value to use
stateOption.id instead (in the map where timeCardStates and stateOption are
used) and update the Select binding/handler (the Select's value prop and
onChange consumer) to expect and store the canonical id rather than the label so
filtering and localization remain stable.

In `@apps/customer-portal/webapp/src/utils/projectDetails.ts`:
- Around line 44-58: getTimeCardStateColorPath is switching on state.id but test
data uses lowercase IDs so valid states fall through; normalize state.id before
the switch (e.g., call toUpperCase()/toLowerCase() on state.id or compute a
normalizedId variable) and use that normalized value in the switch so
comparisons against TIME_CARD_STATE (the enum/consts referenced in the switch)
match correctly; update the switch in getTimeCardStateColorPath (and any callers
if necessary) to use the normalizedId to ensure "approved", "Approved", or
"APPROVED" all map to the correct token like "success.main".

---

Nitpick comments:
In
`@apps/customer-portal/webapp/src/components/project-details/time-tracking/__tests__/ProjectTimeTracking.test.tsx`:
- Around line 37-39: Replace the fully static mock of TimeCardsDateFilter with a
lightweight prop-capturing mock so tests can assert prop wiring (state and
onStateChange) and confirm auto-fetch reset behavior; specifically, update the
mock for TimeCardsDateFilter to render a simple element (e.g., div with
data-testid="date-filter") that also stores or forwards received props (state,
onStateChange) so the test can simulate calling onStateChange and assert that
ProjectTimeTracking (or the tested component) responds by updating state and
triggering the expected fetch/reset logic.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1892e38 and 525dcf7.

📒 Files selected for processing (7)
  • apps/customer-portal/webapp/src/api/useSearchProjectTimeCards.ts
  • apps/customer-portal/webapp/src/components/project-details/time-tracking/ProjectTimeTracking.tsx
  • apps/customer-portal/webapp/src/components/project-details/time-tracking/TimeCardsDateFilter.tsx
  • apps/customer-portal/webapp/src/components/project-details/time-tracking/__tests__/ProjectTimeTracking.test.tsx
  • apps/customer-portal/webapp/src/components/project-details/time-tracking/__tests__/TimeCardsDateFilter.test.tsx
  • apps/customer-portal/webapp/src/components/project-details/time-tracking/__tests__/TimeTrackingCard.test.tsx
  • apps/customer-portal/webapp/src/utils/projectDetails.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/customer-portal/webapp/src/components/project-details/time-tracking/tests/TimeCardsDateFilter.test.tsx

Comment thread apps/customer-portal/webapp/src/utils/projectDetails.ts Outdated
Introduce a paginated "Load More" UI and control auto-fetching: add showLoadMore state, cap automatic fetches with maxAutoFetches, show the Load More button when the cap is reached, reset behavior on project/filter changes, and add a handleLoadMore that resumes fetching. Fix TimeCardsDateFilter to emit stateOption.id (not label) so filters use IDs consistently. Update tests to mock the date filter with props (state, start/end dates, onStateChange) to reflect the new behavior. Normalize time card state IDs in projectDetails util (title-case) before switching to ensure comparisons match TIME_CARD_STATE constants and avoid mismatches.
Refactor pagination auto-fetch logic to prevent request bursts: keep an early return if there's no data or no next page, increment autoFetchCount and fetch up to maxAutoFetches, then stop auto-fetching. Add a dedicated effect to set showLoadMore when there are more pages and the auto-fetch limit has been reached. Remove the redundant setShowLoadMore(false) from the filter-reset effect and rely on the new effect to manage visibility. Also combine a few variable declarations for minor formatting cleanup.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

App/Customer Portal Area/Frontend Platform/Web Type/Improvement Marks enhancements or improvements to existing features Type/New Feature Represents a request or task for a new feature Type/Task General task that does not fit into other categories Type/UX Refers to user experience-related tasks or issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants