Skip to content

[Customer Portal][Web] Enhance Time Display, Enforce Timezone for Scheduling, and Refine Call Request UI Logic - #461

Merged
Rashmika998 merged 22 commits into
wso2-open-operations:mainfrom
dileepapeiris:feat/time-card-converstaion
Apr 3, 2026
Merged

Rashmika998 merged 22 commits into
wso2-open-operations:mainfrom
dileepapeiris:feat/time-card-converstaion

Conversation

@dileepapeiris

@dileepapeiris dileepapeiris commented Apr 3, 2026 •

Copy link
Copy Markdown
Contributor

Description

This pull request introduces several improvements to the display and handling of time and scheduling information in the customer portal, especially around service hours, time tracking, and support call requests. The changes focus on more user-friendly time formatting, stricter enforcement of user time zone requirements for scheduling calls, and improved UI logic for call request actions.

Time formatting and display improvements:

  • Updated the formatting of service hours and time tracking displays to use a more readable "X hr(s)/Y hr(s)" format (e.g., "45 hrs/100 hrs (45%)" instead of "45/100h (45%)"), and refactored the code to use new formatting utility functions for consistency across components (ServiceHoursAllocationsCard.tsx, ServiceHoursStatCards.tsx, TimeTrackingCard.tsx). [1] [2] [3]
  • Adjusted test cases to match the new time formatting, ensuring that UI tests expect the updated strings (e.g., "1 hr" instead of "1 hrs" for singular values) (ServiceHoursAllocationsCard.test.tsx, ServiceHoursStatCards.test.tsx, TimeTrackingCard.test.tsx). [1] [2] [3] [4]

User time zone enforcement for call scheduling:

  • Enhanced the call request flow to require users to set a time zone before they can create or edit a call request. If a user without a time zone attempts these actions, a modal prompts them to set their time zone, and the action is paused until this is completed (CallsPanel.tsx, MissingTimezoneDialog.tsx). [1] [2] [3] [4] [5]
  • Refactored the MissingTimezoneDialog to support a "required" variant that prevents closing the dialog with backdrop clicks or the escape key, ensuring users cannot bypass the prompt when a time zone is mandatory.
  • Ensured that after setting the time zone, the pending call action (create/edit) resumes automatically if the time zone is now set, or the prompt reappears if not (CallsPanel.tsx).

Support call request and modal improvements:

  • Improved logic for hiding customer action buttons (e.g., Approve/Reject) on call requests that are in "notes pending" or "customer rejected" states, based on both status labels and internal state IDs (CallRequestCard.tsx). [1] [2]
  • Updated the delete call request modal to display the user's local time for the scheduled call based on their time zone, and adjusted the reason input label to remove the asterisk, making the UI clearer (DeleteCallRequestModal.tsx). [1] [2] [3] [4]

API and backend interaction:

  • Added support for sending durationInMinutes in the request body when patching a call request, enabling more precise scheduling and updates (usePatchCallRequest.ts).

These changes collectively improve the user experience around time-sensitive actions, make UI feedback more consistent and informative, and ensure correct business logic for scheduling and managing support calls.

Add a null-check to include durationInMinutes from the request params into the PATCH request body when present. Ensures duration updates are sent to the API alongside utcTimes and avoids omitting duration changes.
Change the Select label in EngagementsPage from "Sort" to "Order By" to clarify the control's purpose. This updates the label prop in apps/customer-portal/webapp/src/pages/EngagementsPage.tsx.
Introduce two utility functions to convert API-provided time values into human-readable strings:

- formatServiceHoursDecimalAsHrMin(hours): accepts decimal hours, rounds to nearest minute, and returns strings like "1 hr and 30 min". Handles null/NaN/Infinity by returning "Not Available" and returns "0 min" for zero.
- formatMinutesAsHrMin(minutes): accepts total minutes, rounds to nearest minute, and returns similar human-readable strings with correct singular/plural forms.

