[Customer Portal][Web] Enhance Time Display, Enforce Timezone for Scheduling, and Refine Call Request UI Logic - #461
Conversation
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.
📝 WalkthroughWalkthroughThis 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
Sequence DiagramsequenceDiagram
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ❌ 3❌ Failed checks (2 warnings, 1 inconclusive)
✏️ 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: 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 | 🟡 MinorMake the fallback assertion target total-time specifically.
Line 63 is too broad and can pass due to other
"--"placeholders. This weakens coverage for thetotalTimedisplay 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: ValidatedurationInMinutesbefore 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
📒 Files selected for processing (22)
apps/customer-portal/webapp/src/api/usePatchCallRequest.tsapps/customer-portal/webapp/src/components/project-details/project-overview/service-hours-allocations/ServiceHoursAllocationsCard.tsxapps/customer-portal/webapp/src/components/project-details/project-overview/service-hours-allocations/__tests__/ServiceHoursAllocationsCard.test.tsxapps/customer-portal/webapp/src/components/project-details/time-tracking/ServiceHoursStatCards.tsxapps/customer-portal/webapp/src/components/project-details/time-tracking/TimeTrackingCard.tsxapps/customer-portal/webapp/src/components/project-details/time-tracking/__tests__/ServiceHoursStatCards.test.tsxapps/customer-portal/webapp/src/components/project-details/time-tracking/__tests__/TimeTrackingCard.test.tsxapps/customer-portal/webapp/src/components/support/case-details/calls-tab/CallRequestCard.tsxapps/customer-portal/webapp/src/components/support/case-details/calls-tab/CallsPanel.tsxapps/customer-portal/webapp/src/components/support/case-details/calls-tab/DeleteCallRequestModal.tsxapps/customer-portal/webapp/src/components/support/case-details/calls-tab/MissingTimezoneDialog.tsxapps/customer-portal/webapp/src/components/support/case-details/calls-tab/RequestCallModal.tsxapps/customer-portal/webapp/src/components/support/case-details/calls-tab/__tests__/ApproveCallRequestModal.test.tsxapps/customer-portal/webapp/src/components/support/case-details/calls-tab/__tests__/CallRequestCard.test.tsxapps/customer-portal/webapp/src/components/support/case-details/calls-tab/__tests__/CallsPanel.test.tsxapps/customer-portal/webapp/src/components/support/case-details/calls-tab/__tests__/DeleteCallRequestModal.test.tsxapps/customer-portal/webapp/src/components/support/case-details/calls-tab/__tests__/MissingTimezoneDialog.test.tsxapps/customer-portal/webapp/src/components/support/case-details/calls-tab/__tests__/RequestCallModal.test.tsxapps/customer-portal/webapp/src/constants/supportConstants.tsapps/customer-portal/webapp/src/models/requests.tsapps/customer-portal/webapp/src/pages/EngagementsPage.tsxapps/customer-portal/webapp/src/utils/projectDetails.ts
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:
ServiceHoursAllocationsCard.tsx,ServiceHoursStatCards.tsx,TimeTrackingCard.tsx). [1] [2] [3]ServiceHoursAllocationsCard.test.tsx,ServiceHoursStatCards.test.tsx,TimeTrackingCard.test.tsx). [1] [2] [3] [4]User time zone enforcement for call scheduling:
CallsPanel.tsx,MissingTimezoneDialog.tsx). [1] [2] [3] [4] [5]MissingTimezoneDialogto 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.CallsPanel.tsx).Support call request and modal improvements:
CallRequestCard.tsx). [1] [2]DeleteCallRequestModal.tsx). [1] [2] [3] [4]API and backend interaction:
durationInMinutesin 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.