Skip to content

Commit 674909c

Browse files
wip fixing journey back functionality for DateTimePicker
1 parent 18851fb commit 674909c

File tree

4 files changed

+134
-37
lines changed

4 files changed

+134
-37
lines changed

projects/ccd-case-ui-toolkit/src/lib/components/form/date-input/date-input.component.ts

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core';
22
import { AbstractControl, ControlValueAccessor, FormControl, NG_VALIDATORS, NG_VALUE_ACCESSOR, ValidationErrors, Validator } from '@angular/forms';
3+
import moment from 'moment';
34

45
@Component({
56
selector: 'cut-date-input',
@@ -71,17 +72,32 @@ export class DateInputComponent implements ControlValueAccessor, Validator, OnIn
7172
public writeValue(obj: string): void { // 2018-04-09T08:02:27.542
7273
if (obj) {
7374
this.rawValue = this.removeMilliseconds(obj);
74-
// needs to handle also partial dates, e.g. -05-2016 (missing day)
75-
const [datePart, timePart] = this.rawValue.split('T');
76-
const dateValues = datePart.split('-');
77-
this.year = this.displayYear = dateValues[0] || '';
78-
this.month = this.displayMonth = dateValues[1] || '';
79-
this.day = this.displayDay = dateValues[2] || '';
80-
if (timePart) {
81-
const timeParts = timePart.split(':');
82-
this.hour = this.displayHour = timeParts[0] || '';
83-
this.minute = this.displayMinute = timeParts[1] || '';
84-
this.second = this.displaySecond = timeParts[2] || '';
75+
76+
// for DateTime fields, convert from UTC to local time for display
77+
if (this.isDateTime && this.rawValue.includes('T')) {
78+
const utcMoment = moment.utc(this.rawValue);
79+
const localMoment = utcMoment.local();
80+
81+
this.year = this.displayYear = localMoment.format('YYYY');
82+
this.month = this.displayMonth = localMoment.format('MM');
83+
this.day = this.displayDay = localMoment.format('DD');
84+
this.hour = this.displayHour = localMoment.format('HH');
85+
this.minute = this.displayMinute = localMoment.format('mm');
86+
this.second = this.displaySecond = localMoment.format('ss');
87+
} else {
88+
// for Date fields (no time), parse normally
89+
// needs to handle also partial dates, e.g. -05-2016 (missing day)
90+
const [datePart, timePart] = this.rawValue.split('T');
91+
const dateValues = datePart.split('-');
92+
this.year = this.displayYear = dateValues[0] || '';
93+
this.month = this.displayMonth = dateValues[1] || '';
94+
this.day = this.displayDay = dateValues[2] || '';
95+
if (timePart) {
96+
const timeParts = timePart.split(':');
97+
this.hour = this.displayHour = timeParts[0] || '';
98+
this.minute = this.displayMinute = timeParts[1] || '';
99+
this.second = this.displaySecond = timeParts[2] || '';
100+
}
85101
}
86102
}
87103
}
@@ -227,7 +243,14 @@ export class DateInputComponent implements ControlValueAccessor, Validator, OnIn
227243
this.minute ? this.pad(this.minute) : '',
228244
this.second ? this.pad(this.second) : ''
229245
].join(':');
230-
return `${date}T${time}.000`;
246+
const localDateTimeString = `${date}T${time}.000`;
247+
248+
// convert from local time to UTC for storage
249+
const localMoment = moment(localDateTimeString);
250+
const utcMoment = localMoment.utc();
251+
252+
// return in the expected format
253+
return utcMoment.format('YYYY-MM-DDTHH:mm:ss.000');
231254
} else {
232255
return date;
233256
}
Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
1-
import { Component } from '@angular/core';
1+
import { Component, OnInit } from '@angular/core';
22
import { AbstractFieldReadComponent } from '../base-field/abstract-field-read.component';
33

44
@Component({
55
selector: 'ccd-read-date-field',
6-
template: `<span class="text-16">{{caseField.value | ccdDate:'utc':caseField.dateTimeDisplayFormat}}</span>`
6+
template: `<span class="text-16">{{caseField.value | ccdDate:timeZone:caseField.dateTimeDisplayFormat}}</span>`
77
})
8-
export class ReadDateFieldComponent extends AbstractFieldReadComponent {
8+
export class ReadDateFieldComponent extends AbstractFieldReadComponent implements OnInit{
99
public timeZone = 'utc';
10+
11+
public ngOnInit(): void {
12+
super.ngOnInit();
13+
if (this.caseField?.field_type.id === 'DateTime') {
14+
this.timeZone = 'local';
15+
}
16+
}
1017
}

projects/ccd-case-ui-toolkit/src/lib/shared/components/palette/datetime-picker/datetime-picker.component.html

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,27 @@
11
<div class="govuk-form-group bottom-30" [id]="caseField.id"
2-
[ngClass]="{'form-group-error': dateControl && !dateControl.valid && dateControl.dirty}">
2+
[ngClass]="{'form-group-error': localDisplayControl && !localDisplayControl.valid && localDisplayControl.dirty}">
33
<fieldset>
44
<legend>
55
<span class="form-label" *ngIf="caseField.label">{{(caseField | ccdFieldLabel)}}</span>
66
<span class="form-hint" *ngIf="caseField.hint_text">{{caseField.hint_text | rpxTranslate}}</span>
77
<span class="error-message"
8-
*ngIf="dateControl && dateControl.errors && dateControl.dirty && !(minError || maxError)">{{(dateControl.errors | ccdFirstError:caseField.label)}}</span>
8+
*ngIf="localDisplayControl && localDisplayControl.errors && localDisplayControl.dirty && !(minError || maxError)">{{(localDisplayControl.errors | ccdFirstError:caseField.label)}}</span>
99
<span class="error-message"
10-
*ngIf="dateControl && dateControl.dirty && minError">{{'This date is older than the minimum date allowed' | rpxTranslate}}</span>
10+
*ngIf="localDisplayControl && localDisplayControl.dirty && minError">{{'This date is older than the minimum date allowed' | rpxTranslate}}</span>
1111
<span class="error-message"
12-
*ngIf="dateControl && dateControl.dirty && maxError">{{'This date is later than the maximum date allowed' | rpxTranslate}}</span>
12+
*ngIf="localDisplayControl && localDisplayControl.dirty && maxError">{{'This date is later than the maximum date allowed' | rpxTranslate}}</span>
1313
</legend>
1414
<div class="datepicker-container">
1515
<input class="govuk-input"
1616
#input
1717
attr.aria-label="Please enter a date and time in the format | rpxTranslate {{dateTimeEntryFormat}}"
1818
[min]="minDate(caseField)"
1919
[max]="maxDate(caseField)"
20-
[formControl]="dateControl"
20+
[formControl]="localDisplayControl"
2121
[ngxMatDatetimePicker]="picker"
2222
(focusin)="focusIn()"
2323
(focusout)="focusOut()"
2424
(dateChange)="valueChanged()"
25-
ng-model-options="{timezone:'utc'}"
2625
>
2726
<mat-datepicker-toggle matSuffix [for]="picker" id="pickerOpener"></mat-datepicker-toggle>
2827
<ngx-mat-datetime-picker #picker

projects/ccd-case-ui-toolkit/src/lib/shared/components/palette/datetime-picker/datetime-picker.component.ts

Lines changed: 84 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ import { CUSTOM_MOMENT_FORMATS } from './datetime-picker-utils';
2828
useClass: NgxMatMomentAdapter,
2929
deps: [MAT_LEGACY_DATE_LOCALE, NGX_MAT_MOMENT_DATE_ADAPTER_OPTIONS]
3030
},
31-
{ provide: NGX_MAT_MOMENT_DATE_ADAPTER_OPTIONS, useValue: { useUtc: true } }
31+
// { provide: NGX_MAT_MOMENT_DATE_ADAPTER_OPTIONS, useValue: { useUtc: true } }
32+
{ provide: NGX_MAT_MOMENT_DATE_ADAPTER_OPTIONS, useValue: { useUtc: false } }
3233
]
3334
})
3435

