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);
+ },
};