Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<div class="flex flex-col items-center w-full">
<div class="flex flex-row gap-3 w-full">
<span class="text-sm">{{ dateMin | date : 'yyyy-MM-dd' }}</span>
<input
class="w-full"
type="range"
[min]="0"
[max]="max"
[step]="1"
[(ngModel)]="rangeValue"
(input)="onRangeChange()"
/>
<span class="text-sm">{{ dateMax | date : 'yyyy-MM-dd' }}</span>
</div>
<div *ngIf="data">
<span class="text-sm font-semibold">{{ data }}</span>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -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<DateRangeComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [DateRangeComponent],
}).compileComponents();

fixture = TestBed.createComponent(DateRangeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -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<string>();
/** 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);
}
}
126 changes: 126 additions & 0 deletions libs/shared/src/lib/survey/components/daterange.ts
Original file line number Diff line number Diff line change
@@ -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',
'<svg xmlns="http://www.w3.org/2000/svg" height="18px" viewBox="0 0 24 24" width="18px"><path d="M0 0h24v24H0z" fill="none"/><path d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V10h14v9zm0-11H5V5h14v3z"/><path d="M7 12h2v2H7zm4 0h2v2h-2zm4 0h2v2h-2z"/></svg>'
);

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);
};
7 changes: 7 additions & 0 deletions libs/shared/src/lib/survey/custom-question-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

/**
Expand All @@ -17,6 +18,7 @@ export enum CustomQuestionTypes {
OWNER = 'owner',
USERS = 'users',
GEO_SPATIAL = 'geoSpatial',
DATE_RANGE = 'dateRange',
}

/** Custom question options */
Expand Down Expand Up @@ -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);
},
};