@@ -58,6 +59,8 @@ export class DatetimePickerComponent extends AbstractFormFieldComponent implemen
5859

5960
@Input() public dateControl: FormControl = new FormControl(new Date());
6061

62+
public localDisplayControl: FormControl;
63+
6164
private minimumDate = new Date('01/01/1800');
6265
private maximumDate = null;
6366
private momentFormat = 'YYYY-MM-DDTHH:mm:ss.SSS';
@@ -70,21 +73,67 @@ export class DatetimePickerComponent extends AbstractFormFieldComponent implemen
7073
public ngOnInit(): void {
7174
this.dateTimeEntryFormat = this.formatTranslationService.showOnlyDates(this.caseField.dateTimeEntryFormat);
7275
this.configureDatePicker(this.dateTimeEntryFormat);
73-
// set date control based on mandatory field
76+
77+
const existingControl = (this.parent || this.formGroup)?.controls?.[this.caseField.id];
78+
console.log('[DatetimePicker] ngOnInit for field:', this.caseField.id);
79+
console.log('[DatetimePicker] caseField.value:', this.caseField.value);
80+
console.log('[DatetimePicker] existingControl exists:', !!existingControl);
81+
console.log('[DatetimePicker] existingControl.value:', existingControl?.value);
82+
83+
// register the main control with parent form (will return existing control if present)
84+
// for when we're navigating back to an existing form
7485
this.dateControl = (this.caseField.isMandatory ?
7586
this.registerControl(new FormControl(this.caseField.value || '', [Validators.required]))
7687
: this.registerControl(new FormControl(this.caseField.value))) as FormControl;
88+
89+
console.log('[DatetimePicker] After registerControl, dateControl.value:', this.dateControl.value);
90+
91+
// after registerControl, use dateControl.value as the source of truth
92+
// (it will have the existing value if navigating back, or caseField.value if new)
93+
let initialUtcValue = this.dateControl.value;
94+
let initialLocalValue: string;
95+
96+
// for DateTime fields, convert UTC to local for display
97+
if (initialUtcValue && this.caseField.field_type.type === 'DateTime') {
98+
const utcMoment = moment.utc(initialUtcValue);
99+
const localMoment = utcMoment.local();
100+
initialLocalValue = localMoment.format('YYYY-MM-DDTHH:mm:ss.SSS');
101+
console.log('[DatetimePicker] Converted UTC to local:', initialUtcValue, '->', initialLocalValue);
102+
} else {
103+
initialLocalValue = initialUtcValue || '';
104+
}
105+
106+
// create local display control with correct initial value
107+
this.localDisplayControl = new FormControl(initialLocalValue);
108+
console.log('[DatetimePicker] localDisplayControl initialized with:', initialLocalValue);
109+
110+
// sync local display control to main control with UTC conversion
111+
this.localDisplayControl.valueChanges.subscribe(localValue => {
112+
if (this.caseField.field_type.type === 'DateTime' && localValue) {
113+
const parsedLocal = moment(localValue, this.momentFormat);
114+
if (parsedLocal.isValid()) {
115+
const utcValue = parsedLocal.utc().format(this.momentFormat);
116+
this.dateControl.setValue(utcValue, { emitEvent: false });
117+
} else {
118+
this.dateControl.setValue(localValue, { emitEvent: false });
119+
}
120+
} else {
121+
this.dateControl.setValue(localValue, { emitEvent: false });
122+
}
123+
});
124+
125+
// sync validation errors from local control to main control
126+
this.localDisplayControl.statusChanges.subscribe(() => {
127+
this.minError = this.localDisplayControl.hasError('matDatetimePickerMin');
128+
this.maxError = this.localDisplayControl.hasError('matDatetimePickerMax');
129+
});
130+
77131
// in resetting the format just after the page initialises, the input can be reformatted
78132
// otherwise the last format given will be how the text shown will be displayed
79133
setTimeout(() => {
80134
this.setDateTimeFormat();
81135
this.formatValueAndSetErrors();
82136
}, 1000);
83-
// when the status changes check that the maximum/minimum date has not been exceeded
84-
this.dateControl.statusChanges.subscribe(() => {
85-
this.minError = this.dateControl.hasError('matDatetimePickerMin');
86-
this.maxError = this.dateControl.hasError('matDatetimePickerMax');
87-
});
88137
}
89138

