Start rewriting op-single-date-picker from scratch

feature/42358-standardise-date-pickers
Benjamin Bädorf 2 years ago
parent 0cadc6cdad
commit c8bfaf98c1
No known key found for this signature in database
GPG Key ID: 069CA2D117AB5CCF
  1. 16
      frontend/src/app/features/work-packages/components/filters/filter-date-time-value/filter-date-time-value.component.html
  2. 8
      frontend/src/app/features/work-packages/openproject-work-packages.module.ts
  3. 2
      frontend/src/app/shared/components/datepicker/banner/datepicker-banner.component.ts
  4. 4
      frontend/src/app/shared/components/datepicker/scheduling-mode/datepicker-scheduling-toggle.component.ts
  5. 4
      frontend/src/app/shared/components/datepicker/single-date-form/single-date-form.component.html
  6. 11
      frontend/src/app/shared/components/datepicker/single-date-form/single-date-form.component.ts
  7. 89
      frontend/src/app/shared/components/datepicker/single-date-picker/single-date-picker.component.html
  8. 190
      frontend/src/app/shared/components/datepicker/single-date-picker/single-date-picker.component.ts
  9. 4
      frontend/src/app/shared/components/datepicker/toggle/datepicker-working-days-toggle.component.ts
  10. 17
      frontend/src/app/shared/shared.module.ts

@ -1,13 +1,15 @@
<!--op-single-date-picker
(changed)="value = isoDateParser($event)"
[value]="isoDateFormatter(value)"
[opAutofocus]="shouldFocus"
required="true"
<op-single-date-picker
[id]="'values-' + filter.id"
[name]="'v[' + filter.id + ']'"
required="true"
(valueChange)="value = isoDateParser($event)"
[value]="isoDateFormatter(value)"
[opAutofocus]="shouldFocus"
classes="advanced-filters--date-field"
size="10"
></op-single-date-picker-->
></op-single-date-picker>
<span
class="advanced-filters--tooltip-trigger -multiline"