These helpers standardize display of service-hour and minute values in the customer portal UI and centralize validation/formatting logic.
Introduce a MissingTimezoneDialogVariant type and a new optional `variant` prop (default: "informational"). When `variant` is "required" the dialog becomes non-dismissable via backdrop/escape and hides the "Later" button; the copy is updated to require setting a time zone before requesting/rescheduling calls. Added handleDialogClose to enforce the non-dismiss behavior and kept existing behavior for the informational variant.
Update ServiceHoursAllocationsCard test expectations to match the component's new hours formatting: replace 'h' suffix with 'hrs' and add spaces in allocation strings (e.g. "45/100h (45%)" -> "45 hrs/100 hrs (45%)", "55h" -> "55 hrs"). Adjusts assertions so tests reflect the updated UI display.
Add an optional `durationInMinutes?: number` field to the `PatchCallRequest` interface in apps/customer-portal/webapp/src/models/requests.ts. This allows callers to include or update the call duration (in minutes) when patching a call record.
Introduce a new CallRequestStatus value NOTES_PENDING ("Notes Pending") and export CALL_REQUEST_STATE_NOTES_PENDING_ID = "7". The ID constant represents the API state for Notes Pending and is used to hide customer reschedule/cancel actions when applicable.
Show the Meeting Duration selector in edit mode, require duration > 0 for form validity (including edits), and include durationInMinutes in the update payload. Also update the modal copy to mention meeting duration when editing so users can update both preferred times and duration.
Update tests to match UI text changes: expect singular hour label "1 hr" instead of "1 hrs" (60 minutes = 1 hour), and relax the state placeholder assertion by checking for any "--" occurrences rather than a strict `State: --` regex. These changes align tests with updated component rendering.
Add refetch mocks to useGetUserDetails returns in existing CallsPanel tests to better mirror the hook shape. Add a new test that ensures the required-timezone dialog is shown (and the Request Call modal elements are not present) when the user's profile has no time zone, and mock useGetCallRequests accordingly. Also tighten the Request Call button selector to an exact match to avoid ambiguous matches in the modal-opening test.
Replace the previous prompt time formatting with formatUtcToLocal using the caller's first preferred time and an injected userTimeZone prop. Added optional userTimeZone to the component props and destructuring, compute firstPreferredTime from preferredTimes, and fall back to "--" when unavailable. Also removed the required asterisk from the Reason field label and updated imports accordingly.
Add explicit handling for users missing a timezone when creating or editing call requests. Introduces missingTzVariant and pendingCallAfterTz state to defer a create/edit action until the user sets a timezone, and uses trimmed timezone checks. If timezone is missing, the dialog is shown with a "required" variant and the intended action is queued; after the user updates their profile the code refetches user details and resumes the pending create/edit or reopens the required dialog. Also: add refetch from useGetUserDetails, pass userTimeZone into DeleteCallRequestModal, and pass the variant prop into MissingTimezoneDialog. These changes ensure users must set a timezone before scheduling or editing calls and improve the prompt/resume UX.
Use formatServiceHoursDecimalAsHrMin for consistent, human-friendly display of service hours. Imported the helper from @utils/projectDetails and updated formatHoursDisplay and formatRemaining to delegate formatting to it (replacing the previous raw decimal/hour strings and simplifying null/number handling).
Adds two unit tests to CallRequestCard.test.tsx verifying that the Reschedule and Cancel buttons are not rendered when a call is in the "Notes Pending" or "Customer Rejected" states. Each test creates a mock CallRequest with the appropriate state and asserts those action buttons are absent.
Add a test to MissingTimezoneDialog that verifies the 'required' variant shows the required copy, omits the 'Later' button, and still renders the 'Set Time Zone' button. This improves coverage for the dialog's required-variant behavior.
Add unit tests for the DeleteCallRequestModal component. Tests verify that the preferred time is converted to the user's timezone and displayed in the confirmation text, and that the Reason input renders with a single required indicator. Includes a mock CallRequest fixture and uses Vitest + React Testing Library.
Refine RequestCallModal unit tests to be more specific and robust: import waitFor from testing-library, tighten label/role matchers using anchored regexes for the Request Call and Meeting Duration controls, adjust the editCall test to expect Meeting Duration to be present, rename and change the reschedule test to await the Update Call Request button becoming enabled (adds userTimeZone prop), and wrap the async assertion in waitFor to handle state updates.
Replace manual minutes-to-hours conversion and rounding with the shared formatMinutesAsHrMin utility from @utils/projectDetails. Update imports, compute totalTimeDisplay, and render '--' when the helper reports 'Not Available'. No behavior changes to state color handling—just centralizes and standardizes total time formatting.
Import formatServiceHoursDecimalAsHrMin and use it in formatHoursDisplay and formatRemaining so consumed/total and remaining hours render using the centralized hr:min formatter. Removes the manual "h" suffix and delegates null/undefined handling to the formatter for consistent display.
Adjust ServiceHoursStatCards.test.tsx assertions to match component output that now formats time with the 'hrs' unit (e.g. '45 hrs/100 hrs (45%)', '180 hrs/200 hrs (90%)', '55 hrs', '20 hrs'). This keeps the test expectations in sync with the UI time formatting change.
Import waitFor and make the test async to handle asynchronous rendering. Instead of relying on a hardcoded display value, the test now constructs a future CallRequest with a preferredTimes entry, renders the modal with that call, and uses waitFor to assert the preferred-time input contains the expected date. Keeps the existing check that Preferred Time fields are present.
Import additional call-state constants and add guards to detect notes-pending and customer-rejected states. Introduce isNotesPending, isCustomerRejected and hideCustomerActions to suppress customer action buttons when a request is notes-pending or has been rejected by the customer (including comparisons against state IDs and status text). Keeps existing status color resolution and cancel/completed handling unchanged.
@coderabbitai