90139
public setDateTimeFormat(): void {
@@ -182,16 +231,16 @@ export class DatetimePickerComponent extends AbstractFormFieldComponent implemen
182231

183232
public yearSelected(event: Moment): void {
184233
if (this.startView === 'multi-year' && this.yearSelection) {
185-
this.dateControl.patchValue(event.toISOString());
234+
this.localDisplayControl.patchValue(event.toISOString());
186235
this.datetimePicker.close();
187236
this.valueChanged();
188237
}
189238
}
190239

191240
public monthSelected(event: Moment): void {
192241
if (this.startView === 'multi-year') {
193-
this.dateControl.patchValue(event.toISOString());
194-
this.dateControl.patchValue(event.toISOString());
242+
this.localDisplayControl.patchValue(event.toISOString());
243+
this.localDisplayControl.patchValue(event.toISOString());
195244
this.datetimePicker.close();
196245
this.valueChanged();
197246
}
@@ -200,19 +249,38 @@ export class DatetimePickerComponent extends AbstractFormFieldComponent implemen
200249
private formatValueAndSetErrors(): void {
201250
if (this.inputElement.nativeElement.value) {
202251
let formValue = this.inputElement.nativeElement.value;
203-
formValue = moment(formValue, this.dateTimeEntryFormat).format(this.momentFormat);
204-
if (formValue !== 'Invalid date') {
205-
// if not invalid set the value as the formatted value
206-
this.dateControl.setValue(formValue);
252+
const parsedMoment = moment(formValue, this.dateTimeEntryFormat);
253+
254+
if (parsedMoment.isValid()) {
255+
// format the value in local time
256+
// localDisplayControl will auto-sync to dateControl with UTC conversion
257+
formValue = parsedMoment.format(this.momentFormat);
258+
this.localDisplayControl.setValue(formValue);
207259
} else {
208260
// ensure that the datepicker picks up the invalid error
209261
const keepErrorText = this.inputElement.nativeElement.value;
262+
this.localDisplayControl.setValue(keepErrorText);
210263
this.dateControl.setValue(keepErrorText);
211264
this.inputElement.nativeElement.value = keepErrorText;
212265
}
213266
} else {
214-
// ensure required errors are picked up if relevant
215-
this.dateControl.setValue('');
267+
// input is empty - check if we need to sync from control values
268+
if (this.localDisplayControl.value) {
269+
// control has a value but input doesn't - this happens when navigating back
270+
// manually sync the control value to the input element
271+
console.log('[DatetimePicker] formatValueAndSetErrors: Input empty but control has value, syncing:', this.localDisplayControl.value);
272+
const controlValue = this.localDisplayControl.value;
273+
const parsedMoment = moment(controlValue, this.momentFormat);
274+
if (parsedMoment.isValid()) {
275+
// format according to the display format and update the input
276+
const formattedValue = parsedMoment.format(this.dateTimeEntryFormat);
277+
this.inputElement.nativeElement.value = formattedValue;
278+
console.log('[DatetimePicker] Set input value to:', formattedValue);
279+
}
280+
} else if (!this.dateControl.value) {
281+
this.localDisplayControl.setValue('');
282+
this.dateControl.setValue('');
283+
}
216284
}
217285
}
218286
}

0 commit comments

Comments
 (0)