Compare commits

...

3 Commits

  1. 4
      frontend/src/app/core/services/forms/forms.service.ts
  2. 4
      frontend/src/app/core/services/forms/typings.d.ts
  3. 10
      frontend/src/app/modules/common/dynamic-forms/components/dynamic-inputs/date-input/components/date-picker-control/date-picker-control.component.spec.ts
  4. 9
      frontend/src/app/modules/common/dynamic-forms/components/dynamic-inputs/date-input/components/date-picker-control/date-picker-control.component.ts
  5. 2
      frontend/src/app/modules/common/dynamic-forms/components/dynamic-inputs/date-input/date-input.component.html
  6. 1
      frontend/src/app/modules/common/dynamic-forms/components/dynamic-inputs/select-project-status-input/select-project-status-input.component.html
  7. 32
      frontend/src/app/modules/common/dynamic-forms/components/dynamic-inputs/select-project-status-input/select-project-status-input.component.ts
  8. 4
      frontend/src/app/modules/common/dynamic-forms/dynamic-forms.module.ts
  9. 45
      frontend/src/app/modules/common/dynamic-forms/services/dynamic-fields/dynamic-fields.service.ts

@ -86,8 +86,8 @@ export class FormsService {
// Form.payload resources have a HalLinkSource interface while
// API resource options have a IAllowedValue interface
const resourceValue = Array.isArray(resource) ?
resource.map(resourceElement => ({ href: resourceElement?.href || resourceElement?._links?.self?.href })) :
{ href: resource?.href || resource?._links?.self?.href };
resource.map(resourceElement => ({ href: resourceElement?.href || resourceElement?._links?.self?.href || null})) :
{ href: resource?.href || resource?._links?.self?.href || null };
return {
...result,

@ -95,10 +95,10 @@ interface IOPAttributeGroup {
}
interface IOPAllowedValue {
id:string;
id?:string;
name:string;
[key:string]:unknown;
_links:{
_links?:{
self:HalSource | IOPApiOption;
[key:string]:HalSource;
};

@ -1,20 +1,20 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DatePickerAdapterComponent } from './date-picker-adapter.component';
import { DatePickerControlComponent } from './date-picker-control.component';
xdescribe('DatePickerAdapterComponent', () => {
let component: DatePickerAdapterComponent;
let fixture: ComponentFixture<DatePickerAdapterComponent>;
let component: DatePickerControlComponent;
let fixture: ComponentFixture<DatePickerControlComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ DatePickerAdapterComponent ]
declarations: [ DatePickerControlComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(DatePickerAdapterComponent);
fixture = TestBed.createComponent(DatePickerControlComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

@ -1,4 +1,4 @@
import { AfterViewInit, ChangeDetectorRef, Component, forwardRef, NgZone } from '@angular/core';
import { AfterViewInit, ChangeDetectorRef, Component, forwardRef, Input, NgZone } from '@angular/core';
import { OpDatePickerComponent } from "core-app/modules/common/op-date-picker/op-date-picker.component";
import { TimezoneService } from "core-components/datetime/timezone.service";
import * as moment from "moment";
@ -10,12 +10,15 @@ import { NG_VALUE_ACCESSOR } from "@angular/forms";
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => DatePickerAdapterComponent),
useExisting: forwardRef(() => DatePickerControlComponent),
multi: true
}
]
})
export class DatePickerAdapterComponent extends OpDatePickerComponent implements AfterViewInit {
export class DatePickerControlComponent extends OpDatePickerComponent implements AfterViewInit {
// Avoid Angular warning (It looks like you're using the disabled attribute with a reactive form directive...)
@Input('disable') disabled:boolean;
onControlChange = (_:any) => { }
onControlTouch = () => { }

@ -1,5 +1,5 @@
<op-date-picker-adapter [required]="to.required"
[disabled]="to.disabled"
[disable]="to.disabled"
[formControl]="formControl"
[formlyAttributes]="field">
</op-date-picker-adapter>

@ -1,5 +1,4 @@
<ng-select [items]="to?.options | async"
[(ngModel)]="currentStatus"
[formControl]="formControl"
[formlyAttributes]="field"
bindLabel="name"

@ -1,42 +1,12 @@
import { Component } from '@angular/core';
import { FieldType } from "@ngx-formly/core";
import { projectStatusCodeCssClass, projectStatusI18n } from "core-app/modules/fields/helpers/project-status-helper";
import { Observable } from 'rxjs';
import { I18nService } from "core-app/modules/common/i18n/i18n.service";
import { projectStatusCodeCssClass } from "core-app/modules/fields/helpers/project-status-helper";
@Component({
selector: 'op-select-project-status-input',
templateUrl: './select-project-status-input.component.html'
})
export class SelectProjectStatusInputComponent extends FieldType {
constructor (
private I18n:I18nService
) { super() }
defaultValue = {
id: 'not_set',
name: projectStatusI18n('not_set', this.I18n),
_links: {
self: {
href: null
}
}
}
// This and the ngModel is only necessary so that the
// default value can be set if no value has been set on the model before.
currentStatus = this.defaultValue;
ngOnInit() {
if (this.model._links.status !== null) {
this.currentStatus = this.model._links.status;
}
(this.to.options as Observable<any>).subscribe(values => {
values.unshift(this.defaultValue)
})
}
cssClass(item:any) {
return projectStatusCodeCssClass(item.id)
}

@ -14,7 +14,7 @@ import { SelectProjectStatusInputComponent } from "./components/dynamic-inputs/s
import { NgOptionHighlightModule } from "@ng-select/ng-option-highlight";
import { BooleanInputComponent } from './components/dynamic-inputs/boolean-input/boolean-input.component';
import { DateInputComponent } from './components/dynamic-inputs/date-input/date-input.component';
import { DatePickerAdapterComponent } from './components/dynamic-inputs/date-input/components/date-picker-adapter/date-picker-adapter.component';
import { DatePickerControlComponent } from './components/dynamic-inputs/date-input/components/date-picker-control/date-picker-control.component';
import { FormattableTextareaInputComponent } from './components/dynamic-inputs/formattable-textarea-input/formattable-textarea-input.component';
import { OpenprojectEditorModule } from "core-app/modules/editor/openproject-editor.module";
import { FormattableControlComponent } from './components/dynamic-inputs/formattable-textarea-input/components/formattable-control/formattable-control.component';
@ -68,7 +68,7 @@ import { InviteUserButtonModule } from "core-app/modules/invite-user-modal/butto
IntegerInputComponent,
TextInputComponent,
DateInputComponent,
DatePickerAdapterComponent,
DatePickerControlComponent,
SelectInputComponent,
SelectProjectStatusInputComponent,
FormattableTextareaInputComponent,

@ -8,6 +8,8 @@ import { FormlyFieldConfig } from "@ngx-formly/core";
import { of } from "rxjs";
import { map } from "rxjs/operators";
import { HttpClient } from "@angular/common/http";
import { projectStatusI18n } from "core-app/modules/fields/helpers/project-status-helper";
import { I18nService } from "core-app/modules/common/i18n/i18n.service";
@Injectable()
@ -36,7 +38,7 @@ export class DynamicFieldsService {
type: 'integerInput',
templateOptions: {
type: 'number',
locale: I18n.locale,
locale: this.I18n.locale,
},
},
useForFields: ['Integer', 'Float']
@ -72,7 +74,7 @@ export class DynamicFieldsService {
type: 'selectInput',
templateOptions: {
type: 'number',
locale: I18n.locale,
locale: this.I18n.locale,
bindLabel: 'name',
searchable: true,
virtualScroll: true,
@ -80,7 +82,7 @@ export class DynamicFieldsService {
clearSearchOnAdd: false,
hideSelected: false,
text: {
add_new_action: I18n.t('js.label_create'),
add_new_action: this.I18n.t('js.label_create'),
},
},
expressionProperties: {
@ -97,10 +99,13 @@ export class DynamicFieldsService {
type: 'selectProjectStatusInput',
templateOptions: {
type: 'number',
locale: I18n.locale,
locale: this.I18n.locale,
bindLabel: 'name',
searchable: true,
},
defaultValue: {
name: projectStatusI18n('not_set', this.I18n),
},
expressionProperties: {
'templateOptions.clearable': (model:any, formState:any, field:FormlyFieldConfig) => !field.templateOptions?.required,
},
@ -112,7 +117,8 @@ export class DynamicFieldsService {
];
constructor(
private _httpClient:HttpClient,
private httpClient:HttpClient,
private I18n:I18nService,
) {
}
@ -135,7 +141,14 @@ export class DynamicFieldsService {
}
getFormattedFieldsModel(formModel:IOPFormModel = {}):IOPFormModel {
const { _links: resourcesModel, _meta: metaModel, ...otherElementsModel } = formModel;
const { _links: resourcesModel, _meta: metaModel, ...otherElements } = formModel;
const otherElementsModel = Object.keys(otherElements).reduce((model, key) => {
if (![null, undefined, ''].includes(otherElements[key])) {
model = {...model, [key]: otherElements[key]}
}
return model;
}, {})
const model = {
...otherElementsModel,
@ -194,7 +207,7 @@ export class DynamicFieldsService {
result = {
...result,
[resourceKey]: resourceModel,
...![null, undefined, ''].includes(resourceModel) && {[resourceKey]: resourceModel},
};
return result;
@ -270,19 +283,20 @@ export class DynamicFieldsService {
private getFieldOptions(field:IOPFieldSchemaWithKey) {
const allowedValues = field._embedded?.allowedValues || field._links?.allowedValues;
let options;
if (!allowedValues) {
return;
}
if (Array.isArray(allowedValues)) {
const options = allowedValues[0]?._links?.self?.title ?
const optionValues = allowedValues[0]?._links?.self?.title ?
this.formatAllowedValues(allowedValues) :
allowedValues;
return of(options);
options = of(optionValues);
} else if (allowedValues!.href) {
return this._httpClient
options = this.httpClient
.get(allowedValues!.href!)
.pipe(
map((response:api.v3.Result) => response._embedded.elements),
@ -290,7 +304,16 @@ export class DynamicFieldsService {
);
}
return;
// Backend status options don't include the default 'not set' status
if (options && field.type === 'ProjectStatus') {
const defaultStatusOption = {
name: projectStatusI18n('not_set', this.I18n),
}
options = options.pipe(map(optionValues => ([defaultStatusOption, ...optionValues])))
}
return options;
}
// ng-select needs a 'name' in order to show the label

Loading…
Cancel
Save