coderabbitai Bot commented Apr 3, 2026 •

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR adds duration tracking for call requests with conditional PATCH submission, refactors time formatting utilities to display hour:minute notation across service hours and time tracking components, enhances timezone-aware dialog behavior in call workflows with required vs. informational variants, and introduces new call request states (Notes Pending, Customer Rejected) that conditionally hide customer actions.

Changes

Cohort / File(s) Summary
Time Formatting Utilities
src/utils/projectDetails.ts
Added formatServiceHoursDecimalAsHrMin() and formatMinutesAsHrMin() functions to convert decimal hours/minutes into human-readable hour:minute strings with proper singular/plural forms.
Service Hours Display
src/components/project-details/project-overview/service-hours-allocations/ServiceHoursAllocationsCard.tsx, ServiceHoursStatCards.tsx
Updated components to use new hour:minute formatting functions; changed display from 45/100h format to 45 hrs/100 hrs, removing previous numeric-type checks in remainder formatting.
Service Hours Tests
src/components/project-details/project-overview/.../ServiceHoursAllocationsCard.test.tsx, .../ServiceHoursStatCards.test.tsx
Updated test expectations to match new X hrs/Y hrs format instead of X/Yh format.
Time Tracking Components
src/components/project-details/time-tracking/TimeTrackingCard.tsx
Updated to use formatMinutesAsHrMin() for duration display and handle "Not Available" fallback with "--" rendering.
Time Tracking Tests
src/components/project-details/time-tracking/__tests__/TimeTrackingCard.test.tsx, ApproveCallRequestModal.test.tsx
Updated assertions for time format (1 hrs → 1 hr) and made preferred time assertion asynchronous with future date fixture.
Call Request Duration & PATCH
src/api/usePatchCallRequest.ts, src/models/requests.ts
Added conditional inclusion of durationInMinutes field in PATCH payload; extended PatchCallRequest interface to accept optional durationInMinutes property.
Call Request States & Constants
src/constants/supportConstants.ts, src/components/support/case-details/calls-tab/CallRequestCard.tsx
Added NOTES_PENDING state and CALL_REQUEST_STATE_NOTES_PENDING_ID constant; updated CallRequestCard to derive hideCustomerActions based on notes-pending and customer-rejected states, conditionally rendering no buttons when true.
Timezone Dialog Enhancement
src/components/support/case-details/calls-tab/MissingTimezoneDialog.tsx
Introduced variant prop ("informational" | "required"); conditionally blocks backdrop/escape close and hides "Later" button when variant="required", shows appropriate message text per variant.
Call Request Modal & Panel Flow
src/components/support/case-details/calls-tab/RequestCallModal.tsx, CallsPanel.tsx, DeleteCallRequestModal.tsx
Tightened validation to require durationInMinutes > 0 for all modes; updated RequestCallModal to show meeting duration field in both create and edit modes; enhanced CallsPanel with pendingCallAfterTz state to resume pending actions after timezone setup; threaded userTimeZone through delete modal and updated preferred time formatting logic.
Call Request Tests
src/components/support/case-details/calls-tab/__tests__/CallRequestCard.test.tsx, CallsPanel.test.tsx, DeleteCallRequestModal.test.tsx, MissingTimezoneDialog.test.tsx, RequestCallModal.test.tsx
Added test cases for hidden buttons in Notes Pending/Customer Rejected states, required timezone dialog variant, preferred time conversion with timezone, and updated button selectors and field assertions.
UI Label Update
src/pages/EngagementsPage.tsx
Changed sort-order dropdown label from "Sort" to "Order By".

