feat: disable past dates in Pay Schedule first pay date picker#1311
feat: disable past dates in Pay Schedule first pay date picker#1311larchai wants to merge 7 commits intoGusto:mainfrom
Conversation
| function dateToCalendarDate(date: Date): CalendarDate { | ||
| return parseDate( | ||
| `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`, | ||
| ) | ||
| } |
There was a problem hiding this comment.
I'd ask claude to check if there are existing date utilities in the codebase that could be leveraged here instead
OR
If not, does it make sense to move this function to be with existing date utils
| anchorPayDate: z | ||
| .date() | ||
| .refine( | ||
| date => { | ||
| const today = new Date() | ||
| today.setHours(0, 0, 0, 0) | ||
| return date >= today | ||
| }, | ||
| { message: 'First pay date cannot be in the past' }, | ||
| ) | ||
| .optional(), |
There was a problem hiding this comment.
For simplicity we can likely just roll back this change and keep the schema as is. The UI will already prevent them from selecting a date in the past and this is kinda just an abundance of caution thing
if we do go with this, we'll need to update the schema to be a function so that we can dynamically provide translations here like elsewhere in the codebase, and then wire this up to the date picker error message so that it properly gets it. it's a lot of work for something where we could just rely on disabled dates working in the UI
serikjensen
left a comment
There was a problem hiding this comment.
Code looks good! we should figure out a way for you to check your changes
There was a problem hiding this comment.
Pull request overview
This PR aims to prevent selecting past dates for the Pay Schedule “First pay date” by adding date-constraint props to the DatePicker adapter interface, wiring them through the default react-aria DatePicker implementation, and applying a minimum date in the Pay Schedule edit form (with docs updates for adapter consumers).
Changes:
- Extend
DatePickerPropswithminDate,maxDate, andisDateDisabled. - Map those props to react-aria
minValue,maxValue, andisDateUnavailablein the default DatePicker implementation. - Apply
minDate={new Date()}to the Pay ScheduleanchorPayDatefield and document the new adapter props.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| src/components/Company/PaySchedule/_parts/Edit.tsx | Applies a minimum selectable date to the “First pay date” field. |
| src/components/Common/UI/DatePicker/DatePickerTypes.ts | Adds new optional date-constraint props to the adapter interface. |
| src/components/Common/UI/DatePicker/DatePicker.tsx | Converts adapter props to react-aria constraint props and passes them through. |
| docs/component-adapter/component-inventory.md | Documents the new DatePicker adapter props for partners. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const dateString = formatDateToStringDate(date) | ||
| if (!dateString) throw new Error('Invalid date provided to dateToCalendarDate') |
There was a problem hiding this comment.
dateToCalendarDate relies on formatDateToStringDate, which uses Date#toISOString() (UTC) to derive the YYYY-MM-DD. For users not in UTC, this can shift the day (e.g., evening in US timezones becomes tomorrow in UTC), making minValue/maxValue off by one and potentially disabling today's date. Convert Date to a calendar date using local year/month/day (or a dedicated @internationalized/date helper) instead of toISOString().
| const dateString = formatDateToStringDate(date) | |
| if (!dateString) throw new Error('Invalid date provided to dateToCalendarDate') | |
| if (!(date instanceof Date) || isNaN(date.getTime())) { | |
| throw new Error('Invalid date provided to dateToCalendarDate') | |
| } | |
| const year = date.getFullYear() | |
| const month = date.getMonth() + 1 // getMonth() is 0-indexed | |
| const day = date.getDate() | |
| const dateString = [ | |
| String(year).padStart(4, '0'), | |
| String(month).padStart(2, '0'), | |
| String(day).padStart(2, '0'), | |
| ].join('-') |
|
|
||
| function dateToCalendarDate(date: Date): CalendarDate { | ||
| const dateString = formatDateToStringDate(date) | ||
| if (!dateString) throw new Error('Invalid date provided to dateToCalendarDate') |
There was a problem hiding this comment.
dateToCalendarDate throws if passed an invalid Date. Since minDate/maxDate come from external adapters, a single invalid value would crash rendering. Consider handling invalid dates gracefully (e.g., treat as undefined and optionally log) rather than throwing.
| if (!dateString) throw new Error('Invalid date provided to dateToCalendarDate') | |
| if (!dateString || Number.isNaN(date.getTime())) { | |
| if (typeof process !== 'undefined' && process.env && process.env.NODE_ENV !== 'production') { | |
| // eslint-disable-next-line no-console | |
| console.warn('dateToCalendarDate received an invalid Date and will use a fallback value:', date) | |
| } | |
| const fallbackString = formatDateToStringDate(new Date()) || '1970-01-01' | |
| return parseDate(fallbackString) | |
| } |
| // Convert date constraint props to react-aria DateValue format | ||
| const minValue = minDate ? dateToCalendarDate(minDate) : undefined | ||
| const maxValue = maxDate ? dateToCalendarDate(maxDate) : undefined | ||
| const isDateUnavailable = isDateDisabled | ||
| ? (dateValue: DateValue) => { | ||
| const jsDate = calendarDateValueToDate(dateValue) | ||
| return jsDate ? isDateDisabled(jsDate) : false | ||
| } | ||
| : undefined |
There was a problem hiding this comment.
New constraint props (minDate/maxDate/isDateDisabled) introduce user-facing behavior changes, but there are no accompanying tests to verify that past dates are actually disabled and that minDate is inclusive (today selectable). Since this component already has a test suite, please add coverage for the new props (and their conversion to react-aria’s minValue/maxValue/isDateUnavailable).
021c533 to
7581368
Compare
Addressed Copilot feedback (latest commit)Timezone bug fix: Graceful handling of invalid dates: Since Not addressed (intentionally):
|
src/helpers/dateFormatting.test.ts
Outdated
| if (offsetMinutes > 0) { | ||
| // Machine is west of UTC (Americas) — the bug manifests | ||
| expect(localBasedResult).toBe('2024-03-26') | ||
| expect(utcBasedResult).not.toBe(localBasedResult) | ||
| } else if (offsetMinutes === 0) { | ||
| // Machine is in UTC — both agree (bug hidden but still latent) | ||
| expect(localBasedResult).toBe('2024-03-27') | ||
| expect(utcBasedResult).toBe(localBasedResult) | ||
| } else { | ||
| // Machine is east of UTC (Europe, Asia) — local date is even further ahead | ||
| // At this UTC time, some eastern zones are already March 27 locally too | ||
| } |
There was a problem hiding this comment.
I think we should probably just remove this test. having conditionals in tests like this is not great practice and was likely just done to prove something out locally.
Adds minDate, maxDate, and isDateDisabled props to the DatePicker component adapter interface, and uses minDate on the Pay Schedule anchorPayDate field to prevent selection of past dates. Also adds Zod validation on anchorPayDate as a safety net for programmatic submissions. Resolves SDK-530 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Reuse existing formatDateToStringDate utility in dateToCalendarDate instead of duplicating date string formatting logic - Revert Zod schema validation for anchorPayDate — the UI constraint (minDate) is sufficient and adding validation would require dynamic schema for i18n support Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Regenerates component-inventory.md to include the new minDate, maxDate, and isDateDisabled props added to DatePickerProps. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fetches company payment speed via usePaymentConfigsGet and renders the Pay Schedule first pay date description dynamically (1/2/4 days) instead of hardcoding "2 days". Falls back to 2 if unavailable. Uses i18next count-based pluralization for correct "day" vs "days". Partially addresses SDK-592 (Pay Schedule only; Off-Cycle Pay Period and Contractor Payments still have hardcoded copy) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use local date parts (getFullYear/getMonth/getDate) instead of formatDateToStringDate which uses toISOString (UTC). This prevents timezone-related off-by-one errors where evening times in US timezones would shift to the next day in UTC. - Return undefined instead of throwing on invalid dates, since minDate/maxDate come from external adapters and an invalid value should not crash rendering. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds two test cases proving that formatDateToStringDate (which uses toISOString/UTC) can return a different calendar date than the user's local date. This documents the latent bug that motivated using local date extraction (getFullYear/getMonth/getDate) in dateToCalendarDate. Also includes auto-generated i18next type update for pluralized anchorPayDateDescription keys. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove conditional test cases that were used to locally prove the UTC timezone bug in formatDateToStringDate. As Steve noted, conditionals in tests are bad practice — these served their purpose as local proof and don't belong in the test suite. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
95f29b2 to
ce13be7
Compare
Summary
minDate,maxDate, andisDateDisabledprops to theDatePickercomponent adapter interfaceminDate={new Date()}on the Pay ScheduleanchorPayDatefield to prevent past date selectiondateToCalendarDateidentified by Copilot reviewResolves SDK-530
Partially addresses SDK-592 (Pay Schedule component only; Off-Cycle Pay Period and Contractor Payments still have hardcoded copy)
What changed
SDK-530: Disable past dates (3 files)
DatePickerTypes.tsminDate?: Date,maxDate?: Date,isDateDisabled?: (date: Date) => booleanDatePicker.tsxminDate→minValue,maxDate→maxValue,isDateDisabled→isDateUnavailableinternally for react-aria. Uses local date extraction to avoid UTC shift.Edit.tsxminDate={new Date()}on theanchorPayDateDatePickerFieldSDK-592: Dynamic processing time copy (4 files)
usePaySchedule.tspaymentSpeed?: stringtoPayScheduleContextTypePaySchedule.tsxusePaymentConfigsGet()(same hook used in Contractor/Payroll domains) and passes it through contextEdit.tsxcountparameter to i18n translation based onpaymentSpeed(extracts number from "1-day"/"2-day"/"4-day"), defaults to 2Company.PaySchedule.json_one/_other) using{{count}}interpolationCopilot feedback: UTC timezone fix (1 file)
DatePicker.tsxdateToCalendarDatenow uses local date methods (getFullYear/getMonth/getDate) instead offormatDateToStringDate(which usestoISOString()/UTC). Also returnsundefinedfor invalid dates instead of throwing.Auto-generated
component-inventory.mdi18next.d.tsComponent adapter considerations
New
DatePickerPropsfollow ecosystem conventions (surveyed MUI, Ant Design, react-datepicker, react-aria, shadcn/ui, Chakra UI):minDate/maxDatewith native JSDatetype — most common naming convention, no@internationalized/datedependency for partnersisDateDisabled— combines react-aria's predicate pattern with universal "disabled" terminologyDesign decisions
minDate) is sufficient. Adding schema validation would require converting to a dynamic schema factory for i18n support, which is disproportionate complexity.minDateenforcement for processing buffer — We considered settingminDateto account for ACH processing days but decided this is better enforced server-side via API validation rather than client-side in the SDK.formatDateToStringDate—dateToCalendarDateusesgetFullYear()/getMonth()/getDate()instead of the sharedformatDateToStringDateutility because the utility usestoISOString()(UTC), which can shift the date by one day in non-UTC timezones. This was flagged by Copilot and verified locally.Test plan
anchorEndOfPayPeriod(First Pay Period End Date) is NOT affectednpm run test -- --run🤖 Generated with Claude Code