diff --git a/src/app/court-calendar/change-hearing-details/components/__tests__/__snapshots__/change-hearing-details.component.spec.ts.snap b/src/app/court-calendar/change-hearing-details/components/__tests__/__snapshots__/change-hearing-details.component.spec.ts.snap index 217a8f7..7ba82a8 100644 --- a/src/app/court-calendar/change-hearing-details/components/__tests__/__snapshots__/change-hearing-details.component.spec.ts.snap +++ b/src/app/court-calendar/change-hearing-details/components/__tests__/__snapshots__/change-hearing-details.component.spec.ts.snap @@ -818,7 +818,10 @@ exports[`ChangeHearingDetailsComponent Change Hearing Screen Rendering should re > 28 January 2025

-
+ + + When autocomplete results are available, use the up and down arrows to review and Enter to select. Touch device users, explore options with swipe gestures. + + + +
+
+
+ + + + + + + + +
+

+ Start date +

+

+ 28 January 2025 +

+ + + +
+ + +
+
+
+
+ + + + + + + + + + + +
+ + +
+ + Start time + + +
+
+ + +
+
+ + +
+
+
+
+
+
+ + +
+ + Duration + + +
+
+ + +
+
+ + +
+
+
+
+
+
+
+
+
+
+ +
+ + Edit judiciary + + + +
+ + Judiciary + + +
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+ +
+ + +
+
+
+
+
+
+
+
+
+ + + + + + + + +
+ + Hearing language + + +
+ +
+ + +
+
+ +
+ + +
+
+
+
+
+
+
+ + +
+ + Video hearing + + +
+ +
+ + +
+
+ +
+ + +
+
+
+
+
+
+
+ + +
+ + Do you want to notify all relevant parties? + + +
+ +
+ + +
+
+ +
+ + +
+
+
+
+
+
+
+
+ + + Cancel + +
+ + + + +`; + exports[`ChangeHearingDetailsComponent Form Submission should emit correct payload when submitting a single-day hearing 1`] = ` { fixture.detectChanges(); expect(fixture).toMatchSnapshot(); }); + + it('should render the session dropdown for a Crown hearing', async () => { + mockFixtureInputs(fixture, { + selectedHearing: { + ...selectedHearing, + jurisdictionType: 'CROWN', + hearingDays: [ + { + ...selectedHearing.hearingDays[0], + courtScheduleId: 'court-schedule-id' + } + ] + }, + hearingSlots: [ + { + courtScheduleId: 'court-schedule-id', + businessType: 'test-business-type', + sessionStartTime: `${selectedHearing.startDate}T09:00:00.000Z`, + sessionEndTime: `${selectedHearing.startDate}T12:00:00.000Z` + } as HearingSlot + ] + }); + fixture.detectChanges(); + await fixture.whenStable(); + fixture.detectChanges(); + expect(fixture).toMatchSnapshot(); + }); }); describe('Jurisdiction type control routing', () => { diff --git a/src/app/court-calendar/change-hearing-details/components/change-hearing-details-crown-control/change-hearing-details-crown-control.component.html b/src/app/court-calendar/change-hearing-details/components/change-hearing-details-crown-control/change-hearing-details-crown-control.component.html index 5559cf5..3e1a53c 100644 --- a/src/app/court-calendar/change-hearing-details/components/change-hearing-details-crown-control/change-hearing-details-crown-control.component.html +++ b/src/app/court-calendar/change-hearing-details/components/change-hearing-details-crown-control/change-hearing-details-crown-control.component.html @@ -55,8 +55,17 @@

Start date

} @if (!multiDay.value) { + @if (hearingSlots()?.length > 0) { + + } } diff --git a/src/app/court-calendar/change-hearing-details/components/change-hearing-details-crown-control/change-hearing-details-crown-control.component.ts b/src/app/court-calendar/change-hearing-details/components/change-hearing-details-crown-control/change-hearing-details-crown-control.component.ts index 9ae8ff1..dc0722c 100644 --- a/src/app/court-calendar/change-hearing-details/components/change-hearing-details-crown-control/change-hearing-details-crown-control.component.ts +++ b/src/app/court-calendar/change-hearing-details/components/change-hearing-details-crown-control/change-hearing-details-crown-control.component.ts @@ -34,6 +34,8 @@ import { NonDefaultDaysComponent } from '../../../../shared/components/non-defau import { CourtCentre, HearingDay, JurisdictionType, NonDefaultDay } from '../../../../core'; import { ChangeHearingStartTimeControlsComponent } from '../change-hearing-start-time-controls.component'; import { DatePipe } from '@angular/common'; +import { HearingSlot } from '@cpp/scheduling'; +import { ChangeHearingSessionSelectorComponent } from '../change-hearing-session-selector.component'; const ERROR_MESSAGES: ErrorMessageConfig[] = [ { rule: 'required', message: 'Enter an end date' }, @@ -65,7 +67,8 @@ const ERROR_MESSAGES: ErrorMessageConfig[] = [ PdkCheckBox, PdkDateInput, PdkCore, - DatePipe + DatePipe, + ChangeHearingSessionSelectorComponent ], providers: [ { @@ -101,6 +104,9 @@ export class ChangeHearingDetailsCrownControlComponent implements ControlValueAc readonly duration = input(undefined); readonly jurisdictionType = input(undefined); readonly onValidationError = output(); + readonly hearingSlots = input([]); + readonly hearingCourtScheduleId = input(undefined); + timeToCompare: string; endDateControl: FormControl; errorMessages = ERROR_MESSAGES; dateRange: DateRange; diff --git a/src/app/court-calendar/change-hearing-details/components/change-hearing-details-mags-control/change-hearing-details-mags-control.component.html b/src/app/court-calendar/change-hearing-details/components/change-hearing-details-mags-control/change-hearing-details-mags-control.component.html index 4c0eb2d..f240745 100644 --- a/src/app/court-calendar/change-hearing-details/components/change-hearing-details-mags-control/change-hearing-details-mags-control.component.html +++ b/src/app/court-calendar/change-hearing-details/components/change-hearing-details-mags-control/change-hearing-details-mags-control.component.html @@ -12,34 +12,13 @@

End date

} @if (!isMultiDay) { - @if (sessionTimesOptions?.length > 0) { -
- - - - -
+ @if (hearingSlots()?.length > 0) { + } (undefined, { alias: 'startTime' }); readonly startDate = input(undefined); readonly endDate = input(undefined); @@ -41,22 +38,5 @@ export class ChangeHearingDetailsMagsControlComponent implements OnInit { get isMultiDay() { return this.startDate() !== this.endDate(); } - sessionTimesOptions: SelectOption[] = []; - datePipe = new DatePipe('en-GB'); timeToCompare: string; - - ngOnInit() { - const hearingSlots = this.hearingSlots(); - if (hearingSlots?.length > 0) { - this.sessionTimesOptions = hearingSlots - .map(({ courtScheduleId, sessionStartTime, sessionEndTime, businessType }) => ({ - value: courtScheduleId, - label: `${businessType} ${this.datePipe.transform( - sessionStartTime, - 'hh:mm a' - )} to ${this.datePipe.transform(sessionEndTime, 'hh:mm a')}` - })) - .sort(sortSelectOptionAlphabetical); - } - } } diff --git a/src/app/court-calendar/change-hearing-details/components/change-hearing-details.component.html b/src/app/court-calendar/change-hearing-details/components/change-hearing-details.component.html index 5860e26..b62c3c7 100644 --- a/src/app/court-calendar/change-hearing-details/components/change-hearing-details.component.html +++ b/src/app/court-calendar/change-hearing-details/components/change-hearing-details.component.html @@ -39,6 +39,8 @@ [selectedCourtCentre]="selectedCourtCentre()" [startTime]="initialValues()?.startTime" [duration]="initialValues()?.duration" + [hearingSlots]="hearingSlots()" + [hearingCourtScheduleId]="selectedHearing()?.hearingDays[0]?.courtScheduleId" [jurisdictionType]="selectedHearing()?.jurisdictionType" (onValidationError)="onValidationError.emit($event)" > diff --git a/src/app/court-calendar/change-hearing-details/components/change-hearing-session-selector.component.ts b/src/app/court-calendar/change-hearing-details/components/change-hearing-session-selector.component.ts new file mode 100644 index 0000000..57b25ae --- /dev/null +++ b/src/app/court-calendar/change-hearing-details/components/change-hearing-session-selector.component.ts @@ -0,0 +1,133 @@ +import { + ChangeDetectionStrategy, + Component, + computed, + effect, + forwardRef, + input, + untracked +} from '@angular/core'; +import { + ControlContainer, + ControlValueAccessor, + FormControl, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + NgForm, + ReactiveFormsModule, + ValidationErrors, + Validator +} from '@angular/forms'; +import { PdkForm, PdkSelectComponent, generateId, ErrorMessageConfig } from '@cpp/pdk'; +import { DatePipe } from '@angular/common'; +import { HearingSlot } from '@cpp/scheduling'; +import { SelectOption } from '@kolkov/angular-editor'; +import { sortSelectOptionAlphabetical } from '@cpp/reference-data'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { StartTimeBetweenSessionDirective } from './start-time-between-session-validator.directive'; + +const ERROR_MESSAGES: ErrorMessageConfig[] = [ + { rule: 'required', message: 'Select a session' }, + { rule: 'startTimeInvalid', message: 'The start time must be between valid session timings' } +]; + +@Component({ + selector: 'change-hearing-session-selector', + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [ReactiveFormsModule, PdkForm, PdkSelectComponent, StartTimeBetweenSessionDirective], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + multi: true, + useExisting: forwardRef(() => ChangeHearingSessionSelectorComponent) + }, + { + provide: NG_VALIDATORS, + multi: true, + useExisting: forwardRef(() => ChangeHearingSessionSelectorComponent) + } + ], + template: ` + + + + + `, + viewProviders: [ + { + provide: ControlContainer, + useExisting: NgForm + } + ], + styles: [ + ` + .timeDuration { + display: flex; + justify-content: space-between; + width: 600px; + } + ` + ] +}) +export class ChangeHearingSessionSelectorComponent implements ControlValueAccessor, Validator { + readonly hearingSlots = input([]); + readonly startTime = input(undefined); + readonly id = generateId('hearing-start-time-within-session-time'); + readonly control = new FormControl(null); + readonly errorMessages = ERROR_MESSAGES; + + private readonly datePipe = new DatePipe('en-GB'); + + readonly sessionTimesOptions = computed(() => + (this.hearingSlots() ?? []) + .map(({ courtScheduleId, sessionStartTime, sessionEndTime, businessType }) => ({ + value: courtScheduleId, + label: `${businessType} ${this.datePipe.transform( + sessionStartTime, + 'hh:mm a' + )} to ${this.datePipe.transform(sessionEndTime, 'hh:mm a')}` + })) + .sort(sortSelectOptionAlphabetical) + ); + + private propagateChange: (courtScheduleId: string) => void = () => {}; + private revalidate: () => void = () => {}; + + constructor() { + this.control.valueChanges + .pipe(takeUntilDestroyed()) + .subscribe(courtScheduleId => this.propagateChange(courtScheduleId)); + + effect(() => { + this.startTime(); + untracked(() => this.revalidate()); + }); + } + + writeValue(courtScheduleId: string): void { + this.control.setValue(courtScheduleId ?? null, { emitEvent: false }); + } + + registerOnChange(fn: (courtScheduleId: string) => void): void { + this.propagateChange = fn; + } + + registerOnTouched(): void {} + + registerOnValidatorChange(fn: () => void): void { + this.revalidate = fn; + } + + validate(): ValidationErrors | null { + this.control.updateValueAndValidity({ onlySelf: true, emitEvent: false }); + return this.control.errors; + } +} diff --git a/src/app/court-calendar/change-hearing-details/components/change-hearing-details-mags-control/start-time-between-session-validator.directive.ts b/src/app/court-calendar/change-hearing-details/components/start-time-between-session-validator.directive.ts similarity index 97% rename from src/app/court-calendar/change-hearing-details/components/change-hearing-details-mags-control/start-time-between-session-validator.directive.ts rename to src/app/court-calendar/change-hearing-details/components/start-time-between-session-validator.directive.ts index a45e486..b574593 100644 --- a/src/app/court-calendar/change-hearing-details/components/change-hearing-details-mags-control/start-time-between-session-validator.directive.ts +++ b/src/app/court-calendar/change-hearing-details/components/start-time-between-session-validator.directive.ts @@ -2,7 +2,7 @@ import { DatePipe } from '@angular/common'; import { Directive, effect, forwardRef, inject, input } from '@angular/core'; import { AbstractControl, NG_VALIDATORS, ValidationErrors, Validator } from '@angular/forms'; import { HearingSlot } from '@cpp/scheduling'; -import { CPPDate } from '../../../../core/util'; +import { CPPDate } from '../../../core/util'; @Directive({ selector: '[startTimeBetweenSession]', diff --git a/src/app/court-calendar/guards/get-sessions-for-selected-hearings.guard.ts b/src/app/court-calendar/guards/get-sessions-for-selected-hearings.guard.ts index 2bf1eb5..e68f5aa 100644 --- a/src/app/court-calendar/guards/get-sessions-for-selected-hearings.guard.ts +++ b/src/app/court-calendar/guards/get-sessions-for-selected-hearings.guard.ts @@ -7,7 +7,11 @@ import { getSelectedHearing } from '../state/selectors/court-calendar.selectors' import { ActivatedRouteSnapshot, CanActivateFn, Router } from '@angular/router'; import { ERROR_ROUTE_PATHS } from '@cpp/application'; -import { loadHearingSlotsSuccess, SearchHearingSlotsParams } from '@cpp/scheduling'; +import { + CrownSessionStatus, + loadHearingSlotsSuccess, + SearchHearingSlotsParams +} from '@cpp/scheduling'; import { getOrganisationUnits } from '@cpp/reference-data'; import { SchedulingService } from '@cpp/scheduling'; @@ -23,22 +27,22 @@ export const getSessionsForSeelectedHearingGuard: CanActivateFn = ( withLatestFrom(store.pipe(select(getOrganisationUnits))), take(1), switchMap(([selectedHearing, courtCentres]) => { - if (selectedHearing.jurisdictionType === 'CROWN') { - return of(true); - } const courtCentre = courtCentres.find(({ id }) => selectedHearing.courtCentreId === id); - const { jurisdictionType, hearingDayCount, hearingDays, startDate, endDate } = + const { jurisdictionType, hearingDayCount, hearingDays, startDate, endDate, courtRoomId } = selectedHearing; const isMultiday = !!hearingDayCount ? hearingDayCount > 1 : hearingDays.length > 1; + const isCrown = jurisdictionType === 'CROWN'; const slotsParams: SearchHearingSlotsParams & { isMultiday?: boolean } = { sessionStartDate: startDate, sessionEndDate: endDate, + courtRoomId, panel: 'ADULT,YOUTH', ouCode: courtCentre.oucode, pageSize: 500, pageNumber: 1, showOverbookedSlots: true, jurisdiction: jurisdictionType, + status: isCrown ? CrownSessionStatus.FINAL : undefined, isMultiday }; return scheduleService.searchHearingSlots(slotsParams).pipe(