Sequence Diagram

sequenceDiagram
    participant User
    participant CallsPanel
    participant MissingTimezoneDialog
    participant UserProfileModal
    participant RequestCallModal
    
    User->>CallsPanel: Click "Request Call" or "Edit Call"
    CallsPanel->>CallsPanel: Check userDetails.timeZone (trimmed)
    alt timezone missing/blank
        CallsPanel->>CallsPanel: Store pending action in pendingCallAfterTz
        CallsPanel->>CallsPanel: Set variant="required"
        CallsPanel->>MissingTimezoneDialog: Open with variant="required"
        User->>MissingTimezoneDialog: Click "Set Time Zone"
        MissingTimezoneDialog->>UserProfileModal: Open profile editor
        User->>UserProfileModal: Update timezone
        User->>UserProfileModal: Close modal
        UserProfileModal->>CallsPanel: onClose triggers refetchUserDetails()
        CallsPanel->>CallsPanel: Check timezone again
        alt timezone now valid
            CallsPanel->>RequestCallModal: Open with resumed action
            User->>RequestCallModal: Submit call request
        else timezone still missing
            CallsPanel->>MissingTimezoneDialog: Reopen with variant="required"
        end
    else timezone present
        CallsPanel->>RequestCallModal: Open immediately
        User->>RequestCallModal: Submit call request
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

Type/Feature, Type/Enhancement, App/Customer Portal, Area/Frontend, Platform/Web, Type/UX

Suggested reviewers

  • Rashmika998
  • v15a1
  • cloby99

Poem

🐰 Hours reshape to hrs and min,
Call durations woven in,
Timezone dialogs guide with care,
States pending... customer's despair,
New paths flow when details align! 🕐✨

🚥 Pre-merge checks | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning The pull request description is entirely unfilled; it contains only the template placeholders (Purpose, Goals, Approach, etc.) with no actual content, making it impossible to understand the motivation, implementation details, or impact of the changes. Fill in all required template sections with substantive information: explain the problems being solved, the solutions introduced, the implementation approach, affected user stories, release notes, and any documentation or testing details.
Docstring Coverage ⚠️ Warning Docstring coverage is 71.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Feat/time card converstaion' contains a typo ('converstaion' instead of 'conversion') and is vague about the specific changes; it mentions only 'time card' but the PR encompasses call request duration, timezone handling, status states, and multiple UI formatting changes. Revise the title to be more descriptive and accurate, e.g., 'Add call request duration editing and time-display formatting' or 'Feat: Time and call request formatting improvements', and correct the spelling.

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

✨ 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 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: 7

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__/TimeTrackingCard.test.tsx (1)

51-64: ⚠️ Potential issue | 🟡 Minor

Make the fallback assertion target total-time specifically.

Line 63 is too broad and can pass due to other "--" placeholders. This weakens coverage for the totalTime display path.

