feat(Dashboard): fix pending compensation edit — route date field by job type#1909
Conversation
Fresh Eyes ReviewFound 8 issues in this PR. PR Description Issues
Additional Findings (outside diff)
Info (2)
Download findings.json — drag the file into Claude or use Please 👍🏽 👎🏽 if you found this useful. Generated by Fresh Eyes Reviewer. Get help in #ai-code-reviews |
| @@ -5,6 +5,7 @@ import { useJobForm } from '../../shared/useJobForm' | |||
| import { useCompensationForm } from '../../shared/useCompensationForm' | |||
There was a problem hiding this comment.
Do these files need tests?
| @@ -1,27 +1,17 @@ | |||
| import classNames from 'classnames' | |||
0c952e3 to
a277929
Compare
- Derive pending compensation changes across all jobs using a new
getPendingCompensationChanges helper; stacked future comps are diffed
chronologically so each bullet remains meaningful.
- Single job with a pending change: inline warning alert with bullet
details (pay, title, FLSA status, minimum-wage adjustments) and a
Cancel change button that fires DELETE /v1/compensations/{uuid}.
- Multiple jobs with one pending change: inline alert includes the job
title ("Compensation for {title} will change on {date}") so the
affected job is unambiguous.
- Multiple jobs with multiple pending changes: summary alert with a
Review CTA that opens a modal listing every pending change by job.
- Alert uses disableScrollIntoView; change details render as an
UnorderedList; cancellation fires directly without a confirmation
dialog.
- Extend Alert primitive with an optional action prop for inline CTAs.
- createSdkQueryClient shared factory aligns QueryClient defaults;
mutations invalidate via their own cache keys so no manual
invalidateQueries calls are needed.
- payRateFormats in common.json updated to long-form ("per hour",
"per year") matching design copy; weekly/monthly remain annualised.
- New EMPLOYEE_COMPENSATION_CHANGE_CANCELLED event emitted on success.
- Full unit and integration test coverage for all alert variants,
cancellation success/failure paths, stacked future comps, and the
review modal flow.
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
… display Display weekly and monthly compensation rates as stored ($X per week, $X per month) rather than annualizing them. Introduces formatCompensationRate / useFormatCompensationRate alongside the existing formatPayRate so payroll contexts are unaffected. Also reverts payRateFormats i18n strings to original /hr /yr /paycheck compact format. Co-authored-by: Cursor <cursoragent@cursor.com>
…creating new When a job already has a pending (future-dated) compensation, clicking Edit now routes to ManagementEditPendingCompensation which sends a PUT to update the existing compensation rather than POSTing a new one. Extracts a shared ManagementCompensationFormBody and fixes useCompensationForm to exclude the compensation being edited from the maximumEffectiveDate constraint. Also fixes the alert container width in JobAndPayView to always be 100%, with padding applied only for multi-job layouts. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
For primary new jobs (hire date in the future, no current comp), show a Hire date field that keeps hire_date and comp effective_date in sync on submit, preventing the API from auto-creating a second compensation when the single pending comp moves off the hire date. For secondary new jobs, floor the effective date picker at max(tomorrow, job.hireDate) using local-time parsing to avoid UTC off-by-one issues. Apply the same hire date floor to the Add Another Job form for consistency. Co-authored-by: Cursor <cursoragent@cursor.com>
a277929 to
d2fc1df
Compare
| const primaryHireDateParsed = normalizeToDate(primaryHireDate) | ||
| const minEffectiveDate = | ||
| primaryHireDateParsed && primaryHireDateParsed > tomorrow ? primaryHireDateParsed : tomorrow |
There was a problem hiding this comment.
🟡 Minor | [fresh_eyes]: test-coverage
New minEffectiveDate logic using normalizeToDate(primaryHireDate) was added but AddAnotherJob has no test file at all. The date-flooring logic (max of tomorrow vs primary hire date) is identical to the pattern the PR is fixing for off-by-one issues and would benefit from a test.
Please 👍🏽 👎🏽 if you found this useful. Generated by Fresh Eyes Reviewer. Get help in #ai-code-reviews
| const loadingErrorHandling = composeErrorHandler([jobForm, compensationForm]) | ||
| return <BaseLayout isLoading error={loadingErrorHandling.errors} /> | ||
| } | ||
|
|
||
| // Secondary new job: effective date must be on or after the secondary job's | ||
| // hire date (and at least tomorrow) so we don't set a date before the job | ||
| // even starts. | ||
| const minEffectiveDate = | ||
| isNewJob && !isPrimaryJob && jobForm.data.currentJob?.hireDate |
There was a problem hiding this comment.
🟡 Minor | [fresh_eyes]: test-coverage
The secondary new job path (isNewJob=true, isPrimaryJob=false) that computes minEffectiveDate from the job's hire date is not covered by any test. Tests exist for isNewJob=false and for isNewJob+isPrimaryJob=true, but not this branch. Given the PR explicitly fixes a UTC off-by-one in date floor logic, this path deserves a test to prevent regressions.
Please 👍🏽 👎🏽 if you found this useful. Generated by Fresh Eyes Reviewer. Get help in #ai-code-reviews
| const minEffectiveDate = | ||
| isNewJob && !isPrimaryJob && jobForm.data.currentJob?.hireDate | ||
| ? new Date( | ||
| Math.max( | ||
| addDays(new Date(), 1).getTime(), | ||
| (normalizeToDate(jobForm.data.currentJob.hireDate) ?? addDays(new Date(), 1)).getTime(), | ||
| ), | ||
| ) | ||
| : undefined |
There was a problem hiding this comment.
🟡 Minor | [fresh_eyes]: simplicity
Date-floor computation is hard to parse. The normalizeToDate(...) ?? addDays(new Date(), 1) fallback inside Math.max means both branches equal tomorrow when normalizeToDate returns null — making the Math.max a no-op in that path. Compare with the simpler pattern in AddAnotherJob.tsx (parsed && parsed > tomorrow ? parsed : tomorrow). Extract the tomorrow value once and use the same ternary pattern for clarity.
Please 👍🏽 👎🏽 if you found this useful. Generated by Fresh Eyes Reviewer. Get help in #ai-code-reviews
| const minEffectiveDate = | ||
| isNewJob && !isPrimaryJob && jobForm.data.currentJob?.hireDate | ||
| ? new Date( | ||
| Math.max( | ||
| addDays(new Date(), 1).getTime(), | ||
| (normalizeToDate(jobForm.data.currentJob.hireDate) ?? addDays(new Date(), 1)).getTime(), | ||
| ), | ||
| ) | ||
| : undefined |
There was a problem hiding this comment.
🟡 Minor | [fresh_eyes]: code-duplication
Same minEffectiveDate = max(tomorrow, normalizeToDate(hireDate)) computation exists in AddAnotherJob.tsx (lines 77-82) with slightly different style (addDays vs tomorrow.setDate). Both encode the same business rule: 'effective date cannot precede the job hire date or tomorrow'. Consider extracting a shared helper like computeMinEffectiveDate(hireDate: string | undefined): Date to keep this rule in one place.
Please 👍🏽 👎🏽 if you found this useful. Generated by Fresh Eyes Reviewer. Get help in #ai-code-reviews
Summary
hire_dateon the job andeffective_dateon the compensation are set to the same chosen value, preventing the Gusto API from auto-creating a second compensation to fill the gap at the original hire date.max(tomorrow, job.hireDate)using local-time date parsing (normalizeToDate) to avoid a UTC off-by-one that made dates one day before the hire date selectable.max(tomorrow, primaryHireDate)floor to the Effective date picker for consistency.Test plan
npm run test -- --run src/components/Employee/Compensation/management/EditPendingCompensation/EditPendingCompensation.test.tsx— 10 tests passScreen.Recording.2026-05-21.at.6.44.37.PM.mov
Made with Cursor