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
-
+
`;
+exports[`ChangeHearingDetailsComponent Change Hearing Screen Rendering should render the session dropdown for a Crown hearing 1`] = `
+
+
+
+
+
+
+
+`;
+
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(