💡 Proposed fix
   it("should show fallback '--' for missing values", () => {
     const incompleteCard: TimeCard = {
       ...mockCard,
       state: null,
       approvedBy: null,
       case: { number: "", id: "c1", label: "" },
-      totalTime: 0,
+      totalTime: null as unknown as number,
     };
@@
-    expect(screen.getAllByText("--").length).toBeGreaterThan(0);
+    expect(screen.getByText("--", { selector: "h5" })).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 - 64, The test currently asserts any "--" exists which is too
broad; update the assertion to target the totalTime display specifically for the
TimeTrackingCard test: change the second expect to assert the total-time element
(either by matching the exact label/text like
expect(screen.getByText(/Total\s*time:\s*--/i)).toBeInTheDocument() or by
adding/using a dedicated data-testid (e.g., data-testid="total-time") on the
total time element in TimeTrackingCard and using
expect(screen.getByTestId('total-time')).toHaveTextContent('--'); ensure you
reference the incompleteCard.totalTime being 0 and the TimeTrackingCard
component when making this change.
🧹 Nitpick comments (4)
apps/customer-portal/webapp/src/utils/projectDetails.ts (1)

463-521: Extract shared hr/min formatting logic to prevent drift.

Both helpers duplicate the same string-building path. A shared internal formatter will keep behavior consistent and reduce maintenance risk.

🤖 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 463 -
521, Both formatServiceHoursDecimalAsHrMin and formatMinutesAsHrMin duplicate
the same hr/min string-building logic; extract that into a single internal
helper (e.g. formatHrMinFromTotalMinutes(totalMinutes: number): string) and have
both functions compute totalMinutes (in formatServiceHoursDecimalAsHrMin use
Math.round(hours * 60), in formatMinutesAsHrMin use Math.round(minutes)) then
call the new helper; ensure the helper preserves exact output semantics ("1 hr",
"hrs", "1 min", "min", "X hr and Y min", and "0 min"/"Not Available") and keep
the same input validation and return values in the public functions.
apps/customer-portal/webapp/src/api/usePatchCallRequest.ts (1)

70-72: Validate durationInMinutes before sending PATCH payload.

This currently forwards any non-null number, including 0/negative values, if another caller bypasses UI validation.

♻️ Suggested guard
-      if (rest.durationInMinutes != null) {
-        body.durationInMinutes = rest.durationInMinutes;
-      }
+      if (rest.durationInMinutes != null) {
+        if (rest.durationInMinutes <= 0) {
+          throw new Error("durationInMinutes must be greater than 0");
+        }
+        body.durationInMinutes = rest.durationInMinutes;
+      }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/customer-portal/webapp/src/api/usePatchCallRequest.ts` around lines 70 -
72, The PATCH builder currently assigns rest.durationInMinutes to body without
validation; update the logic in usePatchCallRequest (where body and rest are
handled) to only include durationInMinutes when it is a valid positive integer
(e.g., Number.isInteger(...) && > 0 and isFinite) so that 0/negative/non-integer
values are not sent; perform this guard before setting body.durationInMinutes
and leave all other behavior unchanged.
apps/customer-portal/webapp/src/constants/supportConstants.ts (1)

114-116: Keep call-request state constants type-consistent.

This new constant is string-typed while neighboring state constants are numeric. Keeping one numeric shape reduces accidental mixed-type comparisons.

♻️ Suggested consistency tweak
-export const CALL_REQUEST_STATE_NOTES_PENDING_ID = "7";
+export const CALL_REQUEST_STATE_NOTES_PENDING = 7;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/customer-portal/webapp/src/constants/supportConstants.ts` around lines
114 - 116, The new constant CALL_REQUEST_STATE_NOTES_PENDING_ID is declared as a
string ("7") while neighboring call-request state constants are numeric; change
CALL_REQUEST_STATE_NOTES_PENDING_ID to a numeric literal (7) so its type matches
the other state constants and update any usages expecting a number if necessary
to avoid mixed-type comparisons.
apps/customer-portal/webapp/src/components/support/case-details/calls-tab/__tests__/CallRequestCard.test.tsx (1)

57-87: Good coverage addition; consider an ID-only assertion variant.

Both new tests currently use matching IDs and labels. Adding an ID-only case would better protect the “stable ID over label” path.

Based on learnings: Avoid deriving UI logic from raw backend status label strings and prefer stable status IDs/enums end-to-end.

🤖 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/case-details/calls-tab/__tests__/CallRequestCard.test.tsx`
around lines 57 - 87, Add an ID-only assertion variant to the CallRequestCard
tests: create new test cases (alongside the existing ones) that use the same
mockCall but set state to only include the stable id (e.g., state: { id: "7" }
and state: { id: "4" } with no label) and render <CallRequestCard call={...}
onEditClick={vi.fn()} onDeleteClick={vi.fn()} /> then assert that
screen.queryByRole("button", { name: /Reschedule/i }) and
screen.queryByRole("button", { name: /Cancel/i }) are not in the document; this
ensures the component logic relies on state.id rather than state.label (refer to
CallRequestCard, mockCall and tests in CallRequestCard.test.tsx).
🤖 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/project-overview/service-hours-allocations/__tests__/ServiceHoursAllocationsCard.test.tsx`:
- Line 59: The assertion hardcodes "Apr 30, 2026" which is brittle; replace that
literal with a call to formatProjectDate(...) so the test uses the same
timezone/locale-safe formatting as the app. Locate the failing assertion in
ServiceHoursAllocationsCard.test.tsx (the expect using screen.getByText) and
change it to expect(screen.getByText(formatProjectDate(<the test's project or
date value>))). Ensure you import formatProjectDate at the top of the test and
pass the same date value used to render the component.

In
`@apps/customer-portal/webapp/src/components/project-details/time-tracking/__tests__/ServiceHoursStatCards.test.tsx`:
- Line 50: Replace the hardcoded date assertion in the ServiceHoursStatCards
test (the expect using screen.getByText("Apr 30, 2026")) with a call to the
shared formatter: import and use formatProjectDate(...) to format the same Date
value used in the test setup, then assert using that formatted string so the
test matches the component's timezone-aware output.

In
`@apps/customer-portal/webapp/src/components/support/case-details/calls-tab/CallRequestCard.tsx`:
- Around line 92-95: The current isCustomerRejected check is too broad because
it treats any CallRequestStatus.REJECTED or label containing "customer rejected"
as customer-rejected; narrow it to only the explicit customer-rejected state by
removing the CallRequestStatus.REJECTED and free-text checks and rely on the
stable state ID (CALL_REQUEST_STATE_CUSTOMER_REJECTED) or a dedicated
enum/constant that represents customer-rejected; update the isCustomerRejected
definition (referencing variables statusLabel, statusLower,
CALL_REQUEST_STATE_CUSTOMER_REJECTED, and CallRequestStatus.REJECTED) to only
return true when call.state?.id strictly equals the customer-rejected ID (or a
dedicated customerRejected enum) and remove the fallback string/REJECTED
comparisons.

In
`@apps/customer-portal/webapp/src/components/support/case-details/calls-tab/CallsPanel.tsx`:
- Around line 104-106: The component currently uses userDetails.timeZone
directly (userTimeZone) which can contain untrimmed or ServiceNow-specific
values; normalize it once in CallsPanel by trimming and mapping ServiceNow
profile values to canonical IANA zones (e.g., via an existing map or helper) and
expose that normalized value (e.g., userTimeZoneNormalized) instead of
userTimeZone; update all downstream usages and conditional branches
(CallRequestList, DeleteCallRequestModal, ApproveCallRequestModal,
RequestCallModal and any create/edit guards) to key off the normalized value so
whitespace or non-canonical zones don’t bypass the missing-timezone path.
- Around line 470-488: When closing UserProfileModal the refetch branch doesn't
handle the case where pendingCallAfterTz is null but the mount-time prompt
already set hasShownTzPrompt to true, so the required timezone dialog never
reappears; update the onClose refetch promise handler (inside CallsPanel) so
that after refetching you check for a missing tz even when pendingCallAfterTz is
null: if result.data?.timeZone is falsy then reset hasShownTzPrompt (call
setHasShownTzPrompt(false)) and setMissingTzVariant("required") and
setIsMissingTzDialogOpen(true); otherwise keep the existing pendingCallAfterTz
handling (setEditCall/setIsModalOpen/setPendingCallAfterTz) as-is.

In
`@apps/customer-portal/webapp/src/components/support/case-details/calls-tab/DeleteCallRequestModal.tsx`:
- Around line 98-102: The prompt currently only uses the firstPreferredTime from
call.preferredTimes; update the promptWhen logic to fall back to
call.scheduleTime when preferredTimes is empty or contains no valid entries. In
the block that computes firstPreferredTime and promptWhen (referencing
firstPreferredTime, promptWhen, formatUtcToLocal), attempt to derive a time from
call.preferredTimes first, and if that yields nothing, use call.scheduleTime
(also formatted via formatUtcToLocal with the same "short" and userTimeZone
arguments); if neither exists, keep the "--" fallback. Ensure you reference
call.scheduleTime and maintain the same formatting behavior.

In `@apps/customer-portal/webapp/src/utils/projectDetails.ts`:
- Around line 463-521: Both formatServiceHoursDecimalAsHrMin and
formatMinutesAsHrMin can produce malformed negative outputs for negative inputs;
add a guard at the start of each function to treat negative values as
unavailable by returning "Not Available" (i.e., if hours < 0 or minutes < 0
return "Not Available") before any rounding/conversion so negative durations are
not converted into "-1 hrs and -30 min" strings.

---

Outside diff comments:
In
`@apps/customer-portal/webapp/src/components/project-details/time-tracking/__tests__/TimeTrackingCard.test.tsx`:
- Around line 51-64: The test currently asserts any "--" exists which is too
broad; update the assertion to target the totalTime display specifically for the
TimeTrackingCard test: change the second expect to assert the total-time element
(either by matching the exact label/text like
expect(screen.getByText(/Total\s*time:\s*--/i)).toBeInTheDocument() or by
adding/using a dedicated data-testid (e.g., data-testid="total-time") on the
total time element in TimeTrackingCard and using
expect(screen.getByTestId('total-time')).toHaveTextContent('--'); ensure you
reference the incompleteCard.totalTime being 0 and the TimeTrackingCard
component when making this change.

---

Nitpick comments:
In `@apps/customer-portal/webapp/src/api/usePatchCallRequest.ts`:
- Around line 70-72: The PATCH builder currently assigns rest.durationInMinutes
to body without validation; update the logic in usePatchCallRequest (where body
and rest are handled) to only include durationInMinutes when it is a valid
positive integer (e.g., Number.isInteger(...) && > 0 and isFinite) so that
0/negative/non-integer values are not sent; perform this guard before setting
body.durationInMinutes and leave all other behavior unchanged.

In
`@apps/customer-portal/webapp/src/components/support/case-details/calls-tab/__tests__/CallRequestCard.test.tsx`:
- Around line 57-87: Add an ID-only assertion variant to the CallRequestCard
tests: create new test cases (alongside the existing ones) that use the same
mockCall but set state to only include the stable id (e.g., state: { id: "7" }
and state: { id: "4" } with no label) and render <CallRequestCard call={...}
onEditClick={vi.fn()} onDeleteClick={vi.fn()} /> then assert that
screen.queryByRole("button", { name: /Reschedule/i }) and
screen.queryByRole("button", { name: /Cancel/i }) are not in the document; this
ensures the component logic relies on state.id rather than state.label (refer to
CallRequestCard, mockCall and tests in CallRequestCard.test.tsx).

In `@apps/customer-portal/webapp/src/constants/supportConstants.ts`:
- Around line 114-116: The new constant CALL_REQUEST_STATE_NOTES_PENDING_ID is
declared as a string ("7") while neighboring call-request state constants are
numeric; change CALL_REQUEST_STATE_NOTES_PENDING_ID to a numeric literal (7) so
its type matches the other state constants and update any usages expecting a
number if necessary to avoid mixed-type comparisons.

In `@apps/customer-portal/webapp/src/utils/projectDetails.ts`:
- Around line 463-521: Both formatServiceHoursDecimalAsHrMin and
formatMinutesAsHrMin duplicate the same hr/min string-building logic; extract
that into a single internal helper (e.g.
formatHrMinFromTotalMinutes(totalMinutes: number): string) and have both
functions compute totalMinutes (in formatServiceHoursDecimalAsHrMin use
Math.round(hours * 60), in formatMinutesAsHrMin use Math.round(minutes)) then
call the new helper; ensure the helper preserves exact output semantics ("1 hr",
"hrs", "1 min", "min", "X hr and Y min", and "0 min"/"Not Available") and keep
the same input validation and return values in the public functions.
🪄 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: 9b2cd5df-d385-4189-bf5e-c1555f0e3194

📥 Commits

Reviewing files that changed from the base of the PR and between f02b391 and 5cb2a64.

📒 Files selected for processing (22)
  • apps/customer-portal/webapp/src/api/usePatchCallRequest.ts
  • apps/customer-portal/webapp/src/components/project-details/project-overview/service-hours-allocations/ServiceHoursAllocationsCard.tsx
  • apps/customer-portal/webapp/src/components/project-details/project-overview/service-hours-allocations/__tests__/ServiceHoursAllocationsCard.test.tsx
  • apps/customer-portal/webapp/src/components/project-details/time-tracking/ServiceHoursStatCards.tsx
  • apps/customer-portal/webapp/src/components/project-details/time-tracking/TimeTrackingCard.tsx
  • apps/customer-portal/webapp/src/components/project-details/time-tracking/__tests__/ServiceHoursStatCards.test.tsx
  • apps/customer-portal/webapp/src/components/project-details/time-tracking/__tests__/TimeTrackingCard.test.tsx
  • apps/customer-portal/webapp/src/components/support/case-details/calls-tab/CallRequestCard.tsx
  • apps/customer-portal/webapp/src/components/support/case-details/calls-tab/CallsPanel.tsx
  • apps/customer-portal/webapp/src/components/support/case-details/calls-tab/DeleteCallRequestModal.tsx
  • apps/customer-portal/webapp/src/components/support/case-details/calls-tab/MissingTimezoneDialog.tsx
  • apps/customer-portal/webapp/src/components/support/case-details/calls-tab/RequestCallModal.tsx
  • apps/customer-portal/webapp/src/components/support/case-details/calls-tab/__tests__/ApproveCallRequestModal.test.tsx
  • apps/customer-portal/webapp/src/components/support/case-details/calls-tab/__tests__/CallRequestCard.test.tsx
  • apps/customer-portal/webapp/src/components/support/case-details/calls-tab/__tests__/CallsPanel.test.tsx
  • apps/customer-portal/webapp/src/components/support/case-details/calls-tab/__tests__/DeleteCallRequestModal.test.tsx
  • apps/customer-portal/webapp/src/components/support/case-details/calls-tab/__tests__/MissingTimezoneDialog.test.tsx
  • apps/customer-portal/webapp/src/components/support/case-details/calls-tab/__tests__/RequestCallModal.test.tsx
  • apps/customer-portal/webapp/src/constants/supportConstants.ts
  • apps/customer-portal/webapp/src/models/requests.ts
  • apps/customer-portal/webapp/src/pages/EngagementsPage.tsx
  • apps/customer-portal/webapp/src/utils/projectDetails.ts

Comment thread apps/customer-portal/webapp/src/utils/projectDetails.ts
@Rashmika998
Rashmika998 merged commit 0f96fb9 into wso2-open-operations:main Apr 3, 2026
1 check passed
@dileepapeiris dileepapeiris self-assigned this Apr 3, 2026
@dileepapeiris dileepapeiris changed the title Feat/time card converstaion [Customer Portal][Web] Enhance Time Display, Enforce Timezone for Scheduling, and Refine Call Request UI Logic Apr 3, 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 labels Apr 3, 2026
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/UX Refers to user experience-related tasks or issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants