From bd94bb16a37c6e23487ddf65a7f19ac65dd8f5b3 Mon Sep 17 00:00:00 2001 From: jason-evans-genesys <127438502+jason-evans-genesys@users.noreply.github.com> Date: Fri, 6 Sep 2024 10:54:54 -0400 Subject: [PATCH] feat(time-picker): Added min and max props MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ✅ Closes: COMUI-1736 feat(time-picker): Fixed up min/max logic ✅ Closes: COMUI-1736 feat(time-picker): Fixed up min/max logic to make it more functional ✅ Closes: COMUI-1736 test(time-picker): Added min and max tests ✅ Closes: COMUI-1736 test(time-picker): Fixed 12h test for min and max ✅ Closes: COMUI-1736 chore(time-picker): General refactoring ✅ Closes: COMUI-1736 test(time-picker): Fixed min/max tests ✅ Closes: COMUI-1736 chore(time-picker): Refactoring changes ✅ Closes: COMUI-1736 chore(time-picker): Fixed comment to be more descriptive ✅ Closes: COMUI-1736 test(time-picker): Fixed test title ✅ Closes: COMUI-1736 chore(time-picker): PR feedback ✅ Closes: COMUI-1736 chore(time-picker): Fixed hour conversion logic ✅ Closes: COMUI-1736 chore(time-picker): Variable name change ✅ Closes: COMUI-1736 chore(time-picker): Removed unneeded 12h clock type test ✅ Closes: COMUI-1736 chore(time-picker): Added tests and wrapped around boundary logic ✅ Closes: COMUI-1736 chore(time-picker): Conditional check before applying boundaries ✅ Closes: COMUI-1736 chore(time-picker): Fixed boundary logic to be inclusive of min and max ✅ Closes: COMUI-1736 --- .../stable/gux-time-picker/example.html | 21 +++++++ .../gux-time-picker.service.ts | 52 +++++++++++++++- .../gux-time-picker/gux-time-picker.tsx | 41 +++++++----- .../stable/gux-time-picker/readme.md | 2 + .../tests/gux-time-picker.service.spec.ts | 62 +++++++++++++++++++ 5 files changed, 162 insertions(+), 16 deletions(-) diff --git a/packages/genesys-spark-components/src/components/stable/gux-time-picker/example.html b/packages/genesys-spark-components/src/components/stable/gux-time-picker/example.html index 51750e4ff3..fb2529263f 100644 --- a/packages/genesys-spark-components/src/components/stable/gux-time-picker/example.html +++ b/packages/genesys-spark-components/src/components/stable/gux-time-picker/example.html @@ -45,6 +45,27 @@

Disabled

Has Error

+ +

Min of "03:15" and Max of "10:45" with 24 Hour Time Format

+ + +

+ Wrapped around boundaries: min of "20:30" and Max of "04:45" with 24 Hour + Time Format +

+

Languages

diff --git a/packages/genesys-spark-components/src/components/stable/gux-time-picker/gux-time-picker.service.ts b/packages/genesys-spark-components/src/components/stable/gux-time-picker/gux-time-picker.service.ts index debfc1a4ce..8867d5d58b 100644 --- a/packages/genesys-spark-components/src/components/stable/gux-time-picker/gux-time-picker.service.ts +++ b/packages/genesys-spark-components/src/components/stable/gux-time-picker/gux-time-picker.service.ts @@ -12,7 +12,9 @@ import { export function getTimeDisplayValues( minuteInterval: GuxMinuteInterval, - clockType: GuxClockType + clockType: GuxClockType, + min?: string, + max?: string ): GuxISOHourMinute[] { const minuteOptions = [0, 15, 30, 45] .filter(option => Number.isInteger(option / minuteInterval)) @@ -22,13 +24,59 @@ export function getTimeDisplayValues( ? ['12', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11'] : Array.from(Array(24).keys()).map(x => String(x).padStart(2, '0')); - return hourOptions.reduce((acc, hourOption) => { + const hourOptionsFormatted = hourOptions.reduce((acc, hourOption) => { return acc.concat( minuteOptions.map( minuteOption => `${hourOption}:${minuteOption}` ) as GuxISOHourMinute[] ); }, [] as GuxISOHourMinute[]); + + return clockType === '24h' + ? applyHourBoundaries(hourOptionsFormatted, min, max) + : hourOptionsFormatted; +} + +function applyHourBoundaries(hours: string[], min?: string, max?: string) { + // Check if min and max are wrapped around (e.g. min of 22:00 and max of 04:00) + if (min && max && hourToMilliseconds(min) > hourToMilliseconds(max)) { + hours = hours.filter(hour => { + const hourConverted = hourToMilliseconds(hour); + const minConverted = hourToMilliseconds(min); + const maxConverted = hourToMilliseconds(max); + const dayCeiling = hourToMilliseconds('23:59'); + + return ( + (hourConverted >= minConverted && hourConverted < dayCeiling) || + hourConverted <= maxConverted + ); + }); + } else { + // min and max are not wrapped around (e.g. min of 03:30 and max of 20:00) + if (min) { + hours = hours.filter( + hour => hourToMilliseconds(hour) >= hourToMilliseconds(min) + ); + } + if (max) { + hours = hours.filter( + hour => hourToMilliseconds(hour) <= hourToMilliseconds(max) + ); + } + } + + return hours as GuxISOHourMinute[]; +} + +function hourToMilliseconds(hour: string): number { + // Convert the hour to milliseconds from midnight + const date = new Date(); + const [hours, minutes] = hour.split(':'); + date.setHours(parseFloat(hours)); + date.setMinutes(parseFloat(minutes)); + date.setSeconds(0); + const seconds = date.getTime(); + return seconds; } export function getLocaleClockType(root: HTMLElement): GuxClockType { diff --git a/packages/genesys-spark-components/src/components/stable/gux-time-picker/gux-time-picker.tsx b/packages/genesys-spark-components/src/components/stable/gux-time-picker/gux-time-picker.tsx index ad925dfbed..2369b75d70 100644 --- a/packages/genesys-spark-components/src/components/stable/gux-time-picker/gux-time-picker.tsx +++ b/packages/genesys-spark-components/src/components/stable/gux-time-picker/gux-time-picker.tsx @@ -67,6 +67,12 @@ export class GuxTimePicker { @Prop({ mutable: true }) clockType: GuxClockType; + @Prop() + min?: string; + + @Prop() + max?: string; + @State() expanded: boolean = false; @@ -102,6 +108,10 @@ export class GuxTimePicker { this.i18n = await buildI18nForComponent(this.root, translationResources); this.clockType = this.clockType || getLocaleClockType(this.root); + + if (this.clockType == '12h' && (this.min || this.max)) { + console.error('clock type must be "24h" when using min/max props'); + } } private updateValue( @@ -343,20 +353,23 @@ export class GuxTimePicker { } private renderTimeListItems(): JSX.Element[] { - return getTimeDisplayValues(this.interval, this.clockType).map( - displayValue => { - const value = getValue(displayValue, this.clockType, isAm(this.value)); - - return ( - this.handleClickDropdownValue(displayValue)} - > - {displayValue} - - ) as JSX.Element; - } - ); + return getTimeDisplayValues( + this.interval, + this.clockType, + this.min, + this.max + ).map(displayValue => { + const value = getValue(displayValue, this.clockType, isAm(this.value)); + + return ( + this.handleClickDropdownValue(displayValue)} + > + {displayValue} + + ) as JSX.Element; + }); } private renderTarget(): JSX.Element { diff --git a/packages/genesys-spark-components/src/components/stable/gux-time-picker/readme.md b/packages/genesys-spark-components/src/components/stable/gux-time-picker/readme.md index 7f10575bff..7e4f538f17 100644 --- a/packages/genesys-spark-components/src/components/stable/gux-time-picker/readme.md +++ b/packages/genesys-spark-components/src/components/stable/gux-time-picker/readme.md @@ -13,6 +13,8 @@ | `disabled` | `disabled` | | `boolean` | `false` | | `hasError` | `has-error` | | `boolean` | `false` | | `interval` | `interval` | | `15 \| 30 \| 60` | `60` | +| `max` | `max` | | `string` | `undefined` | +| `min` | `min` | | `string` | `undefined` | | `required` | `required` | | `boolean` | `false` | | `step` | `step` | | `1 \| 10 \| 15 \| 20 \| 30 \| 5 \| 60` | `1` | | `value` | `value` | | ``${string}:${string}`` | `'00:00'` | diff --git a/packages/genesys-spark-components/src/components/stable/gux-time-picker/tests/gux-time-picker.service.spec.ts b/packages/genesys-spark-components/src/components/stable/gux-time-picker/tests/gux-time-picker.service.spec.ts index 11cd55404d..eddf387d36 100644 --- a/packages/genesys-spark-components/src/components/stable/gux-time-picker/tests/gux-time-picker.service.spec.ts +++ b/packages/genesys-spark-components/src/components/stable/gux-time-picker/tests/gux-time-picker.service.spec.ts @@ -331,6 +331,68 @@ describe('gux-time-picker.service', () => { ); }); + describe('#getTimeDisplayValues with set boundaries', () => { + it(`should work as expected for 24h with 30 minute intervals and boundaries of 08:30-20:00`, async () => { + const minuteInterval = 30; + const clockType = '24h'; + const min = '08:30'; + const max = '20:00'; + const expectedOutput = [ + '08:30', + '09:00', + '09:30', + '10:00', + '10:30', + '11:00', + '11:30', + '12:00', + '12:30', + '13:00', + '13:30', + '14:00', + '14:30', + '15:00', + '15:30', + '16:00', + '16:30', + '17:00', + '17:30', + '18:00', + '18:30', + '19:00', + '19:30', + '20:00' + ]; + + expect( + getTimeDisplayValues(minuteInterval, clockType, min, max) + ).toStrictEqual(expectedOutput); + }); + + it(`should work as expected for 24h with 30 minute intervals and wrapped around boundaries of 22:00-02:30`, async () => { + const minuteInterval = 30; + const clockType = '24h'; + const min = '22:00'; + const max = '02:30'; + const expectedOutput = [ + '00:00', + '00:30', + '01:00', + '01:30', + '02:00', + '02:30', + '22:00', + '22:30', + '23:00', + '23:30' + ]; + + expect( + getTimeDisplayValues(minuteInterval, clockType, min, max) + ).toStrictEqual(expectedOutput); + }); + }); + describe('#getLocaleClockType', () => { [ { locale: 'ar' },