diff --git a/backend/src/main/java/ch/puzzle/okr/controller/CheckInController.java b/backend/src/main/java/ch/puzzle/okr/controller/CheckInController.java index 702684a182..0c316597da 100644 --- a/backend/src/main/java/ch/puzzle/okr/controller/CheckInController.java +++ b/backend/src/main/java/ch/puzzle/okr/controller/CheckInController.java @@ -67,4 +67,13 @@ public ResponseEntity updateCheckIn( CheckInDto updatedCheckIn = this.checkInMapper.toDto(this.checkInBusinessService.updateCheckIn(id, checkIn)); return ResponseEntity.status(HttpStatus.OK).body(updatedCheckIn); } + + @Operation(summary = "Delete Check-in by ID", description = "Delete Check-in by ID") + @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Deleted Check-in by ID"), + @ApiResponse(responseCode = "404", description = "Did not find the Check-in with requested ID") }) + @DeleteMapping("/{id}") + public void deleteCheckIn( + @Parameter(description = "The ID of an Check-in to delete it.", required = true) @PathVariable long id) { + this.checkInBusinessService.deleteCheckIn(id); + } } diff --git a/backend/src/main/java/ch/puzzle/okr/dto/keyresult/KeyResultLastCheckInOrdinalDto.java b/backend/src/main/java/ch/puzzle/okr/dto/keyresult/KeyResultLastCheckInOrdinalDto.java index 9b827495db..b4525da0f1 100644 --- a/backend/src/main/java/ch/puzzle/okr/dto/keyresult/KeyResultLastCheckInOrdinalDto.java +++ b/backend/src/main/java/ch/puzzle/okr/dto/keyresult/KeyResultLastCheckInOrdinalDto.java @@ -1,7 +1,9 @@ package ch.puzzle.okr.dto.keyresult; +import ch.puzzle.okr.models.checkin.Zone; + import java.time.LocalDateTime; -public record KeyResultLastCheckInOrdinalDto(Long id, String zone, Integer confidence, LocalDateTime createdOn, +public record KeyResultLastCheckInOrdinalDto(Long id, Zone value, Integer confidence, LocalDateTime createdOn, String changeInfo, String initiatives) implements KeyResultLastCheckIn { } diff --git a/backend/src/main/java/ch/puzzle/okr/mapper/keyresult/KeyResultOrdinalMapper.java b/backend/src/main/java/ch/puzzle/okr/mapper/keyresult/KeyResultOrdinalMapper.java index b44dd07981..e10d0d2c4b 100644 --- a/backend/src/main/java/ch/puzzle/okr/mapper/keyresult/KeyResultOrdinalMapper.java +++ b/backend/src/main/java/ch/puzzle/okr/mapper/keyresult/KeyResultOrdinalMapper.java @@ -57,8 +57,8 @@ public KeyResultLastCheckInOrdinalDto getLastCheckInDto(Long keyResultId) { lastCheckInDto = null; } else { lastCheckInDto = new KeyResultLastCheckInOrdinalDto(lastCheckIn.getId(), - ((CheckInOrdinal) lastCheckIn).getZone().toString(), lastCheckIn.getConfidence(), - lastCheckIn.getCreatedOn(), lastCheckIn.getChangeInfo(), lastCheckIn.getInitiatives()); + ((CheckInOrdinal) lastCheckIn).getZone(), lastCheckIn.getConfidence(), lastCheckIn.getCreatedOn(), + lastCheckIn.getChangeInfo(), lastCheckIn.getInitiatives()); } return lastCheckInDto; } diff --git a/backend/src/main/java/ch/puzzle/okr/service/business/CheckInBusinessService.java b/backend/src/main/java/ch/puzzle/okr/service/business/CheckInBusinessService.java index 5de7eadbe1..96cdd776c9 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/business/CheckInBusinessService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/business/CheckInBusinessService.java @@ -31,6 +31,7 @@ public CheckIn getCheckInById(Long id) { @Transactional public CheckIn createCheckIn(CheckIn checkIn, Jwt token) { checkIn.setCreatedOn(LocalDateTime.now()); + checkIn.setModifiedOn(LocalDateTime.now()); checkIn.setCreatedBy(userBusinessService.getUserByAuthorisationToken(token)); validator.validateOnCreate(checkIn); return checkInPersistenceService.save(checkIn); diff --git a/backend/src/main/resources/db/data-migration/V2_1_2__newQuarterData.sql b/backend/src/main/resources/db/data-migration/V2_1_2__newQuarterData.sql new file mode 100644 index 0000000000..d649876aad --- /dev/null +++ b/backend/src/main/resources/db/data-migration/V2_1_2__newQuarterData.sql @@ -0,0 +1,170 @@ +insert into public.objective (id, description, modified_on, title, created_by_id, quarter_id, team_id, state, + modified_by_id, created_on) +values (19, 'Lorem Ipsum sit amet diri guru humu saguri alam apmach helum di gau', '2023-10-02 15:04:02.000000', + 'Wir verwenden ausschliesslich Angular Material in unseren Designs.', 1, 7, 8, 'NOTSUCCESSFUL', null, + '2023-10-02 09:05:17.000000'), + (20, 'Lorem Ipsum sit amet diri guru humu saguri alam apmach helum di gau', '2023-10-02 15:05:57.000000', + 'Loremsen Inud di doro sim guru', 1, 7, 6, 'SUCCESSFUL', null, '2023-10-02 09:06:41.000000'), + (11, 'Damit wir effizienter und besser zusammenarbeiten, müssen wir den Teamzusammenhalt stärken', + '2023-10-02 13:49:39.000000', 'Wir wollen die Zusammenarbeit im Team steigern.', 1, 7, 5, 'ONGOING', null, + '2023-10-02 08:49:29.000000'), + (14, 'Damit wir motivierte Lernende geben wir Ihnen jeden morgen ein gratis Schoggigipfeli.', + '2023-10-02 14:55:30.000000', 'Wir wollen motivierte, satte Lernende', 1, 7, 4, 'ONGOING', null, + '2023-10-02 08:56:12.000000'), + (15, 'Um die Ausgaben für Getränke zu reduzieren, muss der Konsum im BBT reduziert werden.', + '2023-10-02 14:57:25.000000', 'Wir wollen unseren Konsum von Süssgetränken und Speisen reduzieren.', 1, 7, 4, + 'NOTSUCCESSFUL', null, '2023-10-02 08:58:39.000000'), + (23, + 'Mit der Weiterentwiklung wollen wir ein perfektes Tool für das OKR-Framework entwickeln, damit das volle Potential des Frameworks ausgenutzt werden kann.', + '2023-10-02 14:59:21.000000', 'The Puzzle OKR Tool will be the best application ever developed by Puzzle ITC', + 1, 7, 4, 'ONGOING', null, '2023-10-02 09:00:23.000000'), + (17, 'Lorem Ipsum sit amet diri guru humu saguri alam apmach helum di gau', '2023-10-02 14:01:50.000000', + 'Unsere Mockups werden zu den Besten der Schweiz', 1, 7, 8, 'DRAFT', null, '2023-10-02 09:02:38.000000'), + (18, 'Lorem Ipsum sit amet diri guru humu saguri alam apmach helum di gau', '2023-10-02 14:03:01.000000', + 'Wir pflegen eine offene Kommunikation mit den Entwicklern.', 1, 7, 8, 'NOTSUCCESSFUL', null, + '2023-10-02 09:03:44.000000'), + (12, 'Um die Löhne unserer Mitarbeiter zu erhöhen müssen wir mehr Umsatz machen', '2023-10-02 10:51:02.000000', + 'Wir wollen mehr Umsatz machen.', 1, 7, 5, 'ONGOING', null, '2023-10-02 08:51:40.000000'), + (13, + 'Um eine saubere und natürliche Arbeitsumgebung für die Mitarbeiter zu schaffen, richten wir mehr Pflanzen ein.', + '2023-10-02 12:53:36.000000', 'Wir wollen mehr Pflanzen in den Puzzle Büros.', 1, 7, 5, 'ONGOING', null, + '2023-10-02 08:54:11.000000'), + (22, 'Lorem Ipsum sit amet diri guru humu saguri alam apmach helum di gau', '2023-10-02 13:07:56.000000', + 'Wing Wang Tala Tala Ting Tang', 1, 7, 6, 'DRAFT', null, '2023-10-02 09:08:40.000000'), + (21, 'Lorem Ipsum sit amet diri guru humu saguri alam apmach helum di gau', '2023-10-02 13:07:09.000000', + 'Ting Tang Wala Wala Bing Bang', 1, 7, 6, 'DRAFT', null, '2023-10-02 09:07:39.000000'); + +insert into key_result (id, baseline, description, modified_on, stretch_goal, title, created_by_id, objective_id, + owner_id, unit, key_result_type, created_on, commit_zone, target_zone, stretch_zone) +values (20, 0, + 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lore', + '2023-10-02 13:15:22.000000', 150, + 'Prow scuttle parrel provost Sail ho shrouds spirits boom mizzenmast yardarm.', 1, 11, 1, 'PERCENT', 'metric', + '2023-10-02 09:16:07.000000', null, null, null), + (21, null, + 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lore', + '2023-10-02 13:15:22.000000', null, + 'Clap of thunder bilge aft log crows nest landlubber or just lubber overhaul', 1, 11, 1, '', 'ordinal', + '2023-10-02 09:16:07.000000', 'This is the commit zone', 'This is the target zone', 'This is the stretch zone'), + (22, 40, + 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lore', + '2023-10-02 13:15:22.000000', 300, 'Scourge of the seven seas blow the man down provost hail-shot Yellow Jack', + 1, 11, 1, 'PERCENT', 'metric', '2023-10-02 09:16:07.000000', null, null, null), + (23, 1050, + 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lore', + '2023-10-02 13:15:22.000000', 5000, + 'Sea Legs hogshead yardarm Pieces of Eight boatswain jack mizzen tack belay ballast', 1, 12, 1, 'NUMBER', + 'metric', '2023-10-02 09:16:07.000000', null, null, null), + (24, 50, + 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lore', + '2023-10-02 13:15:22.000000', 100, 'Parrel Shiver me timbers lanyard crows nest fluke gun skysail no prey', 1, + 12, 1, 'CHF', 'metric', '2023-10-02 09:16:07.000000', null, null, null), + (25, 15, + 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lore', + '2023-10-02 13:15:22.000000', 45, 'Driver jack hempen halter poop deck bucko broadside me', 1, 12, 1, 'FTE', + 'metric', '2023-10-02 09:16:07.000000', null, null, null), + (26, null, + 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lore', + '2023-10-02 13:15:22.000000', null, + 'Hornswaggle hands rum Gold Road lugsail spanker Davy Jones Locker pressgang ', 1, 13, 1, '', 'ordinal', + '2023-10-02 09:16:07.000000', 'Reached commit zone here', 'Reached target zone here', + 'Reached stretch zone here'), + (27, null, + 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lore', + '2023-10-02 13:15:22.000000', null, + 'Black spot bilge booty marooned Davy Jones Locker rum scourge of the seven seas Sink ', 1, 13, 1, '', + 'ordinal', '2023-10-02 09:16:07.000000', 'Reached commit zone here', 'Reached target zone here', + 'Reached stretch zone here'), + (28, null, + 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lore', + '2023-10-02 13:15:22.000000', null, + 'Scallywag Spanish Main coxswain brigantine case shot bring a spring upon her cable ', 1, 13, 1, '', 'ordinal', + '2023-10-02 09:16:07.000000', 'Reached commit zone here', 'Reached target zone here', + 'Reached stretch zone here'), + (29, null, + 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lore', + '2023-10-02 13:15:22.000000', null, + 'Smartly aye Pieces of Eight hang the jib gun nipperkin Nelsons folly schooner Pirate Round swab', 1, 18, 1, '', + 'ordinal', '2023-10-02 09:16:07.000000', 'Reached commit zone here', 'Reached target zone here', + 'Reached stretch zone here'), + (30, null, + 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lore', + '2023-10-02 13:15:22.000000', null, + 'Jack Tar strike colors draft Cat onine tails blow the man down skysail mutiny yawl overhaul bilge', 1, 18, 1, + '', 'ordinal', '2023-10-02 09:16:07.000000', 'Commit zone is here', 'Target zone is here', + 'Stretch zone is here'), + (31, 9, + 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lore', + '2023-10-02 13:15:22.000000', 27, + 'Rigging tender flogging gun clipper Plate Fleet bowsprit crack Jennys tea cup gunwalls Davy Jones Locker', 1, + 20, 1, 'NUMBER', 'metric', '2023-10-02 09:16:07.000000', null, null, null), + (32, null, '', null, null, 'Das OKR-Tool wird zum Vorzeigeprojekt für herrvorragende Usability', 1, 23, 1, null, + 'ordinal', '2023-10-02 09:16:07.000000', + 'Wenn ein User die Applikation zum ersten mal benutzt, kann dieser ohne nachfragen seine Objectives und Key Results erfassen und scoren', + 'Für interne Projekte wird das OKR-Tool als zu erreichender Standard angesehen, wir wollen mindestens 1 Anfrage erhalten, wie wir ein Frontendfeature umgesetzt haben.', + 'Das OKR-Tool wird von UX als Beispiel bei mindestens einem Kunde für ein perfektes Usererlebnis verwendet. Weiter richten sich die restlichen internen Projekte (PTime, Cryptopus und PSkills) nach den Icons und Buttonhirarchien, welche in unserem OKR-Tool implementiert sind.'), + (33, 3, + 'Sonar untersucht den Code auf unsaubere Methoden (schlechte performance, security issues), und auf Code, welcher potentiell Bugs herbeiführen kann, dabei erstellt Sonar ein Rating mit 5 stufen, wobei E die schlechteste Stufe ist und A die beste.', + null, 5, 'Das OKR-Tool verbessert den Sonar Bugscore von C auf A', 1, 23, 1, 'Sonar Skala von E bis A', + 'metric', '2023-10-02 09:16:07.000000', null, null, null), + (34, null, + 'Um zukünftige Wartungsarbeiten oder Weiterentwicklungen am Tool sauber auszuführen und um die Funktionalität des Tools sicherzustellen, wird die Applikation mit verschieden Testsgetestet.', + null, null, 'Das OKR-Tool weisst eine ausgezeichnete Testabdenkung auf', 1, 23, 1, null, 'ordinal', + '2023-10-02 09:16:07.000000', + 'Unit Tests erreichen eine Testabdekung von 80% auf den Methoden und 80% auf den Branches, Controller sind mit IntegrationsTests getestet, einfache EndToEnd Tests sind mit Cypress umgesetzt.', + 'Unit Tests erreichen eine Testabdekung von 80% auf Methoden und Branches, Controller sind mit Integrationstests getestet und EndToEnd Tests, testen Rollenspezifisch die verschiedenen Ansichten der Applikation durch.', + 'Unit Tests erreichen eine Testabdekung von 85% auf Branches und Methoden, Controller sind zu 100% mit Integrationstests getestet, EndToEnd Tests, testen für jede Rolle, jede Ansicht, ausserdem überprüffen die End2End tests, ob jeweils die Richtigen ereignisse geschehen wenn eine Aktion ausgeführt wird.'), + (35, null, + 'Der erleichterte Umgang mit dem OKR-Framework soll weitere Members dazu Anregen, das Framework von OKRs zu nutzen.', + null, null, 'Unser Tool macht das OKR-Framework beliebter', 1, 23, 1, null, 'ordinal', + '2023-10-02 09:16:07.000000', + 'Mindestens ein Team von Puzzle, welches bis lang nicht am OKR-Framework teilnimmt, wird dank dem Tool neu auch am OKR Teilnehmen. Wir finden dies heraus anhand von Nachfragen wenn neue Teams dazustossen.', + 'Alle Teams von Puzzle nehmen am OKR-Framework Teil.', + 'Alle Teams von Puzzle nehmen am OKR-Framework Teil, zusätzlich haben wir mindestens eine externe Anfrage für unser Tool.'); + +insert into check_in (id, change_info, created_on, initiatives, modified_on, value_metric, created_by_id, key_result_id, + confidence, check_in_type, zone) +values (21, + 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores ', + '2023-10-02 08:50:44.059000', + 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores ', + '2023-10-02 22:00:00.000000', 150, 1, 20, 10, 'metric', null), + (22, + 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores ', + '2023-10-02 08:50:44.059000', + 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores ', + '2023-10-02 22:00:00.000000', null, 1, 21, 4, 'ordinal', 'FAIL'), + (23, + 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores ', + '2023-10-02 08:50:44.059000', + 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores ', + '2023-10-02 22:00:00.000000', 200, 1, 22, 6, 'metric', null), + (24, + 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores ', + '2023-10-02 08:50:44.059000', + 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores ', + '2023-10-02 22:00:00.000000', 200, 1, 23, 1, 'metric', null), + (25, 'Lorem ipsum dolor sit amet', '2023-10-02 08:50:44.059000', + ' sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat', '2023-10-02 22:00:00.000000', + 75, 1, 24, 2, 'metric', ''), + (26, 'Lorem ipsum dolor sit amet, richi rogsi brokilon', '2023-10-02 08:50:44.059000', + ' sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat', '2023-10-02 22:00:00.000000', + 70, 1, 25, 9, 'metric', null), + (27, 'Lorem ipsum dolor sit amet, richi rogsi brokilon', '2023-10-02 08:50:44.059000', + ' sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat', '2023-10-02 22:00:00.000000', + null, 1, 26, 6, 'ordinal', 'COMMIT'), + (28, 'Lorem ipsum dolor sit amet, richi rogsi brokilon', '2023-10-02 08:50:44.059000', + ' sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat', '2023-10-02 22:00:00.000000', + null, 1, 27, 8, 'ordinal', 'TARGET'), + (29, 'Lorem ipsum dolor sit amet, richi rogsi brokilon', '2023-10-02 08:50:44.059000', + ' sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat', '2023-10-02 22:00:00.000000', + null, 1, 28, 8, 'ordinal', 'STRETCH'), + (30, 'Lorem ipsum dolor sit amet, richi rogsi brokilon', '2023-10-02 08:50:44.059000', + ' sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat', '2023-10-02 22:00:00.000000', + null, 1, 29, 10, 'ordinal', 'STRETCH'), + (31, 'Lorem ipsum dolor sit amet, richi rogsi brokilon', '2023-10-02 08:50:44.059000', + ' sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat', '2023-10-02 22:00:00.000000', + null, 1, 30, 2, 'ordinal', 'FAIL'), + (32, 'Lorem ipsum dolor sit amet, richi rogsi brokilon', '2023-10-02 08:50:44.059000', + ' sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat', '2023-10-02 22:00:00.000000', + 13, 1, 31, 3, 'metric', null); \ No newline at end of file diff --git a/backend/src/test/java/ch/puzzle/okr/KeyResultTestHelpers.java b/backend/src/test/java/ch/puzzle/okr/KeyResultTestHelpers.java index fa00e18fcb..265ddb11f9 100644 --- a/backend/src/test/java/ch/puzzle/okr/KeyResultTestHelpers.java +++ b/backend/src/test/java/ch/puzzle/okr/KeyResultTestHelpers.java @@ -7,6 +7,7 @@ import ch.puzzle.okr.models.User; import ch.puzzle.okr.models.checkin.CheckIn; import ch.puzzle.okr.models.checkin.CheckInMetric; +import ch.puzzle.okr.models.checkin.Zone; import ch.puzzle.okr.models.keyresult.KeyResult; import ch.puzzle.okr.models.keyresult.KeyResultMetric; import ch.puzzle.okr.models.keyresult.KeyResultOrdinal; @@ -32,7 +33,7 @@ public class KeyResultTestHelpers { public static final String TARGET_ZONE = "Ein Baum"; public static final String STRETCH_ZONE = "Ein Wald"; public static final String QUARTER_LABEL = "GJ 22/23-Q4"; - public static final String LAST_CHECK_IN_ZONE = "Baum"; + public static final Zone LAST_CHECK_IN_ZONE = Zone.COMMIT; public static final String FIRSTNAME = "Johnny"; public static final String LASTNAME = "Appleseed"; public static final String START_DATE = "-999999999-01-01"; @@ -56,7 +57,7 @@ public class KeyResultTestHelpers { public static final String JSON_PATH_TARGET_ZONE = "$.targetZone"; public static final String JSON_PATH_STRETCH_ZONE = "$.stretchZone"; public static final String JSON_PATH_LAST_CHECK_IN_ID = "$.lastCheckIn.id"; - public static final String JSON_PATH_LAST_CHECK_IN_ZONE = "$.lastCheckIn.zone"; + public static final String JSON_PATH_LAST_CHECK_IN_ZONE = "$.lastCheckIn.value"; public static final String JSON_PATH_QUARTER_LABEL = "$.objective.keyResultQuarterDto.label"; public static final String JSON_PATH_QUARTER_START_DATE = "$.objective.keyResultQuarterDto.startDate"; public static final String JSON = "{\"title\": \"Keyresult 1\",\"keyResultType\": \"metric\"}"; diff --git a/backend/src/test/java/ch/puzzle/okr/controller/KeyResultControllerIT.java b/backend/src/test/java/ch/puzzle/okr/controller/KeyResultControllerIT.java index 95c8ebcdb1..0732ab2b5a 100644 --- a/backend/src/test/java/ch/puzzle/okr/controller/KeyResultControllerIT.java +++ b/backend/src/test/java/ch/puzzle/okr/controller/KeyResultControllerIT.java @@ -92,7 +92,7 @@ void shouldGetOrdinalKeyResultWithId() throws Exception { .andExpect(jsonPath(JSON_PATH_OBJECTIVE_ID, Is.is(OBJECTIVE_ID))) .andExpect(jsonPath(JSON_PATH_OBJECTIVE_STATE, Is.is(OBJECTIVE_STATE_ONGOING))) .andExpect(jsonPath(JSON_PATH_QUARTER_LABEL, Is.is(QUARTER_LABEL))) - .andExpect(jsonPath(JSON_PATH_LAST_CHECK_IN_ZONE, Is.is(LAST_CHECK_IN_ZONE))) + .andExpect(jsonPath(JSON_PATH_LAST_CHECK_IN_ZONE, Is.is(LAST_CHECK_IN_ZONE.toString()))) .andExpect(jsonPath(JSON_PATH_LAST_CHECK_IN_CONFIDENCE, Is.is(CONFIDENCE))) .andExpect(jsonPath(JSON_PATH_CREATED_ON, Is.is(KEY_RESULT_CREATED_ON))) .andExpect(jsonPath(JSON_PATH_COMMIT_ZONE, Is.is(COMMIT_ZONE))) @@ -214,7 +214,7 @@ void createKeyResultWithEnumKeys() throws Exception { .andExpect(jsonPath(JSON_PATH_KEY_RESULT_TYPE, Is.is(KEY_RESULT_TYPE_ORDINAL))) .andExpect(jsonPath(JSON_PATH_OWNER_FIRSTNAME, Is.is(FIRSTNAME))) .andExpect(jsonPath(JSON_PATH_OBJECTIVE_STATE, Is.is(OBJECTIVE_STATE_ONGOING))) - .andExpect(jsonPath(JSON_PATH_LAST_CHECK_IN_ZONE, Is.is(LAST_CHECK_IN_ZONE))) + .andExpect(jsonPath(JSON_PATH_LAST_CHECK_IN_ZONE, Is.is(LAST_CHECK_IN_ZONE.toString()))) .andExpect(jsonPath(JSON_PATH_LAST_CHECK_IN_CONFIDENCE, Is.is(CONFIDENCE))) .andExpect(jsonPath(JSON_PATH_CREATED_ON, Is.is(KEY_RESULT_CREATED_ON))) .andExpect(jsonPath(JSON_PATH_COMMIT_ZONE, Is.is(COMMIT_ZONE))) diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index 93f470d9c6..72b9c61175 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -47,8 +47,16 @@ import { DrawerInterceptor } from './shared/interceptors/drawer.interceptor'; import { CheckInHistoryDialogComponent } from './shared/dialog/check-in-history-dialog/check-in-history-dialog.component'; import { MatDividerModule } from '@angular/material/divider'; import { ApplicationBannerComponent } from './application-banner/application-banner.component'; +import { CheckInFormMetricComponent } from './shared/dialog/checkin/check-in-form-metric/check-in-form-metric.component'; +import { UnitValueTransformationPipe } from './shared/pipes/unit-value-transformation/unit-value-transformation.pipe'; +import { CheckInFormOrdinalComponent } from './shared/dialog/checkin/check-in-form-ordinal/check-in-form-ordinal.component'; +import { CheckInBaseInformationsComponent } from './shared/dialog/checkin/check-in-base-informations/check-in-base-informations.component'; +import { CustomInputComponent } from './shared/custom/custom-input/custom-input.component'; import { KeyResultDialogComponent } from './key-result-dialog/key-result-dialog.component'; import { ConfirmDialogComponent } from './shared/dialog/confirm-dialog/confirm-dialog.component'; +import { CheckInFormComponent } from './shared/dialog/checkin/check-in-form/check-in-form.component'; +import { UnitLabelTransformationPipe } from './shared/pipes/unit-label-transformation/unit-label-transformation.pipe'; +import { ParseUnitValuePipe } from './shared/pipes/parse-unit-value/parse-unit-value.pipe'; function initOauthFactory(configService: ConfigService, oauthService: OAuthService) { return async () => { @@ -95,6 +103,16 @@ export const MY_FORMATS = { ApplicationBannerComponent, KeyResultDialogComponent, ConfirmDialogComponent, + CheckInFormComponent, + CheckInFormMetricComponent, + UnitValueTransformationPipe, + CheckInFormOrdinalComponent, + CheckInBaseInformationsComponent, + CustomInputComponent, + CheckInFormComponent, + UnitLabelTransformationPipe, + ConfirmDialogComponent, + ParseUnitValuePipe, ], imports: [ CommonModule, @@ -145,6 +163,8 @@ export const MY_FORMATS = { { provide: HTTP_INTERCEPTORS, useClass: DrawerInterceptor, multi: true }, { provide: OAuthStorage, useFactory: storageFactory }, { provide: APP_INITIALIZER, useFactory: initOauthFactory, deps: [ConfigService, OAuthService], multi: true }, + UnitValueTransformationPipe, + ParseUnitValuePipe, ], bootstrap: [AppComponent], schemas: [CUSTOM_ELEMENTS_SCHEMA], diff --git a/frontend/src/app/confidence/confidence.component.html b/frontend/src/app/confidence/confidence.component.html index c05766e27e..f8aebb6bd5 100644 --- a/frontend/src/app/confidence/confidence.component.html +++ b/frontend/src/app/confidence/confidence.component.html @@ -1,14 +1,12 @@ -
+
-

- {{ keyResult.lastCheckIn?.confidence }}/{{ max }} -

+

{{ checkIn.confidence }}/{{ max }}

- +
diff --git a/frontend/src/app/confidence/confidence.component.scss b/frontend/src/app/confidence/confidence.component.scss index 87fb8639f1..43b865b946 100644 --- a/frontend/src/app/confidence/confidence.component.scss +++ b/frontend/src/app/confidence/confidence.component.scss @@ -6,3 +6,7 @@ p { width: 63px; height: 23px; } + +mat-slider { + min-width: 370px; +} diff --git a/frontend/src/app/confidence/confidence.component.spec.ts b/frontend/src/app/confidence/confidence.component.spec.ts index 95d523204f..7b13214ad0 100644 --- a/frontend/src/app/confidence/confidence.component.spec.ts +++ b/frontend/src/app/confidence/confidence.component.spec.ts @@ -3,7 +3,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ConfidenceComponent } from './confidence.component'; import { HarnessLoader } from '@angular/cdk/testing'; import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; -import { keyResultMetricMin } from '../shared/testData'; +import { checkInMetric } from '../shared/testData'; import { MatSliderModule } from '@angular/material/slider'; import { CheckInMin } from '../shared/types/model/CheckInMin'; import { FormsModule } from '@angular/forms'; @@ -24,7 +24,7 @@ describe('ConfidenceComponent', () => { fixture = TestBed.createComponent(ConfidenceComponent); component = fixture.componentInstance; loader = TestbedHarnessEnvironment.loader(fixture); - component.keyResult = keyResultMetricMin; + component.checkIn = checkInMetric; component.edit = true; }); @@ -36,9 +36,9 @@ describe('ConfidenceComponent', () => { [{ confidence: 8 } as CheckInMin, '8'], [null, '5'], ])('should set confidence of component with right value', async (checkIn: CheckInMin | null, expected: string) => { - component.keyResult.lastCheckIn = checkIn; + component.checkIn = checkIn!; component.ngOnChanges({ - keyResult: new SimpleChange(null, component.keyResult, true), + checkIn: new SimpleChange(null, component.checkIn, true), }); fixture.detectChanges(); await fixture.whenStable(); diff --git a/frontend/src/app/confidence/confidence.component.ts b/frontend/src/app/confidence/confidence.component.ts index 466e77db62..30e037816c 100644 --- a/frontend/src/app/confidence/confidence.component.ts +++ b/frontend/src/app/confidence/confidence.component.ts @@ -1,7 +1,5 @@ import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges } from '@angular/core'; -import { KeyresultMin } from '../shared/types/model/KeyresultMin'; import { CheckInMin } from '../shared/types/model/CheckInMin'; -import { KeyResult } from '../shared/types/model/KeyResult'; @Component({ selector: 'app-confidence', @@ -13,12 +11,12 @@ export class ConfidenceComponent implements OnChanges { min: number = 1; max: number = 10; @Input() edit: boolean = true; - @Input() keyResult!: KeyresultMin | KeyResult; + @Input() checkIn!: CheckInMin; @Input() backgroundColor!: string; ngOnChanges(changes: SimpleChanges) { - if (changes['keyResult']?.currentValue?.lastCheckIn === null) { - this.keyResult.lastCheckIn = { confidence: 5 } as CheckInMin; + if (changes['checkIn']?.currentValue === undefined || changes['checkIn']?.currentValue === null) { + this.checkIn = { confidence: 5 } as CheckInMin; } } } diff --git a/frontend/src/app/key-result-dialog/key-result-dialog.component.ts b/frontend/src/app/key-result-dialog/key-result-dialog.component.ts index 6ccbc9b374..d58fc6e874 100644 --- a/frontend/src/app/key-result-dialog/key-result-dialog.component.ts +++ b/frontend/src/app/key-result-dialog/key-result-dialog.component.ts @@ -90,15 +90,17 @@ export class KeyResultDialogComponent implements OnInit { deleteKeyResult() { if (this.data.keyResult.lastCheckIn?.id == undefined) { - //ToDo: Make ConfirmDialogComponent generic since its also used in other cases this.dialog .open(ConfirmDialogComponent, { + data: { + title: 'Key Result', + }, width: '15em', height: 'auto', }) .afterClosed() .subscribe((result) => { - if (result == 'deleteKeyResult') { + if (result) { this.keyResultService .deleteKeyResult(this.data.keyResult.id) .subscribe(() => this.dialogRef.close({ keyResult: this.data.keyResult, delete: true, openNew: false })); diff --git a/frontend/src/app/keyresult-detail/keyresult-detail.component.html b/frontend/src/app/keyresult-detail/keyresult-detail.component.html index db0bc88511..4cbaacd27e 100644 --- a/frontend/src/app/keyresult-detail/keyresult-detail.component.html +++ b/frontend/src/app/keyresult-detail/keyresult-detail.component.html @@ -18,7 +18,12 @@

{{ keyResult.title }}

Confidence

- +
@@ -49,14 +54,22 @@

{{ keyResult.title }}

-
+
+

+ Letztes Check-in ({{ keyResult.lastCheckIn?.modifiedOn | date: "dd.MM.yyy" }}) +

+

{{ keyResult.lastCheckIn?.changeInfo }}

+
+

Letztes Check-in ({{ keyResult.lastCheckIn?.createdOn | date: "dd.MM.yyy" }})

{{ keyResult.lastCheckIn?.changeInfo }}

- +
@@ -65,6 +78,6 @@

Beschrieb

- +
diff --git a/frontend/src/app/keyresult-detail/keyresult-detail.component.ts b/frontend/src/app/keyresult-detail/keyresult-detail.component.ts index d80203e110..032eb08439 100644 --- a/frontend/src/app/keyresult-detail/keyresult-detail.component.ts +++ b/frontend/src/app/keyresult-detail/keyresult-detail.component.ts @@ -5,8 +5,11 @@ import { KeyResultMetric } from '../shared/types/model/KeyResultMetric'; import { KeyResultOrdinal } from '../shared/types/model/KeyResultOrdinal'; import { CheckInHistoryDialogComponent } from '../shared/dialog/check-in-history-dialog/check-in-history-dialog.component'; import { MatDialog } from '@angular/material/dialog'; +import { CheckInFormMetricComponent } from '../shared/dialog/checkin/check-in-form-metric/check-in-form-metric.component'; import { KeyResultDialogComponent } from '../key-result-dialog/key-result-dialog.component'; import { NotifierService } from '../shared/services/notifier.service'; +import { CheckInService } from '../shared/services/check-in.service'; +import { CheckInFormComponent } from '../shared/dialog/checkin/check-in-form/check-in-form.component'; @Component({ selector: 'app-keyresult-detail', @@ -20,10 +23,33 @@ export class KeyresultDetailComponent implements OnChanges { constructor( private keyResultService: KeyresultService, + private checkInService: CheckInService, + private notifierService: NotifierService, private changeDetectorRef: ChangeDetectorRef, private dialog: MatDialog, - private notifierService: NotifierService, - ) {} + ) { + this.notifierService.reopenCheckInHistoryDialog.subscribe((result) => { + /* Update lastCheckIn if it was changed in history dialog */ + if (this.keyResult.lastCheckIn?.id === result?.checkIn?.id) { + this.keyResult = { ...this.keyResult, lastCheckIn: result.checkIn }; + this.changeDetectorRef.detectChanges(); + } + /* Update lastCheckIn to null if it was deleted in history dialog */ + if (result.deleted) { + if (result.checkIn?.id == this.keyResult.lastCheckIn?.id) { + this.keyResultService.getFullKeyResult(this.keyResultId).subscribe((fullKeyResult) => { + this.keyResult = fullKeyResult; + this.changeDetectorRef.markForCheck(); + if (this.keyResult.lastCheckIn != null) { + this.checkInHistory(); + } + }); + } + return; + } + this.checkInHistory(); + }); + } ngOnChanges() { this.keyResultService.getFullKeyResult(this.keyResultId).subscribe((fullKeyResult) => { @@ -35,16 +61,18 @@ export class KeyresultDetailComponent implements OnChanges { castToMetric(keyResult: KeyResult) { return keyResult as KeyResultMetric; } + castToOrdinal(keyResult: KeyResult) { return keyResult as KeyResultOrdinal; } + checkInHistory() { const dialogRef = this.dialog.open(CheckInHistoryDialogComponent, { data: { keyResultId: this.keyResult.id, + keyResult: this.keyResult, }, }); - dialogRef.afterClosed().subscribe(() => {}); } @@ -79,4 +107,21 @@ export class KeyresultDetailComponent implements OnChanges { this.changeDetectorRef.markForCheck(); }); } + + openCheckInForm() { + const dialogRef = this.dialog.open(CheckInFormComponent, { + data: { + keyResult: this.keyResult, + }, + width: '719px', + }); + dialogRef.afterClosed().subscribe((result) => { + if (result != undefined && result != '') { + this.checkInService.createCheckIn(result.data).subscribe((createdCheckIn) => { + this.keyResult = { ...this.keyResult, lastCheckIn: createdCheckIn }; + this.changeDetectorRef.detectChanges(); + }); + } + }); + } } diff --git a/frontend/src/app/keyresult/keyresult.component.html b/frontend/src/app/keyresult/keyresult.component.html index 54bbcf67b5..1b3b4f0968 100644 --- a/frontend/src/app/keyresult/keyresult.component.html +++ b/frontend/src/app/keyresult/keyresult.component.html @@ -12,7 +12,7 @@

Confidence

- +
Noch kein Check-in vorhanden
diff --git a/frontend/src/app/keyresult/keyresult.component.ts b/frontend/src/app/keyresult/keyresult.component.ts index 142a799f21..ae8d1cd941 100644 --- a/frontend/src/app/keyresult/keyresult.component.ts +++ b/frontend/src/app/keyresult/keyresult.component.ts @@ -12,10 +12,12 @@ import { Router } from '@angular/router'; }) export class KeyresultComponent { @Input() keyResult!: KeyresultMin; + constructor( public dialog: MatDialog, private router: Router, ) {} + checkInHistory() { const dialogRef = this.dialog.open(CheckInHistoryDialogComponent, { data: { diff --git a/frontend/src/app/shared/custom/custom-input/custom-input.component.html b/frontend/src/app/shared/custom/custom-input/custom-input.component.html new file mode 100644 index 0000000000..b9877b0e58 --- /dev/null +++ b/frontend/src/app/shared/custom/custom-input/custom-input.component.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/frontend/src/app/shared/custom/custom-input/custom-input.component.scss b/frontend/src/app/shared/custom/custom-input/custom-input.component.scss new file mode 100644 index 0000000000..6c9088d219 --- /dev/null +++ b/frontend/src/app/shared/custom/custom-input/custom-input.component.scss @@ -0,0 +1,3 @@ +input { + height: 32px; +} diff --git a/frontend/src/app/shared/custom/custom-input/custom-input.component.spec.ts b/frontend/src/app/shared/custom/custom-input/custom-input.component.spec.ts new file mode 100644 index 0000000000..b79b0d3d08 --- /dev/null +++ b/frontend/src/app/shared/custom/custom-input/custom-input.component.spec.ts @@ -0,0 +1,52 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { HarnessLoader } from '@angular/cdk/testing'; + +import { CustomInputComponent } from './custom-input.component'; +import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; +import { MatInputHarness } from '@angular/material/input/testing'; +import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; +import { MatDialogModule } from '@angular/material/dialog'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { MatSelectModule } from '@angular/material/select'; +import { MatInputModule } from '@angular/material/input'; +import { MatRadioModule } from '@angular/material/radio'; +import { checkInMetric } from '../../testData'; + +describe('CustomInputComponent', () => { + let component: CustomInputComponent; + let fixture: ComponentFixture; + let loader: HarnessLoader; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + MatDialogModule, + NoopAnimationsModule, + MatSelectModule, + MatInputModule, + MatRadioModule, + ReactiveFormsModule, + ], + declarations: [CustomInputComponent], + }); + fixture = TestBed.createComponent(CustomInputComponent); + component = fixture.componentInstance; + component.formControlNameGiven = 'value'; + component.formGroup = new FormGroup({ + value: new FormControl('', [Validators.required]), + }); + fixture.detectChanges(); + loader = TestbedHarnessEnvironment.loader(fixture); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should save given text in input to form-group in typescript', waitForAsync(async () => { + const input = await loader.getHarness(MatInputHarness); + await input.setValue(checkInMetric.value!.toString()); + + expect(component.formGroup.controls['value'].value).toBe(checkInMetric.value!.toString()); + })); +}); diff --git a/frontend/src/app/shared/custom/custom-input/custom-input.component.ts b/frontend/src/app/shared/custom/custom-input/custom-input.component.ts new file mode 100644 index 0000000000..81da96dc51 --- /dev/null +++ b/frontend/src/app/shared/custom/custom-input/custom-input.component.ts @@ -0,0 +1,15 @@ +import { Component, Input } from '@angular/core'; +import { FormGroup } from '@angular/forms'; + +@Component({ + selector: 'app-custom-input', + templateUrl: './custom-input.component.html', + styleUrls: ['./custom-input.component.scss'], +}) +export class CustomInputComponent { + @Input() + formGroup!: FormGroup; + + @Input() + formControlNameGiven!: string; +} diff --git a/frontend/src/app/shared/dialog/check-in-history-dialog/check-in-history-dialog.component.html b/frontend/src/app/shared/dialog/check-in-history-dialog/check-in-history-dialog.component.html index e35e64ee48..13d878e10e 100644 --- a/frontend/src/app/shared/dialog/check-in-history-dialog/check-in-history-dialog.component.html +++ b/frontend/src/app/shared/dialog/check-in-history-dialog/check-in-history-dialog.component.html @@ -8,13 +8,21 @@

Check-in History

- - {{ checkIn.createdOn | date: "dd.MM.yyyy" }} - - -
- Wert: {{ checkIn.value }}; +
+ {{ checkIn.createdOn | date: DATE_FORMAT }} + - +
+ Wert: {{ checkIn.value }}; +
+ Confidence: {{ checkIn.confidence }} / 10; +
+
+ + + + +
- Confidence: {{ checkIn.confidence }} / 10;

Veränderungen: {{ checkIn.changeInfo }}

Massnahmen: {{ checkIn.initiatives }}

diff --git a/frontend/src/app/shared/dialog/check-in-history-dialog/check-in-history-dialog.component.spec.ts b/frontend/src/app/shared/dialog/check-in-history-dialog/check-in-history-dialog.component.spec.ts index 1b148c1584..9ab5fd0f9c 100644 --- a/frontend/src/app/shared/dialog/check-in-history-dialog/check-in-history-dialog.component.spec.ts +++ b/frontend/src/app/shared/dialog/check-in-history-dialog/check-in-history-dialog.component.spec.ts @@ -1,7 +1,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { CheckInHistoryDialogComponent } from './check-in-history-dialog.component'; -import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog'; import { HttpClientTestingModule } from '@angular/common/http/testing'; describe('CheckInHistoryDialogComponent', () => { @@ -11,7 +11,7 @@ describe('CheckInHistoryDialogComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ declarations: [CheckInHistoryDialogComponent], - imports: [HttpClientTestingModule], + imports: [HttpClientTestingModule, MatDialogModule], providers: [ { provide: MAT_DIALOG_DATA, useValue: {} }, { provide: MatDialogRef, useValue: {} }, diff --git a/frontend/src/app/shared/dialog/check-in-history-dialog/check-in-history-dialog.component.ts b/frontend/src/app/shared/dialog/check-in-history-dialog/check-in-history-dialog.component.ts index cd0cbd0b26..f3b311fa98 100644 --- a/frontend/src/app/shared/dialog/check-in-history-dialog/check-in-history-dialog.component.ts +++ b/frontend/src/app/shared/dialog/check-in-history-dialog/check-in-history-dialog.component.ts @@ -1,8 +1,14 @@ import { Component, Inject, OnInit } from '@angular/core'; import { CheckInMin } from '../../types/model/CheckInMin'; import { CheckInService } from '../../services/check-in.service'; -import { MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog'; import errorMessages from '../../../../assets/errors/error-messages.json'; +import { DATE_FORMAT } from '../../constantLibary'; +import { KeyResult } from '../../types/model/KeyResult'; +import { CheckInFormComponent } from '../checkin/check-in-form/check-in-form.component'; +import { NotifierService } from '../../services/notifier.service'; +import { ConfirmDialogComponent } from '../confirm-dialog/confirm-dialog.component'; +import { CheckIn } from '../../types/model/CheckIn'; @Component({ selector: 'app-check-in-history-dialog', @@ -10,17 +16,60 @@ import errorMessages from '../../../../assets/errors/error-messages.json'; styleUrls: ['./check-in-history-dialog.component.scss'], }) export class CheckInHistoryDialogComponent implements OnInit { + keyResult!: KeyResult; checkInHistory: CheckInMin[] = []; constructor( @Inject(MAT_DIALOG_DATA) public data: any, private checkInService: CheckInService, + private dialog: MatDialog, + public dialogRef: MatDialogRef, + public notifierService: NotifierService, ) {} ngOnInit(): void { + this.keyResult = this.data.keyResult; this.checkInService.getAllCheckInOfKeyResult(this.data.keyResultId).subscribe((result) => { this.checkInHistory = result; }); } + openCheckInDialog(checkIn: CheckInMin) { + this.dialogRef.close(); + const dialogRef = this.dialog.open(CheckInFormComponent, { + data: { + keyResult: this.keyResult, + checkIn: checkIn, + }, + width: '719px', + }); + dialogRef.afterClosed().subscribe((result) => { + if (result.data !== undefined && result.data !== null) { + let updatedCheckIn = { ...result.data, id: checkIn.id }; + this.checkInService.updateCheckIn(updatedCheckIn, updatedCheckIn.id).subscribe((updatedCheckIn) => { + this.notifierService.reopenCheckInHistoryDialog.next({ checkIn: updatedCheckIn, deleted: false }); + }); + } + }); + } + + deleteCheckIn(checkIn: CheckInMin) { + this.dialogRef.close(); + const dialogRef = this.dialog.open(ConfirmDialogComponent, { + data: { + title: 'Check-in', + }, + }); + dialogRef.afterClosed().subscribe((result) => { + if (result) { + this.checkInService.deleteCheckIn(checkIn.id).subscribe(() => { + this.notifierService.reopenCheckInHistoryDialog.next({ checkIn: checkIn as CheckIn, deleted: true }); + }); + } else { + this.notifierService.reopenCheckInHistoryDialog.next({ checkIn: null, deleted: false }); + } + }); + } + protected readonly errorMessages = errorMessages; + protected readonly DATE_FORMAT = DATE_FORMAT; } diff --git a/frontend/src/app/shared/dialog/checkin/check-in-base-informations/check-in-base-informations.component.html b/frontend/src/app/shared/dialog/checkin/check-in-base-informations/check-in-base-informations.component.html new file mode 100644 index 0000000000..daa603472f --- /dev/null +++ b/frontend/src/app/shared/dialog/checkin/check-in-base-informations/check-in-base-informations.component.html @@ -0,0 +1,35 @@ +
+ + Veränderungen seit letztem Check-in (optional) + + +
+ + {{ getErrorMessage(errorKey) }} + +
+
+ + + Massnahmen (optional) + + +
+ + {{ getErrorMessage(errorKey) }} + +
+
+
diff --git a/frontend/src/app/shared/dialog/checkin/check-in-base-informations/check-in-base-informations.component.scss b/frontend/src/app/shared/dialog/checkin/check-in-base-informations/check-in-base-informations.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/src/app/shared/dialog/checkin/check-in-base-informations/check-in-base-informations.component.spec.ts b/frontend/src/app/shared/dialog/checkin/check-in-base-informations/check-in-base-informations.component.spec.ts new file mode 100644 index 0000000000..f04b41d4b5 --- /dev/null +++ b/frontend/src/app/shared/dialog/checkin/check-in-base-informations/check-in-base-informations.component.spec.ts @@ -0,0 +1,77 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; + +import { CheckInBaseInformationsComponent } from './check-in-base-informations.component'; +import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; +import { HarnessLoader } from '@angular/cdk/testing'; +import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; +import { MatInputHarness } from '@angular/material/input/testing'; +import { By } from '@angular/platform-browser'; +// @ts-ignore +import * as errorData from '../../../../../assets/errors/error-messages.json'; +import { MatInputModule } from '@angular/material/input'; +import { MatDialogModule } from '@angular/material/dialog'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { MatSelectModule } from '@angular/material/select'; +import { MatRadioModule } from '@angular/material/radio'; + +describe('CheckInBaseInformationsComponent', () => { + let component: CheckInBaseInformationsComponent; + let fixture: ComponentFixture; + let loader: HarnessLoader; + let errors = errorData; + + let changeInfoText = 'ChangeInfo'; + let initiativesText = 'Initiatives'; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + MatDialogModule, + NoopAnimationsModule, + MatSelectModule, + MatInputModule, + MatRadioModule, + ReactiveFormsModule, + ], + declarations: [CheckInBaseInformationsComponent], + }); + fixture = TestBed.createComponent(CheckInBaseInformationsComponent); + component = fixture.componentInstance; + component.dialogForm = new FormGroup({ + changeInfo: new FormControl('', [Validators.maxLength(4096)]), + initiatives: new FormControl('', [Validators.maxLength(4096)]), + }); + fixture.detectChanges(); + loader = TestbedHarnessEnvironment.loader(fixture); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should save given text in input to form-group in typescript', waitForAsync(async () => { + const inputs = await loader.getAllHarnesses(MatInputHarness); + const changeInfoTextbox = inputs[0]; + const initiativesTextbox = inputs[1]; + + await changeInfoTextbox.setValue(changeInfoText); + await initiativesTextbox.setValue(initiativesText); + + expect(component.dialogForm.controls['changeInfo'].value).toBe(changeInfoText); + expect(component.dialogForm.controls['initiatives'].value).toBe(initiativesText); + })); + + it('should display error message if text input too long', waitForAsync(async () => { + //Insert values into name input which don't match length validator + const inputs = await loader.getAllHarnesses(MatInputHarness); + const changeInfoTextbox = inputs[0]; + const initiativesTextbox = inputs[1]; + + await changeInfoTextbox.setValue('x'.repeat(4097)); + await initiativesTextbox.setValue('x'.repeat(4097)); + + //Verify error message + const errorMessage = fixture.debugElement.query(By.css('mat-error')); + expect(errorMessage.nativeElement.textContent).toContain(errors.MAXLENGTH); + })); +}); diff --git a/frontend/src/app/shared/dialog/checkin/check-in-base-informations/check-in-base-informations.component.ts b/frontend/src/app/shared/dialog/checkin/check-in-base-informations/check-in-base-informations.component.ts new file mode 100644 index 0000000000..99af9befbd --- /dev/null +++ b/frontend/src/app/shared/dialog/checkin/check-in-base-informations/check-in-base-informations.component.ts @@ -0,0 +1,28 @@ +import { Component, Input } from '@angular/core'; +import { FormGroup } from '@angular/forms'; +import errorMessages from '../../../../../assets/errors/error-messages.json'; + +@Component({ + selector: 'app-check-in-base-informations', + templateUrl: './check-in-base-informations.component.html', + styleUrls: ['./check-in-base-informations.component.scss'], +}) +export class CheckInBaseInformationsComponent { + @Input() + dialogForm!: FormGroup; + + isTouchedOrDirty(name: string) { + return this.dialogForm.get(name)?.dirty || this.dialogForm.get(name)?.touched; + } + + getErrorKeysOfFormField(name: string): string[] { + const errors = this.dialogForm.get(name)?.errors; + return errors == null ? [] : Object.keys(errors); + } + + getErrorMessage(key: string) { + return errorMessages[key.toUpperCase() as keyof typeof errorMessages]; + } + + protected readonly errorMessages = errorMessages; +} diff --git a/frontend/src/app/shared/dialog/checkin/check-in-form-metric/check-in-form-metric.component.html b/frontend/src/app/shared/dialog/checkin/check-in-form-metric/check-in-form-metric.component.html new file mode 100644 index 0000000000..bf5bf64eb9 --- /dev/null +++ b/frontend/src/app/shared/dialog/checkin/check-in-form-metric/check-in-form-metric.component.html @@ -0,0 +1,25 @@ + +
+
+ Aktueller Wert ({{ keyResult.unit | unitLabelTransformation }}): + +
+ +
+ + {{ errorMessages[errorKey.toUpperCase()] }} + +
+
+
+
+ Confidence um Target zu erreichen + +
+
diff --git a/frontend/src/app/shared/dialog/checkin/check-in-form-metric/check-in-form-metric.component.scss b/frontend/src/app/shared/dialog/checkin/check-in-form-metric/check-in-form-metric.component.scss new file mode 100644 index 0000000000..7378efdd5a --- /dev/null +++ b/frontend/src/app/shared/dialog/checkin/check-in-form-metric/check-in-form-metric.component.scss @@ -0,0 +1,7 @@ +.error-font { + font-size: 13px; +} + +.custom-input { + margin-top: 3px; +} diff --git a/frontend/src/app/shared/dialog/checkin/check-in-form-metric/check-in-form-metric.component.spec.ts b/frontend/src/app/shared/dialog/checkin/check-in-form-metric/check-in-form-metric.component.spec.ts new file mode 100644 index 0000000000..f2ada76627 --- /dev/null +++ b/frontend/src/app/shared/dialog/checkin/check-in-form-metric/check-in-form-metric.component.spec.ts @@ -0,0 +1,67 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; + +import { CheckInFormMetricComponent } from './check-in-form-metric.component'; +import { UnitValueTransformationPipe } from '../../../pipes/unit-value-transformation/unit-value-transformation.pipe'; +import { checkInMetric, keyResultMetric } from '../../../testData'; +import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; +import { MatDialogModule } from '@angular/material/dialog'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { MatSelectModule } from '@angular/material/select'; +import { MatInputModule } from '@angular/material/input'; +import { MatRadioModule } from '@angular/material/radio'; +import { Unit } from '../../../types/enums/Unit'; +import { ParseUnitValuePipe } from '../../../pipes/parse-unit-value/parse-unit-value.pipe'; + +describe('CheckInFormComponent', () => { + let component: CheckInFormMetricComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + MatDialogModule, + NoopAnimationsModule, + MatSelectModule, + MatInputModule, + MatRadioModule, + ReactiveFormsModule, + ], + declarations: [CheckInFormMetricComponent], + providers: [UnitValueTransformationPipe, ParseUnitValuePipe], + }); + fixture = TestBed.createComponent(CheckInFormMetricComponent); + component = fixture.componentInstance; + component.keyResult = keyResultMetric; + component.checkIn = checkInMetric; + component.dialogForm = new FormGroup({ + value: new FormControl('', [Validators.required]), + confidence: new FormControl(5, [Validators.required, Validators.min(1), Validators.max(10)]), + }); + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should format percent correctly', waitForAsync(async () => { + component.keyResult = { ...keyResultMetric, unit: Unit.PERCENT }; + component.dialogForm.controls['value'].setValue(checkInMetric.value!.toString()); + component.formatValue(); + expect(component.dialogForm.controls['value'].value).toBe(checkInMetric.value + '%'); + })); + + it('should format CHF correctly', waitForAsync(async () => { + component.keyResult = { ...keyResultMetric, unit: Unit.CHF }; + component.dialogForm.controls['value'].setValue(checkInMetric.value!.toString()); + component.formatValue(); + expect(component.dialogForm.controls['value'].value).toBe(checkInMetric.value + '.-'); + })); + + it('should format FTE correctly', waitForAsync(async () => { + component.keyResult = { ...keyResultMetric, unit: Unit.FTE }; + component.dialogForm.controls['value'].setValue(checkInMetric.value!.toString()); + component.formatValue(); + expect(component.dialogForm.controls['value'].value).toBe(checkInMetric.value!.toString()); + })); +}); diff --git a/frontend/src/app/shared/dialog/checkin/check-in-form-metric/check-in-form-metric.component.ts b/frontend/src/app/shared/dialog/checkin/check-in-form-metric/check-in-form-metric.component.ts new file mode 100644 index 0000000000..6217849397 --- /dev/null +++ b/frontend/src/app/shared/dialog/checkin/check-in-form-metric/check-in-form-metric.component.ts @@ -0,0 +1,47 @@ +import { AfterViewInit, ChangeDetectionStrategy, Component, Input } from '@angular/core'; +import { FormGroup } from '@angular/forms'; +import errorMessages from '../../../../../assets/errors/error-messages.json'; +import { KeyResultMetric } from '../../../types/model/KeyResultMetric'; +import { UnitValueTransformationPipe } from '../../../pipes/unit-value-transformation/unit-value-transformation.pipe'; +import { CheckInMin } from '../../../types/model/CheckInMin'; +import { ParseUnitValuePipe } from '../../../pipes/parse-unit-value/parse-unit-value.pipe'; + +@Component({ + selector: 'app-check-in-form-metric', + templateUrl: './check-in-form-metric.component.html', + styleUrls: ['./check-in-form-metric.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class CheckInFormMetricComponent implements AfterViewInit { + @Input() + keyResult!: KeyResultMetric; + @Input() + checkIn!: CheckInMin; + @Input() + dialogForm!: FormGroup; + protected readonly errorMessages: any = errorMessages; + + constructor( + private pipe: UnitValueTransformationPipe, + private parserPipe: ParseUnitValuePipe, + ) {} + + formatValue() { + this.dialogForm?.controls['value'].setValue( + this.pipe.transform(this.parserPipe.transform(this.dialogForm?.controls['value'].value), this.keyResult.unit), + ); + } + + isTouchedOrDirty(name: string) { + return this.dialogForm.get(name)?.dirty || this.dialogForm.get(name)?.touched; + } + + getErrorKeysOfFormField(name: string) { + const errors = this.dialogForm.get(name)?.errors; + return errors === null ? [] : Object.keys(errors!); + } + + ngAfterViewInit(): void { + this.formatValue(); + } +} diff --git a/frontend/src/app/shared/dialog/checkin/check-in-form-ordinal/check-in-form-ordinal.component.html b/frontend/src/app/shared/dialog/checkin/check-in-form-ordinal/check-in-form-ordinal.component.html new file mode 100644 index 0000000000..747a9d2638 --- /dev/null +++ b/frontend/src/app/shared/dialog/checkin/check-in-form-ordinal/check-in-form-ordinal.component.html @@ -0,0 +1,29 @@ +
+

Erreichten Bereich wählen

+ + +
+ Meilenstein (Commit / Target / Stretch) noch nicht erreicht +
+
+ +
+ {{ keyResult.commitZone }} +
+
+ +
+ {{ keyResult.targetZone }} +
+
+ +
+ {{ keyResult.stretchZone }} +
+
+
+
+ Confidence um Target zu erreichen + +
+
diff --git a/frontend/src/app/shared/dialog/checkin/check-in-form-ordinal/check-in-form-ordinal.component.scss b/frontend/src/app/shared/dialog/checkin/check-in-form-ordinal/check-in-form-ordinal.component.scss new file mode 100644 index 0000000000..5c49d70eb2 --- /dev/null +++ b/frontend/src/app/shared/dialog/checkin/check-in-form-ordinal/check-in-form-ordinal.component.scss @@ -0,0 +1,17 @@ +@import "../style/variables"; + +.submit { + background-color: $pz-dark-blue; + border-radius: 8px; +} + +.cancel { + background-color: white; + color: $pz-dark-blue; +} + +.radio-text-container { + background: #f5f5f5; + height: 51px; + width: 625px; +} diff --git a/frontend/src/app/shared/dialog/checkin/check-in-form-ordinal/check-in-form-ordinal.component.spec.ts b/frontend/src/app/shared/dialog/checkin/check-in-form-ordinal/check-in-form-ordinal.component.spec.ts new file mode 100644 index 0000000000..f6caf13f15 --- /dev/null +++ b/frontend/src/app/shared/dialog/checkin/check-in-form-ordinal/check-in-form-ordinal.component.spec.ts @@ -0,0 +1,83 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; + +import { CheckInFormOrdinalComponent } from './check-in-form-ordinal.component'; +import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; +import { keyResultOrdinalMin } from '../../../testData'; +import { KeyResultOrdinal } from '../../../types/model/KeyResultOrdinal'; +import { MatDialogModule } from '@angular/material/dialog'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { MatSelectModule } from '@angular/material/select'; +import { MatInputModule } from '@angular/material/input'; +import { MatRadioModule } from '@angular/material/radio'; +import { Zone } from '../../../types/enums/Zone'; +import { MatRadioButtonHarness } from '@angular/material/radio/testing'; +import { HarnessLoader } from '@angular/cdk/testing'; +import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; + +describe('CheckInFormOrdinalComponent', () => { + let component: CheckInFormOrdinalComponent; + let fixture: ComponentFixture; + let loader: HarnessLoader; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + MatDialogModule, + NoopAnimationsModule, + MatSelectModule, + MatInputModule, + MatRadioModule, + ReactiveFormsModule, + ], + declarations: [CheckInFormOrdinalComponent], + }); + fixture = TestBed.createComponent(CheckInFormOrdinalComponent); + component = fixture.componentInstance; + component.keyResult = keyResultOrdinalMin as unknown as KeyResultOrdinal; + component.dialogForm = new FormGroup({ + value: new FormControl('', [Validators.required]), + confidence: new FormControl(5, [Validators.required, Validators.min(1), Validators.max(10)]), + }); + fixture.detectChanges(); + loader = TestbedHarnessEnvironment.loader(fixture); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should set zone of check-in to fail if value is empty', waitForAsync(async () => { + expect(component.dialogForm.controls['value'].value).toBe(Zone.FAIL); + })); + + it('should set zone to Fail', waitForAsync(async () => { + const radioButtons = await loader.getAllHarnesses(MatRadioButtonHarness); + await radioButtons[0].check(); + expect(component.dialogForm.controls['value'].value).toBe(Zone.FAIL); + })); + + it('should set zone to Commit', waitForAsync(async () => { + const radioButtons = await loader.getAllHarnesses(MatRadioButtonHarness); + await radioButtons[1].check(); + expect(component.dialogForm.controls['value'].value).toBe(Zone.COMMIT); + })); + + it('should set zone to Target', waitForAsync(async () => { + const radioButtons = await loader.getAllHarnesses(MatRadioButtonHarness); + await radioButtons[2].check(); + expect(component.dialogForm.controls['value'].value).toBe(Zone.TARGET); + })); + + it('should set zone to Stretch', waitForAsync(async () => { + const radioButtons = await loader.getAllHarnesses(MatRadioButtonHarness); + await radioButtons[3].check(); + expect(component.dialogForm.controls['value'].value).toBe(Zone.STRETCH); + })); + + it('should be able to switch options ', waitForAsync(async () => { + const radioButtons = await loader.getAllHarnesses(MatRadioButtonHarness); + await radioButtons[3].check(); + await radioButtons[1].check(); + expect(component.dialogForm.controls['value'].value).toBe(Zone.COMMIT); + })); +}); diff --git a/frontend/src/app/shared/dialog/checkin/check-in-form-ordinal/check-in-form-ordinal.component.ts b/frontend/src/app/shared/dialog/checkin/check-in-form-ordinal/check-in-form-ordinal.component.ts new file mode 100644 index 0000000000..e1e31e2f94 --- /dev/null +++ b/frontend/src/app/shared/dialog/checkin/check-in-form-ordinal/check-in-form-ordinal.component.ts @@ -0,0 +1,26 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { FormGroup } from '@angular/forms'; +import { KeyResultOrdinal } from '../../../types/model/KeyResultOrdinal'; +import { Zone } from '../../../types/enums/Zone'; +import { CheckInMin } from '../../../types/model/CheckInMin'; + +@Component({ + selector: 'app-check-in-form-ordinal', + templateUrl: './check-in-form-ordinal.component.html', + styleUrls: ['./check-in-form-ordinal.component.scss'], +}) +export class CheckInFormOrdinalComponent implements OnInit { + @Input() + keyResult!: KeyResultOrdinal; + @Input() + checkIn!: CheckInMin; + @Input() + dialogForm!: FormGroup; + protected readonly Zone = Zone; + + ngOnInit(): void { + if (this.dialogForm.controls['value'].value === '') { + this.dialogForm.controls['value'].setValue(Zone.FAIL); + } + } +} diff --git a/frontend/src/app/shared/dialog/checkin/check-in-form/check-in-form.component.html b/frontend/src/app/shared/dialog/checkin/check-in-form/check-in-form.component.html new file mode 100644 index 0000000000..24f5094d38 --- /dev/null +++ b/frontend/src/app/shared/dialog/checkin/check-in-form/check-in-form.component.html @@ -0,0 +1,57 @@ + +
+

+ Check-in erfassen ({{ currentDate | date: "dd.MM.yyyy" }}) +

+

+ Check-in bearbeiten ({{ checkIn.createdOn | date: "dd.MM.yyyy" }}) +

+

(1/2)

+

(2/2)

+
+ +
+

Key Result

+
+ {{ keyResult.lastCheckIn?.changeInfo }} +
+
+ + + +
+
+ +
+ +
+
+ +
+ + + +
diff --git a/frontend/src/app/shared/dialog/checkin/check-in-form/check-in-form.component.scss b/frontend/src/app/shared/dialog/checkin/check-in-form/check-in-form.component.scss new file mode 100644 index 0000000000..65fd2ef77e --- /dev/null +++ b/frontend/src/app/shared/dialog/checkin/check-in-form/check-in-form.component.scss @@ -0,0 +1,15 @@ +@import "../style/variables"; + +.submit { + background-color: $pz-dark-blue; + border-radius: 8px; +} + +.cancel { + background-color: white; + color: $pz-dark-blue; +} + +.disabled { + background-color: gray; +} diff --git a/frontend/src/app/shared/dialog/checkin/check-in-form/check-in-form.component.spec.ts b/frontend/src/app/shared/dialog/checkin/check-in-form/check-in-form.component.spec.ts new file mode 100644 index 0000000000..bdf2f8baa4 --- /dev/null +++ b/frontend/src/app/shared/dialog/checkin/check-in-form/check-in-form.component.spec.ts @@ -0,0 +1,116 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; + +import { CheckInFormComponent } from './check-in-form.component'; +import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog'; +import { checkInMetric, checkInOrdinal, keyResultMetric, keyResultOrdinal } from '../../../testData'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { MatIconModule } from '@angular/material/icon'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatSelectModule } from '@angular/material/select'; +import { ReactiveFormsModule } from '@angular/forms'; +import { MatInputModule } from '@angular/material/input'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { ParseUnitValuePipe } from '../../../pipes/parse-unit-value/parse-unit-value.pipe'; + +const dialogMock = { + close: jest.fn(), +}; + +describe('CheckInFormComponent', () => { + let component: CheckInFormComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + HttpClientTestingModule, + MatDialogModule, + MatIconModule, + MatFormFieldModule, + MatSelectModule, + ReactiveFormsModule, + MatInputModule, + NoopAnimationsModule, + MatCheckboxModule, + ], + providers: [ + { provide: MAT_DIALOG_DATA, useValue: { keyResult: {} } }, + { provide: MatDialogRef, useValue: dialogMock }, + ParseUnitValuePipe, + ], + declarations: [CheckInFormComponent], + }); + fixture = TestBed.createComponent(CheckInFormComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should return data of check-in correctly if key result is metric', waitForAsync(async () => { + component.checkIn = checkInMetric; + component.keyResult = keyResultMetric; + component.dialogForm.controls['value'].setValue(checkInMetric?.value!.toString()); + component.dialogForm.controls['confidence'].setValue(checkInMetric.confidence); + component.dialogForm.controls['changeInfo'].setValue(checkInMetric.changeInfo); + component.dialogForm.controls['initiatives'].setValue(checkInMetric.initiatives); + + component.saveCheckIn(); + + expect(dialogMock.close).toHaveBeenCalledWith({ + data: { + confidence: checkInMetric.confidence, + value: checkInMetric.value, + changeInfo: checkInMetric.changeInfo, + initiatives: checkInMetric.initiatives, + keyResultId: keyResultMetric.id, + }, + }); + })); + + it('should return data of check-in correctly if key result is ordinal', waitForAsync(async () => { + component.checkIn = checkInOrdinal; + component.keyResult = keyResultOrdinal; + component.dialogForm.controls['value'].setValue(checkInOrdinal?.value as string); + component.dialogForm.controls['confidence'].setValue(checkInOrdinal.confidence); + component.dialogForm.controls['changeInfo'].setValue(checkInOrdinal.changeInfo); + component.dialogForm.controls['initiatives'].setValue(checkInOrdinal.initiatives); + + component.saveCheckIn(); + + expect(dialogMock.close).toHaveBeenCalledWith({ + data: { + confidence: checkInOrdinal.confidence, + value: checkInOrdinal.value, + changeInfo: checkInOrdinal.changeInfo, + initiatives: checkInOrdinal.initiatives, + keyResultId: keyResultOrdinal.id, + }, + }); + })); + + it('should set default values if form check-in input is not null', waitForAsync(async () => { + component.data.checkIn = checkInMetric; + component.setDefaultValues(); + expect(component.dialogForm.value).toStrictEqual({ + confidence: checkInMetric.confidence, + value: checkInMetric.value!.toString(), + changeInfo: checkInMetric.changeInfo, + initiatives: checkInMetric.initiatives, + }); + })); + + it('should set default values if last check-in of key result is not null', waitForAsync(async () => { + component.keyResult = keyResultOrdinal; + component.setDefaultValues(); + expect(component.dialogForm.value).toStrictEqual({ + confidence: keyResultOrdinal.lastCheckIn!.confidence, + value: keyResultOrdinal.lastCheckIn!.value, + changeInfo: '', + initiatives: '', + }); + })); +}); diff --git a/frontend/src/app/shared/dialog/checkin/check-in-form/check-in-form.component.ts b/frontend/src/app/shared/dialog/checkin/check-in-form/check-in-form.component.ts new file mode 100644 index 0000000000..c59217f179 --- /dev/null +++ b/frontend/src/app/shared/dialog/checkin/check-in-form/check-in-form.component.ts @@ -0,0 +1,78 @@ +import { Component, Inject } from '@angular/core'; +import { KeyResultMetric } from '../../../types/model/KeyResultMetric'; +import { FormControl, FormGroup, Validators } from '@angular/forms'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { KeyResult } from '../../../types/model/KeyResult'; +import { KeyResultOrdinal } from '../../../types/model/KeyResultOrdinal'; +import { CheckInMin } from '../../../types/model/CheckInMin'; +import { ParseUnitValuePipe } from '../../../pipes/parse-unit-value/parse-unit-value.pipe'; + +@Component({ + selector: 'app-check-in-form', + templateUrl: './check-in-form.component.html', + styleUrls: ['./check-in-form.component.scss'], +}) +export class CheckInFormComponent { + keyResult: KeyResult; + checkIn!: CheckInMin; + currentDate: Date; + continued: boolean = false; + + dialogForm = new FormGroup({ + value: new FormControl('', [Validators.required]), + confidence: new FormControl(5, [Validators.required, Validators.min(1), Validators.max(10)]), + changeInfo: new FormControl('', [Validators.maxLength(4096)]), + initiatives: new FormControl('', [Validators.maxLength(4096)]), + }); + + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: any, + public parserPipe: ParseUnitValuePipe, + ) { + this.currentDate = new Date(); + this.keyResult = data.keyResult; + this.setDefaultValues(); + } + + setDefaultValues() { + if (this.data.checkIn != null) { + this.checkIn = this.data.checkIn; + this.dialogForm.controls.value.setValue(this.checkIn.value!.toString()); + this.dialogForm.controls.confidence.setValue(this.checkIn.confidence); + this.dialogForm.controls.changeInfo.setValue(this.checkIn.changeInfo); + this.dialogForm.controls.initiatives.setValue(this.checkIn.initiatives); + return; + } + /* If KeyResult has lastCheckIn set checkIn to this value */ + if (this.keyResult.lastCheckIn != null) { + this.checkIn = this.keyResult.lastCheckIn; + this.dialogForm.controls.value.setValue(this.checkIn.value!.toString()); + this.dialogForm.controls.confidence.setValue(this.checkIn.confidence); + return; + } + /* If Check-in is null set as object with confidence 5 default value */ + this.checkIn = { confidence: 5 } as CheckInMin; + } + + saveCheckIn() { + this.dialogForm.controls.confidence.setValue(this.checkIn.confidence); + let checkIn: any = { ...this.dialogForm.value, keyResultId: this.keyResult.id }; + if (this.keyResult.keyResultType === 'metric') { + checkIn = { + ...this.dialogForm.value, + value: this.parserPipe.transform(this.dialogForm?.controls['value'].value!), + keyResultId: this.keyResult.id, + }; + } + this.dialogRef.close({ data: checkIn }); + } + + getKeyResultMetric(): KeyResultMetric { + return this.keyResult as KeyResultMetric; + } + + getKeyResultOrdinal(): KeyResultOrdinal { + return this.keyResult as KeyResultOrdinal; + } +} diff --git a/frontend/src/app/shared/dialog/confirm-dialog/confirm-dialog.component.html b/frontend/src/app/shared/dialog/confirm-dialog/confirm-dialog.component.html index b223838f27..dc62ef17b9 100644 --- a/frontend/src/app/shared/dialog/confirm-dialog/confirm-dialog.component.html +++ b/frontend/src/app/shared/dialog/confirm-dialog/confirm-dialog.component.html @@ -1,5 +1,5 @@ -

Key Result löschen

-
Soll dieses Key Result gelöscht werden?
+

{{ this.data.title }} löschen

+
Soll dieses {{ this.data.title }} gelöscht werden?
diff --git a/frontend/src/app/shared/dialog/confirm-dialog/confirm-dialog.component.spec.ts b/frontend/src/app/shared/dialog/confirm-dialog/confirm-dialog.component.spec.ts index f93f2af047..5efa8680fa 100644 --- a/frontend/src/app/shared/dialog/confirm-dialog/confirm-dialog.component.spec.ts +++ b/frontend/src/app/shared/dialog/confirm-dialog/confirm-dialog.component.spec.ts @@ -1,41 +1,64 @@ -import { ComponentFixture, fakeAsync, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ConfirmDialogComponent } from './confirm-dialog.component'; import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog'; +import { HarnessLoader } from '@angular/cdk/testing'; +import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; +import { MatButtonHarness } from '@angular/material/button/testing'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { MatSelectModule } from '@angular/material/select'; +import { MatInputModule } from '@angular/material/input'; +import { MatRadioModule } from '@angular/material/radio'; import { ReactiveFormsModule } from '@angular/forms'; +const dialogMock = { + close: jest.fn(), +}; + describe('ConfirmDialogComponent', () => { let component: ConfirmDialogComponent; let fixture: ComponentFixture; + let loader: HarnessLoader; beforeEach(() => { TestBed.configureTestingModule({ - imports: [MatDialogModule, ReactiveFormsModule], - providers: [ - { - provide: MatDialogRef, - useValue: {}, - }, - { - provide: MAT_DIALOG_DATA, - useValue: {}, - }, + imports: [ + MatDialogModule, + NoopAnimationsModule, + MatSelectModule, + MatInputModule, + MatRadioModule, + ReactiveFormsModule, ], declarations: [ConfirmDialogComponent], + providers: [ + { provide: MAT_DIALOG_DATA, useValue: {} }, + { provide: MatDialogRef, useValue: dialogMock }, + ], }); fixture = TestBed.createComponent(ConfirmDialogComponent); component = fixture.componentInstance; fixture.detectChanges(); + loader = TestbedHarnessEnvironment.loader(fixture); }); - it('should create', fakeAsync(() => { + it('should create', () => { expect(component).toBeTruthy(); - })); - - it('should have two buttons', fakeAsync(() => { - const buttons = document.querySelectorAll('button'); - expect(buttons.length).toEqual(2); - expect(buttons[0].innerHTML).toEqual('Ja'); - expect(buttons[1].innerHTML).toEqual('Nein'); - })); + }); + + it('should call close method with parameter: true if clicked to submit button', async () => { + let buttons = await loader.getAllHarnesses(MatButtonHarness); + const submitButton = buttons[0]; + await submitButton.click(); + + expect(dialogMock.close).toHaveBeenCalledWith(true); + }); + + it('should call close method with parameter: "" if clicked to cancel button', async () => { + let buttons = await loader.getAllHarnesses(MatButtonHarness); + const cancelButton = buttons[1]; + await cancelButton.click(); + + expect(dialogMock.close).toHaveBeenCalledWith(''); + }); }); diff --git a/frontend/src/app/shared/dialog/confirm-dialog/confirm-dialog.component.ts b/frontend/src/app/shared/dialog/confirm-dialog/confirm-dialog.component.ts index d9105497d2..d3944267bf 100644 --- a/frontend/src/app/shared/dialog/confirm-dialog/confirm-dialog.component.ts +++ b/frontend/src/app/shared/dialog/confirm-dialog/confirm-dialog.component.ts @@ -1,16 +1,18 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { MatDialogRef } from '@angular/material/dialog'; +import { Component, Inject } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; @Component({ selector: 'app-confirm-dialog', templateUrl: './confirm-dialog.component.html', styleUrls: ['./confirm-dialog.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, }) export class ConfirmDialogComponent { - constructor(public dialogRef: MatDialogRef) {} + constructor( + @Inject(MAT_DIALOG_DATA) public data: any, + public dialogRef: MatDialogRef, + ) {} closeAndDelete() { - this.dialogRef.close('deleteKeyResult'); + this.dialogRef.close(true); } } diff --git a/frontend/src/app/shared/pipes/parse-unit-value/parse-unit-value.pipe.spec.ts b/frontend/src/app/shared/pipes/parse-unit-value/parse-unit-value.pipe.spec.ts new file mode 100644 index 0000000000..481b73b0ee --- /dev/null +++ b/frontend/src/app/shared/pipes/parse-unit-value/parse-unit-value.pipe.spec.ts @@ -0,0 +1,28 @@ +import { ParseUnitValuePipe } from './parse-unit-value.pipe'; + +describe('ParseUnitValuePipe', () => { + it('create an instance', () => { + const pipe = new ParseUnitValuePipe(); + expect(pipe).toBeTruthy(); + }); + + it('should replace characters at end of string', () => { + const pipe = new ParseUnitValuePipe(); + expect(pipe.transform('200HelloWorld')).toBe(200); + }); + + it('should replace characters at beginning of string', () => { + const pipe = new ParseUnitValuePipe(); + expect(pipe.transform('HelloWorld200')).toBe(200); + }); + + it('should replace characters between strings', () => { + const pipe = new ParseUnitValuePipe(); + expect(pipe.transform("200'000")).toBe(200000); + }); + + it('should replace special characters', () => { + const pipe = new ParseUnitValuePipe(); + expect(pipe.transform('1050&%ç*')).toBe(1050); + }); +}); diff --git a/frontend/src/app/shared/pipes/parse-unit-value/parse-unit-value.pipe.ts b/frontend/src/app/shared/pipes/parse-unit-value/parse-unit-value.pipe.ts new file mode 100644 index 0000000000..571861a362 --- /dev/null +++ b/frontend/src/app/shared/pipes/parse-unit-value/parse-unit-value.pipe.ts @@ -0,0 +1,11 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { CHAR_REGEX } from '../../regexLibrary'; + +@Pipe({ + name: 'parseUnitValue', +}) +export class ParseUnitValuePipe implements PipeTransform { + transform(value: string): number { + return +value.replace(CHAR_REGEX, ''); + } +} diff --git a/frontend/src/app/shared/pipes/unit-label-transformation/unit-label-transformation.pipe.spec.ts b/frontend/src/app/shared/pipes/unit-label-transformation/unit-label-transformation.pipe.spec.ts new file mode 100644 index 0000000000..5654bf7314 --- /dev/null +++ b/frontend/src/app/shared/pipes/unit-label-transformation/unit-label-transformation.pipe.spec.ts @@ -0,0 +1,35 @@ +import { UnitLabelTransformationPipe } from './unit-label-transformation.pipe'; +import { Unit } from '../../types/enums/Unit'; + +describe('UnitLabelTransformationPipe', () => { + it('create an instance', () => { + const pipe = new UnitLabelTransformationPipe(); + expect(pipe).toBeTruthy(); + }); + + it('Format Percent label', () => { + const pipe = new UnitLabelTransformationPipe(); + expect(pipe.transform(Unit.PERCENT)).toBe(''); + }); + + it('Format FTE label', () => { + const pipe = new UnitLabelTransformationPipe(); + expect(pipe.transform(Unit.FTE)).toBe(Unit.FTE); + }); + + it('Format CHF label', () => { + const pipe = new UnitLabelTransformationPipe(); + expect(pipe.transform(Unit.CHF)).toBe(Unit.CHF); + }); + + it('Format Number label', () => { + const pipe = new UnitLabelTransformationPipe(); + expect(pipe.transform(Unit.NUMBER)).toBe('Zahl'); + }); + + it('Format non-default label', () => { + const pipe = new UnitLabelTransformationPipe(); + const nonDefaultUnit = 'MEMBERS'; + expect(pipe.transform(nonDefaultUnit)).toBe(nonDefaultUnit); + }); +}); diff --git a/frontend/src/app/shared/pipes/unit-label-transformation/unit-label-transformation.pipe.ts b/frontend/src/app/shared/pipes/unit-label-transformation/unit-label-transformation.pipe.ts new file mode 100644 index 0000000000..71d518302d --- /dev/null +++ b/frontend/src/app/shared/pipes/unit-label-transformation/unit-label-transformation.pipe.ts @@ -0,0 +1,23 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { Unit } from '../../types/enums/Unit'; +import translation from '../../../../assets/i18n/de.json'; + +@Pipe({ + name: 'unitLabelTransformation', +}) +export class UnitLabelTransformationPipe implements PipeTransform { + transform(unitLabel: String): String { + switch (unitLabel) { + case Unit.PERCENT: + return ''; + case Unit.FTE: + return Unit.FTE; + case Unit.CHF: + return Unit.CHF; + case Unit.NUMBER: + return translation.UNIT.NUMBER; + default: + return unitLabel; + } + } +} diff --git a/frontend/src/app/shared/pipes/unit-value-transformation/unit-transformation.pipe.spec.ts b/frontend/src/app/shared/pipes/unit-value-transformation/unit-transformation.pipe.spec.ts new file mode 100644 index 0000000000..9578c36a56 --- /dev/null +++ b/frontend/src/app/shared/pipes/unit-value-transformation/unit-transformation.pipe.spec.ts @@ -0,0 +1,39 @@ +import { UnitValueTransformationPipe } from './unit-value-transformation.pipe'; +import { Unit } from '../../types/enums/Unit'; + +describe('UnitTransformationPipe', () => { + it('create an instance', () => { + const pipe = new UnitValueTransformationPipe(); + expect(pipe).toBeTruthy(); + }); + + it('should format as Percent', () => { + const pipe = new UnitValueTransformationPipe(); + expect(pipe.transform(380, Unit.PERCENT)).toBe('380%'); + }); + + it('should format as Number', () => { + const pipe = new UnitValueTransformationPipe(); + expect(pipe.transform(380, Unit.NUMBER)).toBe('380'); + }); + + it('should format as FTE', () => { + const pipe = new UnitValueTransformationPipe(); + expect(pipe.transform(380, Unit.FTE)).toBe('380'); + }); + + it('should format as CHF without double value', () => { + const pipe = new UnitValueTransformationPipe(); + expect(pipe.transform(380, Unit.CHF)).toBe('380.-'); + }); + + it('should format as CHF as double value', () => { + const pipe = new UnitValueTransformationPipe(); + expect(pipe.transform(380.987, Unit.CHF)).toBe('380.99'); + }); + + it('should return with no format if unit is not preset one', () => { + const pipe = new UnitValueTransformationPipe(); + expect(pipe.transform(140, 'MEMBERS')).toBe('140'); + }); +}); diff --git a/frontend/src/app/shared/pipes/unit-value-transformation/unit-value-transformation.pipe.ts b/frontend/src/app/shared/pipes/unit-value-transformation/unit-value-transformation.pipe.ts new file mode 100644 index 0000000000..db639dc6a2 --- /dev/null +++ b/frontend/src/app/shared/pipes/unit-value-transformation/unit-value-transformation.pipe.ts @@ -0,0 +1,26 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { formatCurrency } from '@angular/common'; +import { Unit } from '../../types/enums/Unit'; + +@Pipe({ + name: 'unitValueTransformation', +}) +export class UnitValueTransformationPipe implements PipeTransform { + transform(value: number, unit: String): string { + if (Number.isNaN(value)) { + value = 0; + } + switch (unit) { + case Unit.CHF: + return value % 1 != 0 ? formatCurrency(value, 'en', '') : value + '.-'; + case Unit.PERCENT: + return value + '%'; + case Unit.FTE: + return value.toString(); + case Unit.NUMBER: + return value.toString(); + default: + return value.toString(); + } + } +} diff --git a/frontend/src/app/shared/regexLibrary.ts b/frontend/src/app/shared/regexLibrary.ts index 812d6cc364..4ab3ae50dc 100644 --- a/frontend/src/app/shared/regexLibrary.ts +++ b/frontend/src/app/shared/regexLibrary.ts @@ -1,2 +1,3 @@ export const PERCENT_REGEX = '^-?[0-9][0-9]?(\\.[0-9]*)?$|^100$'; export const NUMBER_REGEX = '^-?[0-9][0-9]*.?[0-9]*?$'; +export const CHAR_REGEX = /[^0-9.]/g; diff --git a/frontend/src/app/shared/services/check-in.service.ts b/frontend/src/app/shared/services/check-in.service.ts index 768f4ea8d4..d3e448cfa5 100644 --- a/frontend/src/app/shared/services/check-in.service.ts +++ b/frontend/src/app/shared/services/check-in.service.ts @@ -2,6 +2,7 @@ import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { CheckInMin } from '../types/model/CheckInMin'; import { Observable } from 'rxjs'; +import { CheckIn } from '../types/model/CheckIn'; @Injectable({ providedIn: 'root', @@ -12,4 +13,16 @@ export class CheckInService { getAllCheckInOfKeyResult(keyResultId: number): Observable { return this.httpclient.get(`/api/v2/keyresults/${keyResultId}/checkins`); } + + createCheckIn(checkIn: any): Observable { + return this.httpclient.post('/api/v2/checkIns', checkIn); + } + + updateCheckIn(checkIn: any, id: number): Observable { + return this.httpclient.put('/api/v2/checkIns/' + id, checkIn); + } + + deleteCheckIn(id: number) { + return this.httpclient.delete('/api/v2/checkIns/' + id); + } } diff --git a/frontend/src/app/shared/services/notifier.service.ts b/frontend/src/app/shared/services/notifier.service.ts index bf8c756577..2b075abdef 100644 --- a/frontend/src/app/shared/services/notifier.service.ts +++ b/frontend/src/app/shared/services/notifier.service.ts @@ -1,5 +1,6 @@ import { Injectable } from '@angular/core'; import { Subject } from 'rxjs'; +import { CheckIn } from '../types/model/CheckIn'; import { KeyResult } from '../types/model/KeyResult'; import { Objective } from '../types/model/Objective'; @@ -8,6 +9,10 @@ import { Objective } from '../types/model/Objective'; }) export class NotifierService { closeDetailSubject: Subject = new Subject(); + reopenCheckInHistoryDialog: Subject<{ checkIn: CheckIn | null; deleted: boolean }> = new Subject<{ + checkIn: CheckIn | null; + deleted: boolean; + }>(); keyResultsChanges: Subject<{ keyResult: KeyResult; changeId: number | null; objective: Objective; delete: boolean }> = new Subject<{ diff --git a/frontend/src/app/shared/testData.ts b/frontend/src/app/shared/testData.ts index bbe8d34eac..7f06e3db1f 100644 --- a/frontend/src/app/shared/testData.ts +++ b/frontend/src/app/shared/testData.ts @@ -12,6 +12,8 @@ import { Quarter } from './types/model/Quarter'; import { KeyResultOrdinal } from './types/model/KeyResultOrdinal'; import { CheckIn } from './types/model/CheckIn'; import { User } from './types/model/User'; +import { KeyResultMetric } from './types/model/KeyResultMetric'; +import { Unit } from './types/enums/Unit'; export const team1: TeamMin = { id: 1, @@ -28,12 +30,16 @@ export const checkInMetric: CheckInMin = { value: 15, confidence: 5, createdOn: '2023-07-20T12:34:56Z' as unknown as Date, + initiatives: 'Initiatives metric', + changeInfo: 'Changeinfo metric', } as CheckInMin; export const checkInOrdinal: CheckInMin = { id: 816, value: 'COMMIT', confidence: 7, createdOn: '2023-07-22T08:45:21Z' as unknown as Date, + initiatives: 'Initiatives ordinal', + changeInfo: 'Changeinfo ordinal', } as CheckInMin; export const keyResultMetricMin: KeyResultMetricMin = { id: 201, @@ -151,3 +157,67 @@ export const keyResult: KeyResultOrdinal = { createdOn: new Date(), modifiedOn: new Date(), }; + +export const keyResultOrdinal: KeyResultOrdinal = { + id: 101, + title: 'Bauen eines Hauses', + description: 'Ein neues Haus für die Puzzle Members', + commitZone: 'Grundriss steht', + targetZone: 'Gebäude gebaut', + stretchZone: 'Inneneinrichtung gestaltet', + owner: { id: 1, firstname: 'firstname', lastname: 'lastname' }, + keyResultType: 'ordinal', + objective: { + id: 301, + state: State.DRAFT, + quarter: { + id: 1, + label: 'GJ 23/24-Q1', + startDate: new Date(), + endDate: new Date(), + } as Quarter, + } as Objective, + lastCheckIn: { + id: 746, + value: 'FAIL', + confidence: 3, + createdOn: new Date(), + modifiedOn: new Date(), + changeInfo: 'Does not look good', + initiatives: 'We have to be faster', + } as CheckIn, + createdOn: new Date(), + modifiedOn: new Date(), +}; + +export const keyResultMetric: KeyResultMetric = { + id: 102, + title: '100% aller Schweizer Kunden betreuen', + description: 'Puzzle ITC erledigt die IT-Aufträge für 100% aller Unternehmen.', + baseline: 30, + stretchGoal: 100, + unit: Unit.PERCENT, + owner: { id: 1, firstname: 'firstname', lastname: 'lastname' }, + keyResultType: 'metric', + objective: { + id: 302, + state: State.DRAFT, + quarter: { + id: 1, + label: 'GJ 23/24-Q1', + startDate: new Date(), + endDate: new Date(), + } as Quarter, + } as Objective, + lastCheckIn: { + id: 746, + value: 45, + confidence: 7, + createdOn: new Date(), + modifiedOn: new Date(), + changeInfo: 'So far so good', + initiatives: 'Work a bit harder', + } as CheckIn, + createdOn: new Date(), + modifiedOn: new Date(), +}; diff --git a/frontend/src/app/shared/types/enums/Unit.ts b/frontend/src/app/shared/types/enums/Unit.ts new file mode 100644 index 0000000000..0da371a27c --- /dev/null +++ b/frontend/src/app/shared/types/enums/Unit.ts @@ -0,0 +1,6 @@ +export enum Unit { + PERCENT = 'PERCENT', + CHF = 'CHF', + FTE = 'FTE', + NUMBER = 'NUMBER', +} diff --git a/frontend/src/app/shared/types/enums/Zone.ts b/frontend/src/app/shared/types/enums/Zone.ts new file mode 100644 index 0000000000..6610eef36e --- /dev/null +++ b/frontend/src/app/shared/types/enums/Zone.ts @@ -0,0 +1,6 @@ +export enum Zone { + FAIL = 'FAIL', + COMMIT = 'COMMIT', + TARGET = 'TARGET', + STRETCH = 'STRETCH', +}