@ -179,10 +179,6 @@ import isNewResource from 'core-app/features/hal/helpers/is-new-resource';
import { OpenprojectStoragesModule } from 'core-app/shared/components/storages/openproject-storages.module';
import { FileLinksResourceService } from 'core-app/core/state/file-links/file-links.service';
import { StoragesResourceService } from 'core-app/core/state/storages/storages.service';
import { DatepickerBannerComponent } from 'core-app/shared/components/datepicker/banner/datepicker-banner.component';
import { MultiDateModalComponent } from 'core-app/shared/components/datepicker/multi-date-modal/multi-date.modal';
import { DatepickerWorkingDaysToggleComponent } from 'core-app/shared/components/datepicker/toggle/datepicker-working-days-toggle.component';
import { DatepickerSchedulingToggleComponent } from 'core-app/shared/components/datepicker/scheduling-mode/datepicker-scheduling-toggle.component';
import { StorageFilesResourceService } from 'core-app/core/state/storage-files/storage-files.service';
@NgModule({
@ -393,10 +389,6 @@ import { StorageFilesResourceService } from 'core-app/core/state/storage-files/s
QuerySharingModalComponent,
SaveQueryModalComponent,
WpDestroyModalComponent,
MultiDateModalComponent,
DatepickerBannerComponent,
DatepickerWorkingDaysToggleComponent,
DatepickerSchedulingToggleComponent,
// CustomActions
WpCustomActionComponent,

@ -45,7 +45,7 @@ import { DateModalRelationsService } from 'core-app/shared/components/datepicker
templateUrl: './datepicker-banner.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DatepickerBannerComponent {
export class OpDatePickerBannerComponent {
@Input() scheduleManually = false;
hasRelations$ = this.dateModalRelations.hasRelations$;

@ -17,11 +17,11 @@ import {
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => DatepickerSchedulingToggleComponent),
useExisting: forwardRef(() => OpDatePickerSchedulingToggleComponent),
multi: true,
}],
})
export class DatepickerSchedulingToggleComponent implements ControlValueAccessor {
export class OpDatePickerSchedulingToggleComponent implements ControlValueAccessor {
text = {
scheduling: {
title: this.I18n.t('js.scheduling.manual'),

@ -1,5 +1,5 @@
<form
class="spot-modal op-datepicker-modal loading-indicator--location"
class="spot-container"
data-qa-selector="op-datepicker-modal"
[attr.id]="htmlId"
#modalContainer
@ -12,7 +12,7 @@
>
<op-datepicker-banner [scheduleManually]="scheduleManually"></op-datepicker-banner>
<div class="spot-modal--body spot-container">
<div class="spot-container">
<div class="op-datepicker-modal--toggle-actions-container">
<op-datepicker-scheduling-toggle
name="scheduleManually"

@ -41,17 +41,14 @@ import {
ViewEncapsulation,
} from '@angular/core';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { DatePicker } from 'core-app/shared/components/op-date-picker/datepicker';
import { DatePicker } from 'core-app/shared/components/datepicker/datepicker';
import { HalResourceEditingService } from 'core-app/shared/components/fields/edit/services/hal-resource-editing.service';
import { ResourceChangeset } from 'core-app/shared/components/fields/changeset/resource-changeset';
import { ConfigurationService } from 'core-app/core/config/configuration.service';
import { TimezoneService } from 'core-app/core/datetime/timezone.service';
import { DayElement } from 'flatpickr/dist/types/instance';
import flatpickr from 'flatpickr';
import {
debounce,
switchMap,
} from 'rxjs/operators';
import { debounce } from 'rxjs/operators';
import { activeFieldContainerClassName } from 'core-app/shared/components/fields/edit/edit-form/edit-form';
import {
Subject,
@ -73,7 +70,7 @@ import { UntilDestroyedMixin } from 'core-app/shared/helpers/angular/until-destr
@Component({
selector: 'op-single-date-form',
templateUrl: './single-date.modal.html',
templateUrl: './single-date-form.component.html',
styleUrls: ['../styles/datepicker.modal.sass', '../styles/datepicker_mobile.modal.sass'],
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
@ -235,7 +232,7 @@ export class OpSingleDateFormComponent extends UntilDestroyedMixin implements Af
mode: 'single',
showMonths: this.deviceService.isMobile ? 1 : 2,
inline: true,
onReady: (_date, _datestr, instance) => {
onReady: (_date:Date[], _datestr:string, instance:flatpickr.Instance) => {
instance.calendarContainer.classList.add('op-datepicker-modal--flatpickr-instance');
this.reposition(jQuery(this.modalContainer.nativeElement), jQuery(`.${activeFieldContainerClassName}`));
},

@ -0,0 +1,89 @@
<spot-drop-modal
[open]="isOpened"
(close)="close()"
>
<button
slot="trigger"
type="button"
class="button"
(click)="open()"
>{{ value || text.date }}</button>
<form
slot="body"
class="spot-container op-datepicker-modal"
data-qa-selector="op-datepicker-modal"
tabindex="0"
cdkFocusInitial
cdkTrapFocus
[cdkTrapFocusAutoCapture]="true"
(submit)="save($event)"
>
<spot-selector-field
[reverseLabel]="true"
[label]="text.ignoreNonWorkingDays.title"
>
<spot-switch
slot="input"
name="ignoreNonWorkingDays"
[(ngModel)]="ignoreNonWorkingDays"
data-qa-selector="op-datepicker-modal--include-non-working-days"
></spot-switch>
</spot-selector-field>
<spot-form-field
label="Name"
>
<spot-text-field
slot="input"
name="name"
class="op-datepicker-modal--date-field"
[(ngModel)]="entityName"
[showClearButton]="true"
></spot-text-field>
</spot-form-field>
<spot-form-field
[label]="text.date"
>
<spot-text-field
slot="input"
name="date"
class="op-datepicker-modal--date-field"
[ngModel]="value"
(ngModelChange)="writeValue($event)"
[showClearButton]="true"
></spot-text-field>
<button
slot="action"
type="button"
class="spot-link"
(click)="setToday()"
[textContent]="text.today">
</button>
</spot-form-field>
<input
id="flatpickr-input"
hidden
>
<div class="spot-action-bar">
<div class="spot-action-bar--right">
<button
type="button"
(click)="close()"
class="button spot-action-bar--action"
data-qa-selector="op-datepicker-modal--action"
[textContent]="text.cancel"
></button>
<button
type="submit"
class="button -highlight spot-action-bar--action"
data-qa-selector="op-datepicker-modal--action"
[textContent]="text.save"
></button>
</div>
</div>
</form>
</spot-drop-modal>

@ -0,0 +1,190 @@
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2022 the OpenProject GmbH
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2013 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See COPYRIGHT and LICENSE files for more details.
//++
import {
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
EventEmitter,
forwardRef,
Injector,
Input,
Output,
ViewEncapsulation,
} from '@angular/core';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import {
mappedDate,
onDayCreate,
parseDate,
setDates,
validDate,
} from 'core-app/shared/components/datepicker/helpers/date-modal.helpers';
import { TimezoneService } from 'core-app/core/datetime/timezone.service';
import { DatePicker } from '../datepicker';
import { DeviceService } from 'core-app/core/browser/device.service';
import flatpickr from 'flatpickr';
import { DayElement } from 'flatpickr/dist/types/instance';
@Component({
selector: 'op-single-date-picker',
templateUrl: './single-date-picker.component.html',
styleUrls: ['../styles/datepicker.modal.sass', '../styles/datepicker_mobile.modal.sass'],
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => OpSingleDatePickerComponent),
multi: true,
},
],
})
export class OpSingleDatePickerComponent implements ControlValueAccessor, AfterViewInit {
@Output('valueChange') valueChange = new EventEmitter();
@Input() value = '';
@Input() id = '';
@Input() name = '';
public workingDate:Date = new Date();
public isOpened = false;
public ignoreNonWorkingDays = false;
public datePickerInstance:DatePicker;
public entityName = '';
text = {
save: this.I18n.t('js.button_save'),
cancel: this.I18n.t('js.button_cancel'),
date: this.I18n.t('js.work_packages.properties.date'),
placeholder: this.I18n.t('js.placeholders.default'),
today: this.I18n.t('js.label_today'),
ignoreNonWorkingDays: {
title: this.I18n.t('js.work_packages.datepicker_modal.ignore_non_working_days.title'),
},
};
constructor(
readonly I18n:I18nService,
readonly timezoneService:TimezoneService,
readonly injector:Injector,
readonly deviceService:DeviceService,
readonly cdRef:ChangeDetectorRef,
) { }
ngAfterViewInit():void {
this.initializeDatepicker(this.workingDate);
}
open() {
this.isOpened = true;
}
close() {
this.isOpened = false;
}
save($event:Event) {
$event.preventDefault();
// Write value to outside first
this.close();
}
setToday():void {
const today = parseDate(new Date()) as Date;
this.workingDate = today;
this.enforceManualChangesToDatepicker(today);
}
private enforceManualChangesToDatepicker(enforceDate?:Date) {
const date = parseDate(this.workingDate || '');
setDates(date, this.datePickerInstance, enforceDate);
}
private initializeDatepicker(minimalDate?:Date|null) {
this.datePickerInstance?.destroy();
this.datePickerInstance = new DatePicker(
this.injector,
'#flatpickr-input',
this.workingDate || '',
{
mode: 'single',
showMonths: this.deviceService.isMobile ? 1 : 2,
inline: true,
onReady: (_date:Date[], _datestr:string, instance:flatpickr.Instance) => {
instance.calendarContainer.classList.add('op-datepicker-modal--flatpickr-instance');
},
onChange: (dates:Date[]) => {
if (dates.length > 0) {
this.writeValue(this.timezoneService.formattedISODate(dates[0]));
this.enforceManualChangesToDatepicker(dates[0]);
}
this.cdRef.detectChanges();
},
onDayCreate: (dObj:Date[], dStr:string, fp:flatpickr.Instance, dayElem:DayElement) => {
onDayCreate(
dayElem,
this.ignoreNonWorkingDays,
this.datePickerInstance?.weekdaysService.isNonWorkingDay(dayElem.dateObj),
minimalDate,
// this.dateModalScheduling.isDayDisabled(dayElem, minimalDate),
true,
);
},
},
null,
);
}
writeValue(value:string):void {
this.value = value;
this.workingDate = new Date(value);
this.valueChange.emit(value);
}
onChange = (_:string):void => {};
onTouched = (_:string):void => {};
registerOnChange(fn:(_:string) => void):void {
this.onChange = fn;
}
registerOnTouched(fn:(_:string) => void):void {
this.onTouched = fn;
}
}

@ -17,11 +17,11 @@ import {
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => DatepickerWorkingDaysToggleComponent),
useExisting: forwardRef(() => OpDatePickerWorkingDaysToggleComponent),
multi: true,
}],
})
export class DatepickerWorkingDaysToggleComponent implements ControlValueAccessor {
export class OpDatePickerWorkingDaysToggleComponent implements ControlValueAccessor {
@Input() ignoreNonWorkingDays:boolean;
@Input() disabled = false;

@ -68,7 +68,14 @@ import {
import { CopyToClipboardDirective } from './components/copy-to-clipboard/copy-to-clipboard.directive';
import { OpDateTimeComponent } from './components/date/op-date-time.component';
import { ToastComponent } from './components/toaster/toast.component';
import { OpSingleDateFormComponent } from './components/datepicker/single-date-form/single-date-form.component';
import { OpSingleDatePickerComponent } from './components/datepicker/single-date-picker/single-date-picker.component';
import { OpDatePickerBannerComponent } from './components/datepicker/banner/datepicker-banner.component';
import { MultiDateModalComponent } from 'core-app/shared/components/datepicker/multi-date-modal/multi-date.modal';
import { OpDatePickerWorkingDaysToggleComponent } from 'core-app/shared/components/datepicker/toggle/datepicker-working-days-toggle.component';
import { OpDatePickerSchedulingToggleComponent } from 'core-app/shared/components/datepicker/scheduling-mode/datepicker-scheduling-toggle.component';
import { ToastsContainerComponent } from './components/toaster/toasts-container.component';
import { UploadProgressComponent } from './components/toaster/upload-progress.component';
import { ResizerComponent } from './components/resizer/resizer.component';
@ -197,6 +204,11 @@ export function bootstrapModule(injector:Injector):void {
// Date pickers
OpSingleDateFormComponent,
OpSingleDatePickerComponent,
OpDatePickerBannerComponent,
MultiDateModalComponent,
OpDatePickerWorkingDaysToggleComponent,
OpDatePickerSchedulingToggleComponent,
],
providers: [
StaticQueriesService,
@ -254,6 +266,11 @@ export function bootstrapModule(injector:Injector):void {
// Date pickers
OpSingleDateFormComponent,
OpSingleDatePickerComponent,
OpDatePickerBannerComponent,
MultiDateModalComponent,
OpDatePickerWorkingDaysToggleComponent,
OpDatePickerSchedulingToggleComponent,
],
})
export class OpSharedModule {

Loading…
Cancel
Save