Skip to content

Commit

Permalink
Implement typeahead
Browse files Browse the repository at this point in the history
  • Loading branch information
lkleisa committed May 6, 2024
1 parent d04f222 commit e9ff46a
Show file tree
Hide file tree
Showing 8 changed files with 192 additions and 77 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package ch.puzzle.okr.controller;

import ch.puzzle.okr.dto.ObjectiveAlignmentsDto;
import ch.puzzle.okr.dto.AlignmentDto;
import ch.puzzle.okr.dto.ObjectiveDto;
import ch.puzzle.okr.mapper.ObjectiveMapper;
import ch.puzzle.okr.models.Objective;
Expand Down Expand Up @@ -48,11 +48,11 @@ public ResponseEntity<ObjectiveDto> getObjective(
@Operation(summary = "Get Alignment possibilities", description = "Get all possibilities to create an Alignment")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Returned all Alignment possibilities for an Objective", content = {
@Content(mediaType = "application/json", schema = @Schema(implementation = ObjectiveAlignmentsDto.class)) }),
@Content(mediaType = "application/json", schema = @Schema(implementation = AlignmentDto.class)) }),
@ApiResponse(responseCode = "401", description = "Not authorized to get Alignment possibilities", content = @Content),
@ApiResponse(responseCode = "404", description = "Did not find any possibilities to create an Alignment", content = @Content) })
@GetMapping("/alignmentPossibilities/{quarterId}")
public ResponseEntity<List<ObjectiveAlignmentsDto>> getAlignmentPossibilities(
public ResponseEntity<List<AlignmentDto>> getAlignmentPossibilities(
@Parameter(description = "The Quarter ID for getting Alignment possibilities.", required = true) @PathVariable Long quarterId) {
return ResponseEntity.status(HttpStatus.OK)
.body(objectiveAuthorizationService.getAlignmentPossibilities(quarterId));
Expand Down
6 changes: 6 additions & 0 deletions backend/src/main/java/ch/puzzle/okr/dto/AlignmentDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package ch.puzzle.okr.dto;

import java.util.List;

public record AlignmentDto(Long teamId, String teamName, List<AlignmentObjectDto> alignmentObjectDtos) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package ch.puzzle.okr.dto;

public record AlignmentObjectDto(Long objectId, String objectTitle, String objectType) {
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package ch.puzzle.okr.service.authorization;

import ch.puzzle.okr.dto.ObjectiveAlignmentsDto;
import ch.puzzle.okr.dto.AlignmentDto;
import ch.puzzle.okr.models.Objective;
import ch.puzzle.okr.models.authorization.AuthorizationUser;
import ch.puzzle.okr.service.business.ObjectiveBusinessService;
Expand All @@ -22,7 +22,7 @@ public Objective duplicateEntity(Long id, Objective objective) {
return getBusinessService().duplicateObjective(id, objective, authorizationUser);
}

public List<ObjectiveAlignmentsDto> getAlignmentPossibilities(Long quarterId) {
public List<AlignmentDto> getAlignmentPossibilities(Long quarterId) {
return getBusinessService().getAlignmentPossibilities(quarterId);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package ch.puzzle.okr.service.business;

import ch.puzzle.okr.dto.ObjectiveAlignmentsDto;
import ch.puzzle.okr.dto.keyresult.KeyResultAlignmentsDto;
import ch.puzzle.okr.dto.AlignmentDto;
import ch.puzzle.okr.dto.AlignmentObjectDto;
import ch.puzzle.okr.models.Objective;
import ch.puzzle.okr.models.Team;
import ch.puzzle.okr.models.authorization.AuthorizationUser;
import ch.puzzle.okr.models.keyresult.KeyResult;
import ch.puzzle.okr.models.keyresult.KeyResultMetric;
Expand All @@ -16,9 +17,7 @@
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.*;

import static ch.puzzle.okr.Constants.KEY_RESULT_TYPE_METRIC;
import static ch.puzzle.okr.Constants.KEY_RESULT_TYPE_ORDINAL;
Expand Down Expand Up @@ -52,26 +51,40 @@ public Objective getEntityById(Long id) {
return objective;
}

public List<ObjectiveAlignmentsDto> getAlignmentPossibilities(Long quarterId) {
public List<AlignmentDto> getAlignmentPossibilities(Long quarterId) {
validator.validateOnGet(quarterId);

List<Objective> objectivesByQuarter = objectivePersistenceService.findObjectiveByQuarterId(quarterId);
List<ObjectiveAlignmentsDto> objectiveAlignmentsDtos = new ArrayList<>();

objectivesByQuarter.forEach(objective -> {
List<KeyResult> keyResults = keyResultBusinessService.getAllKeyResultsByObjective(objective.getId());
List<KeyResultAlignmentsDto> keyResultAlignmentsDtos = new ArrayList<>();
keyResults.forEach(keyResult -> {
KeyResultAlignmentsDto keyResultAlignmentsDto = new KeyResultAlignmentsDto(keyResult.getId(),
"K - " + keyResult.getTitle());
keyResultAlignmentsDtos.add(keyResultAlignmentsDto);
List<AlignmentDto> alignmentDtoList = new ArrayList<>();

List<Team> teamList = new ArrayList<>();
objectivesByQuarter.forEach(objective -> teamList.add(objective.getTeam()));
Set<Team> set = new HashSet<>(teamList);
teamList.clear();
teamList.addAll(set);

teamList.forEach(team -> {
List<Objective> filteredObjectiveList = objectivesByQuarter.stream()
.filter(objective -> objective.getTeam().equals(team)).toList();
List<AlignmentObjectDto> alignmentObjectDtos = new ArrayList<>();

filteredObjectiveList.forEach(objective -> {
AlignmentObjectDto objectiveDto = new AlignmentObjectDto(objective.getId(),
"O - " + objective.getTitle(), "objective");
alignmentObjectDtos.add(objectiveDto);

List<KeyResult> keyResults = keyResultBusinessService.getAllKeyResultsByObjective(objective.getId());
keyResults.forEach(keyResult -> {
AlignmentObjectDto keyResultDto = new AlignmentObjectDto(keyResult.getId(),
"KR - " + keyResult.getTitle(), "keyResult");
alignmentObjectDtos.add(keyResultDto);
});
});
ObjectiveAlignmentsDto objectiveAlignmentsDto = new ObjectiveAlignmentsDto(objective.getId(),
"O - " + objective.getTitle(), keyResultAlignmentsDtos);
objectiveAlignmentsDtos.add(objectiveAlignmentsDto);
AlignmentDto alignmentDto = new AlignmentDto(team.getId(), team.getName(), alignmentObjectDtos);
alignmentDtoList.add(alignmentDto);
});

return objectiveAlignmentsDtos;
return alignmentDtoList;
}

public List<Objective> getEntitiesByTeamId(Long id) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,25 +68,27 @@
</div>

<div class="d-flex flex-column gap-2 col-6">
<label class="text-black" for="alignment">Bezug (optional)</label>
<select
class="custom-select bg-white select-width"
<label class="text-black">Bezug (optional)</label>
<input
#input
type="text"
formControlName="alignment"
id="alignment"
(change)="changeFirstAlignmentPossibility()"
[attr.data-testId]="'alignmentSelect'"
>
<ng-container *ngFor="let alignment of alignmentPossibilities$ | async; let i = index">
<option [value]="'O' + alignment.objectiveId">
{{ alignment.objectiveTitle }}
</option>
<ng-container *ngFor="let keyResult of alignment.keyResultAlignmentsDtos; let i = index">
<option [value]="'K' + keyResult.keyResultId">
{{ keyResult.keyResultTitle }}
</option>
</ng-container>
</ng-container>
</select>
class="custom-select bg-white select-width"
placeholder="Bezug wählen"
[matAutocomplete]="auto"
(input)="filter()"
(focus)="filter(); input.select()"
[value]="displayedValue"
/>
<mat-autocomplete requireSelection #auto="matAutocomplete" [displayWith]="displayWith">
@for (group of filteredOptions; track group) {
<mat-optgroup [label]="group.teamName">
@for (name of group.alignmentObjects; track name) {
<mat-option [value]="name">{{ name.objectTitle }}</mat-option>
}
</mat-optgroup>
}
</mat-autocomplete>
</div>
</div>
<mat-checkbox
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, Component, Inject, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, Component, ElementRef, Inject, OnInit, ViewChild } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { Quarter } from '../../types/model/Quarter';
import { TeamService } from '../../services/team.service';
Expand All @@ -24,12 +24,15 @@ import { AlignmentPossibility } from '../../types/model/AlignmentPossibility';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ObjectiveFormComponent implements OnInit {
@ViewChild('input') input!: ElementRef<HTMLInputElement>;
filteredOptions: AlignmentPossibility[] = [];

objectiveForm = new FormGroup({
title: new FormControl<string>('', [Validators.required, Validators.minLength(2), Validators.maxLength(250)]),
description: new FormControl<string>('', [Validators.maxLength(4096)]),
quarter: new FormControl<number>(0, [Validators.required]),
team: new FormControl<number>({ value: 0, disabled: true }, [Validators.required]),
alignment: new FormControl<string>(''),
alignment: new FormControl<AlignmentPossibility | null>(null),
createKeyResults: new FormControl<boolean>(false),
});
quarters$: Observable<Quarter[]> = of([]);
Expand Down Expand Up @@ -65,6 +68,19 @@ export class ObjectiveFormComponent implements OnInit {
onSubmit(submitType: any): void {
const value = this.objectiveForm.getRawValue();
const state = this.data.objective.objectiveId == null ? submitType : this.state;

let alignmentEntity: string | null = '';
let alignment: any = value.alignment;
if (alignment) {
if (alignment?.objectiveId) {
alignmentEntity = 'O' + alignment.objectiveId;
} else {
alignmentEntity = 'K' + alignment.keyResultId;
}
} else {
alignmentEntity = null;
}

let objectiveDTO: Objective = {
id: this.data.objective.objectiveId,
version: this.version,
Expand All @@ -73,7 +89,7 @@ export class ObjectiveFormComponent implements OnInit {
title: value.title,
teamId: value.team,
state: state,
alignedEntityId: value.alignment == 'Onull' ? null : value.alignment,
alignedEntityId: alignmentEntity,
} as unknown as Objective;

const submitFunction = this.getSubmitFunction(objectiveDTO.id, objectiveDTO);
Expand Down Expand Up @@ -106,14 +122,15 @@ export class ObjectiveFormComponent implements OnInit {
this.teams$.subscribe((value) => {
this.currentTeam.next(value.filter((team) => team.id == teamId)[0]);
});
this.generateAlignmentPossibilities(quarterId);
this.generateAlignmentPossibilities(quarterId, objective, teamId!);

this.objectiveForm.patchValue({
title: objective.title,
description: objective.description,
team: teamId,
quarter: quarterId,
alignment: objective.alignedEntityId ? objective.alignedEntityId : 'Onull',
// alignment: null,
// alignment: objective.alignedEntityId ? objective.alignedEntityId : 'Onull',
});
});
}
Expand Down Expand Up @@ -238,47 +255,120 @@ export class ObjectiveFormComponent implements OnInit {
return GJ_REGEX_PATTERN.test(label);
}

generateAlignmentPossibilities(quarterId: number) {
generateAlignmentPossibilities(quarterId: number, objective: Objective | null, teamId: number | null) {
this.alignmentPossibilities$ = this.objectiveService.getAlignmentPossibilities(quarterId);
this.alignmentPossibilities$.subscribe((value: AlignmentPossibility[]) => {
if (this.objective?.id) {
value = value.filter((item: AlignmentPossibility) => !(item.objectiveId == this.objective!.id));
if (teamId) {
value = value.filter((item: AlignmentPossibility) => !(item.teamId == teamId));
}
let firstSelectOption = {
objectiveId: null,
objectiveTitle: 'Kein Alignment',
keyResultAlignmentsDtos: [],
};
if (value.length != 0) {
if (this.objective?.alignedEntityId) {
if (value[0].objectiveTitle == 'Bitte wählen') {
value.splice(0, 1);
// let firstSelectOption = {
// objectiveId: null,
// objectiveTitle: 'Kein Alignment',
// keyResultAlignmentsDtos: [],
// };
// if (value.length != 0) {
// if (this.objective?.alignedEntityId) {
// if (value[0].objectiveTitle == 'Bitte wählen') {
// value.splice(0, 1);
// }
// } else {
// firstSelectOption.objectiveTitle = 'Bitte wählen';
// }
// }
// value.unshift(firstSelectOption);

if (objective) {
let alignment = objective.alignedEntityId;
if (alignment) {
let alignmentType = alignment.charAt(0);
let alignmentId = parseInt(alignment.substring(1));
if (alignmentType == 'O') {
let element =
value.find((ap) => ap.alignmentObjects.find((apObjective) => apObjective.objectId == alignmentId)) ||
null;
this.objectiveForm.patchValue({
alignment: element,
});
}
} else {
firstSelectOption.objectiveTitle = 'Bitte wählen';
// else {
// for (let objectiveAlignment of value) {
// let keyResult = objectiveAlignment.keyResultAlignmentsDtos.find((kr) => kr.keyResultId == alignmentId);
// if (keyResult) {
// // TODO change this to keyresult
// this.objectiveForm.patchValue({
// alignment: objectiveAlignment,
// });
// }
// }
// }
}
} else {
this.objectiveForm.patchValue({
alignment: null,
});
}
value.unshift(firstSelectOption);

this.filteredOptions = value.slice();
this.alignmentPossibilities$ = of(value);
});
}

updateAlignments() {
this.generateAlignmentPossibilities(this.objectiveForm.value.quarter!);
this.objectiveForm.patchValue({
alignment: 'Onull',
this.currentTeam.subscribe((team) => {
this.generateAlignmentPossibilities(this.objectiveForm.value.quarter!, null, team.id);
});
// this.objectiveForm.patchValue({
// alignment: 'Onull',
// });
}

changeFirstAlignmentPossibility() {
this.alignmentPossibilities$.subscribe((value: AlignmentPossibility[]) => {
let element: AlignmentPossibility = value[0];
element.objectiveTitle = 'Kein Alignment';
value.splice(0, 1);
value.unshift(element);
this.alignmentPossibilities$ = of(value);
// changeFirstAlignmentPossibility() {
// this.alignmentPossibilities$.subscribe((value: AlignmentPossibility[]) => {
// let element: AlignmentPossibility = value[0];
// element.objectiveTitle = 'Kein Alignment';
// value.splice(0, 1);
// value.unshift(element);
// this.alignmentPossibilities$ = of(value);
// });
// }

filter() {
let filterValue = this.input.nativeElement.value.toLowerCase();
this.alignmentPossibilities$.subscribe((value) => {
// this.filteredOptions = value.filter((o) => o.objectiveTitle.toLowerCase().includes(filterValue));
this.filteredOptions = value.filter(
(o) => o.teamName.toLowerCase().includes(filterValue),
// ||
// o.alignmentObjects.some((object) => object.objectTitle.toLowerCase().includes(filterValue))

// o.objectiveTitle.toLowerCase().includes(filterValue) || // Check if objectiveTitle includes the filterValue
// o.keyResultAlignmentsDtos.some((kr) => kr.keyResultTitle.toLowerCase().includes(filterValue)), // Check if any keyResultTitle includes the filterValue
);
console.log(this.filteredOptions);
});
}

displayWith(value: any): string {
return value;
// if (value) {
// if (value.objectiveId) {
// return value.objectiveTitle;
// } else {
// return value.keyResultTitle;
// }
// } else {
// return 'Bitte wählen';
// }
}

get displayedValue(): string {
if (this.input) {
const inputValue = this.input.nativeElement.value;
return inputValue.length > 40 ? inputValue.slice(0, 40) + '...' : inputValue;
} else {
return '';
}
}

protected readonly getQuarterLabel = getQuarterLabel;
}

0 comments on commit e9ff46a

Please sign in to comment.