diff --git a/libs/shared/src/lib/survey/components/date-range/date-range.component.html b/libs/shared/src/lib/survey/components/date-range/date-range.component.html new file mode 100644 index 0000000000..d73d3ad0e3 --- /dev/null +++ b/libs/shared/src/lib/survey/components/date-range/date-range.component.html @@ -0,0 +1,18 @@ +
+
+ {{ dateMin | date : 'yyyy-MM-dd' }} + + {{ dateMax | date : 'yyyy-MM-dd' }} +
+
+ {{ data }} +
+
diff --git a/libs/shared/src/lib/survey/components/date-range/date-range.component.scss b/libs/shared/src/lib/survey/components/date-range/date-range.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libs/shared/src/lib/survey/components/date-range/date-range.component.spec.ts b/libs/shared/src/lib/survey/components/date-range/date-range.component.spec.ts new file mode 100644 index 0000000000..b25c4bf54a --- /dev/null +++ b/libs/shared/src/lib/survey/components/date-range/date-range.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DateRangeComponent } from './date-range.component'; + +describe('DateRangeComponent', () => { + let component: DateRangeComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [DateRangeComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(DateRangeComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/libs/shared/src/lib/survey/components/date-range/date-range.component.ts b/libs/shared/src/lib/survey/components/date-range/date-range.component.ts new file mode 100644 index 0000000000..eb4a698a44 --- /dev/null +++ b/libs/shared/src/lib/survey/components/date-range/date-range.component.ts @@ -0,0 +1,77 @@ +import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; // Required for ngModel +import { CommonModule } from '@angular/common'; +import { DatePipe } from '@angular/common'; + +/** + * Component for displaying the date range question + */ +@Component({ + selector: 'shared-date-range', + standalone: true, + imports: [CommonModule, FormsModule, ReactiveFormsModule], + templateUrl: './date-range.component.html', + styleUrls: ['./date-range.component.scss'], + providers: [DatePipe], +}) +export class DateRangeComponent implements OnInit { + /** Date minimum of the range */ + @Input() dateMin!: string; // Format: 'YYYY-MM-DD' + /** Date maximum of the range */ + @Input() dateMax!: string; // Format: 'YYYY-MM-DD' + /** Data to display on the date range */ + @Input() data!: string; + /** Output for the date change */ + @Output() dateChange = new EventEmitter(); + /** Max value for the range question */ + public max = 1; + /** Range question value */ + public rangeValue = 1; + + /** + * Component for displaying the date range question + * + * @param datePipe Angular date pipe + */ + constructor(private datePipe: DatePipe) {} + + ngOnInit() { + this.buildDateRange(); + } + + /** build date range question */ + private buildDateRange() { + const dateMinFormatted = new Date(this.dateMin); // Parse the ISO string to a Date object + const dateMinMilliseconds = dateMinFormatted.getTime(); // Get the time in milliseconds since the Unix epoch + const dateMinMinutes = Math.floor(dateMinMilliseconds / (1000 * 60)); // Convert milliseconds to minutes + + const dateMaxFormatted = new Date(this.dateMax); // Parse the ISO string to a Date object + const dateMaxMilliseconds = dateMaxFormatted.getTime(); // Get the time in milliseconds since the Unix epoch + const dateMaxMinutes = Math.floor(dateMaxMilliseconds / (1000 * 60)); // Convert milliseconds to minutes + + // set the max value for the range input taking account the window of dates in dateMin and dateMax + this.max = (dateMaxMinutes - dateMinMinutes) / (60 * 24); // convert to days + + if (this.data) { + const currentDateFormatted = new Date(this.data); // Parse the ISO string to a Date object + const currentDateMilliseconds = currentDateFormatted.getTime(); // Get the time in milliseconds since the Unix epoch + const currentDateMinutes = Math.floor( + currentDateMilliseconds / (1000 * 60) + ); // Convert milliseconds to minutes + // set the value in the range input too + this.rangeValue = (currentDateMinutes - dateMinMinutes) / (60 * 24); // convert to days + } + } + + /** On change range */ + public onRangeChange() { + // format date min + const dateMinFormatted = new Date(this.dateMin); + // add range to the date + dateMinFormatted.setDate(dateMinFormatted.getDate() + this.rangeValue); + // transform to the format yyyy-MM-dd + this.data = this.datePipe.transform(dateMinFormatted, 'yyyy-MM-dd') ?? ''; + // emit current value + this.dateChange.emit(this.data); + } +} diff --git a/libs/shared/src/lib/survey/components/daterange.ts b/libs/shared/src/lib/survey/components/daterange.ts new file mode 100644 index 0000000000..52edbe8658 --- /dev/null +++ b/libs/shared/src/lib/survey/components/daterange.ts @@ -0,0 +1,126 @@ +import { + ComponentCollection, + JsonMetadata, + Serializer, + SvgRegistry, +} from 'survey-core'; +import { Question, QuestionText } from '../types'; +import { DomService } from '../../services/dom/dom.service'; +import { CustomPropertyGridComponentTypes } from './utils/components.enum'; +import { registerCustomPropertyEditor } from './utils/component-register'; +import { DateRangeComponent } from './date-range/date-range.component'; + +/** + * Inits the geospatial component. + * + * @param domService DOM service. + * @param componentCollectionInstance ComponentCollection + * @param translateService Angular translate service + */ +export const init = ( + domService: DomService, + componentCollectionInstance: ComponentCollection +): void => { + // registers icon-daterange in the SurveyJS library + SvgRegistry.registerIconFromSvg( + 'icon-daterange', + '' + ); + + const component = { + name: 'daterange', + title: 'Date Range', + iconName: 'icon-daterange', + questionJSON: { + name: 'daterange', + type: 'text', + }, + category: 'Custom Questions', + onInit: (): void => { + const serializer: JsonMetadata = Serializer; + // min date + serializer.addProperty('daterange', { + name: 'dateMin', + type: CustomPropertyGridComponentTypes.dateTypeDisplayer, + category: 'Custom questions', + visibleIndex: 1, + isRequired: true, + onPropertyEditorUpdate: (obj: QuestionText, propertyEditor: any) => { + propertyEditor.inputType = 'date'; + }, + onSetValue: (obj: QuestionText, value: any) => { + obj.setPropertyValue('dateMin', value); + }, + }); + // max date + serializer.addProperty('daterange', { + name: 'dateMax', + type: CustomPropertyGridComponentTypes.dateTypeDisplayer, + category: 'Custom questions', + visibleIndex: 2, + isRequired: true, + onPropertyEditorUpdate: (obj: QuestionText, propertyEditor: any) => { + propertyEditor.inputType = 'date'; + }, + onSetValue: (obj: QuestionText, value: any) => { + obj.setPropertyValue('dateMax', value); + }, + }); + // register the editor for type "date" with kendo date picker + registerCustomPropertyEditor( + CustomPropertyGridComponentTypes.dateTypeDisplayer + ); + }, + /** + * Set default date min and date max + * + * @param question The current resource question + */ + onLoaded(question: Question): void { + const data = question.toJSON(); + if (!data.dateMin) { + question.dateMin = new Date(); + } + + if (!data.dateMax) { + question.dateMax = new Date(new Date().getTime() + 24 * 60 * 60 * 1000); // one day later before the current + question.update; + } + }, + onAfterRender: (question: Question, el: HTMLElement): void => { + // hides the input element + const element = el.getElementsByTagName('input')[0].parentElement; + if (element) element.style.display = 'none'; + + const data = question.toJSON(); + + // check if it has date min and date max before render + if (data.dateMin && data.dateMax) { + const dateMinMilliseconds = new Date(data.dateMin).getTime(); // Get the time in milliseconds since the Unix epoch + const dateMaxMilliseconds = new Date(data.dateMax).getTime(); // Get the time in milliseconds since the Unix epoch + + // check if date max is later than date min + if (dateMaxMilliseconds > dateMinMilliseconds) { + // render the DateRangeComponent + const daterange = domService.appendComponentToBody( + DateRangeComponent, + el + ); + const instance: DateRangeComponent = daterange.instance; + + instance.dateMin = data.dateMin; + instance.dateMax = data.dateMax; + + // inits the map with the value of the question + if (question.value) instance.data = question.value; + + // updates the question value when the range changes + instance.dateChange.subscribe((res) => { + question.value = res; + }); + } + } + }, + }; + componentCollectionInstance.add(component); +}; diff --git a/libs/shared/src/lib/survey/custom-question-types.ts b/libs/shared/src/lib/survey/custom-question-types.ts index 5cdacb6793..b52cc6fac9 100644 --- a/libs/shared/src/lib/survey/custom-question-types.ts +++ b/libs/shared/src/lib/survey/custom-question-types.ts @@ -6,6 +6,7 @@ import * as ResourcesComponent from './components/resources'; import * as OwnerComponent from './components/owner'; import * as UsersComponent from './components/users'; import * as GeospatialComponent from './components/geospatial'; +import * as DateRangeComponent from './components/daterange'; import { Apollo } from 'apollo-angular'; /** @@ -17,6 +18,7 @@ export enum CustomQuestionTypes { OWNER = 'owner', USERS = 'users', GEO_SPATIAL = 'geoSpatial', + DATE_RANGE = 'dateRange', } /** Custom question options */ @@ -56,4 +58,9 @@ export const InitCustomQuestionComponent: { const domService = injector.get(DomService); GeospatialComponent.init(domService, instance); }, + dateRange: (options) => { + const { injector, instance } = options; + const domService = injector.get(DomService); + DateRangeComponent.init(domService, instance); + }, };