diff --git a/alfa-client/apps/alfa-e2e/src/e2e/main-tests/postfach-mail/postfach-mail.cy.ts b/alfa-client/apps/alfa-e2e/src/e2e/main-tests/postfach-mail/postfach-mail.cy.ts index 911f2fd2e419de34cc02083bde0866b2ec5a185e..8f32eb567802ce78e439567c3cdc4bcfb5c0bfb8 100644 --- a/alfa-client/apps/alfa-e2e/src/e2e/main-tests/postfach-mail/postfach-mail.cy.ts +++ b/alfa-client/apps/alfa-e2e/src/e2e/main-tests/postfach-mail/postfach-mail.cy.ts @@ -111,6 +111,7 @@ describe('PostfachMail', () => { }; const vorgangWithoutPostfach: VorgangE2E = createVorgangWithoutPostfachId(); + function createVorgangWithoutPostfachId(): VorgangE2E { return { ...buildVorgang(objectIds[2], 'VorgangWithoutPostfachId'), @@ -523,8 +524,8 @@ describe('PostfachMail', () => { exist(postfachMailPage.getSubnavigation().getBackButton()); }); - it('should show breadcrumb', () => { - contains(postfachMailPage.getBreadcrump(), vorgangWithReply.aktenzeichen); + it('should show Nachrichten heading', () => { + contains(postfachMailPage.getHeading(), postfachMailPage.getHeadingText()); }); }); diff --git a/alfa-client/apps/alfa-e2e/src/e2e/main-tests/postfach-mail/postfach-mail.filtered-by-organisationseinheit.cy.ts b/alfa-client/apps/alfa-e2e/src/e2e/main-tests/postfach-mail/postfach-mail.filtered-by-organisationseinheit.cy.ts index 40940b37bbf3e171df48dac38acb764903278f66..3b132859b84722b706bf2b2bf90147331b553595 100644 --- a/alfa-client/apps/alfa-e2e/src/e2e/main-tests/postfach-mail/postfach-mail.filtered-by-organisationseinheit.cy.ts +++ b/alfa-client/apps/alfa-e2e/src/e2e/main-tests/postfach-mail/postfach-mail.filtered-by-organisationseinheit.cy.ts @@ -92,7 +92,7 @@ describe('PostfachNachrichten filtered by organisationseinheit', () => { visitUrl(authorizedUrl); waitForSpinnerToDisappear(); - exist(postfachMailPage.getBreadcrump()); + exist(postfachMailPage.getHeading()); }); it('should not open postfachNachrichten page', () => { @@ -100,7 +100,7 @@ describe('PostfachNachrichten filtered by organisationseinheit', () => { visitUrl(forbiddenUrl); waitForSpinnerToDisappear(); - notExist(postfachMailPage.getBreadcrump()); + notExist(postfachMailPage.getHeading()); }); it('should show snackbar', () => { @@ -125,7 +125,7 @@ describe('PostfachNachrichten filtered by organisationseinheit', () => { visitUrl(authorizedUrl); waitForSpinnerToDisappear(); - exist(postfachMailPage.getBreadcrump()); + exist(postfachMailPage.getHeading()); }); it('should not open postfachNachrichten page', () => { @@ -133,7 +133,7 @@ describe('PostfachNachrichten filtered by organisationseinheit', () => { visitUrl(forbiddenUrl); waitForSpinnerToDisappear(); - notExist(postfachMailPage.getBreadcrump()); + notExist(postfachMailPage.getHeading()); }); it('should show snackbar', () => { diff --git a/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-list-wiedervorlage/vorgang-list-wiedervorlagen.cy.ts b/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-list-wiedervorlage/vorgang-list-wiedervorlagen.cy.ts index d4344f6bafe227730065bc64264bddddc00264d4..3299c6883e8d07f18fca23e37571d1e17b525621 100644 --- a/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-list-wiedervorlage/vorgang-list-wiedervorlagen.cy.ts +++ b/alfa-client/apps/alfa-e2e/src/e2e/main-tests/vorgang-list-wiedervorlage/vorgang-list-wiedervorlagen.cy.ts @@ -141,7 +141,7 @@ describe('VorgangList Wiedervorlagen Next Frist', () => { vorgangWithWiedervorlageInFuture.name, ); - notContainClass(vorgang.getWiedervorlageNextFrist(), 'red'); + notContainClass(vorgang.getWiedervorlageNextFrist(), 'text-error'); exist(vorgang.getWiedervorlageNextFrist()); }); @@ -150,7 +150,7 @@ describe('VorgangList Wiedervorlagen Next Frist', () => { vorgangWithWiedervorlageInThePast.name, ); - containClass(vorgang.getWiedervorlageNextFrist(), 'red'); + containClass(vorgang.getWiedervorlageNextFrist(), 'text-error'); exist(vorgang.getWiedervorlageNextFrist()); }); }); diff --git a/alfa-client/apps/alfa-e2e/src/e2e/main-tests/wiedervorlage/wiedervorlage.erledigen.cy.ts b/alfa-client/apps/alfa-e2e/src/e2e/main-tests/wiedervorlage/wiedervorlage.erledigen.cy.ts index 18f70ef0d366d1d15565d424f8bfe8759c44b43a..b20cea9b1d2b31d36db3535b7526a8032f1b88ed 100644 --- a/alfa-client/apps/alfa-e2e/src/e2e/main-tests/wiedervorlage/wiedervorlage.erledigen.cy.ts +++ b/alfa-client/apps/alfa-e2e/src/e2e/main-tests/wiedervorlage/wiedervorlage.erledigen.cy.ts @@ -33,7 +33,7 @@ import { WiedervorlageInVorgangE2EComponent } from '../../../components/wiedervo import { WiedervorlageE2EComponent } from '../../../components/wiedervorlage/wiedervorlage-page.e2e.component'; import { WiedervorlageSubnavigationE2EComponent } from '../../../components/wiedervorlage/wiedervorlage-subnavigation'; import { WiedervorlagenInVorgangE2EComponent } from '../../../components/wiedervorlage/wiedervorlagen-in-vorgang.e2e.component'; -import { VorgangE2E } from '../../../model/vorgang'; +import { ClientAttributeNameE2E, VorgangE2E } from '../../../model/vorgang'; import { WiedervorlageE2E } from '../../../model/wiedervorlage'; import { MainPage, @@ -51,12 +51,20 @@ import { notExist, } from '../../../support/cypress.util'; import { loginAsSabine } from '../../../support/user-util'; -import { createVorgang, initVorgang, objectIds } from '../../../support/vorgang-util'; +import { + createAlfaClientAttributes, + createNextWiedervorlageFristClientAttribute, + createVorgang, + initVorgang, + objectIds, +} from '../../../support/vorgang-util'; import { createWiedervorlageAttachedItem, createWiedervorlageItem, } from '../../../support/wiedervorlage-util'; +const wiedervorlageItemFixture: WiedervorlageE2E = require('../../../fixtures/wiedervorlage/wiedervorlage.json'); + describe('Wiedervorlage erledigen/wiedereroeffnen', () => { const mainPage: MainPage = new MainPage(); @@ -76,7 +84,13 @@ describe('Wiedervorlage erledigen/wiedereroeffnen', () => { const wiedervorlageContainer: WiedervorlageE2EComponent = wiedervorlagePage.getWiedervorlageContainer(); - const vorgang: VorgangE2E = createVorgang(); + const vorgang: VorgangE2E = { + ...createVorgang(), + clientAttributes: createAlfaClientAttributes( + ClientAttributeNameE2E.NEXT_WIEDERVORLAGE_FRIST, + createNextWiedervorlageFristClientAttribute(wiedervorlageItemFixture.frist), + ), + }; const wiedervorlageZumErledigen: WiedervorlageE2E = { ...createWiedervorlageItem('WiedervorlageZumErledigen'), @@ -208,36 +222,63 @@ describe('Wiedervorlage erledigen/wiedereroeffnen', () => { }); }); - describe('tiny pentest for erledigen/wiedereroeffnen', () => { + describe('View Wiedervorlage view icon', () => { const wiedervorlage: WiedervorlageInVorgangE2EComponent = wiedervorlageContainerInVorgang.getWiedervorlage(wiedervorlageZumWiedereroeffnen.betreff); + const locatorIconIsOverdue: string = 'wiedervorlage-icon-is-overdue'; + const locatorIconDefault: string = 'wiedervorlage-icon-default'; - it('should open wiedervorlage on click', () => { - wait(500); - wiedervorlage.getLink().click(); - + it('back to vorgang list', () => { + vorgangPage.getSubnavigation().getBackButton().click(); waitForSpinnerToDisappear(); + }); - exist(subnavigation.getRoot()); + it('should show red icon', () => { + exist(wiedervorlagenView.getRoot().findTestElementWithClass(locatorIconIsOverdue)); + }); + + it('Open Vorgang-Detail-Page', () => { + mainPage.getVorgangList().getListItem(vorgang.name).getRoot().click(); + waitForSpinnerToDisappear(); }); it('should mark as erledigt', () => { + const link = wiedervorlage.getLink(); + link.click(); + waitForSpinnerToDisappear(); subnavigation.erledigen(); + waitForSpinnerToDisappear(); - waitforSpinnerToAppear(); + subnavigation.navigateBack(); waitForSpinnerToDisappear(); + }); - containClass(wiedervorlageContainer.getStatusDot(), 'erledigt'); - contains( - snackBar.getMessage(), - `Die Wiedervorlage ${wiedervorlageZumWiedereroeffnen.betreff} wurde erledigt`, - ); + it('back to vorgang list', () => { + vorgangPage.getSubnavigation().getBackButton().click(); + waitForSpinnerToDisappear(); }); - it('should close snackBar on close', () => { - snackBar.getCloseButton().click(); + it('should show default icon', () => { + exist(wiedervorlagenView.getRoot().findTestElementWithClass(locatorIconDefault)); + }); + }); - notExist(snackBar.getMessage()); + describe('tiny lasttest for erledigen/wiedereroeffnen', () => { + const wiedervorlage: WiedervorlageInVorgangE2EComponent = + wiedervorlageContainerInVorgang.getWiedervorlage(wiedervorlageZumWiedereroeffnen.betreff); + + it('Open Vorgang-Detail-Page', () => { + mainPage.getVorgangList().getListItem(vorgang.name).getRoot().click(); + waitForSpinnerToDisappear(); + }); + + it('should open wiedervorlage on click', () => { + wait(500); + wiedervorlage.getLink().click(); + + waitForSpinnerToDisappear(); + + exist(subnavigation.getRoot()); }); it('should mark as open', () => { @@ -297,55 +338,32 @@ describe('Wiedervorlage erledigen/wiedereroeffnen', () => { notExist(snackBar.getMessage()); }); - it('should open vorgang detail on click on back', () => { - subnavigation.navigateBack(); + it('should mark as erledigt', () => { + subnavigation.erledigen(); waitforSpinnerToAppear(); waitForSpinnerToDisappear(); - exist(vorgangDetailHeader.getRoot()); - }); - }); - - //Test an die richtige Stelle verschieben und den Test vereinfachen - describe('View Wiedervorlage view icon', () => { - const wiedervorlage: WiedervorlageInVorgangE2EComponent = - wiedervorlageContainerInVorgang.getWiedervorlage(wiedervorlageZumWiedereroeffnen.betreff); - const locatorIconIsOverdue: string = 'wiedervorlage-icon-is-overdue'; - const locatorIconDefault: string = 'wiedervorlage-icon-default'; - - it('back to vorgang list', () => { - vorgangPage.getSubnavigation().getBackButton().click(); - waitForSpinnerToDisappear(); + containClass(wiedervorlageContainer.getStatusDot(), 'erledigt'); + contains( + snackBar.getMessage(), + `Die Wiedervorlage ${wiedervorlageZumWiedereroeffnen.betreff} wurde erledigt`, + ); }); - it('should show red icon', () => { - exist(wiedervorlagenView.getRoot().findTestElementWithClass(locatorIconIsOverdue)); - }); + it('should close snackBar on close', () => { + snackBar.getCloseButton().click(); - it('Open Vorgang-Detail-Page', () => { - mainPage.getVorgangList().getListItem(vorgang.name).getRoot().click(); - waitForSpinnerToDisappear(); + notExist(snackBar.getMessage()); }); - it('should mark as erledigt', () => { - wait(500, 'Flaky - page content changed while waiting'); - const link = wiedervorlage.getLink(); - link.click(); - waitForSpinnerToDisappear(); - subnavigation.erledigen(); - waitForSpinnerToDisappear(); + it('should open vorgang detail on click on back', () => { subnavigation.navigateBack(); - waitForSpinnerToDisappear(); - }); - it('back to vorgang list', () => { - vorgangPage.getSubnavigation().getBackButton().click(); + waitforSpinnerToAppear(); waitForSpinnerToDisappear(); - }); - it('should show default icon', () => { - exist(wiedervorlagenView.getRoot().findTestElementWithClass(locatorIconDefault)); + exist(vorgangDetailHeader.getRoot()); }); }); }); diff --git a/alfa-client/apps/alfa-e2e/src/fixtures/vorgang/vorgang.json b/alfa-client/apps/alfa-e2e/src/fixtures/vorgang/vorgang.json index 7db3791c3468b1f263a22f255637f3153de69eee..fd4d23ab75d2d554467e4631abb1848acefd52f7 100644 --- a/alfa-client/apps/alfa-e2e/src/fixtures/vorgang/vorgang.json +++ b/alfa-client/apps/alfa-e2e/src/fixtures/vorgang/vorgang.json @@ -12,6 +12,7 @@ "status": "NEU", "inCreation": false, "header": { + "collaborationLevel": 0, "serviceKonto": { "type": "OSI", "postfachAddresses": [ diff --git a/alfa-client/apps/alfa-e2e/src/page-objects/postfach-mail.component.po.ts b/alfa-client/apps/alfa-e2e/src/page-objects/postfach-mail.component.po.ts index bdc5117cb20b887bf36518c3d69e953658dab84a..2fc2899824fd391130ecc1088b277d7d48a1d408 100644 --- a/alfa-client/apps/alfa-e2e/src/page-objects/postfach-mail.component.po.ts +++ b/alfa-client/apps/alfa-e2e/src/page-objects/postfach-mail.component.po.ts @@ -26,10 +26,11 @@ import { PostfachMailSubnavigation } from '../components/postfach/postfach-mail- import { PostfachMailListItem } from '../components/postfach/postfach-mail.e2e.component'; export class PostfachMailPage { - private readonly breadcrump: string = 'postfach-breadcrump'; private readonly root: string = 'postfach-mail-list'; private readonly downloadButton: string = 'postfach-pdf-export-button'; private readonly mailText: string = 'postfach-outgoing-nachricht'; + private readonly heading: string = 'postfach-mail-heading'; + private readonly headingText: string = 'Nachrichten zum Vorgang'; private readonly subnavigation: PostfachMailSubnavigation = new PostfachMailSubnavigation(); @@ -41,8 +42,12 @@ export class PostfachMailPage { return this.subnavigation; } - getBreadcrump() { - return cy.getTestElement(this.breadcrump); + getHeading() { + return cy.getTestElement(this.heading); + } + + getHeadingText() { + return this.headingText; } getListItem(subject: string): PostfachMailListItem { diff --git a/alfa-client/apps/alfa-e2e/src/support/angular.util.ts b/alfa-client/apps/alfa-e2e/src/support/angular.util.ts index 4cfea18d440c803df4eb711ab82f0b7a808b9e9f..c2a2187e14ea6dab14577d723fafb49f8f0b85cb 100644 --- a/alfa-client/apps/alfa-e2e/src/support/angular.util.ts +++ b/alfa-client/apps/alfa-e2e/src/support/angular.util.ts @@ -39,8 +39,8 @@ enum AngularElementE2E { export function hasTooltip(element: any, value: string) { mouseEnter(element); - // element.get('mat-tooltip-component').contains(value); - element.get(`div[title="${value}"]`); + element.get('mat-tooltip-component').contains(value); + // element.get(`div[title="${value}"]`); } export function isChecked(element: any) { diff --git a/alfa-client/apps/alfa/src/app/app.component.html b/alfa-client/apps/alfa/src/app/app.component.html index c6a6d8832b910a957f0760edc25f00836dc39001..089e9de91f369cb64da483eb2a4316543084ae53 100644 --- a/alfa-client/apps/alfa/src/app/app.component.html +++ b/alfa-client/apps/alfa/src/app/app.component.html @@ -28,7 +28,7 @@ <alfa-header-container [apiRootStateResource]="apiRoot"></alfa-header-container> <div class="relative ml-4 mt-16 flex flex-grow items-start justify-between"> - <main class="mat-app-background"><router-outlet></router-outlet></main> + <div class="mat-app-background relative grow"><router-outlet></router-outlet></div> <section class="mat-app-background right-nav"> <alfa-build-info diff --git a/alfa-client/apps/alfa/src/app/app.component.scss b/alfa-client/apps/alfa/src/app/app.component.scss index 5c499a2b70b35c18334696fd0862a60276f9895f..723100bf7df64890adff753bfe4d2edef28ec7e5 100644 --- a/alfa-client/apps/alfa/src/app/app.component.scss +++ b/alfa-client/apps/alfa/src/app/app.component.scss @@ -41,13 +41,6 @@ border-left: 1rem solid $background; } -main { - position: relative; - flex-grow: 1; - max-width: calc(100vw - 2.25rem); - background-color: #fff; -} - .right-nav { flex-shrink: 0; position: sticky; diff --git a/alfa-client/apps/alfa/src/styles/abstracts/_variables.scss b/alfa-client/apps/alfa/src/styles/abstracts/_variables.scss index 21fa9559b6dcd7eafb4486651d33fb27b07d89c2..cc31ef7e6b34ed79d269902dd8e6775c3bc68603 100644 --- a/alfa-client/apps/alfa/src/styles/abstracts/_variables.scss +++ b/alfa-client/apps/alfa/src/styles/abstracts/_variables.scss @@ -75,6 +75,6 @@ $alfaDarkTheme: mat.define-dark-theme( ) ); -$default-font-size: 16px; +$default-font-size: 1rem; $iconHeight: 24px; diff --git a/alfa-client/apps/alfa/src/styles/layout/_main.scss b/alfa-client/apps/alfa/src/styles/layout/_main.scss index 24416fcac3cc06cd96d4740b7b2d120833cfe3f1..9d074a09ec1c0ebff15cca11c891b4cb1ebb1e5e 100644 --- a/alfa-client/apps/alfa/src/styles/layout/_main.scss +++ b/alfa-client/apps/alfa/src/styles/layout/_main.scss @@ -36,7 +36,7 @@ // TODO Wofür ist der box-shadow? // box-shadow: inset 0 -1px 0 0 rgba(0, 0, 0, 0.08), inset 1px 0 0 rgba(0, 0, 0, 0.08), inset -1px 0 0 rgba(0, 0, 0, 0.08); position: relative; - display: block; + display: flex; min-height: calc(100vh - $header-height - $navigation-height); background-color: #fff; } diff --git a/alfa-client/apps/alfa/src/styles/main.scss b/alfa-client/apps/alfa/src/styles/main.scss index 996cd1abf1187ae258b8cfaee88756c340dc702d..bded7a1477336c22f6f4358a41da3ffbc82c14ee 100644 --- a/alfa-client/apps/alfa/src/styles/main.scss +++ b/alfa-client/apps/alfa/src/styles/main.scss @@ -57,7 +57,6 @@ @import 'libs/wiedervorlage/src/lib/wiedervorlage-list-in-vorgang-list-container/wiedervorlage-list-in-vorgang-list-container.theme'; @import 'libs/user-profile/src/lib/user-profile-in-vorgang-container/user-profile-in-vorgang/user-profile-in-vorgang.theme'; @import 'libs/user-profile/src/lib/user-profile-search-container/user-profile-search-container.theme'; -@import 'libs/vorgang-shared-ui/src/lib/vorgang-nummer/vorgang-nummer.component.theme'; @import 'libs/ui/src/lib/icon/postfach-icon/postfach-icon.component.theme'; @import 'libs/ui/src/lib/ui/button-toggle/button-toogle.theme'; @import 'libs/vorgang/src/lib/vorgang-list-page-container/vorgang-list-page/vorgang-filter-menu-container/vorgang-filter-menu/_vorgang-filter-item.theme.scss'; @@ -83,3 +82,27 @@ body.dark { @include mat.all-component-colors($alfaDarkTheme); @include custom-components-theme($alfaDarkTheme); } + +body { + --mdc-filled-text-field-label-text-size: 1rem; + --mdc-form-field-label-text-size: 12rem; +} + +// Material Datepicker font-sizes +.mat-datepicker-content { + .mat-button, .mat-fab, .mat-icon-button, .mat-mini-fab, .mat-raised-button,th,td { + font-size: 1rem + } + } + +.mdc-text-field__input { + font-size: 1rem !important; +} + +.mat-calendar-table-header th { + font-size: 0.875rem !important; +} + +.mdc-button__label { + font-size: 0.875rem !important; +} \ No newline at end of file diff --git a/alfa-client/apps/alfa/src/styles/material/_autocomplete.scss b/alfa-client/apps/alfa/src/styles/material/_autocomplete.scss index f4c86c65ffaf0fdeff8b1c1ee050f786136acdca..7cdf9cd39e3d7374043becae32bd2aca23dce90f 100644 --- a/alfa-client/apps/alfa/src/styles/material/_autocomplete.scss +++ b/alfa-client/apps/alfa/src/styles/material/_autocomplete.scss @@ -46,7 +46,7 @@ .mat-mdc-option { border-top: 1px solid rgba(0, 0, 0, 0.08); - font-size: 14px; + font-size: 0.875rem; padding: 0.5rem 1rem; margin-bottom: 0 !important; diff --git a/alfa-client/apps/alfa/src/styles/material/_dialog.scss b/alfa-client/apps/alfa/src/styles/material/_dialog.scss index ac715993faddf5592a3be69d8bf4f0191cda7639..0de26a163ce47c4047a24c95bf8823be55747fe5 100644 --- a/alfa-client/apps/alfa/src/styles/material/_dialog.scss +++ b/alfa-client/apps/alfa/src/styles/material/_dialog.scss @@ -5,9 +5,9 @@ $color-config: mat.get-color-config($theme); $primary-palette: map.get($color-config, 'primary'); - h1 { + h1.mat-mdc-dialog-title { color: mat.get-color-from-palette($primary-palette) !important; - font-size: 24px !important; + font-size: 1.5rem !important; font-weight: normal !important; } diff --git a/alfa-client/apps/alfa/src/styles/material/_expansion-panel.scss b/alfa-client/apps/alfa/src/styles/material/_expansion-panel.scss index e5c83366af924cf6831cde0584e1bbcfc79e1463..6a112124d5c3d02971ca4150048589d2516fa1da 100644 --- a/alfa-client/apps/alfa/src/styles/material/_expansion-panel.scss +++ b/alfa-client/apps/alfa/src/styles/material/_expansion-panel.scss @@ -43,7 +43,7 @@ margin-bottom: 0; font-weight: 500; margin-left: 16px; - font-size: 16px; + font-size: 1rem; } .mat-expansion-panel-body { @@ -57,7 +57,7 @@ h3 { color: inherit; - font-size: 14px !important; + font-size: 0.875rem !important; font-weight: 500 !important; margin-left: 0; } @@ -93,7 +93,7 @@ background-color: inherit; box-shadow: none; border-radius: 0; - font-size: 14px; + font-size: 0.875rem; } :host-context(.dark) .mat-expansion-panel { diff --git a/alfa-client/apps/alfa/src/styles/material/_formfield.scss b/alfa-client/apps/alfa/src/styles/material/_formfield.scss index 2e22e8b8ecf4a04dc2810a38022639466d2f3962..5cd264736c1e1a67ae882b08427d5c3239d0e895 100644 --- a/alfa-client/apps/alfa/src/styles/material/_formfield.scss +++ b/alfa-client/apps/alfa/src/styles/material/_formfield.scss @@ -26,11 +26,12 @@ ozgcloud-fixed-dialog { body.dark { mat-form-field { - --mdc-theme-error: red; - --mdc-filled-text-field-error-focus-label-text-color: red; - --mdc-outlined-text-field-error-focus-label-text-color: red; - --mdc-filled-text-field-error-label-text-color: red; - --mdc-outlined-text-field-error-label-text-color: red; - --mdc-filled-text-field-disabled-active-indicator-color: red; + --mdc-theme-error: theme('colors.error'); + --mat-form-field-error-text-color: theme('colors.error'); + --mdc-filled-text-field-error-focus-label-text-color: theme('colors.error'); + --mdc-outlined-text-field-error-focus-label-text-color: theme('colors.error'); + --mdc-filled-text-field-error-label-text-color: theme('colors.error'); + --mdc-outlined-text-field-error-label-text-color: theme('colors.error'); + --mdc-filled-text-field-disabled-active-indicator-color: theme('colors.error'); } } diff --git a/alfa-client/apps/alfa/src/styles/material/_menu.scss b/alfa-client/apps/alfa/src/styles/material/_menu.scss index 835661a949456ec3e8e6d43eeb8769b7a43b9a81..44aff52c10c60dbf929877ec4ad7ae9e8f68d7a5 100644 --- a/alfa-client/apps/alfa/src/styles/material/_menu.scss +++ b/alfa-client/apps/alfa/src/styles/material/_menu.scss @@ -19,5 +19,6 @@ alfa-help-menu { height: 40px; width: auto; padding: 0.5rem; + font-size: 0.875rem; } } diff --git a/alfa-client/apps/alfa/src/styles/material/_tabs.scss b/alfa-client/apps/alfa/src/styles/material/_tabs.scss index bee57c8e73b9d5e394d29aa191893e7861a5d667..ea2ff487ba61bf4575526a42460f329721d72a13 100644 --- a/alfa-client/apps/alfa/src/styles/material/_tabs.scss +++ b/alfa-client/apps/alfa/src/styles/material/_tabs.scss @@ -35,3 +35,6 @@ margin-right: 0 !important; } } +.mat-mdc-tab .mdc-tab__text-label { + font-size: 0.875rem; +} \ No newline at end of file diff --git a/alfa-client/apps/alfa/src/styles/material/_tooltip.scss b/alfa-client/apps/alfa/src/styles/material/_tooltip.scss index 543eb47ab24a3ce0049bb07400bcf584e2e58af7..44e46b2feb56657f26f6ac3db31b1fcee8a20c32 100644 --- a/alfa-client/apps/alfa/src/styles/material/_tooltip.scss +++ b/alfa-client/apps/alfa/src/styles/material/_tooltip.scss @@ -1,5 +1,5 @@ .mat-tooltip { - font-size: 12px !important; + font-size: 0.75rem !important; padding-top: 2px !important; padding-bottom: 2px !important; margin-top: 2px !important; diff --git a/alfa-client/apps/demo/src/app/app.component.html b/alfa-client/apps/demo/src/app/app.component.html index 81bc4dff284d5e739981af85b8d4e1cc0c4b3844..7dbdd36d8134a9a38fdd859b1371c5a0362820df 100644 --- a/alfa-client/apps/demo/src/app/app.component.html +++ b/alfa-client/apps/demo/src/app/app.component.html @@ -14,6 +14,16 @@ <nav>NAV</nav> </div> <main class="flex-auto bg-background-50 p-6"> + <div class="my-5"> + <ods-instant-search + headerText="In der OZG-Cloud" + placeholder="zuständige Stelle suchen" + [control]="instantSearchFormControl" + [searchResults]="getInstantSearchResults()" + (searchResultSelected)="selectSearchResult($event)" + (searchQueryChanged)="onSearchQueryChanged($event)" + ></ods-instant-search> + </div> <div class="w-96"> <ods-attachment-wrapper> <ods-attachment @@ -21,6 +31,7 @@ description="234 kB" fileType="pdf" isLoading="true" + loadingCaption="Mein_Bescheid.pdf wird heruntergeladen..." > </ods-attachment> <ods-attachment caption="Mein_Bescheid.xml" description="234 kB" fileType="xml"> @@ -94,7 +105,7 @@ value="abgelehnt" variant="bescheid_abgelehnt" > - <ods-close-icon class="fill-abgelehnt" /> + <ods-close-icon class="fill-abgelehnt" size="large" /> </ods-radio-button-card> </div> </form> @@ -209,14 +220,6 @@ <p text class="text-center">Bescheiddokument<br />hochladen</p></ods-file-upload-button > </div> - - <div class="mt-4"> - <ods-file-upload-button class="w-72" [isLoading]="false" id="upload129"> - <ods-bescheid-upload-icon /> - <ods-spinner-icon spinner size="medium" /> - <div text class="text-center">Anhang hochladen</div></ods-file-upload-button - > - </div> <div class="mt-4"> <ods-file-upload-button class="w-72" [isLoading]="true" id="upload130"> <ods-attachment-icon icon /> diff --git a/alfa-client/apps/demo/src/app/app.component.ts b/alfa-client/apps/demo/src/app/app.component.ts index 5d64b15a623f2330d29201455ec298907bcd94fa..40addd4c8966f682e403357a673cb578348d5213 100644 --- a/alfa-client/apps/demo/src/app/app.component.ts +++ b/alfa-client/apps/demo/src/app/app.component.ts @@ -15,6 +15,7 @@ import { ErrorMessageComponent, FileIconComponent, FileUploadButtonComponent, + InstantSearchComponent, RadioButtonCardComponent, SaveIconComponent, SendIconComponent, @@ -24,6 +25,11 @@ import { TextareaComponent, } from '@ods/system'; +import { EMPTY_STRING } from '@alfa-client/tech-shared'; +import { + InstantSearchQuery, + InstantSearchResult, +} from 'libs/design-system/src/lib/instant-search/instant-search/instant-search.model'; import { BescheidDialogExampleComponent } from './components/bescheid-dialog/bescheid-dialog.component'; import { BescheidPaperComponent } from './components/bescheid-paper/bescheid-paper.component'; import { BescheidStepperComponent } from './components/bescheid-stepper/bescheid-stepper.component'; @@ -46,6 +52,7 @@ import { CustomStepperComponent } from './components/cdk-demo/custom-stepper.com BescheidPaperComponent, RadioButtonCardComponent, ReactiveFormsModule, + InstantSearchComponent, SaveIconComponent, SendIconComponent, StampIconComponent, @@ -64,14 +71,49 @@ import { CustomStepperComponent } from './components/cdk-demo/custom-stepper.com templateUrl: './app.component.html', }) export class AppComponent { - title = 'demo'; - darkMode = signal<boolean>(JSON.parse(window.localStorage.getItem('darkMode') ?? 'false')); @HostBinding('class.dark') get mode() { return this.darkMode(); } + title = 'demo'; + + instantSearchItems: InstantSearchResult<unknown>[] = [ + { + title: 'Landeshauptstadt Kiel - Ordnungsamt, Gewerbe- und Schornsteinfegeraufsicht', + description: 'Fabrikstraße 8-10, 24103 Kiel', + data: { resource: 'dummy 1' }, + }, + { + title: 'Amt für Digitalisierung, Breitband und Vermessung Nürnberg Außenstelle Hersbruck', + description: 'Rathausmarkt 7, Hersbruck', + data: { resource: 'dummy 2' }, + }, + { + title: 'Amt für Digitalisierung, Breitband und Vermessung Stuttgart', + description: 'Rathausmarkt 7, Stuttgart', + data: { resource: 'dummy 3' }, + }, + { + title: 'Amt für Digitalisierung, Breitband und Vermessung Ulm', + description: 'Rathausmarkt 7, Ulm', + data: { resource: 'dummy 4' }, + }, + ]; + instantSearchFormControl = new FormControl(EMPTY_STRING); + + getInstantSearchResults() { + if (this.instantSearchFormControl.value.length < 2) return []; + return this.instantSearchItems.filter((item) => + item.title.toLowerCase().includes(this.instantSearchFormControl.value.toLowerCase()), + ); + } + + selectSearchResult(result: InstantSearchResult<unknown>) { + console.log(result); + } + exampleForm = new FormGroup({ exampleName: new FormControl('bewilligt'), }); @@ -87,4 +129,8 @@ export class AppComponent { window.localStorage.setItem('darkMode', JSON.stringify(this.darkMode())); }); } + + public onSearchQueryChanged(searchQuery: InstantSearchQuery) { + console.info('Search query: %o', searchQuery); + } } diff --git a/alfa-client/libs/admin-settings/src/lib/postfach/postfach-container/postfach-form/postfach-form.component.spec.ts b/alfa-client/libs/admin-settings/src/lib/postfach/postfach-container/postfach-form/postfach-form.component.spec.ts index f786c3e04a73e2bc719cacf654aaf0c01d77039d..f3e6c68f3f15f51d766224ea4add00605a4e05be 100644 --- a/alfa-client/libs/admin-settings/src/lib/postfach/postfach-container/postfach-form/postfach-form.component.spec.ts +++ b/alfa-client/libs/admin-settings/src/lib/postfach/postfach-container/postfach-form/postfach-form.component.spec.ts @@ -175,7 +175,7 @@ describe('PostfachFormComponent', () => { function createProblemDetailForAbsenderName(): ProblemDetail { return { ...createProblemDetail(), - 'invalid-params': [{ ...createInvalidParam(), name: 'settingBody.absender.name' }], + invalidParams: [{ ...createInvalidParam(), name: 'settingBody.absender.name' }], }; } diff --git a/alfa-client/libs/bescheid-shared/src/lib/+state/bescheid.facade.spec.ts b/alfa-client/libs/bescheid-shared/src/lib/+state/bescheid.facade.spec.ts index 49c3f00e0dec46f8de9e73fc639f637cc2cb73b2..329ddf97fb780671d82c886e829226a28260782d 100644 --- a/alfa-client/libs/bescheid-shared/src/lib/+state/bescheid.facade.spec.ts +++ b/alfa-client/libs/bescheid-shared/src/lib/+state/bescheid.facade.spec.ts @@ -4,7 +4,7 @@ import { CommandService, CreateCommand, } from '@alfa-client/command-shared'; -import { EMPTY_STRING, StateResource, createStateResource } from '@alfa-client/tech-shared'; +import { createStateResource, EMPTY_STRING, StateResource } from '@alfa-client/tech-shared'; import { Mock, mock, useFromMock } from '@alfa-client/test-utils'; import { VorgangWithEingangLinkRel, VorgangWithEingangResource } from '@alfa-client/vorgang-shared'; import { Store } from '@ngrx/store'; @@ -43,60 +43,22 @@ describe('BescheidFacade', () => { }); }); - describe('createBescheid', () => { + describe('createBescheidDraft', () => { const createCommand: CreateCommand = createCreateCommand(CommandOrder.CREATE_BESCHEID); - describe('with both Links', () => { - it('should call command service with CREATE_BESCHEID linkRel', () => { - const vorgang: VorgangWithEingangResource = createVorgangWithEingangResource([ - VorgangWithEingangLinkRel.CREATE_BESCHEID, - VorgangWithEingangLinkRel.CREATE_BESCHEID_DRAFT, - ]); - facade.createBescheid(vorgang, createCommand); + it('should create command', () => { + const vorgang: VorgangWithEingangResource = createVorgangWithEingangResource([ + VorgangWithEingangLinkRel.CREATE_BESCHEID_DRAFT, + ]); - expect(commandService.createCommandByProps).toHaveBeenCalledWith({ - resource: vorgang, - linkRel: VorgangWithEingangLinkRel.CREATE_BESCHEID, - command: createCommand, - }); - }); - }); - - describe('with CREATE_BESCHEID_DRAFT link', () => { - it('should call command service with CREATE_BESCHEID_DRAFT linkRel and empty snackBarMessage', () => { - const vorgang: VorgangWithEingangResource = createVorgangWithEingangResource([ - VorgangWithEingangLinkRel.CREATE_BESCHEID_DRAFT, - ]); - facade.createBescheid(vorgang, createCommand); - - expect(commandService.createCommandByProps).toHaveBeenCalledWith({ - resource: vorgang, - linkRel: VorgangWithEingangLinkRel.CREATE_BESCHEID_DRAFT, - command: createCommand, - snackBarMessage: EMPTY_STRING, - }); - }); - }); - - describe('with CREATE_BESCHEID link', () => { - it('should call command service with CREATE_BESCHEID_DRAFT linkRel', () => { - const vorgang: VorgangWithEingangResource = createVorgangWithEingangResource([ - VorgangWithEingangLinkRel.CREATE_BESCHEID, - ]); - - facade.createBescheid(vorgang, createCommand); + facade.createBescheidDraft(vorgang, createCommand); - expect(commandService.createCommandByProps).toHaveBeenCalledWith({ - resource: vorgang, - linkRel: VorgangWithEingangLinkRel.CREATE_BESCHEID, - command: createCommand, - }); + expect(commandService.createCommandByProps).toHaveBeenCalledWith({ + resource: vorgang, + linkRel: VorgangWithEingangLinkRel.CREATE_BESCHEID_DRAFT, + command: createCommand, + snackBarMessage: EMPTY_STRING, }); }); - it('should emit error if link is missing()', () => { - const vorgang: VorgangWithEingangResource = createVorgangWithEingangResource(); - - expect(() => facade.createBescheid(vorgang, createCommand)).toThrowError(); - }); }); }); diff --git a/alfa-client/libs/bescheid-shared/src/lib/+state/bescheid.facade.ts b/alfa-client/libs/bescheid-shared/src/lib/+state/bescheid.facade.ts index 35933803d5b40bdf4c79f94c3973fa1be790c471..d338a17a00b1bdfd2f6805f333036b90635a623d 100644 --- a/alfa-client/libs/bescheid-shared/src/lib/+state/bescheid.facade.ts +++ b/alfa-client/libs/bescheid-shared/src/lib/+state/bescheid.facade.ts @@ -8,7 +8,6 @@ import { EMPTY_STRING, StateResource } from '@alfa-client/tech-shared'; import { VorgangWithEingangLinkRel, VorgangWithEingangResource } from '@alfa-client/vorgang-shared'; import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; -import { hasLink } from '@ngxp/rest'; import { Observable } from 'rxjs'; import * as BescheidSelectors from './bescheid.selectors'; @@ -24,31 +23,6 @@ export class BescheidFacade { return this.store.select(BescheidSelectors.bescheidCommand); } - public createBescheid( - vorgangWithEingang: VorgangWithEingangResource, - command: CreateCommand, - ): void { - if (hasLink(vorgangWithEingang, VorgangWithEingangLinkRel.CREATE_BESCHEID)) { - return this.createBescheidKiel(vorgangWithEingang, command); - } - if (hasLink(vorgangWithEingang, VorgangWithEingangLinkRel.CREATE_BESCHEID_DRAFT)) { - return this.createBescheidDraft(vorgangWithEingang, command); - } - throw new Error('Missing Link: CREATE_BESCHEID or CREATE_BESCHEID_DRAFT expected'); - } - - public createBescheidKiel( - vorgangWithEingang: VorgangWithEingangResource, - command: CreateCommand, - ): void { - const createCommandProps: CreateCommandProps = { - resource: vorgangWithEingang, - linkRel: VorgangWithEingangLinkRel.CREATE_BESCHEID, - command, - }; - this.commandService.createCommandByProps(createCommandProps); - } - public createBescheidDraft( vorgangWithEingang: VorgangWithEingangResource, command: CreateCommand, diff --git a/alfa-client/libs/bescheid-shared/src/lib/+state/bescheid.reducer.spec.ts b/alfa-client/libs/bescheid-shared/src/lib/+state/bescheid.reducer.spec.ts index 67058f2fbdf29b3c9d0fd5623dc297961760b677..e47c8f21156c5727a43406c6cc97551058db83f8 100644 --- a/alfa-client/libs/bescheid-shared/src/lib/+state/bescheid.reducer.spec.ts +++ b/alfa-client/libs/bescheid-shared/src/lib/+state/bescheid.reducer.spec.ts @@ -26,7 +26,7 @@ describe('Bescheid Reducer', () => { const resource: VorgangWithEingangResource = createVorgangWithEingangResource(); const action: Action = CommandActions.createCommand({ resource, - linkRel: VorgangWithEingangLinkRel.CREATE_BESCHEID, + linkRel: VorgangWithEingangLinkRel.CREATE_BESCHEID_DRAFT, command: { ...createCommandResource(), order: CommandOrder.CREATE_BESCHEID }, }); diff --git a/alfa-client/libs/bescheid-shared/src/lib/bescheid.service.spec.ts b/alfa-client/libs/bescheid-shared/src/lib/bescheid.service.spec.ts index 228b24efb9cd33d7d236fbb0a5b4a4499cee4169..f0f740181ae37e11538d269dd8d64a10b36c2b3b 100644 --- a/alfa-client/libs/bescheid-shared/src/lib/bescheid.service.spec.ts +++ b/alfa-client/libs/bescheid-shared/src/lib/bescheid.service.spec.ts @@ -11,13 +11,13 @@ import { } from '@alfa-client/command-shared'; import { ApiError, + createEmptyStateResource, + createErrorStateResource, + createStateResource, EMPTY_ARRAY, EMPTY_STRING, HttpError, StateResource, - createEmptyStateResource, - createErrorStateResource, - createStateResource, } from '@alfa-client/tech-shared'; import { Mock, mock, useFromMock } from '@alfa-client/test-utils'; import { @@ -28,12 +28,12 @@ import { } from '@alfa-client/vorgang-shared'; import { fakeAsync, tick } from '@angular/core/testing'; import faker from '@faker-js/faker'; -import { ResourceUri, getUrl } from '@ngxp/rest'; +import { getUrl, ResourceUri } from '@ngxp/rest'; import { cold } from 'jest-marbles'; import { CommandLinkRel } from 'libs/command-shared/src/lib/command.linkrel'; import { createApiError } from 'libs/tech-shared/test/error'; import { createVorgangWithEingangResource } from 'libs/vorgang-shared/test/vorgang'; -import { Observable, first, of } from 'rxjs'; +import { first, Observable, of } from 'rxjs'; import { createBinaryFileListResource, createBinaryFileResource, @@ -155,7 +155,7 @@ describe('BescheidService', () => { it('should call facade', () => { service.createBescheid(vorgangWithEingang).pipe(first()).subscribe(); - expect(facade.createBescheid).toHaveBeenCalledWith(vorgangWithEingang, { + expect(facade.createBescheidDraft).toHaveBeenCalledWith(vorgangWithEingang, { order: CommandOrder.CREATE_BESCHEID, body: null, }); diff --git a/alfa-client/libs/bescheid-shared/src/lib/bescheid.service.ts b/alfa-client/libs/bescheid-shared/src/lib/bescheid.service.ts index 76b74767032eac3979371d834d17c59217323600..277891fe474da829e8b3c8adacdcbf233fee73ee 100644 --- a/alfa-client/libs/bescheid-shared/src/lib/bescheid.service.ts +++ b/alfa-client/libs/bescheid-shared/src/lib/bescheid.service.ts @@ -13,19 +13,19 @@ import { tapOnCommandSuccessfullyDone, } from '@alfa-client/command-shared'; import { - EMPTY_ARRAY, - HttpError, - ResourceListService, - StateResource, createEmptyStateResource, createStateResource, + EMPTY_ARRAY, filterIsLoadedOrHasError, getEmbeddedResources, hasStateResourceError, + HttpError, isLoaded, isNotEmpty, isNotNil, + ResourceListService, sortByGermanDateStr, + StateResource, } from '@alfa-client/tech-shared'; import { VorgangCommandService, @@ -35,15 +35,15 @@ import { } from '@alfa-client/vorgang-shared'; import { getEmpfaenger } from '@alfa-client/vorgang-shared-ui'; import { Injectable } from '@angular/core'; -import { ResourceUri, getUrl, hasLink } from '@ngxp/rest'; +import { getUrl, hasLink, ResourceUri } from '@ngxp/rest'; import { BehaviorSubject, - Observable, - Subscription, filter, first, map, + Observable, startWith, + Subscription, switchMap, take, tap, @@ -172,7 +172,7 @@ export class BescheidService { vorgangWithEingang: VorgangWithEingangResource, bescheid?: Bescheid, ): Observable<StateResource<CommandResource>> { - this.facade.createBescheid(vorgangWithEingang, buildCreateBescheidCommand(bescheid)); + this.facade.createBescheidDraft(vorgangWithEingang, buildCreateBescheidCommand(bescheid)); return this.getBescheidCommand().pipe( tapOnCommandSuccessfullyDone((commandStateResource: StateResource<CommandResource>) => this.updateBescheidDraft(commandStateResource.resource), diff --git a/alfa-client/libs/bescheid/src/index.ts b/alfa-client/libs/bescheid/src/index.ts index 6e1524f86904bd889f89cbdfa55b147a978d67a5..9f2c0268f34d8c2099f74dbb30ffae0bed8a8a21 100644 --- a/alfa-client/libs/bescheid/src/index.ts +++ b/alfa-client/libs/bescheid/src/index.ts @@ -1,3 +1,2 @@ export * from './lib/bescheid.module'; export * from './lib/beschieden-date-in-vorgang-container/beschieden-date-in-vorgang-container.component'; -export * from './lib/create-bescheid-button-container/create-bescheid-button-container.component'; diff --git a/alfa-client/libs/bescheid/src/lib/bescheid.module.ts b/alfa-client/libs/bescheid/src/lib/bescheid.module.ts index e6faf4a67f1e937d0aab66ae583415d8a4030bd9..e80dff3c3e0610a0d46b4b5babfa41c1d713d01a 100644 --- a/alfa-client/libs/bescheid/src/lib/bescheid.module.ts +++ b/alfa-client/libs/bescheid/src/lib/bescheid.module.ts @@ -12,8 +12,6 @@ import { BescheidListInVorgangComponent } from './bescheid-list-in-vorgang-conta import { DocumentInBescheidContainerComponent } from './bescheid-list-in-vorgang-container/bescheid-list-in-vorgang/document-in-bescheid-container/document-in-bescheid-container.component'; import { BeschiedenDateContainerComponent } from './beschieden-date-in-vorgang-container/beschieden-date-container/beschieden-date-container.component'; import { BeschiedenDateInVorgangContainerComponent } from './beschieden-date-in-vorgang-container/beschieden-date-in-vorgang-container.component'; -import { CreateBescheidButtonContainerComponent } from './create-bescheid-button-container/create-bescheid-button-container.component'; -import { CreateBescheidButtonComponent } from './create-bescheid-button-container/create-bescheid-button/create-bescheid-button.component'; import { BescheidStatusTextComponent, @@ -36,8 +34,6 @@ import { CloseIconComponent, ], declarations: [ - CreateBescheidButtonContainerComponent, - CreateBescheidButtonComponent, BescheidInVorgangContainerComponent, BescheidInVorgangComponent, BescheidListInVorgangContainerComponent, @@ -48,7 +44,6 @@ import { ], exports: [ BescheidInVorgangContainerComponent, - CreateBescheidButtonContainerComponent, BescheidListInVorgangContainerComponent, BeschiedenDateInVorgangContainerComponent, ], diff --git a/alfa-client/libs/bescheid/src/lib/beschieden-date-in-vorgang-container/beschieden-date-container/beschieden-date-container.component.html b/alfa-client/libs/bescheid/src/lib/beschieden-date-in-vorgang-container/beschieden-date-container/beschieden-date-container.component.html index 704a6ddd28f903eee9a9fb887c8ac40d4d56683d..61ae1d119505cb9e60d173dce420dd872cfa784e 100644 --- a/alfa-client/libs/bescheid/src/lib/beschieden-date-in-vorgang-container/beschieden-date-container/beschieden-date-container.component.html +++ b/alfa-client/libs/bescheid/src/lib/beschieden-date-in-vorgang-container/beschieden-date-container/beschieden-date-container.component.html @@ -9,7 +9,6 @@ <ods-close-icon *ngIf="!bescheid.bewilligt" data-test-id="abgelehnt-icon" - size="small" class="fill-abgelehnt" /> diff --git a/alfa-client/libs/bescheid/src/lib/create-bescheid-button-container/create-bescheid-button-container.component.html b/alfa-client/libs/bescheid/src/lib/create-bescheid-button-container/create-bescheid-button-container.component.html deleted file mode 100644 index 1cc547861a553e8955f7f4481a862c2d5daeb535..0000000000000000000000000000000000000000 --- a/alfa-client/libs/bescheid/src/lib/create-bescheid-button-container/create-bescheid-button-container.component.html +++ /dev/null @@ -1,6 +0,0 @@ -<alfa-create-bescheid-button - data-test-id="create-bescheid-button-component" - [createBescheidCommand]="createBescheidInCommand$ | async" - (createBescheid)="create()" -> -</alfa-create-bescheid-button> diff --git a/alfa-client/libs/bescheid/src/lib/create-bescheid-button-container/create-bescheid-button-container.component.scss b/alfa-client/libs/bescheid/src/lib/create-bescheid-button-container/create-bescheid-button-container.component.scss deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/alfa-client/libs/bescheid/src/lib/create-bescheid-button-container/create-bescheid-button-container.component.spec.ts b/alfa-client/libs/bescheid/src/lib/create-bescheid-button-container/create-bescheid-button-container.component.spec.ts deleted file mode 100644 index 55dc7e67d6a90ba7555860c8ead94e494bc39316..0000000000000000000000000000000000000000 --- a/alfa-client/libs/bescheid/src/lib/create-bescheid-button-container/create-bescheid-button-container.component.spec.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { Mock, dispatchEventFromFixture, mock } from '@alfa-client/test-utils'; -import { VorgangWithEingangResource } from '@alfa-client/vorgang-shared'; -import { BescheidService } from 'libs/bescheid-shared/src/lib/bescheid.service'; -import { getDataTestIdOf } from 'libs/tech-shared/test/data-test'; -import { createVorgangWithEingangResource } from 'libs/vorgang-shared/test/vorgang'; -import { MockComponent } from 'ng-mocks'; -import { CreateBescheidButtonContainerComponent } from './create-bescheid-button-container.component'; -import { CreateBescheidButtonComponent } from './create-bescheid-button/create-bescheid-button.component'; - -describe('CreateBescheidButtonContainerComponent', () => { - let component: CreateBescheidButtonContainerComponent; - let fixture: ComponentFixture<CreateBescheidButtonContainerComponent>; - - const createBescheidComponent: string = getDataTestIdOf('create-bescheid-button-component'); - const service: Mock<BescheidService> = mock(BescheidService); - const vorgangWithEingang: VorgangWithEingangResource = createVorgangWithEingangResource(); - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ - CreateBescheidButtonContainerComponent, - MockComponent(CreateBescheidButtonComponent), - ], - providers: [ - { - provide: BescheidService, - useValue: service, - }, - ], - }).compileComponents(); - - fixture = TestBed.createComponent(CreateBescheidButtonContainerComponent); - component = fixture.componentInstance; - component.vorgangWithEingang = vorgangWithEingang; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); - - describe('ngOnInit', () => { - it('should get bescheid command', () => { - component.ngOnInit(); - - expect(service.getBescheidCommand).toHaveBeenCalled(); - }); - }); - - describe('create', () => { - it('should call service', () => { - dispatchEventFromFixture(fixture, createBescheidComponent, 'createBescheid'); - - expect(service.createBescheid).toHaveBeenCalledWith(vorgangWithEingang); - }); - }); -}); diff --git a/alfa-client/libs/bescheid/src/lib/create-bescheid-button-container/create-bescheid-button-container.component.ts b/alfa-client/libs/bescheid/src/lib/create-bescheid-button-container/create-bescheid-button-container.component.ts deleted file mode 100644 index a896daae64dd1c1e9470222356fe83b7c9f41b95..0000000000000000000000000000000000000000 --- a/alfa-client/libs/bescheid/src/lib/create-bescheid-button-container/create-bescheid-button-container.component.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { CommandResource } from '@alfa-client/command-shared'; -import { StateResource, createEmptyStateResource } from '@alfa-client/tech-shared'; -import { VorgangWithEingangResource } from '@alfa-client/vorgang-shared'; -import { Component, Input, OnInit } from '@angular/core'; -import { BescheidService } from 'libs/bescheid-shared/src/lib/bescheid.service'; -import { Observable, of } from 'rxjs'; - -@Component({ - selector: 'alfa-create-bescheid-button-container', - templateUrl: './create-bescheid-button-container.component.html', - styleUrls: ['./create-bescheid-button-container.component.scss'], -}) -export class CreateBescheidButtonContainerComponent implements OnInit { - @Input() vorgangWithEingang: VorgangWithEingangResource; - - public createBescheidInCommand$: Observable<StateResource<CommandResource>> = of( - createEmptyStateResource<CommandResource>(), - ); - - constructor(private bescheidService: BescheidService) {} - - ngOnInit(): void { - this.createBescheidInCommand$ = this.bescheidService.getBescheidCommand(); - } - - public create(): void { - this.bescheidService.createBescheid(this.vorgangWithEingang); - } -} diff --git a/alfa-client/libs/bescheid/src/lib/create-bescheid-button-container/create-bescheid-button/create-bescheid-button.component.html b/alfa-client/libs/bescheid/src/lib/create-bescheid-button-container/create-bescheid-button/create-bescheid-button.component.html deleted file mode 100644 index ac1efb73c28ad0ed875c79f084ebf9a5ee55dec6..0000000000000000000000000000000000000000 --- a/alfa-client/libs/bescheid/src/lib/create-bescheid-button-container/create-bescheid-button/create-bescheid-button.component.html +++ /dev/null @@ -1,8 +0,0 @@ -<ozgcloud-icon-button-with-spinner - data-test-id="create-bescheid-icon-button" - icon="description" - toolTip="Bescheid erstellen" - [stateResource]="createBescheidCommand" - (clickEmitter)="createBescheid.emit()" -> -</ozgcloud-icon-button-with-spinner> diff --git a/alfa-client/libs/bescheid/src/lib/create-bescheid-button-container/create-bescheid-button/create-bescheid-button.component.scss b/alfa-client/libs/bescheid/src/lib/create-bescheid-button-container/create-bescheid-button/create-bescheid-button.component.scss deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/alfa-client/libs/bescheid/src/lib/create-bescheid-button-container/create-bescheid-button/create-bescheid-button.component.spec.ts b/alfa-client/libs/bescheid/src/lib/create-bescheid-button-container/create-bescheid-button/create-bescheid-button.component.spec.ts deleted file mode 100644 index d15b7638e4686c64dd015cae79be7e3cee5915f4..0000000000000000000000000000000000000000 --- a/alfa-client/libs/bescheid/src/lib/create-bescheid-button-container/create-bescheid-button/create-bescheid-button.component.spec.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { dispatchEventFromFixture } from '@alfa-client/test-utils'; -import { IconButtonWithSpinnerComponent } from '@alfa-client/ui'; -import { getDataTestIdOf } from 'libs/tech-shared/test/data-test'; -import { MockComponent } from 'ng-mocks'; -import { CreateBescheidButtonComponent } from './create-bescheid-button.component'; - -describe('CreateBescheidButtonComponent', () => { - let component: CreateBescheidButtonComponent; - let fixture: ComponentFixture<CreateBescheidButtonComponent>; - - const createBescheidButton: string = getDataTestIdOf('create-bescheid-icon-button'); - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [CreateBescheidButtonComponent, MockComponent(IconButtonWithSpinnerComponent)], - }).compileComponents(); - - fixture = TestBed.createComponent(CreateBescheidButtonComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); - - describe('on button click', () => { - it('should emit createBescheid', () => { - jest.spyOn(component.createBescheid, 'emit'); - - dispatchEventFromFixture(fixture, createBescheidButton, 'clickEmitter'); - - expect(component.createBescheid.emit).toHaveBeenCalled(); - }); - }); -}); diff --git a/alfa-client/libs/bescheid/src/lib/create-bescheid-button-container/create-bescheid-button/create-bescheid-button.component.ts b/alfa-client/libs/bescheid/src/lib/create-bescheid-button-container/create-bescheid-button/create-bescheid-button.component.ts deleted file mode 100644 index d01c5ecd049877b9fd5a63e4e2ee4cde9cd034b0..0000000000000000000000000000000000000000 --- a/alfa-client/libs/bescheid/src/lib/create-bescheid-button-container/create-bescheid-button/create-bescheid-button.component.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Component, EventEmitter, Input, Output } from '@angular/core'; -import { CommandResource } from '@alfa-client/command-shared'; -import { StateResource, createEmptyStateResource } from '@alfa-client/tech-shared'; - -@Component({ - selector: 'alfa-create-bescheid-button', - templateUrl: './create-bescheid-button.component.html', - styleUrls: ['./create-bescheid-button.component.scss'], -}) -export class CreateBescheidButtonComponent { - @Input() createBescheidCommand: StateResource<CommandResource> = - createEmptyStateResource<CommandResource>(); - - @Output() createBescheid: EventEmitter<void> = new EventEmitter(); -} diff --git a/alfa-client/libs/binary-file-shared/src/lib/binary-file.service.spec.ts b/alfa-client/libs/binary-file-shared/src/lib/binary-file.service.spec.ts index 9d69ae78a151079401a9d94caceff99e8c980675..328960de1f9b9a87f8442595ad8b609f339f9da8 100644 --- a/alfa-client/libs/binary-file-shared/src/lib/binary-file.service.spec.ts +++ b/alfa-client/libs/binary-file-shared/src/lib/binary-file.service.spec.ts @@ -262,7 +262,7 @@ describe('BinaryFileService', () => { service.handleSnackBar(buildUnprocessableEntityErrorResponse(), true); expect(snackBarService.showError).toHaveBeenCalledWith( - VALIDATION_MESSAGES[ValidationMessageCode.VALIDATION_FIELD_FILE_SIZE_EXCEEDED], + VALIDATION_MESSAGES[ValidationMessageCode.FIELD_FILE_SIZE_EXCEEDED], ); }); @@ -274,7 +274,7 @@ describe('BinaryFileService', () => { it('should not call snackbarService if not file size exceeded error', () => { service.handleSnackBar( - buildUnprocessableEntityErrorResponse(ValidationMessageCode.VALIDATION_FIELD_EMPTY), + buildUnprocessableEntityErrorResponse(ValidationMessageCode.FIELD_EMPTY), true, ); @@ -283,15 +283,15 @@ describe('BinaryFileService', () => { }); function buildUnprocessableEntityErrorResponse( - validationMessageCode: ValidationMessageCode = ValidationMessageCode.VALIDATION_FIELD_FILE_SIZE_EXCEEDED, + validationMessageCode: ValidationMessageCode = ValidationMessageCode.FIELD_FILE_SIZE_EXCEEDED, ): HttpErrorResponse { return <HttpErrorResponse>{ status: 422, error: { - issues: [ + invalidParams: [ { - messageCode: validationMessageCode, - parameters: [], + reason: validationMessageCode, + constraintParameters: [], }, ], }, diff --git a/alfa-client/libs/binary-file-shared/src/lib/binary-file.service.ts b/alfa-client/libs/binary-file-shared/src/lib/binary-file.service.ts index bb6592cb76931ff30edca949cc28a864686ecfce..57773cccbfa61d4dab2af4984a88d6d3f9cf7b14 100644 --- a/alfa-client/libs/binary-file-shared/src/lib/binary-file.service.ts +++ b/alfa-client/libs/binary-file-shared/src/lib/binary-file.service.ts @@ -29,7 +29,7 @@ import { createEmptyStateResource, createErrorStateResource, createStateResource, - getMessageForIssue, + getMessageForInvalidParam, isNotNil, isUnprocessableEntity, isValidationFieldFileSizeExceedError, @@ -90,7 +90,9 @@ export class BinaryFileService { handleSnackBar(error: HttpErrorResponse, showValidationErrorSnackBar: boolean) { if (showValidationErrorSnackBar && isValidationFieldFileSizeExceedError(error.error)) { - this.snackbarService.showError(getMessageForIssue(EMPTY_STRING, error.error.issues[0])); + this.snackbarService.showError( + getMessageForInvalidParam(EMPTY_STRING, error.error.invalidParams[0]), + ); } } diff --git a/alfa-client/libs/binary-file/src/lib/binary-file-container/binary-file-container.component.scss b/alfa-client/libs/binary-file/src/lib/binary-file-container/binary-file-container.component.scss index 7dcaaf304d76dd842af6bcbcc811021fa5c8904f..75e46ace311d763326b4b99335aee48d5ef44996 100644 --- a/alfa-client/libs/binary-file/src/lib/binary-file-container/binary-file-container.component.scss +++ b/alfa-client/libs/binary-file/src/lib/binary-file-container/binary-file-container.component.scss @@ -25,5 +25,5 @@ position: relative; max-width: 100%; padding: 4px 0; - font-size: 14px; + font-size: 0.875rem; } diff --git a/alfa-client/libs/binary-file/src/lib/binary-file-container/binary-file/binary-file.component.scss b/alfa-client/libs/binary-file/src/lib/binary-file-container/binary-file/binary-file.component.scss index fb3024df1b4d16d68a56c7751548f96af38dabbd..17be8d4a626b041450d293d151ef4b4d128e6079 100644 --- a/alfa-client/libs/binary-file/src/lib/binary-file-container/binary-file/binary-file.component.scss +++ b/alfa-client/libs/binary-file/src/lib/binary-file-container/binary-file/binary-file.component.scss @@ -89,7 +89,7 @@ } .size { - font-size: 12px; + font-size: 0.75rem; flex-shrink: 0; margin-right: 4px; diff --git a/alfa-client/libs/binary-file/src/lib/binary-file-container/binary-file/binary-file.component.spec.ts b/alfa-client/libs/binary-file/src/lib/binary-file-container/binary-file/binary-file.component.spec.ts index 7accc9f495a262685793bd3e50f34d5b96bcf712..91e0d49606a54669edc018f3e97350e240de02b2 100644 --- a/alfa-client/libs/binary-file/src/lib/binary-file-container/binary-file/binary-file.component.spec.ts +++ b/alfa-client/libs/binary-file/src/lib/binary-file-container/binary-file/binary-file.component.spec.ts @@ -21,21 +21,18 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; -import { MatIcon } from '@angular/material/icon'; -import { faker } from '@faker-js/faker'; import { ApiDownloadToken } from '@alfa-client/api-root-shared'; import { BinaryFileLinkRel, BinaryFileResource } from '@alfa-client/binary-file-shared'; -import { createEmptyStateResource, FileSizePipe, HasLinkPipe } from '@alfa-client/tech-shared'; +import { FileSizePipe, HasLinkPipe, createEmptyStateResource } from '@alfa-client/tech-shared'; import { getElementFromFixture } from '@alfa-client/test-utils'; -import { - IconButtonWithSpinnerComponent, - MatTooltipDirective, - SpinnerComponent, -} from '@alfa-client/ui'; +import { IconButtonWithSpinnerComponent, SpinnerComponent } from '@alfa-client/ui'; +import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; +import { MatIcon } from '@angular/material/icon'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { faker } from '@faker-js/faker'; import { createBinaryFileResource } from 'libs/binary-file-shared/test/binary-file'; import { getDataTestClassOf } from 'libs/tech-shared/test/data-test'; -import { MockComponent, MockDirective } from 'ng-mocks'; +import { MockComponent, MockModule } from 'ng-mocks'; import { BinaryFileComponent } from './binary-file.component'; describe('BinaryFileComponent', () => { @@ -52,7 +49,7 @@ describe('BinaryFileComponent', () => { MatIcon, FileSizePipe, HasLinkPipe, - MockDirective(MatTooltipDirective), + MockModule(MatTooltipModule), MockComponent(SpinnerComponent), MockComponent(IconButtonWithSpinnerComponent), ], diff --git a/alfa-client/libs/binary-file/src/lib/vertical-binary-file-list/vertical-binary-file-list.component.html b/alfa-client/libs/binary-file/src/lib/vertical-binary-file-list/vertical-binary-file-list.component.html index 187ab55187039b6ca7ea2f9d6b1ab8e0cc456a4a..1dc4313e5e3710b0c1550043dd4c1fa4f6e3d04d 100644 --- a/alfa-client/libs/binary-file/src/lib/vertical-binary-file-list/vertical-binary-file-list.component.html +++ b/alfa-client/libs/binary-file/src/lib/vertical-binary-file-list/vertical-binary-file-list.component.html @@ -27,8 +27,12 @@ *ngIf="binaryFileListStateResource.resource" [stateResource]="binaryFileListStateResource" > - <ods-attachment-wrapper [title]="title" data-test-id="file-list"> - <ods-attachment-header [title]="title"> + <ods-attachment-wrapper data-test-id="file-list"> + <ods-attachment-header + [title]="title" + *ngIf="title || archiveDownloadUri" + data-test-id="file-list-header" + > <alfa-download-archive-file-button-container *ngIf="archiveDownloadUri" data-test-class="download-archive-file-button" diff --git a/alfa-client/libs/binary-file/src/lib/vertical-binary-file-list/vertical-binary-file-list.component.spec.ts b/alfa-client/libs/binary-file/src/lib/vertical-binary-file-list/vertical-binary-file-list.component.spec.ts index a42980975bd6362343a865a3dae296bbd3191728..f0ec5b2c573b721bfd18afefde63750a310a0310 100644 --- a/alfa-client/libs/binary-file/src/lib/vertical-binary-file-list/vertical-binary-file-list.component.spec.ts +++ b/alfa-client/libs/binary-file/src/lib/vertical-binary-file-list/vertical-binary-file-list.component.spec.ts @@ -37,7 +37,7 @@ import { createBinaryFileListResource, createBinaryFileResource, } from 'libs/binary-file-shared/test/binary-file'; -import { getDataTestClassOf } from 'libs/tech-shared/test/data-test'; +import { getDataTestClassOf, getDataTestIdOf } from 'libs/tech-shared/test/data-test'; import { MockComponent } from 'ng-mocks'; import { BinaryFile2ContainerComponent } from '../binary-file2-container/binary-file2-container.component'; import { DownloadArchiveFileButtonContainerComponent } from '../download-archive-file-button-container/download-archive-file-button-container.component'; @@ -48,6 +48,7 @@ describe('VerticalBinaryFileListComponent', () => { let fixture: ComponentFixture<VerticalBinaryFileListComponent>; const downloadArchiveFileButton: string = getDataTestClassOf('download-archive-file-button'); + const fileListHeader: string = getDataTestIdOf('file-list-header'); const binaryFile: BinaryFileResource = createBinaryFileResource(); @@ -109,7 +110,37 @@ describe('VerticalBinaryFileListComponent', () => { }); }); + describe('attachment header', () => { + it('should show header if there is title', () => { + component.title = 'Title'; + + fixture.detectChanges(); + + existsAsHtmlElement(fixture, fileListHeader); + }); + + it('should show header if there is uri', () => { + component.archiveDownloadUri = faker.internet.url(); + + fixture.detectChanges(); + + existsAsHtmlElement(fixture, fileListHeader); + }); + + it('should not show header if there is no uri or title', () => { + component.title = ''; + + fixture.detectChanges(); + + notExistsAsHtmlElement(fixture, fileListHeader); + }); + }); + describe('download archive button', () => { + beforeEach(() => { + component.title = 'Test title'; + }); + const downloadUri: ResourceUri = faker.internet.url(); it('should be visible if uri exists', () => { diff --git a/alfa-client/libs/collaboration/.eslintrc.json b/alfa-client/libs/collaboration/.eslintrc.json new file mode 100644 index 0000000000000000000000000000000000000000..243c51741f65cc7afb3a7d85531c24afdcab5e56 --- /dev/null +++ b/alfa-client/libs/collaboration/.eslintrc.json @@ -0,0 +1,33 @@ +{ + "extends": ["../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts"], + "extends": ["plugin:@nx/angular", "plugin:@angular-eslint/template/process-inline-templates"], + "rules": { + "@angular-eslint/directive-selector": [ + "error", + { + "type": "attribute", + "prefix": "alfa", + "style": "camelCase" + } + ], + "@angular-eslint/component-selector": [ + "error", + { + "type": "element", + "prefix": "alfa", + "style": "kebab-case" + } + ] + } + }, + { + "files": ["*.html"], + "extends": ["plugin:@nx/angular-template"], + "rules": {} + } + ] +} diff --git a/alfa-client/libs/collaboration/README.md b/alfa-client/libs/collaboration/README.md new file mode 100644 index 0000000000000000000000000000000000000000..53577986259ad6d8403d1446c6c1426d24e6d06d --- /dev/null +++ b/alfa-client/libs/collaboration/README.md @@ -0,0 +1,7 @@ +# collaboration + +This library was generated with [Nx](https://nx.dev). + +## Running unit tests + +Run `nx test collaboration` to execute the unit tests. diff --git a/alfa-client/libs/collaboration/jest.config.ts b/alfa-client/libs/collaboration/jest.config.ts new file mode 100644 index 0000000000000000000000000000000000000000..fdccd307388de8b7fb2256a71ec07808ec5b05de --- /dev/null +++ b/alfa-client/libs/collaboration/jest.config.ts @@ -0,0 +1,22 @@ +/* eslint-disable */ +export default { + displayName: 'collaboration', + preset: '../../jest.preset.js', + setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'], + coverageDirectory: '../../coverage/libs/collaboration', + transform: { + '^.+\\.(ts|mjs|js|html)$': [ + 'jest-preset-angular', + { + tsconfig: '<rootDir>/tsconfig.spec.json', + stringifyContentPathRegex: '\\.(html|svg)$', + }, + ], + }, + transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'], + snapshotSerializers: [ + 'jest-preset-angular/build/serializers/no-ng-attributes', + 'jest-preset-angular/build/serializers/ng-snapshot', + 'jest-preset-angular/build/serializers/html-comment', + ], +}; diff --git a/alfa-client/libs/collaboration/project.json b/alfa-client/libs/collaboration/project.json new file mode 100644 index 0000000000000000000000000000000000000000..39d9434a1f4c96550e4d71423eed803a6c397993 --- /dev/null +++ b/alfa-client/libs/collaboration/project.json @@ -0,0 +1,21 @@ +{ + "name": "collaboration", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "libs/collaboration/src", + "prefix": "alfa", + "projectType": "library", + "tags": [], + "targets": { + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/libs/collaboration"], + "options": { + "tsConfig": "libs//collaboration/tsconfig.spec.json", + "jestConfig": "libs/collaboration/jest.config.ts" + } + }, + "lint": { + "executor": "@nx/eslint:lint" + } + } +} diff --git a/alfa-client/libs/collaboration/src/index.ts b/alfa-client/libs/collaboration/src/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..fe7fae5e1dd38a384cd93d2ad8425d93ac37ae03 --- /dev/null +++ b/alfa-client/libs/collaboration/src/index.ts @@ -0,0 +1,2 @@ +export * from './lib/collaboration-in-vorgang-container/collaboration-in-vorgang-container.component'; +export * from './lib/collaboration.module'; diff --git a/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-in-vorgang-container.component.html b/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-in-vorgang-container.component.html new file mode 100644 index 0000000000000000000000000000000000000000..9b55f329b9326a6ebff530a431714165d7a89a0c --- /dev/null +++ b/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-in-vorgang-container.component.html @@ -0,0 +1,3 @@ +<ods-button variant="outline" text="Anfrage erstellen" dataTestId="anfrage-erstellen-button"> + <ods-collaboration-icon icon /> +</ods-button> diff --git a/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-in-vorgang-container.component.spec.ts b/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-in-vorgang-container.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..bbb2fbafa9d8d9513f0d8a54702dcbb83a0f244d --- /dev/null +++ b/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-in-vorgang-container.component.spec.ts @@ -0,0 +1,27 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ButtonComponent, CollaborationIconComponent, SaveIconComponent } from '@ods/system'; +import { MockComponent } from 'ng-mocks'; +import { CollaborationInVorgangContainerComponent } from './collaboration-in-vorgang-container.component'; + +describe('CollaborationInVorgangContainerComponent', () => { + let component: CollaborationInVorgangContainerComponent; + let fixture: ComponentFixture<CollaborationInVorgangContainerComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ButtonComponent, SaveIconComponent], + declarations: [ + CollaborationInVorgangContainerComponent, + MockComponent(CollaborationIconComponent), + ], + }).compileComponents(); + + fixture = TestBed.createComponent(CollaborationInVorgangContainerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-in-vorgang-container.component.ts b/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-in-vorgang-container.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..816cd743cf9dbc2a992d115a7e3598c7a7590ee2 --- /dev/null +++ b/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-in-vorgang-container.component.ts @@ -0,0 +1,7 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'alfa-collaboration-in-vorgang-container', + templateUrl: './collaboration-in-vorgang-container.component.html', +}) +export class CollaborationInVorgangContainerComponent {} diff --git a/alfa-client/libs/collaboration/src/lib/collaboration.module.spec.ts b/alfa-client/libs/collaboration/src/lib/collaboration.module.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..1fa74dca425307c2116f48587c5179882184daa1 --- /dev/null +++ b/alfa-client/libs/collaboration/src/lib/collaboration.module.spec.ts @@ -0,0 +1,14 @@ +import { TestBed } from '@angular/core/testing'; +import { CollaborationModule } from './collaboration.module'; + +describe('CollaborationModule', () => { + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [CollaborationModule], + }).compileComponents(); + }); + + it('should create', () => { + expect(CollaborationModule).toBeDefined(); + }); +}); diff --git a/alfa-client/libs/collaboration/src/lib/collaboration.module.ts b/alfa-client/libs/collaboration/src/lib/collaboration.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..adcfd33ce0ff125a1dab9099fc4a8ffebe986055 --- /dev/null +++ b/alfa-client/libs/collaboration/src/lib/collaboration.module.ts @@ -0,0 +1,11 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { ButtonComponent, CollaborationIconComponent } from '@ods/system'; +import { CollaborationInVorgangContainerComponent } from './collaboration-in-vorgang-container/collaboration-in-vorgang-container.component'; + +@NgModule({ + imports: [CommonModule, ButtonComponent, CollaborationIconComponent], + declarations: [CollaborationInVorgangContainerComponent], + exports: [CollaborationInVorgangContainerComponent], +}) +export class CollaborationModule {} diff --git a/alfa-client/libs/collaboration/src/test-setup.ts b/alfa-client/libs/collaboration/src/test-setup.ts new file mode 100644 index 0000000000000000000000000000000000000000..6b07c0bac34c40aa6afeef02c18c8db08f79de48 --- /dev/null +++ b/alfa-client/libs/collaboration/src/test-setup.ts @@ -0,0 +1,15 @@ +import '@testing-library/jest-dom'; +import 'jest-preset-angular/setup-jest'; + +import { getTestBed } from '@angular/core/testing'; +import { + BrowserDynamicTestingModule, + platformBrowserDynamicTesting, +} from '@angular/platform-browser-dynamic/testing'; + +getTestBed().resetTestEnvironment(); +getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), { + teardown: { destroyAfterEach: false }, + errorOnUnknownProperties: true, + errorOnUnknownElements: true, +}); diff --git a/alfa-client/libs/collaboration/tsconfig.json b/alfa-client/libs/collaboration/tsconfig.json new file mode 100644 index 0000000000000000000000000000000000000000..7cc6baf2f58ed5ccfba098131996f579979e9f18 --- /dev/null +++ b/alfa-client/libs/collaboration/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ], + "compilerOptions": { + "target": "es2022" + } +} diff --git a/alfa-client/libs/collaboration/tsconfig.lib.json b/alfa-client/libs/collaboration/tsconfig.lib.json new file mode 100644 index 0000000000000000000000000000000000000000..4cab05d46338c6e9d4dfe6512fd7eb7f60340d3d --- /dev/null +++ b/alfa-client/libs/collaboration/tsconfig.lib.json @@ -0,0 +1,12 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "declaration": true, + "declarationMap": true, + "inlineSources": true, + "types": [] + }, + "exclude": ["src/**/*.spec.ts", "src/test-setup.ts", "jest.config.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/alfa-client/libs/collaboration/tsconfig.spec.json b/alfa-client/libs/collaboration/tsconfig.spec.json new file mode 100644 index 0000000000000000000000000000000000000000..7870b7c011681fb77d6114001f44d3eeca69975b --- /dev/null +++ b/alfa-client/libs/collaboration/tsconfig.spec.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "target": "es2016", + "types": ["jest", "node"] + }, + "files": ["src/test-setup.ts"], + "include": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts", "src/**/*.d.ts"] +} diff --git a/alfa-client/libs/design-component/src/lib/form/formcontrol-editor.abstract.component.ts b/alfa-client/libs/design-component/src/lib/form/formcontrol-editor.abstract.component.ts index a023a009d1780be33395159d25234da39285f745..cb0f3f5842b32a3ce76321d194097c9a2f64229a 100644 --- a/alfa-client/libs/design-component/src/lib/form/formcontrol-editor.abstract.component.ts +++ b/alfa-client/libs/design-component/src/lib/form/formcontrol-editor.abstract.component.ts @@ -21,7 +21,7 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { Issue } from '@alfa-client/tech-shared'; +import { InvalidParam } from '@alfa-client/tech-shared'; import { Component, OnDestroy, OnInit, Optional, Self } from '@angular/core'; import { ControlValueAccessor, NgControl, UntypedFormControl } from '@angular/forms'; import { isEmpty } from 'lodash-es'; @@ -89,9 +89,11 @@ export abstract class FormControlEditorAbstractComponent } } - get issues(): Issue[] { + get invalidParams(): InvalidParam[] { return this.fieldControl.errors ? - Object.keys(this.fieldControl.errors).map((key) => <Issue>this.fieldControl.errors[key]) + Object.keys(this.fieldControl.errors).map( + (key) => <InvalidParam>this.fieldControl.errors[key], + ) : []; } } diff --git a/alfa-client/libs/design-component/src/lib/form/text-editor/text-editor.component.html b/alfa-client/libs/design-component/src/lib/form/text-editor/text-editor.component.html index 544b9312b03adba3e0e097e8eba8a20751e5b013..9252326a5bdcc9d9c25ec164981bd0e24915bafa 100644 --- a/alfa-client/libs/design-component/src/lib/form/text-editor/text-editor.component.html +++ b/alfa-client/libs/design-component/src/lib/form/text-editor/text-editor.component.html @@ -10,7 +10,7 @@ > <ods-validation-error error - [issues]="issues" + [invalidParams]="invalidParams" [label]="label" [attr.data-test-id]="(label | convertForDataTest) + '-text-editor-error'" ></ods-validation-error> diff --git a/alfa-client/libs/design-component/src/lib/form/text-editor/text-editor.component.ts b/alfa-client/libs/design-component/src/lib/form/text-editor/text-editor.component.ts index c78a7554800ed6f61eb9d189617811e7f9d0490a..c40849cf31e2279a3727527898213567bab07c4c 100644 --- a/alfa-client/libs/design-component/src/lib/form/text-editor/text-editor.component.ts +++ b/alfa-client/libs/design-component/src/lib/form/text-editor/text-editor.component.ts @@ -26,6 +26,6 @@ export class TextEditorComponent extends FormControlEditorAbstractComponent { @Input() focus: boolean = false; get variant(): string { - return this.issues.length > 0 ? 'error' : 'default'; + return this.invalidParams.length > 0 ? 'error' : 'default'; } } diff --git a/alfa-client/libs/design-component/src/lib/form/textarea-editor/textarea-editor.component.html b/alfa-client/libs/design-component/src/lib/form/textarea-editor/textarea-editor.component.html index 5f3959bd34260ac44c11db6a108f24583986b9b0..c27f425a3f9ca703f85b9961a8c87175a50a0a9f 100644 --- a/alfa-client/libs/design-component/src/lib/form/textarea-editor/textarea-editor.component.html +++ b/alfa-client/libs/design-component/src/lib/form/textarea-editor/textarea-editor.component.html @@ -10,7 +10,7 @@ > <ods-validation-error error - [issues]="issues" + [invalidParams]="invalidParams" [label]="label" [attr.data-test-id]="(label | convertForDataTest) + '-textarea-editor-error'" ></ods-validation-error> diff --git a/alfa-client/libs/design-component/src/lib/form/textarea-editor/textarea-editor.component.ts b/alfa-client/libs/design-component/src/lib/form/textarea-editor/textarea-editor.component.ts index f78ee2c938fdb89b91a17060ba862ecadcff04aa..a47b955192b12c4a9ce1379062cd16543e455e2a 100644 --- a/alfa-client/libs/design-component/src/lib/form/textarea-editor/textarea-editor.component.ts +++ b/alfa-client/libs/design-component/src/lib/form/textarea-editor/textarea-editor.component.ts @@ -26,6 +26,6 @@ export class TextareaEditorComponent extends FormControlEditorAbstractComponent @Input() focus: boolean = false; get variant(): string { - return this.issues.length > 0 ? 'error' : 'default'; + return this.invalidParams.length > 0 ? 'error' : 'default'; } } diff --git a/alfa-client/libs/design-component/src/lib/form/validation-error/validation-error.component.html b/alfa-client/libs/design-component/src/lib/form/validation-error/validation-error.component.html index 703f757c2b17228e8cdfb7d158a7bb7e6a058a93..28ffaaac9b7880787146c5091eceedee3d2a662e 100644 --- a/alfa-client/libs/design-component/src/lib/form/validation-error/validation-error.component.html +++ b/alfa-client/libs/design-component/src/lib/form/validation-error/validation-error.component.html @@ -1,3 +1,3 @@ -<ng-container *ngFor="let issue of issues" - ><ods-error-message [text]="message(issue)"></ods-error-message +<ng-container *ngFor="let invalidParam of invalidParams" + ><ods-error-message [text]="message(invalidParam)"></ods-error-message ></ng-container> diff --git a/alfa-client/libs/design-component/src/lib/form/validation-error/validation-error.component.spec.ts b/alfa-client/libs/design-component/src/lib/form/validation-error/validation-error.component.spec.ts index 6abe6133bddfc36ca6ca42e2aa28d049fa96ef4d..845bf47eb456d09d581f97fae71493c8808729ac 100644 --- a/alfa-client/libs/design-component/src/lib/form/validation-error/validation-error.component.spec.ts +++ b/alfa-client/libs/design-component/src/lib/form/validation-error/validation-error.component.spec.ts @@ -1,6 +1,7 @@ -import { getMessageForIssue } from '@alfa-client/tech-shared'; +import { getMessageForInvalidParam, InvalidParam } from '@alfa-client/tech-shared'; import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { createIssue } from 'libs/tech-shared/test/error'; +import { ValidationMessageCode } from 'libs/tech-shared/src/lib/validation/tech.validation.messages'; +import { createInvalidParam } from 'libs/tech-shared/test/error'; import { ValidationErrorComponent } from './validation-error.component'; describe('ValidationErrorComponent', () => { @@ -21,32 +22,29 @@ describe('ValidationErrorComponent', () => { expect(component).toBeTruthy(); }); - describe('get message for issue', () => { + describe('get message from invalidParam', () => { const fieldLabel: string = 'Field Label'; + const invalidParam: InvalidParam = { + ...createInvalidParam(), + reason: ValidationMessageCode.FIELD_SIZE, + }; - it('should return message', () => { - const msg: string = getMessageForIssue(fieldLabel, { - ...createIssue(), - messageCode: 'validation_field_size', - }); + it('should contain', () => { + const msg: string = getMessageForInvalidParam(fieldLabel, invalidParam); expect(msg).toContain('muss mindestens'); }); it('should set field label', () => { - const msg: string = getMessageForIssue(fieldLabel, { - ...createIssue(), - messageCode: 'validation_field_size', - }); + const msg: string = getMessageForInvalidParam(fieldLabel, invalidParam); expect(msg).toContain(fieldLabel); }); it('should replace min param', () => { - const msg: string = getMessageForIssue(fieldLabel, { - ...createIssue(), - messageCode: 'validation_field_size', - parameters: [{ name: 'min', value: '3' }], + const msg: string = getMessageForInvalidParam(fieldLabel, { + ...invalidParam, + constraintParameters: [{ name: 'min', value: '3' }], }); expect(msg).toContain('3'); diff --git a/alfa-client/libs/design-component/src/lib/form/validation-error/validation-error.component.ts b/alfa-client/libs/design-component/src/lib/form/validation-error/validation-error.component.ts index d47b675b4984616c8ff76acaa04d3c73ebafad42..4d8a67a6e5f9ee9d39a99c9fe5fd97fe6136fe2c 100644 --- a/alfa-client/libs/design-component/src/lib/form/validation-error/validation-error.component.ts +++ b/alfa-client/libs/design-component/src/lib/form/validation-error/validation-error.component.ts @@ -1,4 +1,4 @@ -import { Issue, getMessageForIssue } from '@alfa-client/tech-shared'; +import { InvalidParam, getMessageForInvalidParam } from '@alfa-client/tech-shared'; import { CommonModule } from '@angular/common'; import { Component, Input } from '@angular/core'; import { ErrorMessageComponent } from '@ods/system'; @@ -11,9 +11,9 @@ import { ErrorMessageComponent } from '@ods/system'; }) export class ValidationErrorComponent { @Input() label: string; - @Input() issues: Issue[]; + @Input() invalidParams: InvalidParam[]; - public message(issue: Issue): string { - return getMessageForIssue(this.label, issue); + public message(invalidParam: InvalidParam): string { + return getMessageForInvalidParam(this.label, invalidParam); } } diff --git a/alfa-client/libs/design-system/src/index.ts b/alfa-client/libs/design-system/src/index.ts index 57c220dc85cf06f66d1e23edd66bc7d9bff947ea..9a59b13cbc25c6174275daa3727bdd8388fb0dc4 100644 --- a/alfa-client/libs/design-system/src/index.ts +++ b/alfa-client/libs/design-system/src/index.ts @@ -14,6 +14,7 @@ export * from './lib/icons/attachment-icon/attachment-icon.component'; export * from './lib/icons/bescheid-generate-icon/bescheid-generate-icon.component'; export * from './lib/icons/bescheid-upload-icon/bescheid-upload-icon.component'; export * from './lib/icons/close-icon/close-icon.component'; +export * from './lib/icons/collaboration-icon/collaboration-icon.component'; export * from './lib/icons/exclamation-icon/exclamation-icon.component'; export * from './lib/icons/file-icon/file-icon.component'; export * from './lib/icons/iconVariants'; @@ -21,4 +22,5 @@ export * from './lib/icons/save-icon/save-icon.component'; export * from './lib/icons/send-icon/send-icon.component'; export * from './lib/icons/spinner-icon/spinner-icon.component'; export * from './lib/icons/stamp-icon/stamp-icon.component'; +export * from './lib/instant-search/instant-search/instant-search.component'; export * from './lib/testbtn/testbtn.component'; diff --git a/alfa-client/libs/design-system/src/lib/aria-live-region/aria-live-region.component.spec.ts b/alfa-client/libs/design-system/src/lib/aria-live-region/aria-live-region.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..c0848c08f798f58fd91f850597ce0d15f267e065 --- /dev/null +++ b/alfa-client/libs/design-system/src/lib/aria-live-region/aria-live-region.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { AriaLiveRegionComponent } from './aria-live-region.component'; + +describe('AriaLiveRegionComponent', () => { + let component: AriaLiveRegionComponent; + let fixture: ComponentFixture<AriaLiveRegionComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [AriaLiveRegionComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(AriaLiveRegionComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/alfa-client/libs/design-system/src/lib/aria-live-region/aria-live-region.component.ts b/alfa-client/libs/design-system/src/lib/aria-live-region/aria-live-region.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..3cec9afff4c23502077704daea8b04f125158bdd --- /dev/null +++ b/alfa-client/libs/design-system/src/lib/aria-live-region/aria-live-region.component.ts @@ -0,0 +1,12 @@ +import { CommonModule } from '@angular/common'; +import { Component, Input } from '@angular/core'; + +@Component({ + selector: 'ods-aria-live-region', + standalone: true, + imports: [CommonModule], + template: `<span aria-live="polite" class="sr-only" role="status">{{ text }}</span>`, +}) +export class AriaLiveRegionComponent { + @Input() text: string = ''; +} diff --git a/alfa-client/libs/design-system/src/lib/aria-live-region/aria-live-region.stories.ts b/alfa-client/libs/design-system/src/lib/aria-live-region/aria-live-region.stories.ts new file mode 100644 index 0000000000000000000000000000000000000000..7af950ebf415e5e2a634be01a2edaa32a9d78a01 --- /dev/null +++ b/alfa-client/libs/design-system/src/lib/aria-live-region/aria-live-region.stories.ts @@ -0,0 +1,25 @@ +import { argsToTemplate, type Meta, type StoryObj } from '@storybook/angular'; +import { AriaLiveRegionComponent } from './aria-live-region.component'; + +const meta: Meta<AriaLiveRegionComponent> = { + title: 'Aria live region', + component: AriaLiveRegionComponent, + excludeStories: /.*Data$/, + tags: ['autodocs'], +}; + +export default meta; +type Story = StoryObj<AriaLiveRegionComponent>; + +export const Default: Story = { + args: { + text: '', + }, + render: (args) => ({ + props: args, + template: ` + <h2>This component is suitable for screen reader. Fill text field to make screen reader read it aloud.</h2> + <ods-aria-live-region ${argsToTemplate(args)} /> + `, + }), +}; diff --git a/alfa-client/libs/design-system/src/lib/attachment-header/attachment-header.component.spec.ts b/alfa-client/libs/design-system/src/lib/attachment-header/attachment-header.component.spec.ts index b1484d6418804db087a5418a8eb26a8e41114398..deccecf135ec6549e66369281d7ca0338a76b2e1 100644 --- a/alfa-client/libs/design-system/src/lib/attachment-header/attachment-header.component.spec.ts +++ b/alfa-client/libs/design-system/src/lib/attachment-header/attachment-header.component.spec.ts @@ -1,3 +1,4 @@ +import { existsAsHtmlElement, notExistsAsHtmlElement } from '@alfa-client/test-utils'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { AttachmentHeaderComponent } from './attachment-header.component'; @@ -18,4 +19,19 @@ describe('AttachmentHeaderComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + describe('title', () => { + it('should show heading', () => { + component.title = 'Test'; + fixture.detectChanges(); + + existsAsHtmlElement(fixture, 'h4'); + }); + + it('should not show heading', () => { + component.title = ''; + fixture.detectChanges(); + + notExistsAsHtmlElement(fixture, 'h4'); + }); + }); }); diff --git a/alfa-client/libs/design-system/src/lib/attachment-header/attachment-header.component.ts b/alfa-client/libs/design-system/src/lib/attachment-header/attachment-header.component.ts index dadeccb218137f87612c2d67a12c7087a6286927..6dd5bdaa1839e7782d20867a60327d9ab32cded6 100644 --- a/alfa-client/libs/design-system/src/lib/attachment-header/attachment-header.component.ts +++ b/alfa-client/libs/design-system/src/lib/attachment-header/attachment-header.component.ts @@ -5,8 +5,8 @@ import { Component, Input } from '@angular/core'; selector: 'ods-attachment-header', standalone: true, imports: [CommonModule], - template: `<div class="flex h-11 items-center justify-between px-3"> - <h4 class="text-sm font-medium text-text">{{ title }}</h4> + template: `<div class="flex h-11 items-center justify-between px-3 empty:hidden"> + <h4 class="text-sm font-medium text-text" *ngIf="title">{{ title }}</h4> <ng-content select="[action-buttons]"></ng-content> </div>`, }) diff --git a/alfa-client/libs/design-system/src/lib/attachment-wrapper/attachment-wrapper.stories.ts b/alfa-client/libs/design-system/src/lib/attachment-wrapper/attachment-wrapper.stories.ts index 485acda2600b59166e331fe38bffba7392c1e783..7d8f0fbec59aeb00afc99d2377c1902abd20c73d 100644 --- a/alfa-client/libs/design-system/src/lib/attachment-wrapper/attachment-wrapper.stories.ts +++ b/alfa-client/libs/design-system/src/lib/attachment-wrapper/attachment-wrapper.stories.ts @@ -1,7 +1,8 @@ -import { argsToTemplate, moduleMetadata, type Meta, type StoryObj } from '@storybook/angular'; +import { moduleMetadata, type Meta, type StoryObj } from '@storybook/angular'; import { DownloadButtonComponent } from '../../../../design-component/src/lib/download-button/download-button.component'; +import { AttachmentHeaderComponent } from '../attachment-header/attachment-header.component'; import { AttachmentComponent } from '../attachment/attachment.component'; import { AttachmentWrapperComponent } from './attachment-wrapper.component'; @@ -17,7 +18,12 @@ const meta: Meta<AttachmentWrapperComponent> = { }, decorators: [ moduleMetadata({ - imports: [AttachmentWrapperComponent, AttachmentComponent, DownloadButtonComponent], + imports: [ + AttachmentWrapperComponent, + AttachmentComponent, + DownloadButtonComponent, + AttachmentHeaderComponent, + ], }), ], excludeStories: /.*Data$/, @@ -28,18 +34,11 @@ export default meta; type Story = StoryObj<AttachmentWrapperComponent>; export const Default: Story = { - args: { - title: 'Anhänge', - }, - argTypes: { - title: { - description: 'Title for group of files', - }, - }, - render: (args) => ({ - props: args, - template: `<ods-attachment-wrapper ${argsToTemplate(args)}> - <ods-download-button action-buttons /> + render: () => ({ + template: `<ods-attachment-wrapper> + <ods-attachment-header title="Anhänge"> + <ods-download-button action-buttons /> + </ods-attachment-header> <ods-attachment caption="Attachment" description="200 kB" fileType="pdf"></ods-attachment> <ods-attachment caption="Second attachment" description="432 kB" fileType="doc"></ods-attachment> </ods-attachment-wrapper>`, diff --git a/alfa-client/libs/design-system/src/lib/bescheid-status-text/bescheid-status-text.component.ts b/alfa-client/libs/design-system/src/lib/bescheid-status-text/bescheid-status-text.component.ts index 9ed55dd799191cfb77c2462d25a357425f60c2ab..586fcb8a140c698f4e90a5d8b0b53f0e97556ac8 100644 --- a/alfa-client/libs/design-system/src/lib/bescheid-status-text/bescheid-status-text.component.ts +++ b/alfa-client/libs/design-system/src/lib/bescheid-status-text/bescheid-status-text.component.ts @@ -13,7 +13,7 @@ import { StampIconComponent } from '../icons/stamp-icon/stamp-icon.component'; ><ods-stamp-icon size="medium" class="fill-bewilligt" />Bewilligt am {{ dateText }}</span > <span class="flex items-center gap-2" *ngIf="!bewilligt" - ><ods-close-icon size="medium" class="fill-abgelehnt" />Abgelehnt am + ><ods-close-icon class="fill-abgelehnt" />Abgelehnt am {{ dateText }} </span> <span diff --git a/alfa-client/libs/design-system/src/lib/button/button.component.ts b/alfa-client/libs/design-system/src/lib/button/button.component.ts index 58ba7ec0d96c8c0eb0516c9fbc367b885d686837..277dbfbf830d140408f411526c2e84f33748e379 100644 --- a/alfa-client/libs/design-system/src/lib/button/button.component.ts +++ b/alfa-client/libs/design-system/src/lib/button/button.component.ts @@ -6,7 +6,10 @@ import { IconVariants } from '../icons/iconVariants'; import { SpinnerIconComponent } from '../icons/spinner-icon/spinner-icon.component'; export const buttonVariants = cva( - 'flex cursor-pointer items-center gap-4 rounded-md font-medium disabled:cursor-wait text-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary', + [ + 'flex items-center gap-4 rounded-md disabled:cursor-wait text-sm font-medium box-border', + 'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus', + ], { variants: { variant: { diff --git a/alfa-client/libs/design-system/src/lib/form/radio-button-card/radio-button-card.stories.ts b/alfa-client/libs/design-system/src/lib/form/radio-button-card/radio-button-card.stories.ts index 96ec7406916b5f4773f6aa448ad12112c913ebfc..dd949aeef16caab452a0ee6c8aee76bb0624cd79 100644 --- a/alfa-client/libs/design-system/src/lib/form/radio-button-card/radio-button-card.stories.ts +++ b/alfa-client/libs/design-system/src/lib/form/radio-button-card/radio-button-card.stories.ts @@ -53,7 +53,7 @@ export const Default: Story = { value="abgelehnt" variant="bescheid_abgelehnt" > - <ods-close-icon class="fill-abgelehnt" /> + <ods-close-icon class="fill-abgelehnt" size="large" /> </ods-radio-button-card> </div>`, }), diff --git a/alfa-client/libs/design-system/src/lib/form/text-input/text-input.component.ts b/alfa-client/libs/design-system/src/lib/form/text-input/text-input.component.ts index e136a50bb991af6df9384d7ceca287879b3cdcc8..a374f53e940446a07e40774135bdb40d7578461d 100644 --- a/alfa-client/libs/design-system/src/lib/form/text-input/text-input.component.ts +++ b/alfa-client/libs/design-system/src/lib/form/text-input/text-input.component.ts @@ -1,12 +1,12 @@ -import { convertForDataTest, TechSharedModule } from '@alfa-client/tech-shared'; +import { convertForDataTest, EMPTY_STRING, TechSharedModule } from '@alfa-client/tech-shared'; import { CommonModule } from '@angular/common'; -import { Component, ElementRef, Input, ViewChild } from '@angular/core'; +import { Component, ElementRef, EventEmitter, Input, Output, ViewChild } from '@angular/core'; import { FormControl, ReactiveFormsModule } from '@angular/forms'; import { cva, VariantProps } from 'class-variance-authority'; import { ErrorMessageComponent } from '../error-message/error-message.component'; const textInputVariants = cva( - 'block w-full rounded-lg border bg-background-50 px-3 py-2 text-base leading-5 text-text focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2', + 'w-full h-10 rounded-lg border bg-background-50 px-3 py-2 text-base leading-5 text-text focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2', { variants: { variant: { @@ -28,23 +28,40 @@ type TextInputVariants = VariantProps<typeof textInputVariants>; standalone: true, imports: [CommonModule, ErrorMessageComponent, ReactiveFormsModule, TechSharedModule], template: ` - <div> + <div class="relative"> <label [for]="id" class="text-md mb-2 block font-medium text-text"> {{ inputLabel }}<ng-container *ngIf="required"><i aria-hidden="true">*</i></ng-container> </label> <div class="mt-2"> + <div + *ngIf="withPrefix" + class="pointer-events-none absolute bottom-2 left-2 flex size-6 items-center justify-center" + > + <ng-content select="[prefix]" /> + </div> <input type="text" [id]="id" [formControl]="fieldControl" - [ngClass]="textInputVariants({ variant })" + [ngClass]="[ + textInputVariants({ variant }), + withPrefix ? 'pl-10' : '', + withSuffix ? 'pr-10' : '', + ]" [placeholder]="placeholder" [autocomplete]="autocomplete" [attr.aria-required]="required" [attr.aria-invalid]="variant === 'error'" [attr.data-test-id]="(inputLabel | convertForDataTest) + '-text-input'" + (click)="clickEmitter.emit()" #inputElement /> + <div + *ngIf="withSuffix" + class="absolute bottom-2 right-2 flex size-6 items-center justify-center" + > + <ng-content select="[suffix]" /> + </div> </div> <ng-content select="[error]"></ng-content> </div> @@ -60,8 +77,10 @@ export class TextInputComponent { @Input() placeholder: string = ''; @Input() autocomplete: string = 'off'; @Input() variant: TextInputVariants['variant']; - @Input() fieldControl: FormControl; + @Input() fieldControl: FormControl = new FormControl(EMPTY_STRING); @Input() required: boolean = false; + @Input() withPrefix: boolean = false; + @Input() withSuffix: boolean = false; @Input() set focus(value: boolean) { if (value && this.inputElement) { @@ -69,6 +88,8 @@ export class TextInputComponent { } } + @Output() clickEmitter: EventEmitter<MouseEvent> = new EventEmitter<MouseEvent>(); + inputLabel: string; id: string; textInputVariants = textInputVariants; diff --git a/alfa-client/libs/design-system/src/lib/form/textarea/textarea.component.ts b/alfa-client/libs/design-system/src/lib/form/textarea/textarea.component.ts index 90a4fa85f7dcb0b5a55f6a926a0d08ab63e332a9..da75590e40ed4afedee224ff82f89a741c9d5bef 100644 --- a/alfa-client/libs/design-system/src/lib/form/textarea/textarea.component.ts +++ b/alfa-client/libs/design-system/src/lib/form/textarea/textarea.component.ts @@ -1,4 +1,4 @@ -import { TechSharedModule, convertForDataTest } from '@alfa-client/tech-shared'; +import { EMPTY_STRING, TechSharedModule, convertForDataTest } from '@alfa-client/tech-shared'; import { CommonModule } from '@angular/common'; import { Component, ElementRef, Input, ViewChild } from '@angular/core'; import { FormControl, ReactiveFormsModule } from '@angular/forms'; @@ -59,7 +59,7 @@ export class TextareaComponent { @Input() rows: number = 3; @Input() autocomplete: string = 'off'; @Input() variant: TextareaVariants['variant']; - @Input() fieldControl: FormControl; + @Input() fieldControl: FormControl = new FormControl(EMPTY_STRING); @Input() required: boolean = false; @Input() set focus(value: boolean) { diff --git a/alfa-client/libs/design-system/src/lib/icons/close-icon/close-icon.component.ts b/alfa-client/libs/design-system/src/lib/icons/close-icon/close-icon.component.ts index 5de688d73e98d67b1296d1519332fd7e3b7c9660..48fe0de3b897881d8bb669774572ea8f68a6895e 100644 --- a/alfa-client/libs/design-system/src/lib/icons/close-icon/close-icon.component.ts +++ b/alfa-client/libs/design-system/src/lib/icons/close-icon/close-icon.component.ts @@ -12,16 +12,16 @@ import { IconVariants, iconVariants } from '../iconVariants'; xmlns="http://www.w3.org/2000/svg" [ngClass]="[twMerge(iconVariants({ size }), 'fill-black', class)]" aria-hidden="true" - viewBox="0 0 14 14" + viewBox="0 0 24 24" fill="inherit" > <path - d="M14 1.41L12.59 0L7 5.59L1.41 0L0 1.41L5.59 7L0 12.59L1.41 14L7 8.41L12.59 14L14 12.59L8.41 7L14 1.41Z" + d="M19 6.41L17.59 5L12 10.59L6.41 5L5 6.41L10.59 12L5 17.59L6.41 19L12 13.41L17.59 19L19 17.59L13.41 12L19 6.41Z" /> </svg>`, }) export class CloseIconComponent { - @Input() size: IconVariants['size'] = 'small'; + @Input() size: IconVariants['size'] = 'medium'; @Input() class: string = undefined; iconVariants = iconVariants; diff --git a/alfa-client/libs/design-system/src/lib/icons/close-icon/close-icon.stories.ts b/alfa-client/libs/design-system/src/lib/icons/close-icon/close-icon.stories.ts index b8a8eda0b8afab576ba5b7086d226aa2f0adcc96..9cd750050413bd481bcdc246810c8eeb24b53c8c 100644 --- a/alfa-client/libs/design-system/src/lib/icons/close-icon/close-icon.stories.ts +++ b/alfa-client/libs/design-system/src/lib/icons/close-icon/close-icon.stories.ts @@ -20,7 +20,7 @@ export const Default: Story = { options: ['small', 'medium', 'large', 'extra-large', 'full'], description: 'Size of icon. Property "full" means 100%', table: { - defaultValue: { summary: 'small' }, + defaultValue: { summary: 'medium' }, }, }, }, diff --git a/alfa-client/libs/design-system/src/lib/icons/collaboration-icon/collaboration-icon.component.spec.ts b/alfa-client/libs/design-system/src/lib/icons/collaboration-icon/collaboration-icon.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..c1a2136a653c2b9dfc52bc039fb0aa1d48ce0733 --- /dev/null +++ b/alfa-client/libs/design-system/src/lib/icons/collaboration-icon/collaboration-icon.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { CollaborationIconComponent } from './collaboration-icon.component'; + +describe('CollaborationIconComponent', () => { + let component: CollaborationIconComponent; + let fixture: ComponentFixture<CollaborationIconComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [CollaborationIconComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(CollaborationIconComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/alfa-client/libs/design-system/src/lib/icons/collaboration-icon/collaboration-icon.component.ts b/alfa-client/libs/design-system/src/lib/icons/collaboration-icon/collaboration-icon.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..8565574eac373499fcedaea5b514e7f616b344e1 --- /dev/null +++ b/alfa-client/libs/design-system/src/lib/icons/collaboration-icon/collaboration-icon.component.ts @@ -0,0 +1,53 @@ +import { CommonModule } from '@angular/common'; +import { Component, Input } from '@angular/core'; +import { twMerge } from 'tailwind-merge'; +import { IconVariants, iconVariants } from '../iconVariants'; + +@Component({ + selector: 'ods-collaboration-icon', + standalone: true, + imports: [CommonModule], + template: `<svg + viewBox="0 0 24 24" + [ngClass]="[twMerge(iconVariants({ size }), 'stroke-primary', class)]" + aria-hidden="true" + fill="inherit" + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M16 21V19C16 17.9391 15.5786 16.9217 14.8284 16.1716C14.0783 15.4214 13.0609 15 12 15H6C4.93913 15 3.92172 15.4214 3.17157 16.1716C2.42143 16.9217 2 17.9391 2 19V21" + stroke-width="2" + stroke-linecap="round" + stroke-linejoin="round" + fill="none" + /> + <path + d="M9 11C11.2091 11 13 9.20914 13 7C13 4.79086 11.2091 3 9 3C6.79086 3 5 4.79086 5 7C5 9.20914 6.79086 11 9 11Z" + stroke-width="2" + stroke-linecap="round" + stroke-linejoin="round" + fill="none" + /> + <path + d="M22 20.9999V18.9999C21.9993 18.1136 21.7044 17.2527 21.1614 16.5522C20.6184 15.8517 19.8581 15.3515 19 15.1299" + stroke-width="2" + stroke-linecap="round" + stroke-linejoin="round" + fill="none" + /> + <path + d="M16 3.12988C16.8604 3.35018 17.623 3.85058 18.1676 4.55219C18.7122 5.2538 19.0078 6.11671 19.0078 7.00488C19.0078 7.89305 18.7122 8.75596 18.1676 9.45757C17.623 10.1592 16.8604 10.6596 16 10.8799" + stroke-width="2" + stroke-linecap="round" + stroke-linejoin="round" + fill="none" + /> + </svg>`, +}) +export class CollaborationIconComponent { + @Input() size: IconVariants['size'] = 'medium'; + @Input() class: string = undefined; + + iconVariants = iconVariants; + twMerge = twMerge; +} diff --git a/alfa-client/libs/design-system/src/lib/icons/collaboration-icon/collaboration-icon.stories.ts b/alfa-client/libs/design-system/src/lib/icons/collaboration-icon/collaboration-icon.stories.ts new file mode 100644 index 0000000000000000000000000000000000000000..dbc1440939f920bb66062062c12ac343a347ddce --- /dev/null +++ b/alfa-client/libs/design-system/src/lib/icons/collaboration-icon/collaboration-icon.stories.ts @@ -0,0 +1,27 @@ +import type { Meta, StoryObj } from '@storybook/angular'; + +import { CollaborationIconComponent } from './collaboration-icon.component'; + +const meta: Meta<CollaborationIconComponent> = { + title: 'Icons/Collaboration icon', + component: CollaborationIconComponent, + excludeStories: /.*Data$/, + tags: ['autodocs'], +}; + +export default meta; +type Story = StoryObj<CollaborationIconComponent>; + +export const Default: Story = { + args: { size: 'medium' }, + argTypes: { + size: { + control: 'select', + options: ['small', 'medium', 'large', 'extra-large', 'full'], + description: 'Size of icon. Property "full" means 100%', + table: { + defaultValue: { summary: 'medium' }, + }, + }, + }, +}; diff --git a/alfa-client/libs/design-system/src/lib/icons/search-icon/search-icon.component.spec.ts b/alfa-client/libs/design-system/src/lib/icons/search-icon/search-icon.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..5981e81db28b29d1dfe651b306e3f3f938ec0bfb --- /dev/null +++ b/alfa-client/libs/design-system/src/lib/icons/search-icon/search-icon.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { SearchIconComponent } from './search-icon.component'; + +describe('SearchIconComponent', () => { + let component: SearchIconComponent; + let fixture: ComponentFixture<SearchIconComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [SearchIconComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(SearchIconComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/alfa-client/libs/design-system/src/lib/icons/search-icon/search-icon.component.ts b/alfa-client/libs/design-system/src/lib/icons/search-icon/search-icon.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..641713c3842c42cadb4d4075051fc51141d5f2e3 --- /dev/null +++ b/alfa-client/libs/design-system/src/lib/icons/search-icon/search-icon.component.ts @@ -0,0 +1,28 @@ +import { NgClass } from '@angular/common'; +import { Component, Input } from '@angular/core'; +import { twMerge } from 'tailwind-merge'; + +import { IconVariants, iconVariants } from '../iconVariants'; + +@Component({ + selector: 'ods-search-icon', + standalone: true, + imports: [NgClass], + template: `<svg + [ngClass]="twMerge(iconVariants({ size }), 'fill-primary', class)" + viewBox="0 0 24 24" + fill="none" + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M15.5 14H14.71L14.43 13.73C15.41 12.59 16 11.11 16 9.5C16 5.91 13.09 3 9.5 3C5.91 3 3 5.91 3 9.5C3 13.09 5.91 16 9.5 16C11.11 16 12.59 15.41 13.73 14.43L14 14.71V15.5L19 20.49L20.49 19L15.5 14ZM9.5 14C7.01 14 5 11.99 5 9.5C5 7.01 7.01 5 9.5 5C11.99 5 14 7.01 14 9.5C14 11.99 11.99 14 9.5 14Z" + /> + </svg>`, +}) +export class SearchIconComponent { + @Input() size: IconVariants['size'] = 'medium'; + @Input() class: string = ''; + + iconVariants = iconVariants; + twMerge = twMerge; +} diff --git a/alfa-client/libs/design-system/src/lib/icons/search-icon/search-icon.stories.ts b/alfa-client/libs/design-system/src/lib/icons/search-icon/search-icon.stories.ts new file mode 100644 index 0000000000000000000000000000000000000000..1952260187ba8fc7f462803ad2e2674f6d071856 --- /dev/null +++ b/alfa-client/libs/design-system/src/lib/icons/search-icon/search-icon.stories.ts @@ -0,0 +1,27 @@ +import type { Meta, StoryObj } from '@storybook/angular'; + +import { SearchIconComponent } from './search-icon.component'; + +const meta: Meta<SearchIconComponent> = { + title: 'Icons/Search icon', + component: SearchIconComponent, + excludeStories: /.*Data$/, + tags: ['autodocs'], +}; + +export default meta; +type Story = StoryObj<SearchIconComponent>; + +export const Default: Story = { + args: { size: 'large' }, + argTypes: { + size: { + control: 'select', + options: ['small', 'medium', 'large', 'extra-large', 'full'], + description: 'Size of icon. Property "full" means 100%', + table: { + defaultValue: { summary: 'medium' }, + }, + }, + }, +}; diff --git a/alfa-client/libs/design-system/src/lib/instant-search/instant-search/instant-search.component.spec.ts b/alfa-client/libs/design-system/src/lib/instant-search/instant-search/instant-search.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..f900a81ba73a568c8be41ba225930acfc7d687a0 --- /dev/null +++ b/alfa-client/libs/design-system/src/lib/instant-search/instant-search/instant-search.component.spec.ts @@ -0,0 +1,609 @@ +import { Mock, mock, useFromMock } from '@alfa-client/test-utils'; +import { EventEmitter } from '@angular/core'; +import { + ComponentFixture, + TestBed, + discardPeriodicTasks, + fakeAsync, + tick, +} from '@angular/core/testing'; +import { Subscription } from 'rxjs'; +import { InstantSearchComponent } from './instant-search.component'; +import { InstantSearchQuery, InstantSearchResult } from './instant-search.model'; + +describe('InstantSearchComponent', () => { + let component: InstantSearchComponent; + let fixture: ComponentFixture<InstantSearchComponent>; + + const searchResults: InstantSearchResult<unknown>[] = [ + { title: 'test', description: 'test' }, + { title: 'caption', description: 'desc' }, + ]; + const searchBy: string = 'query'; + + let searchQueryChanged: Mock<EventEmitter<any>>; + let searchResultSelected: Mock<EventEmitter<any>>; + + beforeEach(async () => { + searchQueryChanged = <any>mock(EventEmitter); + searchResultSelected = <any>mock(EventEmitter); + + await TestBed.configureTestingModule({ + imports: [InstantSearchComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(InstantSearchComponent); + component = fixture.componentInstance; + component.searchQueryChanged = useFromMock(searchQueryChanged); + component.searchResultSelected = useFromMock(searchResultSelected); + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('ngOnInit', () => { + it('should handle value changes', () => { + component.handleValueChanges = jest.fn(); + + component.ngOnInit(); + + expect(component.handleValueChanges).toHaveBeenCalled(); + }); + }); + + describe('handleValueChanges', () => { + beforeEach(() => { + component.showResults = jest.fn(); + }); + + it('should subscribe to value changes', () => { + component.control.valueChanges.subscribe = jest.fn(); + + component.handleValueChanges(); + + expect(component.control.valueChanges.subscribe).toHaveBeenCalled(); + }); + + it('should emit query', fakeAsync(() => { + component.handleValueChanges(); + + component.control.setValue(searchBy); + + tick(InstantSearchComponent.DEBOUNCE_TIME_IN_MILLIS); + component.control.valueChanges.subscribe(); + + expect(searchQueryChanged.emit).toHaveBeenCalledWith({ searchBy } as InstantSearchQuery); + })); + + it('should not emit query', fakeAsync(() => { + component.handleValueChanges(); + + const searchBy: string = 'q'; + component.control.setValue(searchBy); + + tick(InstantSearchComponent.DEBOUNCE_TIME_IN_MILLIS); + component.control.valueChanges.subscribe(); + + expect(searchQueryChanged.emit).not.toHaveBeenCalled(); + })); + + describe('result are already visible', () => { + beforeEach(() => { + component.areResultsVisible = true; + }); + + it('should not show results', fakeAsync(() => { + component.handleValueChanges(); + + component.control.setValue(searchBy); + tick(InstantSearchComponent.DEBOUNCE_TIME_IN_MILLIS); + + component.control.valueChanges.subscribe(); + expect(component.showResults).not.toHaveBeenCalled(); + + discardPeriodicTasks(); + })); + }); + + describe('results are not visible', () => { + beforeEach(() => { + component.areResultsVisible = false; + }); + + it('should show results', fakeAsync(() => { + component.handleValueChanges(); + + component.control.setValue(searchBy); + tick(InstantSearchComponent.DEBOUNCE_TIME_IN_MILLIS); + + component.control.valueChanges.subscribe(); + expect(component.showResults).toHaveBeenCalled(); + + discardPeriodicTasks(); + })); + + it('should not show results if debounce time not reached', fakeAsync(() => { + component.handleValueChanges(); + + component.control.setValue(searchBy); + tick(InstantSearchComponent.DEBOUNCE_TIME_IN_MILLIS - 1); + + component.control.valueChanges.subscribe(); + expect(component.showResults).not.toHaveBeenCalled(); + + discardPeriodicTasks(); + })); + + it('should not show results if not enough characters entered', fakeAsync(() => { + component.handleValueChanges(); + + component.control.setValue('q'); + tick(InstantSearchComponent.DEBOUNCE_TIME_IN_MILLIS); + + component.control.valueChanges.subscribe(); + expect(component.showResults).not.toHaveBeenCalled(); + + discardPeriodicTasks(); + })); + }); + }); + + describe('ngOnDestroy', () => { + it('should subscribe to value changes', () => { + component.formControlSubscription = new Subscription(); + component.formControlSubscription.unsubscribe = jest.fn(); + + component.ngOnDestroy(); + + expect(component.formControlSubscription.unsubscribe).toHaveBeenCalled(); + }); + }); + + describe('set searchResults', () => { + describe('on different results', () => { + it('should call setSearchResults', () => { + component.setSearchResults = jest.fn(); + + component.searchResults = searchResults; + + expect(component.setSearchResults).toHaveBeenCalled(); + }); + }); + + describe('on same results', () => { + it('should not call setSearchResults', () => { + component.setSearchResults = jest.fn(); + + component.searchResults = []; + + expect(component.setSearchResults).not.toHaveBeenCalled(); + }); + }); + + describe('on null or undefined', () => { + it.each([null, undefined])( + 'should not call setSearchResults for %s', + (searchResults: InstantSearchResult<unknown>[]) => { + component.setSearchResults = jest.fn(); + + component.searchResults = searchResults; + + expect(component.setSearchResults).not.toHaveBeenCalled(); + }, + ); + }); + }); + + describe('setSearchResults', () => { + it('should set results', () => { + component.setSearchResults(searchResults); + + expect(component.results).toEqual(searchResults); + }); + + it('should call buildAriaLiveText with search results length', () => { + component.buildAriaLiveText = jest.fn(); + + component.setSearchResults(searchResults); + + expect(component.buildAriaLiveText).toHaveBeenCalledWith(searchResults.length); + }); + }); + + describe('setFocusOnResultItem', () => { + beforeEach(() => { + component.resultsRef.get = jest.fn().mockReturnValue({ setFocus: jest.fn() }); + }); + + it('should call get for resultsRef with index', () => { + component.setFocusOnResultItem(1); + + expect(component.resultsRef.get).toHaveBeenCalledWith(1); + }); + + it('should call setFocus', () => { + component.setFocusOnResultItem(1); + + expect(component.resultsRef.get(1).setFocus).toHaveBeenCalled(); + }); + }); + + describe('handleArrowNavigation', () => { + const event: KeyboardEvent = new KeyboardEvent('arrow'); + + beforeEach(() => { + component.getResultIndexForKey = jest.fn(); + component.setFocusOnResultItem = jest.fn(); + }); + + it('should call prevent default', () => { + event.preventDefault = jest.fn(); + + component.handleArrowNavigation(event); + + expect(event.preventDefault).toHaveBeenCalled(); + }); + + it('should call getResultIndexForKey', () => { + component.handleArrowNavigation(event); + + expect(component.getResultIndexForKey).toHaveBeenCalledWith(event.key); + }); + + it('should call setFocusOnResultItem', () => { + component.getResultIndexForKey = jest.fn().mockReturnValue(0); + + component.handleArrowNavigation(event); + + expect(component.setFocusOnResultItem).toHaveBeenCalledWith(0); + }); + }); + + describe('handleEscape', () => { + const event: KeyboardEvent = new KeyboardEvent('esc'); + + it('should call prevent default', () => { + event.preventDefault = jest.fn(); + + component.handleEscape(event); + + expect(event.preventDefault).toHaveBeenCalled(); + }); + + it('should call hideResults', () => { + component.hideResults = jest.fn(); + + component.handleEscape(event); + + expect(component.hideResults).toHaveBeenCalled(); + }); + }); + + describe('getNextResultIndex', () => { + it('should return 0 if index is undefined', () => { + const result: number = component.getNextResultIndex(undefined, 2); + + expect(result).toBe(0); + }); + + it('should return 0 if current index is last', () => { + const result: number = component.getNextResultIndex(1, 2); + + expect(result).toBe(0); + }); + + it('should return next search result index', () => { + const result: number = component.getNextResultIndex(0, 2); + + expect(result).toBe(1); + }); + }); + + describe('getPreviousResultIndex', () => { + it('should return last index if current index is undefined', () => { + const result: number = component.getPreviousResultIndex(undefined, 2); + + expect(result).toBe(1); + }); + + it('should return last index if current index is first', () => { + const result: number = component.getPreviousResultIndex(0, 2); + + expect(result).toBe(1); + }); + + it('should return previous search result index', () => { + const result: number = component.getPreviousResultIndex(1, 2); + + expect(result).toBe(0); + }); + }); + + describe('getResultIndexForKey', () => { + it('should call getNextResultIndex if ArrowDown', () => { + component.getNextResultIndex = jest.fn(); + + component.getResultIndexForKey('ArrowDown'); + + expect(component.getNextResultIndex).toHaveBeenCalled(); + }); + + it('should call getPreviousResultIndex if ArrowUp', () => { + component.getPreviousResultIndex = jest.fn(); + + component.getResultIndexForKey('ArrowUp'); + + expect(component.getPreviousResultIndex).toHaveBeenCalled(); + }); + }); + + describe('getLastItemIndex', () => { + it('should return 0', () => { + const result: number = component.getLastItemIndex(0); + + expect(result).toBe(0); + }); + + it('should return decrement of array length', () => { + const result: number = component.getLastItemIndex(5); + + expect(result).toBe(4); + }); + }); + + describe('buildAriaLiveText', () => { + beforeEach(() => { + component.control.setValue('test'); + }); + + it('should return text for one result', () => { + const result: string = component.buildAriaLiveText(1); + + expect(result).toBe( + 'Ein Suchergebnis für Eingabe test. Nutze Pfeiltaste nach unten, um das zu erreichen.', + ); + }); + + it('should return text for many results', () => { + const result: string = component.buildAriaLiveText(4); + + expect(result).toBe( + '4 Suchergebnisse für Eingabe test. Nutze Pfeiltaste nach unten, um diese zu erreichen.', + ); + }); + + it('should return text for no results', () => { + const result: string = component.buildAriaLiveText(0); + + expect(result).toBe('Keine Ergebnisse'); + }); + }); + + describe('showResults', () => { + it('should set isShowResults to true', () => { + component.showResults(); + + expect(component.areResultsVisible).toBe(true); + }); + }); + + describe('hideResults', () => { + it('should set isShowResults to false', () => { + component.hideResults(); + + expect(component.areResultsVisible).toBe(false); + }); + }); + + describe('onKeydownHandler', () => { + const keyboardEvent: KeyboardEvent = new KeyboardEvent('a'); + + beforeEach(() => { + component.isSearchResultsEmpty = jest.fn(); + component.isArrowNavigationKey = jest.fn(); + component.isEscapeKey = jest.fn(); + component.handleArrowNavigation = jest.fn(); + component.handleEscape = jest.fn(); + }); + + it('should check for empty result', () => { + component.onKeydownHandler(keyboardEvent); + + expect(component.isSearchResultsEmpty).toHaveBeenCalled(); + }); + + describe('search result is empty', () => { + beforeEach(() => { + component.isSearchResultsEmpty = jest.fn().mockReturnValue(true); + }); + + it('should ignore key navigation', () => { + component.onKeydownHandler(keyboardEvent); + + expect(component.isArrowNavigationKey).not.toHaveBeenCalled(); + }); + + it('should ignore escape key handling', () => { + component.onKeydownHandler(keyboardEvent); + + expect(component.isEscapeKey).not.toHaveBeenCalled(); + }); + }); + + describe('search result is not empty', () => { + beforeEach(() => { + component.isSearchResultsEmpty = jest.fn().mockReturnValue(false); + }); + + it('should check if arrow navigation', () => { + component.onKeydownHandler(keyboardEvent); + + expect(component.isArrowNavigationKey).toHaveBeenCalled(); + }); + + it('should handle arrow navigation', () => { + component.isArrowNavigationKey = jest.fn().mockReturnValue(true); + + component.onKeydownHandler(keyboardEvent); + + expect(component.handleArrowNavigation).toHaveBeenCalled(); + }); + + it('should not handle arrow navigation', () => { + component.isArrowNavigationKey = jest.fn().mockReturnValue(false); + + component.onKeydownHandler(keyboardEvent); + + expect(component.handleArrowNavigation).not.toHaveBeenCalled(); + }); + + describe('is not arrow navigation', () => { + beforeEach(() => { + component.isArrowNavigationKey = jest.fn().mockReturnValue(false); + }); + + it('should check for escape key', () => { + component.onKeydownHandler(keyboardEvent); + + expect(component.isEscapeKey).toHaveBeenCalled(); + }); + + it('should handle escape key', () => { + component.isEscapeKey = jest.fn().mockReturnValue(true); + + component.onKeydownHandler(keyboardEvent); + + expect(component.handleEscape).toHaveBeenCalled(); + }); + + it('should not handle escape key', () => { + component.isEscapeKey = jest.fn().mockReturnValue(false); + + component.onKeydownHandler(keyboardEvent); + + expect(component.handleEscape).not.toHaveBeenCalled(); + }); + }); + }); + }); + + describe('isSearchResultsEmpty', () => { + it('should return true', () => { + component.results = []; + + const result: boolean = component.isSearchResultsEmpty(); + + expect(result).toBe(true); + }); + + it('should return false', () => { + component.results = searchResults; + + const result: boolean = component.isSearchResultsEmpty(); + + expect(result).toBe(false); + }); + }); + + describe('isArrowNavigationKey', () => { + it.each(['ArrowUp', 'ArrowDown'])('should return true for key %s', (key: string) => { + const keyboardEvent: KeyboardEvent = { ...new KeyboardEvent('key'), key }; + + const result: boolean = component.isArrowNavigationKey(keyboardEvent); + + expect(result).toBeTruthy(); + }); + + it('should return false', () => { + const result: boolean = component.isArrowNavigationKey(new KeyboardEvent('not arrow')); + + expect(result).toBe(false); + }); + }); + + describe('isEscapeKey', () => { + it('should return true', () => { + const escapeKeyEvent = { ...new KeyboardEvent('esc'), key: 'Escape' }; + + const result: boolean = component.isEscapeKey(escapeKeyEvent); + + expect(result).toBe(true); + }); + + it('should return false', () => { + const result: boolean = component.isEscapeKey(new KeyboardEvent('not escape')); + + expect(result).toBe(false); + }); + }); + + describe('isLastItemOrOutOfArray', () => { + it.each([3, 5])('should return true for %s', (index: number) => { + const result: boolean = component.isLastItemOrOutOfArray(index, 4); + + expect(result).toBe(true); + }); + + it('should return false', () => { + const result: boolean = component.isLastItemOrOutOfArray(1, 3); + + expect(result).toBe(false); + }); + }); + + describe('isFirstItemOrOutOfArray', () => { + it.each([0, -1])('should return true for %s', (index: number) => { + const result: boolean = component.isFirstItemOrOutOfArray(index); + + expect(result).toBe(true); + }); + + it('should return false', () => { + const result: boolean = component.isFirstItemOrOutOfArray(1); + + expect(result).toBe(false); + }); + }); + + describe('onItemClicked', () => { + beforeEach(() => { + component.hideResults = jest.fn(); + }); + + it('should emit searchResultSelected', () => { + component.onItemClicked(searchResults[0], 0); + + expect(searchResultSelected.emit).toHaveBeenCalledWith(searchResults[0]); + }); + + it('should hide results', () => { + component.onItemClicked(searchResults[0], 0); + + expect(component.hideResults).toHaveBeenCalled(); + }); + }); + + describe('onClickHandler', () => { + const e: MouseEvent = { ...new MouseEvent('test') }; + + beforeEach(() => { + component.hideResults = jest.fn(); + }); + + it('should call hideResults if instant search does not contain event target', () => { + component.onClickHandler(e); + + expect(component.hideResults).toHaveBeenCalled(); + }); + + it('should not call hideResults if instant search contains event target', () => { + component.ref.nativeElement.contains = jest.fn().mockReturnValue(true); + + component.onClickHandler(e); + + expect(component.hideResults).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/alfa-client/libs/design-system/src/lib/instant-search/instant-search/instant-search.component.ts b/alfa-client/libs/design-system/src/lib/instant-search/instant-search/instant-search.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..c16d9fab5189ca71bb978839e0b602d114d35882 --- /dev/null +++ b/alfa-client/libs/design-system/src/lib/instant-search/instant-search/instant-search.component.ts @@ -0,0 +1,224 @@ +import { EMPTY_STRING, isNotNil } from '@alfa-client/tech-shared'; +import { CommonModule } from '@angular/common'; +import { + Component, + ElementRef, + EventEmitter, + HostListener, + Input, + OnDestroy, + OnInit, + Output, + QueryList, + ViewChildren, +} from '@angular/core'; +import { FormControl } from '@angular/forms'; +import { isEqual, isUndefined } from 'lodash-es'; +import { Subscription, debounceTime, distinctUntilChanged, filter } from 'rxjs'; +import { AriaLiveRegionComponent } from '../../aria-live-region/aria-live-region.component'; +import { SearchFieldComponent } from '../search-field/search-field.component'; +import { SearchResultHeaderComponent } from '../search-result-header/search-result-header.component'; +import { SearchResultItemComponent } from '../search-result-item/search-result-item.component'; +import { SearchResultLayerComponent } from '../search-result-layer/search-result-layer.component'; +import { InstantSearchQuery, InstantSearchResult } from './instant-search.model'; + +@Component({ + selector: 'ods-instant-search', + standalone: true, + imports: [ + CommonModule, + SearchFieldComponent, + SearchResultHeaderComponent, + SearchResultItemComponent, + SearchResultLayerComponent, + AriaLiveRegionComponent, + ], + template: ` <div class="relative"> + <ods-search-field + [placeholder]="placeholder" + [label]="label" + [attr.aria-expanded]="results.length" + [control]="control" + aria-controls="results" + (inputClicked)="showResults()" + #searchField + /> + <ods-aria-live-region [text]="ariaLiveText" /> + <ods-search-result-layer + *ngIf="results.length && areResultsVisible" + class="absolute z-50 mt-3 w-full" + id="results" + > + <ods-search-result-header [text]="headerText" [count]="results.length" header /> + <ods-search-result-item + *ngFor="let result of results; let i = index" + [title]="result.title" + [description]="result.description" + (itemClicked)="onItemClicked(result, i)" + #results + ></ods-search-result-item> + </ods-search-result-layer> + </div>`, +}) +export class InstantSearchComponent implements OnInit, OnDestroy { + static readonly DEBOUNCE_TIME_IN_MILLIS: number = 300; + + @Input() label: string = EMPTY_STRING; + @Input() placeholder: string = EMPTY_STRING; + @Input() headerText: string = EMPTY_STRING; + @Input() control: FormControl<string> = new FormControl(EMPTY_STRING); + + @Input() set searchResults(searchResults: InstantSearchResult<unknown>[]) { + if (!isEqual(searchResults, this.results) && isNotNil(searchResults)) { + this.setSearchResults(searchResults); + } + } + + @Output() searchResultSelected: EventEmitter<InstantSearchResult<unknown>> = new EventEmitter< + InstantSearchResult<unknown> + >(); + @Output() searchQueryChanged: EventEmitter<InstantSearchQuery> = + new EventEmitter<InstantSearchQuery>(); + + readonly FIRST_ITEM_INDEX: number = 0; + readonly PREVIEW_SEARCH_STRING_MIN_LENGTH: number = 2; + results: InstantSearchResult<unknown>[] = []; + ariaLiveText: string = ''; + areResultsVisible: boolean = true; + private focusedResult: number | undefined = undefined; + formControlSubscription: Subscription; + + constructor(public ref: ElementRef) {} + + @ViewChildren('results') resultsRef: QueryList<SearchResultItemComponent>; + + ngOnInit(): void { + this.handleValueChanges(); + } + + handleValueChanges() { + this.formControlSubscription = this.control.valueChanges + .pipe( + debounceTime(InstantSearchComponent.DEBOUNCE_TIME_IN_MILLIS), + filter((value: string) => value.length >= this.PREVIEW_SEARCH_STRING_MIN_LENGTH), + distinctUntilChanged(), + ) + .subscribe((searchBy: string) => { + this.searchQueryChanged.emit({ searchBy }); + if (!this.areResultsVisible) { + this.showResults(); + } + }); + } + + ngOnDestroy(): void { + if (isNotNil(this.formControlSubscription)) this.formControlSubscription.unsubscribe(); + } + + @HostListener('document:keydown', ['$event']) + onKeydownHandler(e: KeyboardEvent): void { + if (this.isSearchResultsEmpty()) return; + if (this.isArrowNavigationKey(e)) this.handleArrowNavigation(e); + if (this.isEscapeKey(e)) this.handleEscape(e); + } + + @HostListener('document:click', ['$event']) + onClickHandler(e: MouseEvent): void { + if (!this.ref.nativeElement.contains(e.target)) { + this.hideResults(); + } + } + + handleArrowNavigation(e: KeyboardEvent): void { + e.preventDefault(); + const newIndex = this.getResultIndexForKey(e.key); + this.focusedResult = newIndex; + this.setFocusOnResultItem(newIndex); + } + + handleEscape(e: KeyboardEvent): void { + e.preventDefault(); + this.hideResults(); + } + + setFocusOnResultItem(index: number): void { + this.resultsRef.get(index).setFocus(); + } + + setSearchResults(searchResults: InstantSearchResult<unknown>[]): void { + this.results = searchResults; + this.ariaLiveText = this.buildAriaLiveText(searchResults.length); + } + + getNextResultIndex(index: number | undefined, resultLength: number): number { + if (isUndefined(index)) return this.FIRST_ITEM_INDEX; + if (this.isLastItemOrOutOfArray(index, resultLength)) return this.FIRST_ITEM_INDEX; + return index + 1; + } + + getPreviousResultIndex(index: number | undefined, resultLength: number): number { + if (isUndefined(index)) return this.getLastItemIndex(resultLength); + if (this.isFirstItemOrOutOfArray(index)) return this.getLastItemIndex(resultLength); + return index - 1; + } + + getLastItemIndex(arrayLength: number): number { + if (arrayLength < 1) return this.FIRST_ITEM_INDEX; + return arrayLength - 1; + } + + getResultIndexForKey(key: string): number { + switch (key) { + case 'ArrowDown': + return this.getNextResultIndex(this.focusedResult, this.results.length); + case 'ArrowUp': + return this.getPreviousResultIndex(this.focusedResult, this.results.length); + default: + console.error('Key %s not allowed', key); + } + } + + buildAriaLiveText(resultsLength: number): string { + if (resultsLength === 1) + return `Ein Suchergebnis für Eingabe ${this.control.value}. Nutze Pfeiltaste nach unten, um das zu erreichen.`; + if (resultsLength > 1) + return `${resultsLength} Suchergebnisse für Eingabe ${this.control.value}. Nutze Pfeiltaste nach unten, um diese zu erreichen.`; + return 'Keine Ergebnisse'; + } + + showResults(): void { + this.areResultsVisible = true; + this.focusedResult = undefined; + } + + hideResults(): void { + this.areResultsVisible = false; + this.focusedResult = undefined; + } + + isLastItemOrOutOfArray(index: number, arrayLength: number): boolean { + return index >= arrayLength - 1; + } + + isFirstItemOrOutOfArray(index: number): boolean { + return index <= this.FIRST_ITEM_INDEX; + } + + isSearchResultsEmpty(): boolean { + return this.results.length === 0; + } + + isArrowNavigationKey(e: KeyboardEvent): boolean { + return e.key === 'ArrowDown' || e.key === 'ArrowUp'; + } + + isEscapeKey(e: KeyboardEvent): boolean { + return e.key === 'Escape'; + } + + onItemClicked(searchResult: InstantSearchResult<unknown>, index: number) { + this.searchResultSelected.emit(searchResult); + this.focusedResult = index; + this.hideResults(); + } +} diff --git a/alfa-client/libs/design-system/src/lib/instant-search/instant-search/instant-search.model.ts b/alfa-client/libs/design-system/src/lib/instant-search/instant-search/instant-search.model.ts new file mode 100644 index 0000000000000000000000000000000000000000..5debae8ceb40be63d765fbb9a401cb47d601e5c5 --- /dev/null +++ b/alfa-client/libs/design-system/src/lib/instant-search/instant-search/instant-search.model.ts @@ -0,0 +1,9 @@ +export interface InstantSearchResult<T> { + title: string; + description: string; + data?: T; +} + +export interface InstantSearchQuery { + searchBy: string; +} diff --git a/alfa-client/libs/design-system/src/lib/instant-search/instant-search/instant-search.stories.ts b/alfa-client/libs/design-system/src/lib/instant-search/instant-search/instant-search.stories.ts new file mode 100644 index 0000000000000000000000000000000000000000..bfe67d23232b417e6cddd3b33f53245a793b161e --- /dev/null +++ b/alfa-client/libs/design-system/src/lib/instant-search/instant-search/instant-search.stories.ts @@ -0,0 +1,40 @@ +import { type Meta, type StoryObj } from '@storybook/angular'; +import { InstantSearchComponent } from './instant-search.component'; + +const meta: Meta<InstantSearchComponent> = { + title: 'Instant search/Instant search', + component: InstantSearchComponent, + excludeStories: /.*Data$/, + tags: ['autodocs'], +}; + +export default meta; +type Story = StoryObj<InstantSearchComponent>; + +export const Default: Story = { + args: { + label: '', + placeholder: 'zuständige Stelle suchen', + headerText: 'In der OZG-Cloud', + }, +}; + +export const SearchResults: Story = { + args: { + label: '', + placeholder: 'zuständige Stelle suchen', + headerText: 'In der OZG-Cloud', + searchResults: [ + { + text: 'Landeshauptstadt Kiel - Ordnungsamt, Gewerbe- und Schornsteinfegeraufsicht', + subText: 'Fabrikstraße 8-10, 24103 Kiel', + onClick: () => undefined, + }, + { + text: 'Amt für Digitalisierung, Breitband und Vermessung Nürnberg Außenstelle Hersbruck', + subText: 'Rathausmarkt 7, Hersbruck', + onClick: () => undefined, + }, + ], + }, +}; diff --git a/alfa-client/libs/design-system/src/lib/instant-search/search-field/search-field.component.spec.ts b/alfa-client/libs/design-system/src/lib/instant-search/search-field/search-field.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..f1f730f80e1b6cf82b90568b16df0ffd8b3a76bd --- /dev/null +++ b/alfa-client/libs/design-system/src/lib/instant-search/search-field/search-field.component.spec.ts @@ -0,0 +1,47 @@ +import { EMPTY_STRING } from '@alfa-client/tech-shared'; +import { getElementFromFixtureByType, mock } from '@alfa-client/test-utils'; +import { EventEmitter } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormControl } from '@angular/forms'; +import { TextInputComponent } from '../../form/text-input/text-input.component'; +import { SearchFieldComponent } from './search-field.component'; + +describe('SearchFieldComponent', () => { + let component: SearchFieldComponent; + let fixture: ComponentFixture<SearchFieldComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [SearchFieldComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(SearchFieldComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('inputClicked', () => { + it('should emit event', () => { + component.inputClicked = <any>mock(EventEmitter); + const input = getElementFromFixtureByType(fixture, TextInputComponent); + + input.inputElement.nativeElement.click(); + + expect(component.inputClicked.emit).toHaveBeenCalled(); + }); + }); + + describe('clearValue', () => { + it('should set empty value', () => { + component.control = new FormControl('test'); + + component.clearInput(); + + expect(component.control.value).toBe(EMPTY_STRING); + }); + }); +}); diff --git a/alfa-client/libs/design-system/src/lib/instant-search/search-field/search-field.component.ts b/alfa-client/libs/design-system/src/lib/instant-search/search-field/search-field.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..1f2e01bd046af2d15ce345fed69c9a9f43122cc6 --- /dev/null +++ b/alfa-client/libs/design-system/src/lib/instant-search/search-field/search-field.component.ts @@ -0,0 +1,38 @@ +import { CommonModule } from '@angular/common'; +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { FormControl } from '@angular/forms'; +import { EMPTY_STRING } from '../../../../../tech-shared/src'; +import { TextInputComponent } from '../../form/text-input/text-input.component'; +import { CloseIconComponent } from '../../icons/close-icon/close-icon.component'; +import { SearchIconComponent } from '../../icons/search-icon/search-icon.component'; + +@Component({ + selector: 'ods-search-field', + standalone: true, + imports: [CommonModule, TextInputComponent, SearchIconComponent, CloseIconComponent], + template: `<ods-text-input + [label]="label" + [fieldControl]="control" + [placeholder]="placeholder" + [withPrefix]="true" + [withSuffix]="true" + (clickEmitter)="inputClicked.emit()" + role="combobox" + > + <ods-search-icon prefix aria-hidden="true" aria-label="Suchfeld" /> + <button suffix *ngIf="control.value" (click)="clearInput()" aria-label="Eingabe löschen"> + <ods-close-icon class="fill-primary hover:fill-primary-hover" /> + </button> + </ods-text-input>`, +}) +export class SearchFieldComponent { + @Input() label: string = EMPTY_STRING; + @Input() placeholder: string = EMPTY_STRING; + @Input() control = new FormControl(EMPTY_STRING); + + @Output() inputClicked: EventEmitter<MouseEvent> = new EventEmitter<MouseEvent>(); + + clearInput() { + this.control.setValue(EMPTY_STRING); + } +} diff --git a/alfa-client/libs/design-system/src/lib/instant-search/search-field/search-field.stories.ts b/alfa-client/libs/design-system/src/lib/instant-search/search-field/search-field.stories.ts new file mode 100644 index 0000000000000000000000000000000000000000..fec43fa8f65cd51c47e4639fd002a34e33c8b119 --- /dev/null +++ b/alfa-client/libs/design-system/src/lib/instant-search/search-field/search-field.stories.ts @@ -0,0 +1,19 @@ +import { type Meta, type StoryObj } from '@storybook/angular'; +import { SearchFieldComponent } from './search-field.component'; + +const meta: Meta<SearchFieldComponent> = { + title: 'Instant search/Search field', + component: SearchFieldComponent, + excludeStories: /.*Data$/, + tags: ['autodocs'], +}; + +export default meta; +type Story = StoryObj<SearchFieldComponent>; + +export const Default: Story = { + args: { + label: '', + placeholder: 'search something...', + }, +}; diff --git a/alfa-client/libs/design-system/src/lib/instant-search/search-result-header/search-result-header.component.spec.ts b/alfa-client/libs/design-system/src/lib/instant-search/search-result-header/search-result-header.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..7c00b9a408879b4ce0c96c2a374edce5df6739c2 --- /dev/null +++ b/alfa-client/libs/design-system/src/lib/instant-search/search-result-header/search-result-header.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { SearchResultHeaderComponent } from './search-result-header.component'; + +describe('SearchResultHeaderComponent', () => { + let component: SearchResultHeaderComponent; + let fixture: ComponentFixture<SearchResultHeaderComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [SearchResultHeaderComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(SearchResultHeaderComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/alfa-client/libs/design-system/src/lib/instant-search/search-result-header/search-result-header.component.ts b/alfa-client/libs/design-system/src/lib/instant-search/search-result-header/search-result-header.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..4ba8d7894dbddd157532a1418f175e28fa25c3b0 --- /dev/null +++ b/alfa-client/libs/design-system/src/lib/instant-search/search-result-header/search-result-header.component.ts @@ -0,0 +1,17 @@ +import { CommonModule } from '@angular/common'; +import { Component, Input } from '@angular/core'; + +@Component({ + selector: 'ods-search-result-header', + standalone: true, + imports: [CommonModule], + template: ` + <h3 class="mx-6 my-3 w-fit border-b-2 border-primary py-1 text-sm font-semibold text-text"> + {{ text }} ({{ count }}) + </h3> + `, +}) +export class SearchResultHeaderComponent { + @Input({ required: true }) text!: string; + @Input() count: number = 0; +} diff --git a/alfa-client/libs/design-system/src/lib/instant-search/search-result-item/search-result-item.component.spec.ts b/alfa-client/libs/design-system/src/lib/instant-search/search-result-item/search-result-item.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..089692b213692a024ccf7d6dc07071bd9c25d812 --- /dev/null +++ b/alfa-client/libs/design-system/src/lib/instant-search/search-result-item/search-result-item.component.spec.ts @@ -0,0 +1,46 @@ +import { getElementFromFixture, mock } from '@alfa-client/test-utils'; +import { EventEmitter } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { getDataTestIdOf } from 'libs/tech-shared/test/data-test'; +import { SearchResultItemComponent } from './search-result-item.component'; + +describe('SearchResultItemComponent', () => { + let component: SearchResultItemComponent; + let fixture: ComponentFixture<SearchResultItemComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [SearchResultItemComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(SearchResultItemComponent); + component = fixture.componentInstance; + component.title = 'Test'; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('itemClicked', () => { + it('should emit event', () => { + component.itemClicked = <any>mock(EventEmitter); + const button = getElementFromFixture(fixture, getDataTestIdOf('item-button')); + + button.click(); + + expect(component.itemClicked.emit).toHaveBeenCalled(); + }); + }); + + describe('setFocus', () => { + it('should focus native element', () => { + component.buttonRef.nativeElement.focus = jest.fn(); + + component.setFocus(); + + expect(component.buttonRef.nativeElement.focus).toHaveBeenCalled(); + }); + }); +}); diff --git a/alfa-client/libs/design-system/src/lib/instant-search/search-result-item/search-result-item.component.ts b/alfa-client/libs/design-system/src/lib/instant-search/search-result-item/search-result-item.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..e02da6ddc31ea1fda0572f40be876b6a1969012b --- /dev/null +++ b/alfa-client/libs/design-system/src/lib/instant-search/search-result-item/search-result-item.component.ts @@ -0,0 +1,38 @@ +import { CommonModule } from '@angular/common'; +import { Component, ElementRef, EventEmitter, Input, Output, ViewChild } from '@angular/core'; + +@Component({ + selector: 'ods-search-result-item', + standalone: true, + imports: [CommonModule], + template: `<button + *ngIf="title" + [ngClass]="[ + 'flex w-full justify-between border-2 border-transparent px-6 py-3', + 'hover:border-focus focus:border-focus focus:outline-none', + ]" + role="listitem" + tabindex="-1" + (click)="itemClicked.emit()" + data-test-id="item-button" + #button + > + <div class="flex flex-col items-start justify-between text-text"> + <p class="text-base font-medium">{{ title }}</p> + <p class="text-sm">{{ description }}</p> + </div> + <ng-content select="[action-button]" /> + </button>`, +}) +export class SearchResultItemComponent { + @Input({ required: true }) title!: string; + @Input() description: string = ''; + + @Output() public itemClicked: EventEmitter<MouseEvent> = new EventEmitter<MouseEvent>(); + + @ViewChild('button') buttonRef: ElementRef; + + public setFocus() { + this.buttonRef.nativeElement.focus(); + } +} diff --git a/alfa-client/libs/design-system/src/lib/instant-search/search-result-layer/search-result-layer.component.spec.ts b/alfa-client/libs/design-system/src/lib/instant-search/search-result-layer/search-result-layer.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..d2c91333316ff8253ec828a228ce83b84514113c --- /dev/null +++ b/alfa-client/libs/design-system/src/lib/instant-search/search-result-layer/search-result-layer.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { SearchResultLayerComponent } from './search-result-layer.component'; + +describe('SearchResultLayerComponent', () => { + let component: SearchResultLayerComponent; + let fixture: ComponentFixture<SearchResultLayerComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [SearchResultLayerComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(SearchResultLayerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/alfa-client/libs/design-system/src/lib/instant-search/search-result-layer/search-result-layer.component.ts b/alfa-client/libs/design-system/src/lib/instant-search/search-result-layer/search-result-layer.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..971e06b585e2292b36be429b6972ba91090ccaa0 --- /dev/null +++ b/alfa-client/libs/design-system/src/lib/instant-search/search-result-layer/search-result-layer.component.ts @@ -0,0 +1,15 @@ +import { CommonModule } from '@angular/common'; +import { Component } from '@angular/core'; + +@Component({ + selector: 'ods-search-result-layer', + standalone: true, + imports: [CommonModule], + template: `<div class="rounded-lg border border-primary-600/50 bg-background-50 shadow-lg"> + <ng-content select="[header]" /> + <ul role="list"> + <ng-content /> + </ul> + </div>`, +}) +export class SearchResultLayerComponent {} diff --git a/alfa-client/libs/design-system/src/lib/instant-search/search-result-layer/search-result-layer.stories.ts b/alfa-client/libs/design-system/src/lib/instant-search/search-result-layer/search-result-layer.stories.ts new file mode 100644 index 0000000000000000000000000000000000000000..02aa94c67050ca8ec652784afb653f56d596454b --- /dev/null +++ b/alfa-client/libs/design-system/src/lib/instant-search/search-result-layer/search-result-layer.stories.ts @@ -0,0 +1,30 @@ +import { moduleMetadata, type Meta, type StoryObj } from '@storybook/angular'; +import { SearchResultHeaderComponent } from '../search-result-header/search-result-header.component'; +import { SearchResultItemComponent } from '../search-result-item/search-result-item.component'; +import { SearchResultLayerComponent } from './search-result-layer.component'; + +const meta: Meta<SearchResultLayerComponent> = { + title: 'Instant search/Search result layer', + component: SearchResultLayerComponent, + excludeStories: /.*Data$/, + tags: ['autodocs'], + decorators: [ + moduleMetadata({ + imports: [SearchResultItemComponent, SearchResultHeaderComponent], + }), + ], +}; + +export default meta; +type Story = StoryObj<SearchResultLayerComponent>; + +export const Default: Story = { + args: {}, + render: () => ({ + template: `<ods-search-result-layer> + <ods-search-result-header text="In der OZG-Cloud" [count]="2" header /> + <ods-search-result-item text="Amt Rantznau - Ordnungsamt" subText="Rathausmarkt 7, Kronshagen" /> + <ods-search-result-item text="Amt Burg-St. Michaelisdonn - Der Amtsvorsteher" subText="Holzmarkt 7, 25712 Rantznau" /> + </ods-search-result-layer>`, + }), +}; diff --git a/alfa-client/libs/historie/src/lib/historie-container/historie-list/historie-item-header/historie-item-header.component.scss b/alfa-client/libs/historie/src/lib/historie-container/historie-list/historie-item-header/historie-item-header.component.scss index 9eea5298d9ed6bb65a9907b621cd78aad006e467..98885dff4e1dd8036f867f865eb57eb5c0fb4fa6 100644 --- a/alfa-client/libs/historie/src/lib/historie-container/historie-list/historie-item-header/historie-item-header.component.scss +++ b/alfa-client/libs/historie/src/lib/historie-container/historie-list/historie-item-header/historie-item-header.component.scss @@ -26,7 +26,7 @@ white-space: nowrap; align-items: center; min-height: 44px; - font-size: 14px; + font-size: 0.875rem; width: 100%; } diff --git a/alfa-client/libs/historie/src/lib/historie-container/historie-list/historie-item-vorgang-created/historie-item-vorgang-created.component.scss b/alfa-client/libs/historie/src/lib/historie-container/historie-list/historie-item-vorgang-created/historie-item-vorgang-created.component.scss index f4e6e49e807966d56e3f9beae6b8a010ba4ed9a7..349c181b396d022cfc4fb9687f4baa47ea8a246b 100644 --- a/alfa-client/libs/historie/src/lib/historie-container/historie-list/historie-item-vorgang-created/historie-item-vorgang-created.component.scss +++ b/alfa-client/libs/historie/src/lib/historie-container/historie-list/historie-item-vorgang-created/historie-item-vorgang-created.component.scss @@ -29,7 +29,7 @@ display: block; margin-top: 0.5rem; min-height: 44px; - font-size: 14px; + font-size: 0.875rem; } p { diff --git a/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang/kommentar-list-item-in-vorgang/kommentar-list-item-in-vorgang.component.html b/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang/kommentar-list-item-in-vorgang/kommentar-list-item-in-vorgang.component.html index 9a94d2795a84d8bad27b23be6e11bde8eec441c3..5564806d9103ba21af82622fda3aae70e8880595 100644 --- a/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang/kommentar-list-item-in-vorgang/kommentar-list-item-in-vorgang.component.html +++ b/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang/kommentar-list-item-in-vorgang/kommentar-list-item-in-vorgang.component.html @@ -24,7 +24,7 @@ --> -<div *ngIf="!editMode" class="plain-text"> +<div *ngIf="!editMode" class="plain-text text-sm"> <button [attr.data-test-id]="'kommentar-item-' + (kommentar.text | convertForDataTest)" (click)="edit()" @@ -36,7 +36,7 @@ [kommentar]="kommentar" data-test-class="kommentar-created-by" ></alfa-user-profile-in-kommentar-container> - <span data-test-id="kommentar-created-at" class="date">{{ + <span data-test-id="kommentar-created-at" class="date text-sm">{{ kommentar.createdAt | formatDateWithTimePipe: false }}</span> </div> diff --git a/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang/kommentar-list-item-in-vorgang/kommentar-list-item-in-vorgang.component.scss b/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang/kommentar-list-item-in-vorgang/kommentar-list-item-in-vorgang.component.scss index bf52bd6b8383f611228097a4f129859c93fbdf78..9c44c9feeccad1edba5bb907ce9624b8d4c05b96 100644 --- a/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang/kommentar-list-item-in-vorgang/kommentar-list-item-in-vorgang.component.scss +++ b/alfa-client/libs/kommentar/src/lib/kommentar-list-in-vorgang-container/kommentar-list-in-vorgang/kommentar-list-item-in-vorgang/kommentar-list-item-in-vorgang.component.scss @@ -54,7 +54,6 @@ button { .date { flex-shrink: 0; - font-size: 13px; } } diff --git a/alfa-client/libs/navigation/src/lib/build-info/build-info.component.html b/alfa-client/libs/navigation/src/lib/build-info/build-info.component.html index 3befc6c17ff6c1c67843bb0969acfb0ed2ce4ef3..41cce668006927fdd81db4f1c49e8eaca49358cd 100644 --- a/alfa-client/libs/navigation/src/lib/build-info/build-info.component.html +++ b/alfa-client/libs/navigation/src/lib/build-info/build-info.component.html @@ -30,6 +30,6 @@ <span data-test-id="build-time">{{ buildTime }}</span> </ng-container> </p> -<p *ngIf="isNotProduction" data-test-id="not-production-text" class="test-environment"> +<p *ngIf="isNotProduction" data-test-id="not-production-text" class="test-environment text-error"> Achtung Testumgebung </p> diff --git a/alfa-client/libs/navigation/src/lib/build-info/build-info.component.scss b/alfa-client/libs/navigation/src/lib/build-info/build-info.component.scss index ec3f82e9c13c393371836db8fecfca6ecc6467d5..73f433970c0765f31b0f88b78a30fad26513afdb 100644 --- a/alfa-client/libs/navigation/src/lib/build-info/build-info.component.scss +++ b/alfa-client/libs/navigation/src/lib/build-info/build-info.component.scss @@ -46,7 +46,7 @@ p { white-space: nowrap; font-style: normal; font-weight: 400; - font-size: 11px; + font-size: 0.6875rem; color: #999; &.version { @@ -61,5 +61,4 @@ p { margin-right: $navigation-height + $header-height; letter-spacing: 0.42em; text-transform: uppercase; - color: #ff0000; } diff --git a/alfa-client/libs/navigation/src/lib/header-container/header/header.component.html b/alfa-client/libs/navigation/src/lib/header-container/header/header.component.html index 3ef7590196924bd350d246c4b084c71a1cc7cdd3..100cd3141dbcb8562608433313d8cdf30c0af56b 100644 --- a/alfa-client/libs/navigation/src/lib/header-container/header/header.component.html +++ b/alfa-client/libs/navigation/src/lib/header-container/header/header.component.html @@ -30,7 +30,7 @@ <div class="middle"> <alfa-vorgang-search-container></alfa-vorgang-search-container> </div> - <div class="right"> + <div class="flex items-center text-ozggray-800 dark:text-ozggray-300"> <alfa-help-menu [apiRootStateResource]="apiRootStateResource" data-test-id="help-menu" diff --git a/alfa-client/libs/navigation/src/lib/header-container/header/header.component.scss b/alfa-client/libs/navigation/src/lib/header-container/header/header.component.scss index 167169bea4acdf42c9032a84041bf48006b45809..4e6bde87b4b462a01803569bc823015ce6b9f535 100644 --- a/alfa-client/libs/navigation/src/lib/header-container/header/header.component.scss +++ b/alfa-client/libs/navigation/src/lib/header-container/header/header.component.scss @@ -53,9 +53,3 @@ header { min-width: 240px; } } - -.right { - color: $grey; - display: flex; - align-items: center; -} diff --git a/alfa-client/libs/postfach/src/lib/postfach-mail-form/postfach-mail-form.component.scss b/alfa-client/libs/postfach/src/lib/postfach-mail-form/postfach-mail-form.component.scss index 5b22d572e2a37eb567ba59013f2fc06473826ff6..30d9608bbff2af095d57a37beb36da3bf8b91f7d 100644 --- a/alfa-client/libs/postfach/src/lib/postfach-mail-form/postfach-mail-form.component.scss +++ b/alfa-client/libs/postfach/src/lib/postfach-mail-form/postfach-mail-form.component.scss @@ -49,8 +49,7 @@ margin-bottom: 4px; .label { - font-size: 10.5px; - opacity: 0.6; + font-size: 0.75rem; } .value { diff --git a/alfa-client/libs/postfach/src/lib/postfach-mail-list-container/postfach-mail-list/postfach-mail/outgoing-mail/outgoing-mail-error-container/outgoing-mail-error/outgoing-mail-error.component.html b/alfa-client/libs/postfach/src/lib/postfach-mail-list-container/postfach-mail-list/postfach-mail/outgoing-mail/outgoing-mail-error-container/outgoing-mail-error/outgoing-mail-error.component.html index ba6a2c55856a17a9780c64cbb785fd4ea54f96b8..ba91c347f14db1b8a045f6f1c19ee795f3618cb7 100644 --- a/alfa-client/libs/postfach/src/lib/postfach-mail-list-container/postfach-mail-list/postfach-mail/outgoing-mail/outgoing-mail-error-container/outgoing-mail-error/outgoing-mail-error.component.html +++ b/alfa-client/libs/postfach/src/lib/postfach-mail-list-container/postfach-mail-list/postfach-mail/outgoing-mail/outgoing-mail-error-container/outgoing-mail-error/outgoing-mail-error.component.html @@ -29,7 +29,7 @@ class="mail-send-error" > <span data-test-id="mail-send-error-text">{{ message }}</span> - <mat-icon data-test-id="mail-send-error-icon" class="mail-send-error__icon mat-icon-error" + <mat-icon data-test-id="mail-send-error-icon" class="mail-send-error__icon text-error" >error_outline_white</mat-icon > </div> diff --git a/alfa-client/libs/postfach/src/lib/postfach-mail-list-container/postfach-mail-list/postfach-mail/outgoing-mail/outgoing-mail-error-container/outgoing-mail-error/outgoing-mail-error.component.scss b/alfa-client/libs/postfach/src/lib/postfach-mail-list-container/postfach-mail-list/postfach-mail/outgoing-mail/outgoing-mail-error-container/outgoing-mail-error/outgoing-mail-error.component.scss index d62fb722598b2db40eaa946e2e79722616ecaf0d..aadb9a11f319d4c53e79987a439f2647db86c7f8 100644 --- a/alfa-client/libs/postfach/src/lib/postfach-mail-list-container/postfach-mail-list/postfach-mail/outgoing-mail/outgoing-mail-error-container/outgoing-mail-error/outgoing-mail-error.component.scss +++ b/alfa-client/libs/postfach/src/lib/postfach-mail-list-container/postfach-mail-list/postfach-mail/outgoing-mail/outgoing-mail-error-container/outgoing-mail-error/outgoing-mail-error.component.scss @@ -25,10 +25,6 @@ @use '@angular/material' as mat; @import 'variables'; -.mat-icon-error { - color: mat.get-color-from-palette($warnPalette); -} - .mail-send-error { display: flex; align-items: center; diff --git a/alfa-client/libs/postfach/src/lib/postfach-mail-list-container/postfach-mail-list/postfach-mail/postfach-mail-date/postfach-mail-date.component.scss b/alfa-client/libs/postfach/src/lib/postfach-mail-list-container/postfach-mail-list/postfach-mail/postfach-mail-date/postfach-mail-date.component.scss index 68891d3304c26291de9f600fa56848c0313f3974..3bfec3b5734dae42f7ecef3a563d6b859bf8111b 100644 --- a/alfa-client/libs/postfach/src/lib/postfach-mail-list-container/postfach-mail-list/postfach-mail/postfach-mail-date/postfach-mail-date.component.scss +++ b/alfa-client/libs/postfach/src/lib/postfach-mail-list-container/postfach-mail-list/postfach-mail/postfach-mail-date/postfach-mail-date.component.scss @@ -22,6 +22,6 @@ * unter der Lizenz sind dem Lizenztext zu entnehmen. */ .date { - font-size: 13px; + font-size: 0.875rem; white-space: nowrap; } diff --git a/alfa-client/libs/postfach/src/lib/postfach-page-container/postfach-page/postfach-page-mail-list/postfach-page-mail-list.component.html b/alfa-client/libs/postfach/src/lib/postfach-page-container/postfach-page/postfach-page-mail-list/postfach-page-mail-list.component.html index 3aac0b25924886e054d181ae7558219d0545e68d..42008dc0e49821e2041d2b5cf06a456f2026b8e4 100644 --- a/alfa-client/libs/postfach/src/lib/postfach-page-container/postfach-page/postfach-page-mail-list/postfach-page-mail-list.component.html +++ b/alfa-client/libs/postfach/src/lib/postfach-page-container/postfach-page/postfach-page-mail-list/postfach-page-mail-list.component.html @@ -30,7 +30,7 @@ | toEmbeddedResources: postfachMailListLinkRel.POSTFACH_MAIL_LIST " [attr.data-test-id]="(postfachMail.subject | convertForDataTest) + '-item'" - class="postfach postfach-links-disabled" + class="postfach postfach-links-disabled w-full lg:w-1/2" [postfachMail]="postfachMail" > </alfa-postfach-mail> diff --git a/alfa-client/libs/postfach/src/lib/postfach-page-container/postfach-page/postfach-page-mail-list/postfach-page-mail-list.component.scss b/alfa-client/libs/postfach/src/lib/postfach-page-container/postfach-page/postfach-page-mail-list/postfach-page-mail-list.component.scss index 7aa658e02c12f707953b25897c98bbec80c6d55a..5d7c585646f315886cd9f453d97c87449abcb8b1 100644 --- a/alfa-client/libs/postfach/src/lib/postfach-page-container/postfach-page/postfach-page-mail-list/postfach-page-mail-list.component.scss +++ b/alfa-client/libs/postfach/src/lib/postfach-page-container/postfach-page/postfach-page-mail-list/postfach-page-mail-list.component.scss @@ -24,7 +24,6 @@ .postfach { display: flex; flex-direction: column; - width: 100%; border-bottom: 1px solid rgb(0 0 0 / 8%); padding: 16px 24px 16px 28px; margin: 0; @@ -33,7 +32,7 @@ .subject { display: flex; align-items: center; - font-size: 16px; + font-size: 1rem; font-weight: 400; margin: 2px 0; position: relative; @@ -53,7 +52,7 @@ } .created-at { - font-size: 13px; + font-size: 0.875rem; position: absolute; right: 0; top: 0; diff --git a/alfa-client/libs/postfach/src/lib/postfach-page-container/postfach-page/postfach-page.component.html b/alfa-client/libs/postfach/src/lib/postfach-page-container/postfach-page/postfach-page.component.html index 8c220b59d27e750b5a1256abdfd85aaae346a9d6..0ffd6b1df26988e4f6fe93c69f4af1e52f9fc8a3 100644 --- a/alfa-client/libs/postfach/src/lib/postfach-page-container/postfach-page/postfach-page.component.html +++ b/alfa-client/libs/postfach/src/lib/postfach-page-container/postfach-page/postfach-page.component.html @@ -27,8 +27,10 @@ <ozgcloud-back-button linkTo="../" label="zurück zur Detailseite"></ozgcloud-back-button> </ozgcloud-subnavigation> -<div class="l-scroll-area--full"> - <alfa-vorgang-in-postfach-breadcrumb-container></alfa-vorgang-in-postfach-breadcrumb-container> +<div class="l-scroll-area--full flex flex-col"> + <h1 data-test-id="postfach-mail-heading" class="pl-7 pt-4 text-lg font-medium"> + Nachrichten zum Vorgang + </h1> <alfa-postfach-page-mail-list [postfachMailListStateResource]="postfachMailListStateResource" data-test-id="postfach-mail-list" diff --git a/alfa-client/libs/postfach/src/lib/postfach-page-container/postfach-page/postfach-page.component.spec.ts b/alfa-client/libs/postfach/src/lib/postfach-page-container/postfach-page/postfach-page.component.spec.ts index 3fa88754e5c94f6717ac4fea2a8d901c3022b6f4..034ad05b31e47a42fd9322fed0202822a746f9e7 100644 --- a/alfa-client/libs/postfach/src/lib/postfach-page-container/postfach-page/postfach-page.component.spec.ts +++ b/alfa-client/libs/postfach/src/lib/postfach-page-container/postfach-page/postfach-page.component.spec.ts @@ -21,10 +21,9 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ +import { BackButtonComponent, SubnavigationComponent } from '@alfa-client/ui'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { MatIcon } from '@angular/material/icon'; -import { BackButtonComponent, SubnavigationComponent } from '@alfa-client/ui'; -import { VorgangInPostfachBreadcrumbContainerComponent } from '@alfa-client/vorgang-shared-ui'; import { MockComponent } from 'ng-mocks'; import { PostfachPageMailListComponent } from './postfach-page-mail-list/postfach-page-mail-list.component'; import { PostfachPageComponent } from './postfach-page.component'; @@ -41,7 +40,6 @@ describe('PostfachPageComponent', () => { MockComponent(BackButtonComponent), MockComponent(SubnavigationComponent), MockComponent(PostfachPageMailListComponent), - MockComponent(VorgangInPostfachBreadcrumbContainerComponent), ], }).compileComponents(); }); diff --git a/alfa-client/libs/tech-shared/src/index.ts b/alfa-client/libs/tech-shared/src/index.ts index 64e34b9ffcb66244bb7687070f685ed6090b4950..6447da0179658e13795f1dbe8bfa8f0df2adeda3 100644 --- a/alfa-client/libs/tech-shared/src/index.ts +++ b/alfa-client/libs/tech-shared/src/index.ts @@ -33,6 +33,7 @@ export * from './lib/message-code'; export * from './lib/ngrx/actions'; export * from './lib/pipe/convert-api-error-to-error-messages.pipe'; export * from './lib/pipe/convert-for-data-test.pipe'; +export * from './lib/pipe/convert-problem-detail-to-error-messages.pipe'; export * from './lib/pipe/convert-to-boolean.pipe'; export * from './lib/pipe/enum-to-label.pipe'; export * from './lib/pipe/file-size.pipe'; diff --git a/alfa-client/libs/tech-shared/src/lib/pipe/convert-problem-detail-to-error-messages.pipe.spec.ts b/alfa-client/libs/tech-shared/src/lib/pipe/convert-problem-detail-to-error-messages.pipe.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..ffef78f2195217763693fbd09abf727915afac23 --- /dev/null +++ b/alfa-client/libs/tech-shared/src/lib/pipe/convert-problem-detail-to-error-messages.pipe.spec.ts @@ -0,0 +1,42 @@ +import { createInvalidParam, createProblemDetail } from '../../../test/error'; +import { InvalidParam, ProblemDetail } from '../tech.model'; +import { ValidationMessageCode } from '../validation/tech.validation.messages'; +import * as TechValidationUtil from '../validation/tech.validation.util'; +import { ConvertProblemDetailToErrorMessagesPipe } from './convert-problem-detail-to-error-messages.pipe'; + +describe('convertProblemDetailToErrorMessages', () => { + const pipe = new ConvertProblemDetailToErrorMessagesPipe(); + + it('create an instance', () => { + expect(pipe).toBeTruthy(); + }); + + describe('transform', () => { + const getMessageForInvalidParam = jest.spyOn(TechValidationUtil, 'getMessageForInvalidParam'); + + it('should not call getMessageForInvalidParam', () => { + pipe.transform(null); + + expect(getMessageForInvalidParam).not.toHaveBeenCalled(); + }); + + it('should call getMessageForInvalidParam', () => { + pipe.transform(createProblemDetail()); + + expect(getMessageForInvalidParam).toHaveBeenCalled(); + }); + + it('should return array of error messages', () => { + const expectedErrorMessage = 'Bitte ausfüllen'; + const invalidParam: InvalidParam = { + ...createInvalidParam(), + reason: ValidationMessageCode.FIELD_EMPTY, + }; + const problemDetail: ProblemDetail = createProblemDetail([invalidParam]); + + const errorMessages: string[] = pipe.transform(problemDetail); + + expect(errorMessages).toEqual([expectedErrorMessage]); + }); + }); +}); diff --git a/alfa-client/libs/tech-shared/src/lib/pipe/convert-problem-detail-to-error-messages.pipe.ts b/alfa-client/libs/tech-shared/src/lib/pipe/convert-problem-detail-to-error-messages.pipe.ts new file mode 100644 index 0000000000000000000000000000000000000000..437483a923c7f85353b96a745a2c24e1cc88e0fb --- /dev/null +++ b/alfa-client/libs/tech-shared/src/lib/pipe/convert-problem-detail-to-error-messages.pipe.ts @@ -0,0 +1,17 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { isNil } from 'lodash-es'; +import { InvalidParam, ProblemDetail } from '../tech.model'; +import { EMPTY_STRING } from '../tech.util'; +import { getMessageForInvalidParam } from '../validation/tech.validation.util'; + +@Pipe({ name: 'convertProblemDetailToErrorMessages' }) +export class ConvertProblemDetailToErrorMessagesPipe implements PipeTransform { + transform(value: ProblemDetail) { + if (isNil(value)) { + return []; + } + return value.invalidParams.map((invalidParam: InvalidParam) => + getMessageForInvalidParam(EMPTY_STRING, invalidParam), + ); + } +} diff --git a/alfa-client/libs/tech-shared/src/lib/service/formservice.abstract.spec.ts b/alfa-client/libs/tech-shared/src/lib/service/formservice.abstract.spec.ts index 1472e6fe3c880b33faeaf14e875f6973dde18a73..ee4a624cb890cbf0da636f80ae01f1aeb7fdc616 100644 --- a/alfa-client/libs/tech-shared/src/lib/service/formservice.abstract.spec.ts +++ b/alfa-client/libs/tech-shared/src/lib/service/formservice.abstract.spec.ts @@ -25,12 +25,12 @@ import { CommandResource } from '@alfa-client/command-shared'; import { UntypedFormBuilder, UntypedFormControl, UntypedFormGroup } from '@angular/forms'; import { Resource } from '@ngxp/rest'; import { cold } from 'jest-marbles'; -import { createApiError, createInvalidParam, createIssue, createProblemDetail } from 'libs/tech-shared/test/error'; +import { createInvalidParam, createProblemDetail } from 'libs/tech-shared/test/error'; import { Observable, of } from 'rxjs'; import { AbstractFormService } from './formservice.abstract'; import { createEmptyStateResource, createErrorStateResource, createStateResource, StateResource } from '../resource/resource.util'; -import { ApiError, HttpError, InvalidParam, Issue, ProblemDetail } from '../tech.model'; +import { HttpError, InvalidParam, ProblemDetail } from '../tech.model'; import { createCommandResource } from '../../../../command-shared/test/command'; import * as ValidationUtil from '../validation/tech.validation.util'; @@ -47,16 +47,16 @@ describe('AbstractFormService', () => { }); describe('submit', () => { - describe('with api error', () => { - const stateResourceWithError: StateResource<ApiError> = - createErrorStateResource(createApiError()); + describe('with ProblemDetail', () => { + const stateResourceWithError: StateResource<ProblemDetail> = + createErrorStateResource(createProblemDetail()); beforeEach(() => { TestFormService.SUBMIT_OBSERVABLE = () => of(stateResourceWithError); formService.handleResponse = jest.fn((stateResource) => stateResource); }); - it('should call handle response for api error', (done) => { + it('should call handle response for ProblemDetail', (done) => { formService.submit().subscribe(() => { expect(formService.handleResponse).toHaveBeenCalledWith(stateResourceWithError); done(); @@ -97,8 +97,8 @@ describe('AbstractFormService', () => { }); describe('handleResponse', () => { - const apiError: ApiError = createApiError(); - const stateResource: StateResource<CommandResource> = createErrorStateResource(apiError); + const problemDetail: ProblemDetail = createProblemDetail(); + const stateResource: StateResource<CommandResource> = createErrorStateResource(problemDetail); beforeEach(() => { formService.handleError = jest.fn(); @@ -107,7 +107,7 @@ describe('AbstractFormService', () => { it('should handleError on validation error', () => { formService.handleResponse({ ...stateResource, loading: false }); - expect(formService.handleError).toHaveBeenCalledWith(apiError); + expect(formService.handleError).toHaveBeenCalledWith(problemDetail); }); it('should return stateresource while loading', () => { @@ -129,34 +129,6 @@ describe('AbstractFormService', () => { expect(formService.setErrorByProblemDetail).toHaveBeenCalledWith(problemDetail); }); - - it('should set api error', () => { - formService.setErrorByApiError = jest.fn(); - const apiError: ApiError = createApiError(); - - formService.handleError(apiError); - - expect(formService.setErrorByApiError).toHaveBeenCalledWith(apiError); - }); - }); - - describe('set error by api error', () => { - const issue: Issue = createIssue(); - const apiError: ApiError = createApiError([issue]); - - it('should call setIssueValidationError', () => { - const setInvalidParamValidationErrorSpy: jest.SpyInstance<void> = jest - .spyOn(ValidationUtil, 'setIssueValidationError') - .mockImplementation(); - - formService.setErrorByApiError(apiError); - - expect(setInvalidParamValidationErrorSpy).toHaveBeenCalledWith( - formService.form, - issue, - TestFormService.PATH_PREFIX, - ); - }); }); describe('set error by problem detail', () => { diff --git a/alfa-client/libs/tech-shared/src/lib/service/formservice.abstract.ts b/alfa-client/libs/tech-shared/src/lib/service/formservice.abstract.ts index ab647437a8483fb73bccc4fa0e4d083ef8c48cde..1af38f9f8065ae4685acb7326d1a65b3d88fcd69 100644 --- a/alfa-client/libs/tech-shared/src/lib/service/formservice.abstract.ts +++ b/alfa-client/libs/tech-shared/src/lib/service/formservice.abstract.ts @@ -28,16 +28,16 @@ import { isNil } from 'lodash-es'; import { identity, Observable, OperatorFunction } from 'rxjs'; import { map } from 'rxjs/operators'; import { hasStateResourceError, StateResource } from '../resource/resource.util'; -import { ApiError, HttpError, InvalidParam, Issue, ProblemDetail } from '../tech.model'; +import { HttpError, InvalidParam, ProblemDetail } from '../tech.model'; import { isNotUndefined } from '../tech.util'; -import { setInvalidParamValidationError, setIssueValidationError } from '../validation/tech.validation.util'; +import { setInvalidParamValidationError } from '../validation/tech.validation.util'; export abstract class AbstractFormService { form: UntypedFormGroup; pathPrefix: string; source: any; - private readonly PROBLEM_DETAIL_INVALID_PARAMS_KEY: string = 'invalid-params'; + private readonly PROBLEM_DETAIL_INVALID_PARAMS_KEY: string = 'invalidParams'; constructor(public formBuilder: UntypedFormBuilder) { this.form = this.initForm(); @@ -65,28 +65,15 @@ export abstract class AbstractFormService { } handleError(error: HttpError): void { - if (this.isApiError(error)) { - this.setErrorByApiError(<ApiError>error); - } - if (this.isProblemDetail(error)) { + if (this.hasError(error)) { this.setErrorByProblemDetail(<ProblemDetail>error); } } - private isApiError(error: HttpError): boolean { - return isNotUndefined((<ApiError>error).issues); - } - - private isProblemDetail(error: HttpError): boolean { + private hasError(error: HttpError): boolean { return isNotUndefined((<ProblemDetail>error)[this.PROBLEM_DETAIL_INVALID_PARAMS_KEY]); } - setErrorByApiError(apiError: ApiError): void { - apiError.issues.forEach((issue: Issue) => - setIssueValidationError(this.form, issue, this.getPathPrefix()), - ); - } - setErrorByProblemDetail(error: ProblemDetail): void { error[this.PROBLEM_DETAIL_INVALID_PARAMS_KEY].forEach((invalidParam: InvalidParam) => { setInvalidParamValidationError(this.form, invalidParam, this.getPathPrefix()); diff --git a/alfa-client/libs/tech-shared/src/lib/tech-shared.module.ts b/alfa-client/libs/tech-shared/src/lib/tech-shared.module.ts index 331031b298487fcfa77df7c10041a33cfedec7bb..a91930ffa732c82e0be57db7f18516b24eb393c9 100644 --- a/alfa-client/libs/tech-shared/src/lib/tech-shared.module.ts +++ b/alfa-client/libs/tech-shared/src/lib/tech-shared.module.ts @@ -29,6 +29,7 @@ import { HttpXsrfInterceptor } from './interceptor/http-xsrf.interceptor'; import { XhrInterceptor } from './interceptor/xhr.interceptor'; import { ConvertApiErrorToErrorMessagesPipe } from './pipe/convert-api-error-to-error-messages.pipe'; import { ConvertForDataTestPipe } from './pipe/convert-for-data-test.pipe'; +import { ConvertProblemDetailToErrorMessagesPipe } from './pipe/convert-problem-detail-to-error-messages.pipe'; import { ConvertToBooleanPipe } from './pipe/convert-to-boolean.pipe'; import { EnumToLabelPipe } from './pipe/enum-to-label.pipe'; import { FileSizePlainPipe } from './pipe/file-size-plain.pipe'; @@ -69,6 +70,7 @@ import { ToTrafficLightPipe } from './pipe/to-traffic-light.pipe'; GetUrlPipe, ConvertToBooleanPipe, ConvertApiErrorToErrorMessagesPipe, + ConvertProblemDetailToErrorMessagesPipe, ], exports: [ FormatToPrettyDatePipe, @@ -90,6 +92,7 @@ import { ToTrafficLightPipe } from './pipe/to-traffic-light.pipe'; GetUrlPipe, ConvertToBooleanPipe, ConvertApiErrorToErrorMessagesPipe, + ConvertProblemDetailToErrorMessagesPipe, ], providers: [ { diff --git a/alfa-client/libs/tech-shared/src/lib/tech.model.ts b/alfa-client/libs/tech-shared/src/lib/tech.model.ts index c85852676e5184152aa17856cca07bd33b3a3223..869313d09ddd0b5c7703f80ea79f23bf72403e07 100644 --- a/alfa-client/libs/tech-shared/src/lib/tech.model.ts +++ b/alfa-client/libs/tech-shared/src/lib/tech.model.ts @@ -47,12 +47,19 @@ export interface ProblemDetail { status: HttpStatusCode; detail: string; instance: string; - 'invalid-params': InvalidParam[]; + invalidParams: InvalidParam[]; } export interface InvalidParam { + constraintParameters: ConstraintParameter[]; name: string; reason: ValidationMessageCode; + value: string; +} + +export interface ConstraintParameter { + name: string; + value: string; } export declare type HttpError = ProblemDetail | ApiError; diff --git a/alfa-client/libs/tech-shared/src/lib/validation/tech.validation.messages.ts b/alfa-client/libs/tech-shared/src/lib/validation/tech.validation.messages.ts index 6c71c22768bb5ae231daf6b3522002a9110bda00..82da103d936c9c009cdfb20a9b1e652cf5b41648 100644 --- a/alfa-client/libs/tech-shared/src/lib/validation/tech.validation.messages.ts +++ b/alfa-client/libs/tech-shared/src/lib/validation/tech.validation.messages.ts @@ -22,23 +22,31 @@ * unter der Lizenz sind dem Lizenztext zu entnehmen. */ export enum ValidationMessageCode { - VALIDATION_FIELD_FILE_SIZE_EXCEEDED = 'validation_field_file_size_exceeded', - VALIDATION_FIELD_EMPTY = 'validation_field_empty', - VALIDATION_FIELD_FILE_CONTENT_TYPE_INVALID = 'validation_field_file_content_type_invalid', + FIELD_FILE_SIZE_EXCEEDED = 'validation_field_file_size_exceeded', + FIELD_EMPTY = 'validation_field_empty', + FIELD_SIZE = 'validation_field_size', + FIELD_FILE_CONTENT_TYPE_INVALID = 'validation_field_file_content_type_invalid', + FIELD_MIN_SIZE = 'validation_field_min_size', + FIELD_MAX_SIZE = 'validation_field_max_size', + FIELD_DATE_PAST = 'validation_field_date_past', + FIELD_INVALID = 'validation_field_invalid', + FIELD_DATE_FORMAT_INVALID = 'validation_field_date_format_invalid', + FIELD_ASSIGN_BEARBEITER_NOT_EXIST = 'fe_only_validation_bearbeiter_not_exist', } export const VALIDATION_MESSAGES: { [code: string]: string } = { - [ValidationMessageCode.VALIDATION_FIELD_EMPTY]: 'Bitte {field} ausfüllen', - validation_field_max_size: '{field} darf höchstens {max} Zeichen enthalten', - validation_field_min_size: '{field} muss aus mindestens {min} Zeichen bestehen', - validation_field_size: '{field} muss mindestens {min} und darf höchstens {max} Zeichen enthalten', - validation_field_date_past: 'Das Datum für {field} muss in der Zukunft liegen', - validation_field_invalid: 'Bitte {field} korrekt ausfüllen', - [ValidationMessageCode.VALIDATION_FIELD_FILE_SIZE_EXCEEDED]: + [ValidationMessageCode.FIELD_EMPTY]: 'Bitte {field} ausfüllen', + [ValidationMessageCode.FIELD_MAX_SIZE]: '{field} darf höchstens {max} Zeichen enthalten', + [ValidationMessageCode.FIELD_MIN_SIZE]: '{field} muss aus mindestens {min} Zeichen bestehen', + [ValidationMessageCode.FIELD_SIZE]: + '{field} muss mindestens {min} und darf höchstens {max} Zeichen enthalten', + [ValidationMessageCode.FIELD_DATE_PAST]: 'Das Datum für {field} muss in der Zukunft liegen', + [ValidationMessageCode.FIELD_INVALID]: 'Bitte {field} korrekt ausfüllen', + [ValidationMessageCode.FIELD_FILE_SIZE_EXCEEDED]: 'Anhänge größer {max}{unit} können nicht hinzugefügt werden.', fe_only_validation_bearbeiter_not_exist: 'Der Bearbeiter existiert nicht', - validation_field_date_format_invalid: 'Geben Sie ein gültiges Datum ein', - [ValidationMessageCode.VALIDATION_FIELD_FILE_CONTENT_TYPE_INVALID]: + [ValidationMessageCode.FIELD_DATE_FORMAT_INVALID]: 'Geben Sie ein gültiges Datum ein', + [ValidationMessageCode.FIELD_FILE_CONTENT_TYPE_INVALID]: 'Erlaubte Dateiendungen: pdf, jpg, png, jpeg', }; diff --git a/alfa-client/libs/tech-shared/src/lib/validation/tech.validation.util.spec.ts b/alfa-client/libs/tech-shared/src/lib/validation/tech.validation.util.spec.ts index 9e0902270b2423a9510872b1a5f15828549eb8d6..bb2e5b51d1be004bced187df35871e16a00d738a 100644 --- a/alfa-client/libs/tech-shared/src/lib/validation/tech.validation.util.spec.ts +++ b/alfa-client/libs/tech-shared/src/lib/validation/tech.validation.util.spec.ts @@ -21,23 +21,12 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { - AbstractControl, - FormControl, - FormGroup, - UntypedFormControl, - UntypedFormGroup, -} from '@angular/forms'; -import { createInvalidParam, createIssue } from '../../../test/error'; +import { AbstractControl, FormControl, FormGroup, UntypedFormControl, UntypedFormGroup } from '@angular/forms'; +import { faker } from '@faker-js/faker'; +import { createInvalidParam, createIssue, createProblemDetail } from '../../../test/error'; import { InvalidParam, Issue } from '../tech.model'; -import { - getControlForInvalidParam, - getControlForIssue, - getMessageForInvalidParam, - getMessageForIssue, - setInvalidParamValidationError, - setIssueValidationError, -} from './tech.validation.util'; +import { VALIDATION_MESSAGES, ValidationMessageCode } from './tech.validation.messages'; +import { getControlForInvalidParam, getControlForIssue, getFieldPath, getMessageForInvalidParam, getMessageForIssue, getMessageReason, setInvalidParamValidationError, setIssueValidationError } from './tech.validation.util'; describe('ValidationUtils', () => { const baseField1Control: FormControl = new UntypedFormControl(); @@ -55,7 +44,7 @@ describe('ValidationUtils', () => { describe('set issue validation error', () => { describe('get control for issue', () => { it('should return base field control', () => { - const issue: Issue = { ...createIssue(), field: 'baseField1' }; + const issue: Issue = { ...createIssue(), field: 'class.resource.baseField1' }; const control: AbstractControl = getControlForIssue(form, issue); @@ -63,24 +52,24 @@ describe('ValidationUtils', () => { }); it('should return sub group field', () => { - const issue: Issue = { ...createIssue(), field: 'subGroup.subGroupField1' }; + const issue: Issue = { ...createIssue(), field: 'class.resource.subGroup.subGroupField1' }; - const control: AbstractControl = getControlForIssue(form, issue); + const control: AbstractControl = getControlForIssue(form, issue, 'resource'); expect(control).toBe(subGroupFieldControl); }); it('should ignore path prefix', () => { - const issue: Issue = { ...createIssue(), field: 'pathprefix.resource.baseField1' }; + const issue: Issue = { ...createIssue(), field: 'class.resource.baseField1' }; - const control: AbstractControl = getControlForIssue(form, issue, 'pathprefix.resource'); + const control: AbstractControl = getControlForIssue(form, issue, 'resource'); expect(control).toBe(baseField1Control); }); }); describe('in base field', () => { - const issue: Issue = { ...createIssue(), field: 'baseField1' }; + const issue: Issue = { ...createIssue(), field: 'class.resource.baseField1' }; it('should set error in control', () => { setIssueValidationError(form, issue); @@ -108,10 +97,10 @@ describe('ValidationUtils', () => { }); describe('in subGroup Field', () => { - const issue: Issue = { ...createIssue(), field: 'subGroup.subGroupField1' }; + const issue: Issue = { ...createIssue(), field: 'class.resource.subGroup.subGroupField1' }; it('should set error in control', () => { - setIssueValidationError(form, issue); + setIssueValidationError(form, issue, 'resource'); expect(subGroupFieldControl.errors).not.toBeNull(); }); @@ -150,102 +139,200 @@ describe('ValidationUtils', () => { }); }); - describe('invalid param', () => { - const formPrefixes: string[] = ['', 'some-prefix']; - const fieldNames: string[] = ['baseField1', 'baseField2', 'subGroup.subGroupField1']; - const prefixNameCombinations: string[][] = formPrefixes.flatMap((prefix) => - fieldNames.map((name) => [prefix, name]), - ); - const unknownName = 'unknown-field'; - - describe.each(prefixNameCombinations)( - 'with prefix "%s" and fieldName "%s"', - (prefix, fieldName) => { - let invalidParam: InvalidParam; - - beforeEach(() => { - form.reset(); - invalidParam = { - ...createInvalidParam(), - name: prefix.length ? `${prefix}.${fieldName}` : fieldName, - }; - }); - - describe('get message for invalid param', () => { - it('should return', () => { - const msg: string = getMessageForInvalidParam(invalidParam, prefix); - - expect(msg).toEqual(`Bitte ${fieldName} ausfüllen`); - }); - }); - - describe('get control for invalid param', () => { - it('should find', () => { - const control: AbstractControl = getControlForInvalidParam(form, invalidParam, prefix); - - expect(control).toBeTruthy(); - }); - }); - - describe('set invalid param validation error', () => { - it('should assign invalidParam to form control error without prefix', () => { - const message: string = getMessageForInvalidParam(invalidParam, prefix); - - setInvalidParamValidationError(form, invalidParam, prefix); - - const errorMessage: string = form.getError(invalidParam.reason, fieldName); - expect(errorMessage).toBe(message); - }); - - it('should mark form as touched', () => { - setInvalidParamValidationError(form, invalidParam, prefix); - - expect(form.touched).toBeTruthy(); - }); - }); - }, - ); - - describe.each([ - ['', '', 'unknown-field'], - ['valid-prefix', 'valid-prefix', 'unknown-field'], - ['valid-prefix', 'valid-prefix', 'subGroup.unknown-field'], - ['unknown-prefix', 'valid-prefix', 'unknown-field'], - ['unknown-prefix', 'valid-prefix', 'baseField1'], - ])( - 'with pathPrefix "%s", paramPrefix "%s", and field-name "%s"', - (pathPrefix, paramPrefix, fieldName) => { - let invalidParam: InvalidParam; - - beforeEach(() => { - form.reset(); - invalidParam = createInvalidParam(); - invalidParam.name = paramPrefix.length > 0 ? `${paramPrefix}.${fieldName}` : fieldName; - }); - - it('should not find form control', () => { - const control: AbstractControl = getControlForInvalidParam( - form, - invalidParam, - pathPrefix, - ); - - expect(control).toBeFalsy(); - }); - - it('should not assign to field control error', () => { - setInvalidParamValidationError(form, invalidParam, pathPrefix); - - const errorMessage = form.getError(invalidParam.reason, unknownName); - expect(errorMessage).toBeFalsy(); - }); - - it('should not mark as touched', () => { - setInvalidParamValidationError(form, invalidParam, pathPrefix); - - expect(form.touched).toBeFalsy(); - }); - }, - ); + describe('set invalidParam validation error', () => { + describe('get control for invalidParam', () => { + it('should return base field control', () => { + const invalidParam: InvalidParam = { + ...createInvalidParam(), + name: 'class.resource.baseField1', + }; + + const control: AbstractControl = getControlForInvalidParam(form, invalidParam); + + expect(control).toBe(baseField1Control); + }); + + it('should return sub group field', () => { + const invalidParam: InvalidParam = { + ...createInvalidParam(), + name: 'class.resource.subGroup.subGroupField1', + }; + + const control: AbstractControl = getControlForInvalidParam(form, invalidParam, 'resource'); + + expect(control).toBe(subGroupFieldControl); + }); + + it('should ignore path prefix', () => { + const invalidParam: InvalidParam = { + ...createInvalidParam(), + name: 'class.resource.baseField1', + }; + + const control: AbstractControl = getControlForInvalidParam(form, invalidParam, 'resource'); + + expect(control).toBe(baseField1Control); + }); + }); + + describe('in base field', () => { + const invalidParam: InvalidParam = { + ...createInvalidParam(), + name: 'class.resource.baseField1', + }; + + it('should set error in control', () => { + setInvalidParamValidationError(form, invalidParam); + + expect(baseField1Control.errors).not.toBeNull(); + }); + + it('should set message code in control', () => { + setInvalidParamValidationError(form, invalidParam); + + expect(baseField1Control.hasError(invalidParam.reason)).toBe(true); + }); + + it('should set control touched', () => { + setInvalidParamValidationError(form, invalidParam); + + expect(baseField1Control.touched).toBe(true); + }); + + it('should not set error in other control', () => { + setInvalidParamValidationError(form, invalidParam); + + expect(baseField2Control.errors).toBeNull(); + }); + }); + + describe('in subGroup Field', () => { + const invalidParam: InvalidParam = { + ...createInvalidParam(), + name: 'class.resource.subGroup.subGroupField1', + }; + + it('should set error in control', () => { + setInvalidParamValidationError(form, invalidParam, 'resource'); + + expect(subGroupFieldControl.errors).not.toBeNull(); + }); + }); + }); + + describe('getFieldPath', () => { + const resource: string = 'resource'; + const backendClassName: string = 'class'; + + it('should return field path', () => { + const fieldPath: string = 'field1'; + const fullPath: string = `${backendClassName}.${resource}.${fieldPath}`; + + const result: string = getFieldPath(fullPath, resource); + + expect(result).toBe(fieldPath); + }); + + it('should get all parts after the prefix', () => { + const fieldPath: string = 'group.field1'; + const fullPath: string = `${backendClassName}.${resource}.${fieldPath}`; + + const result: string = getFieldPath(fullPath, resource); + + expect(result).toBe(fieldPath); + }); + + it('should return field from full path when resource is undefined', () => { + const fieldPath: string = 'field1'; + const fullPath: string = `${backendClassName}.${resource}.${fieldPath}`; + + const result: string = getFieldPath(fullPath, undefined); + + expect(result).toBe(fieldPath); + }); + + it('should return field from field when resource is undefined', () => { + const fieldPath: string = 'field1'; + + const result: string = getFieldPath(fieldPath, undefined); + + expect(result).toBe(fieldPath); + }); + }); + + describe('getMessageReason', () => { + it('should return reason', () => { + const problemDetail = createProblemDetail(); + + const reason: ValidationMessageCode = getMessageReason(problemDetail); + + expect(reason).toEqual(problemDetail.invalidParams[0].reason); + }); + + it('should return null', () => { + const problemDetail = createProblemDetail([{ ...createInvalidParam(), reason: null }]); + + const reason: ValidationMessageCode = getMessageReason(problemDetail); + + expect(reason).toBeNull(); + }); + }); + + describe('getMessageForInvalidParam', () => { + const label: string = faker.random.word(); + + it('should return undefined reason', () => { + const invalidParam: InvalidParam = createInvalidParam(); + + const message: string = getMessageForInvalidParam(label, { + ...invalidParam, + reason: undefined, + }); + + expect(message).toBeUndefined(); + }); + + it('should return message', () => { + const invalidParam: InvalidParam = createInvalidParam(); + + const message: string = getMessageForInvalidParam(label, { + ...invalidParam, + reason: ValidationMessageCode.FIELD_DATE_FORMAT_INVALID, + }); + expect(message).toEqual(VALIDATION_MESSAGES[ValidationMessageCode.FIELD_DATE_FORMAT_INVALID]); + }); + + it('should return message with field placeholder', () => { + const invalidParam: InvalidParam = createInvalidParam(); + + const message: string = getMessageForInvalidParam(label, { + ...invalidParam, + reason: ValidationMessageCode.FIELD_INVALID, + }); + expect(message).toEqual( + VALIDATION_MESSAGES[ValidationMessageCode.FIELD_INVALID].replace('{field}', label), + ); + }); + + it('should return message with placeholders', () => { + const invalidParam: InvalidParam = createInvalidParam(); + const min: string = '1'; + const max: string = '5'; + + const message: string = getMessageForInvalidParam(label, { + ...invalidParam, + reason: ValidationMessageCode.FIELD_SIZE, + constraintParameters: [ + { name: 'min', value: min }, + { name: 'max', value: max }, + ], + }); + expect(message).toEqual( + VALIDATION_MESSAGES[ValidationMessageCode.FIELD_SIZE] + .replace('{field}', label) + .replace('{min}', min) + .replace('{max}', max), + ); + }); }); }); diff --git a/alfa-client/libs/tech-shared/src/lib/validation/tech.validation.util.ts b/alfa-client/libs/tech-shared/src/lib/validation/tech.validation.util.ts index 4a34005de2f231027678dc6ce606a8cc2e3e1bed..d562a4543baec28671348cd0ef81402c055b154d 100644 --- a/alfa-client/libs/tech-shared/src/lib/validation/tech.validation.util.ts +++ b/alfa-client/libs/tech-shared/src/lib/validation/tech.validation.util.ts @@ -22,9 +22,9 @@ * unter der Lizenz sind dem Lizenztext zu entnehmen. */ import { AbstractControl, UntypedFormGroup } from '@angular/forms'; -import { isNil } from 'lodash-es'; -import { ApiError, InvalidParam, Issue, IssueParam } from '../tech.model'; -import { isNotNil, replacePlaceholder } from '../tech.util'; +import { isEmpty, isNil } from 'lodash-es'; +import { ApiError, InvalidParam, Issue, IssueParam, ProblemDetail } from '../tech.model'; +import { replacePlaceholder } from '../tech.util'; import { VALIDATION_MESSAGES, ValidationMessageCode } from './tech.validation.messages'; export function isValidationError(issue: Issue): boolean { @@ -47,7 +47,7 @@ export function getControlForIssue( issue: Issue, pathPrefix?: string, ): AbstractControl { - const fieldPath: string = getFieldPathWithoutPrefix(issue.field, pathPrefix); + const fieldPath: string = getFieldPath(issue.field, pathPrefix); let curControl: AbstractControl = form; fieldPath @@ -72,8 +72,12 @@ export function getMessageForIssue(label: string, issue: Issue): string { return msg; } -export function isValidationFieldFileSizeExceedError(error: any) { - return getMessageCode(error) === ValidationMessageCode.VALIDATION_FIELD_FILE_SIZE_EXCEEDED; +export function isValidationFieldFileSizeExceedError(error: ProblemDetail): boolean { + return getMessageReason(error) === ValidationMessageCode.FIELD_FILE_SIZE_EXCEEDED; +} + +export function getMessageReason(problemDetail: ProblemDetail): ValidationMessageCode | null { + return problemDetail.invalidParams[0].reason ?? null; } export function getMessageCode(apiError: ApiError): string { @@ -86,12 +90,9 @@ export function setInvalidParamValidationError( pathPrefix?: string, ): void { const control: AbstractControl = getControlForInvalidParam(form, invalidParam, pathPrefix); - if (isNotNil(control)) { - control.setErrors({ - [invalidParam.reason]: getMessageForInvalidParam(invalidParam, pathPrefix), - }); - control.markAsTouched(); - } + + control.setErrors({ [invalidParam.reason]: invalidParam }); + control.markAsTouched(); } export function getControlForInvalidParam( @@ -99,17 +100,29 @@ export function getControlForInvalidParam( invalidParam: InvalidParam, pathPrefix?: string, ): AbstractControl { - return form.get(getFieldPathWithoutPrefix(invalidParam.name, pathPrefix)); + return form.get(getFieldPath(invalidParam.name, pathPrefix)); } -export function getMessageForInvalidParam(item: InvalidParam, pathPrefix: string): string { - return replacePlaceholder( - VALIDATION_MESSAGES[item.reason], - 'field', - getFieldPathWithoutPrefix(item.name, pathPrefix), +export function getMessageForInvalidParam(label: string, invalidParam: InvalidParam): string { + let msg: string = VALIDATION_MESSAGES[invalidParam.reason]; + + if (isNil(msg)) { + console.warn('No message for code ' + invalidParam.reason + ' found.'); + return invalidParam.reason; + } + + msg = replacePlaceholder(msg, 'field', label); + invalidParam.constraintParameters.forEach( + (param: IssueParam) => (msg = replacePlaceholder(msg, param.name, param.value)), ); + return msg; } -function getFieldPathWithoutPrefix(name: string, pathPrefix?: string): string { - return pathPrefix ? name.substring(pathPrefix.length + 1) : name; +export function getFieldPath(name: string, pathPrefix: string): string { + if (isEmpty(pathPrefix)) { + return name.split('.').pop(); + } + + const indexOfField = name.lastIndexOf(pathPrefix) + pathPrefix.length + 1; + return name.slice(indexOfField); } diff --git a/alfa-client/libs/tech-shared/test/error.ts b/alfa-client/libs/tech-shared/test/error.ts index 9803a5a5ad90888ceb7125bc535f5c379548d126..3f80af06d43d07792780eaf023c378bcfd697345 100644 --- a/alfa-client/libs/tech-shared/test/error.ts +++ b/alfa-client/libs/tech-shared/test/error.ts @@ -23,7 +23,14 @@ */ import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http'; import { faker } from '@faker-js/faker'; -import { ApiError, InvalidParam, Issue, IssueParam, ProblemDetail } from '../src/lib/tech.model'; +import { + ApiError, + ConstraintParameter, + InvalidParam, + Issue, + IssueParam, + ProblemDetail, +} from '../src/lib/tech.model'; import { ValidationMessageCode } from '../src/lib/validation/tech.validation.messages'; export function createIssueParam(): IssueParam { @@ -63,10 +70,22 @@ export function createProblemDetail( type: faker.random.word(), instance: faker.internet.url(), detail: faker.random.word(), - 'invalid-params': invalidParams, + invalidParams: invalidParams, }; } export function createInvalidParam(): InvalidParam { - return { name: faker.random.word(), reason: ValidationMessageCode.VALIDATION_FIELD_EMPTY }; + return { + name: faker.random.word(), + reason: ValidationMessageCode.FIELD_EMPTY, + value: faker.random.words(10), + constraintParameters: [createInvalidParamConstraintParameter()], + }; +} + +export function createInvalidParamConstraintParameter(): ConstraintParameter { + return { + name: faker.random.word(), + value: faker.random.word(), + }; } diff --git a/alfa-client/libs/ui/src/index.ts b/alfa-client/libs/ui/src/index.ts index 18cbad77e78dfb553c9cc351c0f6a084446b41a0..ed84f3db5d20804637786f2d47d4e2ad8fed0f65 100644 --- a/alfa-client/libs/ui/src/index.ts +++ b/alfa-client/libs/ui/src/index.ts @@ -44,7 +44,6 @@ export * from './lib/ui/file-upload/file-upload.component'; export * from './lib/ui/fixed-dialog/fixed-dialog-data.model'; export * from './lib/ui/fixed-dialog/fixed-dialog.component'; export * from './lib/ui/icon-button-with-spinner/icon-button-with-spinner.component'; -export * from './lib/ui/mattooltip/mattooltip.directive'; export * from './lib/ui/menu-item/menu-item.component'; export * from './lib/ui/messages'; export * from './lib/ui/open-url-button/open-url-button.component'; diff --git a/alfa-client/libs/ui/src/lib/assets/update.svg b/alfa-client/libs/ui/src/lib/assets/update.svg index 1eb6a0751c7673b2de4a52c06aba58f191f32570..64c8cf90dc1a17b7183fd9dc03a66effd3273449 100644 --- a/alfa-client/libs/ui/src/lib/assets/update.svg +++ b/alfa-client/libs/ui/src/lib/assets/update.svg @@ -1 +1,4 @@ -<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="M480-120q-75 0-140.5-28.5t-114-77q-48.5-48.5-77-114T120-480q0-75 28.5-140.5t77-114q48.5-48.5 114-77T480-840q82 0 155.5 35T760-706v-94h80v240H600v-80h110q-41-56-101-88t-129-32q-117 0-198.5 81.5T200-480q0 117 81.5 198.5T480-200q105 0 183.5-68T756-440h82q-15 137-117.5 228.5T480-120Zm112-192L440-464v-216h80v184l128 128-56 56Z"/></svg> \ No newline at end of file +<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="inherit"> + <path + d="M480-120q-75 0-140.5-28.5t-114-77q-48.5-48.5-77-114T120-480q0-75 28.5-140.5t77-114q48.5-48.5 114-77T480-840q82 0 155.5 35T760-706v-94h80v240H600v-80h110q-41-56-101-88t-129-32q-117 0-198.5 81.5T200-480q0 117 81.5 198.5T480-200q105 0 183.5-68T756-440h82q-15 137-117.5 228.5T480-120Zm112-192L440-464v-216h80v184l128 128-56 56Z" /> +</svg> \ No newline at end of file diff --git a/alfa-client/libs/ui/src/lib/ui/back-button/back-button.component.spec.ts b/alfa-client/libs/ui/src/lib/ui/back-button/back-button.component.spec.ts index f216b9a4171c9cb0f3bccde97f17b4d4d73b6bbf..4e90d76c66ed69088cd5b40760d2aff4f17dda7e 100644 --- a/alfa-client/libs/ui/src/lib/ui/back-button/back-button.component.spec.ts +++ b/alfa-client/libs/ui/src/lib/ui/back-button/back-button.component.spec.ts @@ -1,10 +1,11 @@ import { getElementFromFixture } from '@alfa-client/test-utils'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { MatIcon } from '@angular/material/icon'; +import { MatTooltipModule } from '@angular/material/tooltip'; import { RouterTestingModule } from '@angular/router/testing'; import faker from '@faker-js/faker'; import { getDataTestIdOf } from 'libs/tech-shared/test/data-test'; -import { MatTooltipDirective } from 'libs/ui/src/lib/ui/mattooltip/mattooltip.directive'; +import { MockModule } from 'ng-mocks'; import { BackButtonComponent } from './back-button.component'; describe('BackButtonComponent', () => { @@ -17,7 +18,7 @@ describe('BackButtonComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [BackButtonComponent, MatTooltipDirective], + declarations: [BackButtonComponent, MockModule(MatTooltipModule)], imports: [MatIcon, RouterTestingModule], }).compileComponents(); @@ -41,12 +42,6 @@ describe('BackButtonComponent', () => { expect(backButtonElement).toHaveAttribute('aria-label', component.label); }); - - it('should set title attribute', () => { - const backButtonElement: HTMLAnchorElement = getElementFromFixture(fixture, backButton); - - expect(backButtonElement).toHaveAttribute('title', component.label); - }); }); describe('linkTo', () => { diff --git a/alfa-client/libs/ui/src/lib/ui/download-button/download-button.component.html b/alfa-client/libs/ui/src/lib/ui/download-button/download-button.component.html index 2d3bb50998667c75d6e563071d9de2a9ab088eb6..7d38323f46aa6ab9afabe4ab3226220cf2730d89 100644 --- a/alfa-client/libs/ui/src/lib/ui/download-button/download-button.component.html +++ b/alfa-client/libs/ui/src/lib/ui/download-button/download-button.component.html @@ -8,5 +8,5 @@ [color]="'primary'" > <mat-icon>save_alt</mat-icon> - <span>{{ text }}</span> + <span class="text-sm">{{ text }}</span> </a> diff --git a/alfa-client/libs/ui/src/lib/ui/editor/autocomplete-editor/autocomplete-editor.component.html b/alfa-client/libs/ui/src/lib/ui/editor/autocomplete-editor/autocomplete-editor.component.html index 6f484f807eca85ab9ccc7bc42c43c12c9bdc850f..8a0fb969d94a66f00007edfff7f6f84f55edd969 100644 --- a/alfa-client/libs/ui/src/lib/ui/editor/autocomplete-editor/autocomplete-editor.component.html +++ b/alfa-client/libs/ui/src/lib/ui/editor/autocomplete-editor/autocomplete-editor.component.html @@ -54,7 +54,7 @@ <mat-error> <ozgcloud-validation-error [attr.data-test-id]="(label | convertForDataTest) + '-autocomplete-error'" - [issues]="issues" + [invalidParams]="invalidParams" [label]="label" > </ozgcloud-validation-error> diff --git a/alfa-client/libs/ui/src/lib/ui/editor/date-editor/date-editor.component.html b/alfa-client/libs/ui/src/lib/ui/editor/date-editor/date-editor.component.html index 589e1679db0ad9e505aa9b98a1c981b366e5abfb..07ec3ad16554c885825d55b1f14139a7e05a246f 100644 --- a/alfa-client/libs/ui/src/lib/ui/editor/date-editor/date-editor.component.html +++ b/alfa-client/libs/ui/src/lib/ui/editor/date-editor/date-editor.component.html @@ -43,7 +43,7 @@ <mat-error> <ozgcloud-validation-error [attr.data-test-id]="(label | convertForDataTest) + '-date-error'" - [issues]="issues" + [invalidParams]="invalidParams" [label]="label" ></ozgcloud-validation-error> </mat-error> diff --git a/alfa-client/libs/ui/src/lib/ui/editor/file-upload-editor/file-upload-editor.component.html b/alfa-client/libs/ui/src/lib/ui/editor/file-upload-editor/file-upload-editor.component.html index a6f83cf4e2fa94e8a84d5ee0aebbfd9fa1efd8d1..94acabf0cf558593f633a252fb6bdb35f07f5fd0 100644 --- a/alfa-client/libs/ui/src/lib/ui/editor/file-upload-editor/file-upload-editor.component.html +++ b/alfa-client/libs/ui/src/lib/ui/editor/file-upload-editor/file-upload-editor.component.html @@ -48,7 +48,7 @@ <mat-error> <ozgcloud-validation-error [attr.data-test-id]="(label | convertForDataTest) + '-file-upload-error'" - [issues]="issues" + [invalidParams]="invalidParams" [label]="label" > </ozgcloud-validation-error> diff --git a/alfa-client/libs/ui/src/lib/ui/editor/text-editor/text-editor.component.html b/alfa-client/libs/ui/src/lib/ui/editor/text-editor/text-editor.component.html index bcf6e536c27a4a13aab0a99e43c2c155920bc241..8a8e01260fd7b5cb4af14320ea4af0d264b9a980 100644 --- a/alfa-client/libs/ui/src/lib/ui/editor/text-editor/text-editor.component.html +++ b/alfa-client/libs/ui/src/lib/ui/editor/text-editor/text-editor.component.html @@ -58,7 +58,7 @@ <mat-error> <ozgcloud-validation-error [attr.data-test-id]="(getPlaceholderLabel() | convertForDataTest) + '-text-error'" - [issues]="issues" + [invalidParams]="invalidParams" [label]="getPlaceholderLabel()" ></ozgcloud-validation-error> </mat-error> diff --git a/alfa-client/libs/ui/src/lib/ui/editor/textarea-editor/textarea-editor.component.html b/alfa-client/libs/ui/src/lib/ui/editor/textarea-editor/textarea-editor.component.html index cf3135707e7a986230e2dc9957326bb1766a6a88..14034ce169db28951cd062e19343976a81ca8d7b 100644 --- a/alfa-client/libs/ui/src/lib/ui/editor/textarea-editor/textarea-editor.component.html +++ b/alfa-client/libs/ui/src/lib/ui/editor/textarea-editor/textarea-editor.component.html @@ -39,8 +39,8 @@ <mat-error> <ozgcloud-validation-error - [issues]="issues" [label]="label" + [invalidParams]="invalidParams" [attr.data-test-id]="(label | convertForDataTest) + '-textarea-error'" > </ozgcloud-validation-error> diff --git a/alfa-client/libs/ui/src/lib/ui/expansion-panel/_expansion-panel.theme.scss b/alfa-client/libs/ui/src/lib/ui/expansion-panel/_expansion-panel.theme.scss index 6847720e6a809da6652b2ae2a87c87f9ccf04a48..ba505ad18cce11ca6b59ad4fecbf9e0f17b8be62 100644 --- a/alfa-client/libs/ui/src/lib/ui/expansion-panel/_expansion-panel.theme.scss +++ b/alfa-client/libs/ui/src/lib/ui/expansion-panel/_expansion-panel.theme.scss @@ -43,7 +43,7 @@ ozgcloud-expansion-panel { margin-bottom: 0; font-weight: 500; margin-left: 8px; - font-size: 16px; + font-size: 1rem; } .mat-expansion-panel-body { diff --git a/alfa-client/libs/ui/src/lib/ui/fixed-dialog/_fixed-dialog.theme.scss b/alfa-client/libs/ui/src/lib/ui/fixed-dialog/_fixed-dialog.theme.scss index f71cbc89bc0ca6eccd4030537e653e21b0ef83b2..5e3662430bdfb5e5f7106997756cd7645620ed10 100644 --- a/alfa-client/libs/ui/src/lib/ui/fixed-dialog/_fixed-dialog.theme.scss +++ b/alfa-client/libs/ui/src/lib/ui/fixed-dialog/_fixed-dialog.theme.scss @@ -33,7 +33,7 @@ margin: 0; padding: 0 4px 0 16px; color: #fff; - font-size: 16px; + font-size: 1rem; font-weight: normal; justify-content: space-between; align-items: center; @@ -52,7 +52,7 @@ padding: 0; } .mat-mdc-form-field-error { - font-size: 12px; + font-size: 0.75rem; } .mdc-dialog__content { line-height: 20px; diff --git a/alfa-client/libs/ui/src/lib/ui/http-error-dialog/connection-timeout-retry-dialog/connection-timeout-retry-dialog.component.scss b/alfa-client/libs/ui/src/lib/ui/http-error-dialog/connection-timeout-retry-dialog/connection-timeout-retry-dialog.component.scss index 6d01e5239cf92f45f39c7c0f6344a683f858e0bf..547ff80f6141d27b901ff4f2e28a6b5ac3049b03 100644 --- a/alfa-client/libs/ui/src/lib/ui/http-error-dialog/connection-timeout-retry-dialog/connection-timeout-retry-dialog.component.scss +++ b/alfa-client/libs/ui/src/lib/ui/http-error-dialog/connection-timeout-retry-dialog/connection-timeout-retry-dialog.component.scss @@ -23,13 +23,13 @@ */ h1 { font-weight: normal; - font-size: 24px; + font-size: 1.5rem; display: flex; align-items: center; .mat-icon { margin-right: 8px; - font-size: 30px; + font-size: 1.875rem; width: 30px; height: 30px; } diff --git a/alfa-client/libs/ui/src/lib/ui/http-error-dialog/connection-timeout-retry-fail-dialog/connection-timeout-retry-fail-dialog.component.scss b/alfa-client/libs/ui/src/lib/ui/http-error-dialog/connection-timeout-retry-fail-dialog/connection-timeout-retry-fail-dialog.component.scss index 48d8d08527c969b705c347d217d527c30aa6cd39..c3fa5daffcee059afdc534b5fdf6fb369c99b9f0 100644 --- a/alfa-client/libs/ui/src/lib/ui/http-error-dialog/connection-timeout-retry-fail-dialog/connection-timeout-retry-fail-dialog.component.scss +++ b/alfa-client/libs/ui/src/lib/ui/http-error-dialog/connection-timeout-retry-fail-dialog/connection-timeout-retry-fail-dialog.component.scss @@ -23,13 +23,13 @@ */ h2 { font-weight: normal; - font-size: 24px; + font-size: 1.5rem; display: flex; align-items: center; .mat-icon { margin-right: 8px; - font-size: 30px; + font-size: 1.875rem; width: 30px; height: 30px; } diff --git a/alfa-client/libs/ui/src/lib/ui/icon-button-with-spinner/icon-button-with-spinner.component.spec.ts b/alfa-client/libs/ui/src/lib/ui/icon-button-with-spinner/icon-button-with-spinner.component.spec.ts index e136723944a8a3abc875efe9bf7204cc4407f748..cc423f9a8086d18af5fdc13a0db41ab2fae861df 100644 --- a/alfa-client/libs/ui/src/lib/ui/icon-button-with-spinner/icon-button-with-spinner.component.spec.ts +++ b/alfa-client/libs/ui/src/lib/ui/icon-button-with-spinner/icon-button-with-spinner.component.spec.ts @@ -21,11 +21,11 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ +import { createEmptyStateResource } from '@alfa-client/tech-shared'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { MatIcon } from '@angular/material/icon'; -import { createEmptyStateResource } from '@alfa-client/tech-shared'; -import { MockComponent, MockDirective } from 'ng-mocks'; -import { MatTooltipDirective } from '../mattooltip/mattooltip.directive'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { MockComponent, MockModule } from 'ng-mocks'; import { SpinnerComponent } from '../spinner/spinner.component'; import { IconButtonWithSpinnerComponent } from './icon-button-with-spinner.component'; @@ -41,8 +41,8 @@ describe('IconButtonWithSpinnerComponent', () => { declarations: [ IconButtonWithSpinnerComponent, MatIcon, - MockDirective(MatTooltipDirective), MockComponent(SpinnerComponent), + MockModule(MatTooltipModule), ], }); }); diff --git a/alfa-client/libs/ui/src/lib/ui/mattooltip/mattooltip.default.ts b/alfa-client/libs/ui/src/lib/ui/mattooltip/mattooltip.default.ts new file mode 100644 index 0000000000000000000000000000000000000000..3c11d7da1b6616bf23f2d13790a38510cbf0e536 --- /dev/null +++ b/alfa-client/libs/ui/src/lib/ui/mattooltip/mattooltip.default.ts @@ -0,0 +1,9 @@ +import { MatTooltipDefaultOptions } from '@angular/material/tooltip'; + +export const matTooltipDefaultOptions: MatTooltipDefaultOptions = { + showDelay: 1500, + hideDelay: 0, + touchendHideDelay: 1500, + positionAtOrigin: true, + disableTooltipInteractivity: true, +}; diff --git a/alfa-client/libs/ui/src/lib/ui/mattooltip/mattooltip.directive.spec.ts b/alfa-client/libs/ui/src/lib/ui/mattooltip/mattooltip.directive.spec.ts deleted file mode 100644 index 2ebf4a59ad1ce37a0135097f9181b2b35fbc703a..0000000000000000000000000000000000000000 --- a/alfa-client/libs/ui/src/lib/ui/mattooltip/mattooltip.directive.spec.ts +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2022 Das Land Schleswig-Holstein vertreten durch den - * Ministerpräsidenten des Landes Schleswig-Holstein - * Staatskanzlei - * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung - * - * Lizenziert unter der EUPL, Version 1.2 oder - sobald - * diese von der Europäischen Kommission genehmigt wurden - - * Folgeversionen der EUPL ("Lizenz"); - * Sie dürfen dieses Werk ausschließlich gemäß - * dieser Lizenz nutzen. - * Eine Kopie der Lizenz finden Sie hier: - * - * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * - * Sofern nicht durch anwendbare Rechtsvorschriften - * gefordert oder in schriftlicher Form vereinbart, wird - * die unter der Lizenz verbreitete Software "so wie sie - * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - - * ausdrücklich oder stillschweigend - verbreitet. - * Die sprachspezifischen Genehmigungen und Beschränkungen - * unter der Lizenz sind dem Lizenztext zu entnehmen. - */ -import { ElementRef } from '@angular/core'; -import faker from '@faker-js/faker'; -import { - MatTooltipClassDirective, - MatTooltipDirective, - MatTooltipDisabledDirective, -} from './mattooltip.directive'; - -describe('MatToolTip Directives', () => { - describe('MatTooltipDirective', () => { - const el: ElementRef = <ElementRef>{}; - const tooltipText: string = faker.lorem.text(); - - it('should create an instance', () => { - const directive = new MatTooltipDirective(el); - expect(directive).toBeTruthy(); - }); - }); - - describe('MatTooltipDisabledDirective', () => { - const el: ElementRef = <ElementRef>{}; - - it('should create an instance', () => { - const directive = new MatTooltipDisabledDirective(el); - expect(directive).toBeTruthy(); - }); - }); - - describe('MatTooltipClassDirective', () => { - it('should create an instance', () => { - const directive = new MatTooltipClassDirective(); - expect(directive).toBeTruthy(); - }); - }); -}); diff --git a/alfa-client/libs/ui/src/lib/ui/mattooltip/mattooltip.directive.ts b/alfa-client/libs/ui/src/lib/ui/mattooltip/mattooltip.directive.ts deleted file mode 100644 index 593d0c45b3a996c3eb87d347bfd92553bed9c3fe..0000000000000000000000000000000000000000 --- a/alfa-client/libs/ui/src/lib/ui/mattooltip/mattooltip.directive.ts +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2022 Das Land Schleswig-Holstein vertreten durch den - * Ministerpräsidenten des Landes Schleswig-Holstein - * Staatskanzlei - * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung - * - * Lizenziert unter der EUPL, Version 1.2 oder - sobald - * diese von der Europäischen Kommission genehmigt wurden - - * Folgeversionen der EUPL ("Lizenz"); - * Sie dürfen dieses Werk ausschließlich gemäß - * dieser Lizenz nutzen. - * Eine Kopie der Lizenz finden Sie hier: - * - * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * - * Sofern nicht durch anwendbare Rechtsvorschriften - * gefordert oder in schriftlicher Form vereinbart, wird - * die unter der Lizenz verbreitete Software "so wie sie - * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - - * ausdrücklich oder stillschweigend - verbreitet. - * Die sprachspezifischen Genehmigungen und Beschränkungen - * unter der Lizenz sind dem Lizenztext zu entnehmen. - */ -import { Directive, ElementRef, Input, OnChanges, OnInit } from '@angular/core'; -import { EMPTY_STRING } from '@alfa-client/tech-shared'; - -/** - * Workaround: MatTooltipModule aus Angular 13 schafft es nicht immer, - * das Tooltip-HTML aus dem DOM zu löschen, wenn die Maus das entsprechende - * Element verlässt. Diese Directiven implementieren Tooltips auf Basis - * des HTML-Attributes title. Sie können als Ersatz für MatTooltipModule - * verwendet werden. - * - * Implementiert sind nur die Directiven, die bei uns zum Einsatz kommen. - */ - -const titleAttribute: string = 'title'; -const ariaDescribedBy: string = 'aria-describedby'; - -@Directive({ - selector: '[matTooltip]', -}) -export class MatTooltipDirective implements OnChanges { - @Input() matTooltip: string = EMPTY_STRING; - - constructor(private el: ElementRef) {} - - ngOnChanges(): void { - this.el.nativeElement.setAttribute(titleAttribute, this.matTooltip); - this.el.nativeElement.setAttribute(ariaDescribedBy, this.matTooltip); - } -} - -@Directive({ - selector: '[matTooltipDisabled]', -}) -export class MatTooltipDisabledDirective implements OnInit { - @Input() matTooltipDisabled: boolean = false; - - constructor(private el: ElementRef) {} - - ngOnInit(): void { - if (this.matTooltipDisabled) { - this.el.nativeElement.setAttribute(titleAttribute, EMPTY_STRING); - this.el.nativeElement.setAttribute(ariaDescribedBy, EMPTY_STRING); - } - } -} - -@Directive({ - selector: '[matTooltipClass]', -}) -export class MatTooltipClassDirective { - @Input() matTooltipClass: string = EMPTY_STRING; -} diff --git a/alfa-client/libs/ui/src/lib/ui/menu-item/menu-item.component.html b/alfa-client/libs/ui/src/lib/ui/menu-item/menu-item.component.html index 2a127b78127ac601a4ba02e88f6b428795d26970..43c0239c59f88b1bc4f8d9c49e428ba5a97a2c5f 100644 --- a/alfa-client/libs/ui/src/lib/ui/menu-item/menu-item.component.html +++ b/alfa-client/libs/ui/src/lib/ui/menu-item/menu-item.component.html @@ -6,7 +6,7 @@ </ng-template> <div> - <div class="headline">{{ headline }}</div> - <div class="text">{{ text }}</div> + <div class="text-base font-medium">{{ headline }}</div> + <div class="mb-3 text-sm font-normal">{{ text }}</div> <ng-content></ng-content> </div> diff --git a/alfa-client/libs/ui/src/lib/ui/menu-item/menu-item.component.scss b/alfa-client/libs/ui/src/lib/ui/menu-item/menu-item.component.scss index 41f783ee481e672a91b6b728e277ef0ccbf7f868..369725c1daec6c0954ab191e89b33aac2cf12aac 100644 --- a/alfa-client/libs/ui/src/lib/ui/menu-item/menu-item.component.scss +++ b/alfa-client/libs/ui/src/lib/ui/menu-item/menu-item.component.scss @@ -15,17 +15,6 @@ ozgcloud-svgicon-big { margin-right: 1rem; } -.headline { - font-weight: 500; - font-size: 16px; -} - -.text { - font-weight: 400; - font-size: 14px; - margin-bottom: 10px; -} - :host-context(.dark) { color: #fff; background-color: #393939; diff --git a/alfa-client/libs/ui/src/lib/ui/notification/internal-server-error-dialog/internal-server-error-dialog.component.scss b/alfa-client/libs/ui/src/lib/ui/notification/internal-server-error-dialog/internal-server-error-dialog.component.scss index cc051218ffa5dbef0d2c3ad72469e6e01b8baeb9..6f31abcc6bb5a5554ca7f88a30b784eb2f48f450 100644 --- a/alfa-client/libs/ui/src/lib/ui/notification/internal-server-error-dialog/internal-server-error-dialog.component.scss +++ b/alfa-client/libs/ui/src/lib/ui/notification/internal-server-error-dialog/internal-server-error-dialog.component.scss @@ -27,7 +27,7 @@ h1 { .mat-icon { margin-right: 8px; - font-size: 30px; + font-size: 1.875rem; width: 30px; height: 30px; } diff --git a/alfa-client/libs/ui/src/lib/ui/open-url-button/open-url-button.component.html b/alfa-client/libs/ui/src/lib/ui/open-url-button/open-url-button.component.html index d5da9183f237f187be716d871c9181e5691438d5..f9b4506ca32ddc37a7ae623293999da1391d81ba 100644 --- a/alfa-client/libs/ui/src/lib/ui/open-url-button/open-url-button.component.html +++ b/alfa-client/libs/ui/src/lib/ui/open-url-button/open-url-button.component.html @@ -10,5 +10,5 @@ class="button" > <mat-icon>open_in_new</mat-icon> - <span>{{ text }}</span> + <span class="text-sm">{{ text }}</span> </a> diff --git a/alfa-client/libs/ui/src/lib/ui/ozgcloud-button/ozgcloud-button-with-spinner/ozgcloud-button-with-spinner.component.spec.ts b/alfa-client/libs/ui/src/lib/ui/ozgcloud-button/ozgcloud-button-with-spinner/ozgcloud-button-with-spinner.component.spec.ts index 5d3fdd9d32ccf8ee3a109b44db621c93892eae4f..f89d7c53c18bfea6fed7ca930681a0cc574b1138 100644 --- a/alfa-client/libs/ui/src/lib/ui/ozgcloud-button/ozgcloud-button-with-spinner/ozgcloud-button-with-spinner.component.spec.ts +++ b/alfa-client/libs/ui/src/lib/ui/ozgcloud-button/ozgcloud-button-with-spinner/ozgcloud-button-with-spinner.component.spec.ts @@ -24,10 +24,10 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { MatButton } from '@angular/material/button'; import { MatRipple } from '@angular/material/core'; +import { MatTooltipModule } from '@angular/material/tooltip'; import { createCommandResource } from 'libs/command-shared/test/command'; import { getDataTestClassOf } from 'libs/tech-shared/test/data-test'; -import { MatTooltipDirective } from 'libs/ui/src/lib/ui/mattooltip/mattooltip.directive'; -import { MockComponent } from 'ng-mocks'; +import { MockComponent, MockModule } from 'ng-mocks'; import { OzgcloudButtonContentComponent } from '../shared/ozgcloud-button-content/ozgcloud-button-content.component'; import { OzgcloudButtonWithSpinnerComponent } from './ozgcloud-button-with-spinner.component'; @@ -45,7 +45,7 @@ describe('OzgcloudButtonWithSpinnerComponent', () => { MatButton, MatRipple, OzgcloudButtonWithSpinnerComponent, - MatTooltipDirective, + MockModule(MatTooltipModule), MockComponent(OzgcloudButtonContentComponent), ], }).compileComponents(); diff --git a/alfa-client/libs/ui/src/lib/ui/ozgcloud-button/ozgcloud-icon-button-primary/ozgcloud-icon-button-primary.component.spec.ts b/alfa-client/libs/ui/src/lib/ui/ozgcloud-button/ozgcloud-icon-button-primary/ozgcloud-icon-button-primary.component.spec.ts index 91c58127ea53ad7eb01d20ff19ea86c472a3de47..42de6220cbeb942d053d8e2e493f12972845056b 100644 --- a/alfa-client/libs/ui/src/lib/ui/ozgcloud-button/ozgcloud-icon-button-primary/ozgcloud-icon-button-primary.component.spec.ts +++ b/alfa-client/libs/ui/src/lib/ui/ozgcloud-button/ozgcloud-icon-button-primary/ozgcloud-icon-button-primary.component.spec.ts @@ -1,12 +1,12 @@ +import { createAriaLabelForIconButton } from '@alfa-client/tech-shared'; +import { getElementFromFixture } from '@alfa-client/test-utils'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { MatIcon } from '@angular/material/icon'; -import { MockComponent, MockDirective } from 'ng-mocks'; -import { MatTooltipDirective } from '@alfa-client/ui'; -import { OzgcloudIconButtonPrimaryComponent } from './ozgcloud-icon-button-primary.component'; -import { getElementFromFixture } from '@alfa-client/test-utils'; -import { getDataTestClassOf } from 'libs/tech-shared/test/data-test'; import { MatIconTestingModule } from '@angular/material/icon/testing'; -import { createAriaLabelForIconButton } from '@alfa-client/tech-shared'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { getDataTestClassOf } from 'libs/tech-shared/test/data-test'; +import { MockComponent, MockModule } from 'ng-mocks'; +import { OzgcloudIconButtonPrimaryComponent } from './ozgcloud-icon-button-primary.component'; jest.mock('@alfa-client/tech-shared'); const createAriaLabelForIconButtonMock = createAriaLabelForIconButton as jest.Mock; @@ -21,7 +21,7 @@ describe('IconButtonPrimaryWithSpinnerComponent', () => { declarations: [ OzgcloudIconButtonPrimaryComponent, MockComponent(MatIcon), - MockDirective(MatTooltipDirective), + MockModule(MatTooltipModule), ], }); }); diff --git a/alfa-client/libs/ui/src/lib/ui/ozgcloud-button/ozgcloud-stroked-button-with-spinner/ozgcloud-stroked-button-with-spinner.component.spec.ts b/alfa-client/libs/ui/src/lib/ui/ozgcloud-button/ozgcloud-stroked-button-with-spinner/ozgcloud-stroked-button-with-spinner.component.spec.ts index 99b39988e7a25efb6c0577647dad825faac6740d..5affb48a358a939b035eff579d80d44b65bae040 100644 --- a/alfa-client/libs/ui/src/lib/ui/ozgcloud-button/ozgcloud-stroked-button-with-spinner/ozgcloud-stroked-button-with-spinner.component.spec.ts +++ b/alfa-client/libs/ui/src/lib/ui/ozgcloud-button/ozgcloud-stroked-button-with-spinner/ozgcloud-stroked-button-with-spinner.component.spec.ts @@ -24,10 +24,10 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { MatButton } from '@angular/material/button'; import { MatRipple } from '@angular/material/core'; +import { MatTooltipModule } from '@angular/material/tooltip'; import { createCommandResource } from 'libs/command-shared/test/command'; import { getDataTestClassOf } from 'libs/tech-shared/test/data-test'; -import { MatTooltipDirective } from 'libs/ui/src/lib/ui/mattooltip/mattooltip.directive'; -import { MockComponent } from 'ng-mocks'; +import { MockComponent, MockModule } from 'ng-mocks'; import { OzgcloudButtonContentComponent } from '../shared/ozgcloud-button-content/ozgcloud-button-content.component'; import { OzgcloudStrokedButtonWithSpinnerComponent } from './ozgcloud-stroked-button-with-spinner.component'; @@ -45,7 +45,7 @@ describe('OzgcloudStrokedButtonWithSpinnerComponent', () => { MatButton, MatRipple, OzgcloudStrokedButtonWithSpinnerComponent, - MatTooltipDirective, + MockModule(MatTooltipModule), MockComponent(OzgcloudButtonContentComponent), ], }).compileComponents(); diff --git a/alfa-client/libs/ui/src/lib/ui/ozgcloud-button/shared/ozgcloud-button-content/ozgcloud-button-content.component.html b/alfa-client/libs/ui/src/lib/ui/ozgcloud-button/shared/ozgcloud-button-content/ozgcloud-button-content.component.html index 43d399abb04e8fc3417be4b88a13c6d5d2d5b810..30f7ac2c092a194e06bafd2e9cafa606a858a8ee 100644 --- a/alfa-client/libs/ui/src/lib/ui/ozgcloud-button/shared/ozgcloud-button-content/ozgcloud-button-content.component.html +++ b/alfa-client/libs/ui/src/lib/ui/ozgcloud-button/shared/ozgcloud-button-content/ozgcloud-button-content.component.html @@ -16,7 +16,7 @@ > </mat-icon> -<span *ngIf="text" data-test-class="button-with-spinner-text">{{ text }}</span> +<span *ngIf="text" class="text-sm" data-test-class="button-with-spinner-text">{{ text }}</span> <ozgcloud-spinner [diameter]="22" padding="0" [stateResource]="stateResource" [show]="showSpinner"> </ozgcloud-spinner> diff --git a/alfa-client/libs/ui/src/lib/ui/ozgcloud-paste-text-button/ozgcloud-paste-text-button.component.spec.ts b/alfa-client/libs/ui/src/lib/ui/ozgcloud-paste-text-button/ozgcloud-paste-text-button.component.spec.ts index 2173708cb5c899e1a77535619409507f2917957d..cca860ae0eb78e8f61babd0a52caf54f006533a1 100644 --- a/alfa-client/libs/ui/src/lib/ui/ozgcloud-paste-text-button/ozgcloud-paste-text-button.component.spec.ts +++ b/alfa-client/libs/ui/src/lib/ui/ozgcloud-paste-text-button/ozgcloud-paste-text-button.component.spec.ts @@ -1,14 +1,14 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { MatIcon } from '@angular/material/icon'; -import { OzgcloudPasteTextButtonComponent } from './ozgcloud-paste-text-button.component'; -import { MatTooltipDirective } from '../mattooltip/mattooltip.directive'; -import { getDataTestIdOf } from 'libs/tech-shared/test/data-test'; import { isClipboardReadSupported } from '@alfa-client/tech-shared'; import { getElementFromFixture, mock } from '@alfa-client/test-utils'; -import { MatIconTestingModule } from '@angular/material/icon/testing'; -import { EventEmitter } from '@angular/core'; -import { MockComponent, MockDirective } from 'ng-mocks'; import { OzgcloudIconButtonPrimaryComponent } from '@alfa-client/ui'; +import { EventEmitter } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MatIcon } from '@angular/material/icon'; +import { MatIconTestingModule } from '@angular/material/icon/testing'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { getDataTestIdOf } from 'libs/tech-shared/test/data-test'; +import { MockComponent, MockModule } from 'ng-mocks'; +import { OzgcloudPasteTextButtonComponent } from './ozgcloud-paste-text-button.component'; jest.mock('@alfa-client/tech-shared'); const isClipboardReadSupportedMock = isClipboardReadSupported as jest.Mock; @@ -32,7 +32,7 @@ describe('OzgcloudPasteTextButtonComponent', () => { OzgcloudPasteTextButtonComponent, MockComponent(MatIcon), MockComponent(OzgcloudIconButtonPrimaryComponent), - MockDirective(MatTooltipDirective), + MockModule(MatTooltipModule), ], }).compileComponents(); diff --git a/alfa-client/libs/ui/src/lib/ui/slide-toggle/slide-toggle.component.html b/alfa-client/libs/ui/src/lib/ui/slide-toggle/slide-toggle.component.html index a76b8bea8332621394acda1e83b7aebfbd987394..5812a93c4d9789a4127f65101278302a13ce02e5 100644 --- a/alfa-client/libs/ui/src/lib/ui/slide-toggle/slide-toggle.component.html +++ b/alfa-client/libs/ui/src/lib/ui/slide-toggle/slide-toggle.component.html @@ -29,5 +29,5 @@ [matTooltip]="toolTip" [checked]="checked" (change)="valueChanged.emit($event.checked)" - >{{ label }} + ><span class="text-sm">{{ label }}</span> </mat-slide-toggle> diff --git a/alfa-client/libs/ui/src/lib/ui/slide-toggle/slide-toggle.component.spec.ts b/alfa-client/libs/ui/src/lib/ui/slide-toggle/slide-toggle.component.spec.ts index fbbf1480130f7976d0ed19037265b1b8e73a0abb..1d001dae9a6b947774bf7a4b0674d99b9e6d35b8 100644 --- a/alfa-client/libs/ui/src/lib/ui/slide-toggle/slide-toggle.component.spec.ts +++ b/alfa-client/libs/ui/src/lib/ui/slide-toggle/slide-toggle.component.spec.ts @@ -24,7 +24,8 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { MatRippleModule } from '@angular/material/core'; import { MatSlideToggle } from '@angular/material/slide-toggle'; -import { MatTooltipDirective } from 'libs/ui/src/lib/ui/mattooltip/mattooltip.directive'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { MockModule } from 'ng-mocks'; import { SlideToggleComponent } from './slide-toggle.component'; describe('SlideToggleComponent', () => { @@ -34,7 +35,7 @@ describe('SlideToggleComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ imports: [MatRippleModule, MatSlideToggle], - declarations: [SlideToggleComponent, MatTooltipDirective], + declarations: [SlideToggleComponent, MockModule(MatTooltipModule)], }).compileComponents(); fixture = TestBed.createComponent(SlideToggleComponent); diff --git a/alfa-client/libs/ui/src/lib/ui/ui.module.ts b/alfa-client/libs/ui/src/lib/ui/ui.module.ts index 3957646680c24d26727e4085b0f2628292004c78..3344655723a5b0f505918ac3cae9e9b77e966839 100644 --- a/alfa-client/libs/ui/src/lib/ui/ui.module.ts +++ b/alfa-client/libs/ui/src/lib/ui/ui.module.ts @@ -46,6 +46,7 @@ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { MatSlideToggleModule } from '@angular/material/slide-toggle'; import { MatSnackBarModule } from '@angular/material/snack-bar'; import { MatTabsModule } from '@angular/material/tabs'; +import { MAT_TOOLTIP_DEFAULT_OPTIONS, MatTooltipModule } from '@angular/material/tooltip'; import { RouterModule } from '@angular/router'; import { FileUploadButtonComponent, SpinnerIconComponent } from '@ods/system'; import { de } from 'date-fns/locale'; @@ -75,11 +76,7 @@ import { FixedDialogComponent } from './fixed-dialog/fixed-dialog.component'; import { ConnectionTimeoutRetryDialogComponent } from './http-error-dialog/connection-timeout-retry-dialog/connection-timeout-retry-dialog.component'; import { ConnectionTimeoutRetryFailDialogComponent } from './http-error-dialog/connection-timeout-retry-fail-dialog/connection-timeout-retry-fail-dialog.component'; import { IconButtonWithSpinnerComponent } from './icon-button-with-spinner/icon-button-with-spinner.component'; -import { - MatTooltipClassDirective, - MatTooltipDirective, - MatTooltipDisabledDirective, -} from './mattooltip/mattooltip.directive'; +import { matTooltipDefaultOptions } from './mattooltip/mattooltip.default'; import { MenuItemComponent } from './menu-item/menu-item.component'; import { InternalServerErrorDialogComponent } from './notification/internal-server-error-dialog/internal-server-error-dialog.component'; import { OpenUrlButtonComponent } from './open-url-button/open-url-button.component'; @@ -122,9 +119,6 @@ import { ValidationErrorComponent } from './validation-error/validation-error.co EnumEditorComponent, InternalServerErrorDialogComponent, FileUploadEditorComponent, - MatTooltipDirective, - MatTooltipDisabledDirective, - MatTooltipClassDirective, MenuItemComponent, PostfachIconComponent, AccordionComponent, @@ -171,6 +165,7 @@ import { ValidationErrorComponent } from './validation-error/validation-error.co MatAutocompleteModule, MatDialogModule, MatTabsModule, + MatTooltipModule, MatBadgeModule, CommonModule, TechSharedModule, @@ -200,6 +195,7 @@ import { ValidationErrorComponent } from './validation-error/validation-error.co MatAutocompleteModule, MatDialogModule, MatTabsModule, + MatTooltipModule, MatBadgeModule, CommonModule, TechSharedModule, @@ -226,9 +222,6 @@ import { ValidationErrorComponent } from './validation-error/validation-error.co EnumEditorComponent, InternalServerErrorDialogComponent, FileUploadEditorComponent, - MatTooltipDirective, - MatTooltipDisabledDirective, - MatTooltipClassDirective, MenuItemComponent, PostfachIconComponent, AccordionComponent, @@ -273,6 +266,7 @@ import { ValidationErrorComponent } from './validation-error/validation-error.co useClass: DateFnsAdapter, deps: [MAT_DATE_LOCALE], }, + { provide: MAT_TOOLTIP_DEFAULT_OPTIONS, useValue: matTooltipDefaultOptions }, ], }) export class UiModule {} diff --git a/alfa-client/libs/ui/src/lib/ui/validation-error/validation-error.component.html b/alfa-client/libs/ui/src/lib/ui/validation-error/validation-error.component.html index 13f1ef45c46566b76aad56247845108a014310e6..2c47c8ccdafa3929335cd41715394e7c2e11357d 100644 --- a/alfa-client/libs/ui/src/lib/ui/validation-error/validation-error.component.html +++ b/alfa-client/libs/ui/src/lib/ui/validation-error/validation-error.component.html @@ -23,4 +23,4 @@ unter der Lizenz sind dem Lizenztext zu entnehmen. --> -<span *ngFor="let issue of issues">{{ message(issue) }}</span> +<span *ngFor="let invalidParam of invalidParams">{{ message(invalidParam) }}</span> diff --git a/alfa-client/libs/ui/src/lib/ui/validation-error/validation-error.component.spec.ts b/alfa-client/libs/ui/src/lib/ui/validation-error/validation-error.component.spec.ts index 299af463232908e4f3edec96f97f7fd912379b0e..e486895e359ed8cbb702456e19b5f88530683e48 100644 --- a/alfa-client/libs/ui/src/lib/ui/validation-error/validation-error.component.spec.ts +++ b/alfa-client/libs/ui/src/lib/ui/validation-error/validation-error.component.spec.ts @@ -21,8 +21,10 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ +import { InvalidParam, getMessageForInvalidParam } from '@alfa-client/tech-shared'; import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { createIssue } from 'libs/tech-shared/test/error'; +import { ValidationMessageCode } from 'libs/tech-shared/src/lib/validation/tech.validation.messages'; +import { createInvalidParam } from 'libs/tech-shared/test/error'; import { ValidationErrorComponent } from './validation-error.component'; describe('ValidationErrorComponent', () => { @@ -45,9 +47,32 @@ describe('ValidationErrorComponent', () => { expect(component).toBeTruthy(); }); - it('should get message', () => { - var msg = component.message({ ...createIssue(), messageCode: 'validation_field_size' }); + describe('get message from invalidParam', () => { + const fieldLabel: string = 'Field Label'; + const invalidParam: InvalidParam = { + ...createInvalidParam(), + reason: ValidationMessageCode.FIELD_SIZE, + }; - expect(msg).not.toHaveLength(0); + it('should contain ', () => { + const msg: string = getMessageForInvalidParam(fieldLabel, invalidParam); + + expect(msg).toContain('muss mindestens'); + }); + + it('should set field label', () => { + const msg: string = getMessageForInvalidParam(fieldLabel, invalidParam); + + expect(msg).toContain(fieldLabel); + }); + + it('should replace min param', () => { + const msg: string = getMessageForInvalidParam(fieldLabel, { + ...invalidParam, + constraintParameters: [{ name: 'min', value: '3' }], + }); + + expect(msg).toContain('3'); + }); }); }); diff --git a/alfa-client/libs/ui/src/lib/ui/validation-error/validation-error.component.ts b/alfa-client/libs/ui/src/lib/ui/validation-error/validation-error.component.ts index 0432ac003dae9960503559194148fb5d79f52c45..1c5e6df544c2a3d480dd40d6915b72ef166c7273 100644 --- a/alfa-client/libs/ui/src/lib/ui/validation-error/validation-error.component.ts +++ b/alfa-client/libs/ui/src/lib/ui/validation-error/validation-error.component.ts @@ -21,8 +21,8 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ +import { InvalidParam, getMessageForInvalidParam } from '@alfa-client/tech-shared'; import { Component, Input } from '@angular/core'; -import { getMessageForIssue, Issue } from '@alfa-client/tech-shared'; @Component({ selector: 'ozgcloud-validation-error', @@ -31,9 +31,9 @@ import { getMessageForIssue, Issue } from '@alfa-client/tech-shared'; }) export class ValidationErrorComponent { @Input() label: string; - @Input() issues: Issue[]; + @Input() invalidParams: InvalidParam[]; - public message(issue: Issue): string { - return getMessageForIssue(this.label, issue); + public message(invalidParam: InvalidParam): string { + return getMessageForInvalidParam(this.label, invalidParam); } } diff --git a/alfa-client/libs/user-assistance/src/lib/help-menu/help-menu.component.html b/alfa-client/libs/user-assistance/src/lib/help-menu/help-menu.component.html index 2b0db504b3f464f1624afcbceb95df15cfb6e3e0..8c00cbfc6abd899bf3da59fc774efc61a8c19755 100644 --- a/alfa-client/libs/user-assistance/src/lib/help-menu/help-menu.component.html +++ b/alfa-client/libs/user-assistance/src/lib/help-menu/help-menu.component.html @@ -5,9 +5,9 @@ [matMenuTriggerFor]="helpMenu.matMenu" data-test-id="help-menu-button" > - <div class="help-menu"> - <ozgcloud-icon icon="help_outline"></ozgcloud-icon> - <div class="text">Hilfe</div> + <div class="flex items-center text-ozggray-800 dark:text-ozggray-300"> + <ozgcloud-icon class="mr-1" icon="help_outline"></ozgcloud-icon> + <div>Hilfe</div> </div> </button> <ozgcloud-menu #helpMenu> diff --git a/alfa-client/libs/user-assistance/src/lib/help-menu/help-menu.component.scss b/alfa-client/libs/user-assistance/src/lib/help-menu/help-menu.component.scss deleted file mode 100644 index 99cbeef04c23dba9c76fa256dbdb4764f5a1e2b4..0000000000000000000000000000000000000000 --- a/alfa-client/libs/user-assistance/src/lib/help-menu/help-menu.component.scss +++ /dev/null @@ -1,11 +0,0 @@ -@import 'variables'; - -.help-menu { - display: flex; - align-items: center; - color: $grey; -} - -ozgcloud-icon { - margin-right: 4px; -} diff --git a/alfa-client/libs/user-assistance/src/lib/help-menu/help-menu.component.ts b/alfa-client/libs/user-assistance/src/lib/help-menu/help-menu.component.ts index 602c0ee4effc7366a69024c531427d7ff5410ae9..5c7c59840a98bac1696c1a1b3a5b5de3c9b14618 100644 --- a/alfa-client/libs/user-assistance/src/lib/help-menu/help-menu.component.ts +++ b/alfa-client/libs/user-assistance/src/lib/help-menu/help-menu.component.ts @@ -1,12 +1,11 @@ -import { Component, Input } from '@angular/core'; import { ApiRootLinkRel, ApiRootResource } from '@alfa-client/api-root-shared'; import { StateResource } from '@alfa-client/tech-shared'; +import { Component, Input } from '@angular/core'; import { hasLink } from '@ngxp/rest'; @Component({ selector: 'alfa-help-menu', templateUrl: './help-menu.component.html', - styleUrls: ['./help-menu.component.scss'], }) export class HelpMenuComponent { @Input() apiRootStateResource: StateResource<ApiRootResource>; diff --git a/alfa-client/libs/user-profile/src/lib/link-with-user-name-tooltip-container/link-with-user-name-tooltip/link-with-user-name-tooltip.component.spec.ts b/alfa-client/libs/user-profile/src/lib/link-with-user-name-tooltip-container/link-with-user-name-tooltip/link-with-user-name-tooltip.component.spec.ts index e4744d1ed7c6aa2da22ea3e94e2724c5171ec01e..ea9c2c0373d77ab24f56e9f961c794292aa75a83 100644 --- a/alfa-client/libs/user-profile/src/lib/link-with-user-name-tooltip-container/link-with-user-name-tooltip/link-with-user-name-tooltip.component.spec.ts +++ b/alfa-client/libs/user-profile/src/lib/link-with-user-name-tooltip-container/link-with-user-name-tooltip/link-with-user-name-tooltip.component.spec.ts @@ -21,12 +21,13 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { RouterTestingModule } from '@angular/router/testing'; import { createStateResource } from '@alfa-client/tech-shared'; import { UserProfileResource } from '@alfa-client/user-profile-shared'; -import { MatTooltipDirective } from 'libs/ui/src/lib/ui/mattooltip/mattooltip.directive'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { RouterTestingModule } from '@angular/router/testing'; import { createUserProfileResource } from 'libs/user-profile-shared/test/user-profile'; +import { MockModule } from 'ng-mocks'; import { LinkWithUserNameTooltipComponent } from './link-with-user-name-tooltip.component'; describe('LinkWithUserNameTooltipComponent', () => { @@ -39,7 +40,7 @@ describe('LinkWithUserNameTooltipComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ imports: [RouterTestingModule], - declarations: [LinkWithUserNameTooltipComponent, MatTooltipDirective], + declarations: [LinkWithUserNameTooltipComponent, MockModule(MatTooltipModule)], }).compileComponents(); }); diff --git a/alfa-client/libs/user-profile/src/lib/text-with-user-name-tooltip-container/text-with-user-name-tooltip/text-with-user-name-tooltip.component.spec.ts b/alfa-client/libs/user-profile/src/lib/text-with-user-name-tooltip-container/text-with-user-name-tooltip/text-with-user-name-tooltip.component.spec.ts index f3b28df09eddef757fc9fcc23590f6818d1fafce..68ab1321d6fbfed2cf46e2c4ea9fc0e8137575aa 100644 --- a/alfa-client/libs/user-profile/src/lib/text-with-user-name-tooltip-container/text-with-user-name-tooltip/text-with-user-name-tooltip.component.spec.ts +++ b/alfa-client/libs/user-profile/src/lib/text-with-user-name-tooltip-container/text-with-user-name-tooltip/text-with-user-name-tooltip.component.spec.ts @@ -21,11 +21,12 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { ComponentFixture, TestBed } from '@angular/core/testing'; import { createStateResource } from '@alfa-client/tech-shared'; import { UserProfileResource } from '@alfa-client/user-profile-shared'; -import { MatTooltipDirective } from 'libs/ui/src/lib/ui/mattooltip/mattooltip.directive'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MatTooltipModule } from '@angular/material/tooltip'; import { createUserProfileResource } from 'libs/user-profile-shared/test/user-profile'; +import { MockModule } from 'ng-mocks'; import { TextWithUserNameTooltipComponent } from './text-with-user-name-tooltip.component'; describe('TextWithUserNameTooltipComponent', () => { @@ -37,7 +38,7 @@ describe('TextWithUserNameTooltipComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [TextWithUserNameTooltipComponent, MatTooltipDirective], + declarations: [TextWithUserNameTooltipComponent, MockModule(MatTooltipModule)], }).compileComponents(); }); diff --git a/alfa-client/libs/user-profile/src/lib/user-icon/user-icon.component.spec.ts b/alfa-client/libs/user-profile/src/lib/user-icon/user-icon.component.spec.ts index af8f79ad791e7e28eddddcece342916ae14ad4ee..c2c4f8ef4539fc85530fc656c9733dd514894cf4 100644 --- a/alfa-client/libs/user-profile/src/lib/user-icon/user-icon.component.spec.ts +++ b/alfa-client/libs/user-profile/src/lib/user-icon/user-icon.component.spec.ts @@ -21,9 +21,6 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { MatIcon } from '@angular/material/icon'; -import { faker } from '@faker-js/faker'; import { ApiError, EMPTY_STRING, @@ -39,13 +36,13 @@ import { UserProfileResource, userProfileMessage, } from '@alfa-client/user-profile-shared'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MatIcon } from '@angular/material/icon'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { faker } from '@faker-js/faker'; import { getDataTestClassOf } from 'libs/tech-shared/test/data-test'; -import { - MatTooltipDirective, - MatTooltipDisabledDirective, -} from 'libs/ui/src/lib/ui/mattooltip/mattooltip.directive'; import { createUserProfileResource } from 'libs/user-profile-shared/test/user-profile'; -import { MockComponent } from 'ng-mocks'; +import { MockComponent, MockModule } from 'ng-mocks'; import { createApiError, createIssue } from '../../../../tech-shared/test/error'; import { UserIconComponent } from './user-icon.component'; @@ -65,9 +62,8 @@ describe('UserIconComponent', () => { declarations: [ UserIconComponent, MatIcon, - MatTooltipDirective, - MatTooltipDisabledDirective, MockComponent(SpinnerComponent), + MockModule(MatTooltipModule), ], }); }); diff --git a/alfa-client/libs/user-profile/src/lib/user-profile-in-header-container/user-profile-in-header/user-profile-in-header.component.html b/alfa-client/libs/user-profile/src/lib/user-profile-in-header-container/user-profile-in-header/user-profile-in-header.component.html index 3fb9f5af72a952068f8d799e64ab20156999d96f..6a738849018cd80575928861fbe1a876b1c8dde3 100644 --- a/alfa-client/libs/user-profile/src/lib/user-profile-in-header-container/user-profile-in-header/user-profile-in-header.component.html +++ b/alfa-client/libs/user-profile/src/lib/user-profile-in-header-container/user-profile-in-header/user-profile-in-header.component.html @@ -38,6 +38,6 @@ <ozgcloud-menu #accountMenu data-test-id="account-menu"> <button mat-menu-item (click)="logoutEmitter.emit()" data-test-id="logout-button"> <mat-icon>logout</mat-icon> - <span>Abmelden</span> + <span class="text-base">Abmelden</span> </button> </ozgcloud-menu> diff --git a/alfa-client/libs/user-profile/src/lib/user-profile-search-container/user-profile-search/user-profile.search.formservice.ts b/alfa-client/libs/user-profile/src/lib/user-profile-search-container/user-profile-search/user-profile.search.formservice.ts index 4ec86ff50492e343fb1fe25fe2936d8cab1e8e9a..7293348a84e515fc1ae97109279f3ea8e49375d4 100644 --- a/alfa-client/libs/user-profile/src/lib/user-profile-search-container/user-profile-search/user-profile.search.formservice.ts +++ b/alfa-client/libs/user-profile/src/lib/user-profile-search-container/user-profile-search/user-profile.search.formservice.ts @@ -21,12 +21,13 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ +import { AbstractFormService, ProblemDetail, StateResource } from '@alfa-client/tech-shared'; +import { UserProfileListResource, UserProfileService } from '@alfa-client/user-profile-shared'; import { Injectable, OnDestroy } from '@angular/core'; import { UntypedFormBuilder, UntypedFormControl, UntypedFormGroup } from '@angular/forms'; -import { AbstractFormService, StateResource } from '@alfa-client/tech-shared'; -import { UserProfileListResource, UserProfileService } from '@alfa-client/user-profile-shared'; import { isNil } from 'lodash-es'; import { Observable, Subscription } from 'rxjs'; +import { ValidationMessageCode } from '../../../../../tech-shared/src/lib/validation/tech.validation.messages'; @Injectable() export class UserProfileSearchFormService extends AbstractFormService implements OnDestroy { @@ -62,11 +63,11 @@ export class UserProfileSearchFormService extends AbstractFormService implements } public setEmptyUserProfileError(): void { - this.setErrorByApiError(emptyUserProfileError); + this.setErrorByProblemDetail(emptyUserProfileError); } public setNoUserProfileFoundError(): void { - this.setErrorByApiError(noUserProfileFoundError); + this.setErrorByProblemDetail(noUserProfileFoundError); } ngOnDestroy(): void { @@ -74,23 +75,34 @@ export class UserProfileSearchFormService extends AbstractFormService implements } } -const noUserProfileFoundError = { - issues: [ +const noUserProfileFoundError: ProblemDetail = { + type: null, + title: null, + status: null, + detail: null, + instance: null, + invalidParams: [ { - field: 'only.fe.searchBy', - message: 'fe_only_validation_bearbeiter_not_exist', - messageCode: 'fe_only_validation_bearbeiter_not_exist', - parameters: [], + name: 'only.fe.searchBy', + reason: ValidationMessageCode.FIELD_ASSIGN_BEARBEITER_NOT_EXIST, + constraintParameters: [], + value: null, }, ], }; -const emptyUserProfileError = { - issues: [ + +const emptyUserProfileError: ProblemDetail = { + type: null, + title: null, + status: null, + detail: null, + instance: null, + invalidParams: [ { - field: 'only.fe.searchBy', - message: 'validation_field_empty', - messageCode: 'validation_field_empty', - parameters: [], + name: 'only.fe.searchBy', + reason: ValidationMessageCode.FIELD_EMPTY, + constraintParameters: [], + value: null, }, ], }; diff --git a/alfa-client/libs/vorgang-detail/src/lib/aktenzeichen-edit-dialog/aktenzeichen-edit-dialog.component.scss b/alfa-client/libs/vorgang-detail/src/lib/aktenzeichen-edit-dialog/aktenzeichen-edit-dialog.component.scss index b61ea6c2be7bb5ea25305e910e0a68287228372b..7f465ba13ac6b4c51d5570ab26e7b4e77e93a158 100644 --- a/alfa-client/libs/vorgang-detail/src/lib/aktenzeichen-edit-dialog/aktenzeichen-edit-dialog.component.scss +++ b/alfa-client/libs/vorgang-detail/src/lib/aktenzeichen-edit-dialog/aktenzeichen-edit-dialog.component.scss @@ -50,7 +50,7 @@ .hinweis { grid-area: hinweis; padding-left: 16px; - font-size: 14px; + font-size: 0.875rem; } ozgcloud-stroked-button-with-spinner { diff --git a/alfa-client/libs/vorgang-detail/src/lib/aktenzeichen-editable/aktenzeichen-editable.component.html b/alfa-client/libs/vorgang-detail/src/lib/aktenzeichen-editable/aktenzeichen-editable.component.html index 12e280dd9853ea90cd3db02843929d801bf39301..3f94a6da451f1731445c8b37487d6dfc03eb0fe6 100644 --- a/alfa-client/libs/vorgang-detail/src/lib/aktenzeichen-editable/aktenzeichen-editable.component.html +++ b/alfa-client/libs/vorgang-detail/src/lib/aktenzeichen-editable/aktenzeichen-editable.component.html @@ -23,14 +23,14 @@ unter der Lizenz sind dem Lizenztext zu entnehmen. --> -<div class="horizontal"> - <div alfa-aktenzeichen class="ellipsis" [vorgang]="vorgang"></div> - <ng-container *ngIf="vorgang | hasLink: linkRel.SET_AKTENZEICHEN"> +<div class="flex flex-shrink" alfa-aktenzeichen [vorgang]="vorgang"></div> +<ng-container *ngIf="vorgang | hasLink: linkRel.SET_AKTENZEICHEN"> + <div class="relative w-12"> <ozgcloud-icon-button-primary svgIcon="edit" tooltip="Aktenzeichen bearbeiten" data-test-id="aktenzeichen-editieren" (clickEmitter)="onEdit()" ></ozgcloud-icon-button-primary> - </ng-container> -</div> + </div> +</ng-container> diff --git a/alfa-client/libs/vorgang-detail/src/lib/aktenzeichen-editable/aktenzeichen-editable.component.scss b/alfa-client/libs/vorgang-detail/src/lib/aktenzeichen-editable/aktenzeichen-editable.component.scss index be66b5c81cdb771090d274585ae82dfe028a4b6d..dbb9218a330a285d37db355ff911cac5a991f9a9 100644 --- a/alfa-client/libs/vorgang-detail/src/lib/aktenzeichen-editable/aktenzeichen-editable.component.scss +++ b/alfa-client/libs/vorgang-detail/src/lib/aktenzeichen-editable/aktenzeichen-editable.component.scss @@ -22,21 +22,8 @@ * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -.horizontal { - display: flex; - align-items: center; - font-size: 16px; - font-weight: 300; -} - -.ellipsis { - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; -} - // Workaround Material's fixed 48px height :host ::ng-deep ozgcloud-icon-button-primary button { position: absolute; - top: -24px; + top: -14px; } diff --git a/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-action-buttons/vorgang-detail-action-buttons.component.html b/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-action-buttons/vorgang-detail-action-buttons.component.html index 85de971a72be9d72870985a6c9c32c35939b0079..2c9d56aca5b60504793eed40f24a3066de934e84 100644 --- a/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-action-buttons/vorgang-detail-action-buttons.component.html +++ b/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-action-buttons/vorgang-detail-action-buttons.component.html @@ -82,9 +82,3 @@ [showAsIconButton]="showAsIconButton" [vorgang]="vorgangWithEingang" ></alfa-postfach-mail-button-container> - -<alfa-create-bescheid-button-container - *ngIf="vorgangWithEingang | hasLink: vorgangWithEingangLinkRel.CREATE_BESCHEID" - [vorgangWithEingang]="vorgangWithEingang" - data-test-id="create-bescheid-button-container" -></alfa-create-bescheid-button-container> diff --git a/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-action-buttons/vorgang-detail-action-buttons.component.spec.ts b/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-action-buttons/vorgang-detail-action-buttons.component.spec.ts index d62f61ebe56e9a706ee873724f56e4524b8c7d91..3d3eab79b4d777303aa0d2be366e76e21dcb3f23 100644 --- a/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-action-buttons/vorgang-detail-action-buttons.component.spec.ts +++ b/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-action-buttons/vorgang-detail-action-buttons.component.spec.ts @@ -21,13 +21,7 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { CreateBescheidButtonContainerComponent } from '@alfa-client/bescheid'; -import { - EndgueltigLoeschenButtonContainerComponent, - LoeschAnforderungZuruecknehmenButtonContainerComponent, - LoeschenAnfordernButtonContainerComponent, -} from '@alfa-client/loesch-anforderung'; +import { EndgueltigLoeschenButtonContainerComponent, LoeschAnforderungZuruecknehmenButtonContainerComponent, LoeschenAnfordernButtonContainerComponent } from '@alfa-client/loesch-anforderung'; import { PostfachMailButtonContainerComponent } from '@alfa-client/postfach'; import { HasLinkPipe } from '@alfa-client/tech-shared'; import { existsAsHtmlElement, notExistsAsHtmlElement } from '@alfa-client/test-utils'; @@ -35,6 +29,7 @@ import { IconButtonWithSpinnerComponent } from '@alfa-client/ui'; import { AssignUserProfileButtonContainerComponent } from '@alfa-client/user-profile'; import { VorgangWithEingangLinkRel } from '@alfa-client/vorgang-shared'; import { CreateWiedervorlageButtonContainerComponent } from '@alfa-client/wiedervorlage'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; import { getDataTestIdOf } from 'libs/tech-shared/test/data-test'; import { createVorgangWithEingangResource } from 'libs/vorgang-shared/test/vorgang'; import { MockComponent } from 'ng-mocks'; @@ -77,7 +72,6 @@ describe('VorgangDetailActionButtonsComponent', () => { MockComponent(LoeschenAnfordernButtonContainerComponent), MockComponent(EndgueltigLoeschenButtonContainerComponent), MockComponent(LoeschAnforderungZuruecknehmenButtonContainerComponent), - MockComponent(CreateBescheidButtonContainerComponent), ], }); }); diff --git a/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-antragsteller/vorgang-detail-antragsteller.component.html b/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-antragsteller/vorgang-detail-antragsteller.component.html index d0a6bdc22500a72a94d95c18d503d083c005f7a9..11c9b0139c83dc5fbef7f1235df0c3a3559e19cb 100644 --- a/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-antragsteller/vorgang-detail-antragsteller.component.html +++ b/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-antragsteller/vorgang-detail-antragsteller.component.html @@ -23,13 +23,13 @@ unter der Lizenz sind dem Lizenztext zu entnehmen. --> -<h3 class="font-medium leading-6 mb-2">Antragsteller</h3> -<ul - *ngIf="antragstellerExists; else noAntragsteller" - data-test-id="antragsteller-list" - class="text-sm leading-6 my-1" +<h3 class="mb-2 font-medium leading-6">Antragsteller</h3> +<ul + *ngIf="antragstellerExists; else noAntragsteller" + data-test-id="antragsteller-list" + class="my-1 text-sm leading-6" > - <div class="font-medium mb-2"> + <div class="mb-2 font-medium"> <h4 *ngIf="!isFirma && name" data-test-id="antragsteller-name" class="my-1"> {{ name }} </h4> @@ -40,12 +40,9 @@ <li *ngIf="antragstellerStrasseHausnummer || antragstellerPlzOrt" data-test-id="antragsteller-adresse" - class="flex gap-2 my-2" + class="my-2 flex gap-2" > - <mat-icon - class="material-icons-outlined" - aria-label="Adresse" - aria-hidden="false" + <mat-icon class="material-icons-outlined" aria-label="Adresse" aria-hidden="false" >location_on</mat-icon > <div class="flex flex-col"> @@ -58,34 +55,26 @@ </div> </li> <div *ngIf="isFirma && name" class="mb-1"> - <h4 class="my-1 font-medium"> - Ansprechpartner - </h4> + <h4 class="my-1 font-medium">Ansprechpartner</h4> <p class="my-1" data-test-id="antragsteller-ansprechpartner-name">{{ name }}</p> </div> - <li *ngIf="antragstellerData.email" [title]="email" class="flex gap-2 my-2"> - <mat-icon - class="material-icons-outlined" - aria-label="E-Mail-Adresse" - aria-hidden="false" + <li *ngIf="antragstellerData.email" [title]="antragstellerData.email" class="my-2 flex gap-2"> + <mat-icon class="material-icons-outlined" aria-label="E-Mail-Adresse" aria-hidden="false" >email</mat-icon > <p data-test-id="antragsteller-email"> {{ antragstellerData.email }} </p> </li> - <li *ngIf="antragstellerData.telefon" class="flex gap-2 my-2"> - <mat-icon - class="material-icons-outlined" - aria-label="Telefonnummer" - aria-hidden="false" + <li *ngIf="antragstellerData.telefon" class="my-2 flex gap-2"> + <mat-icon class="material-icons-outlined" aria-label="Telefonnummer" aria-hidden="false" >call_black</mat-icon > <p data-test-id="antragsteller-telefon"> {{ antragstellerData.telefon }} </p> </li> - <li *ngIf="geburt" class="flex gap-2 my-2"> + <li *ngIf="geburt" class="my-2 flex gap-2"> <mat-icon class="material-icons-outlined" aria-label="Geburtsdatum und Geburtsort" diff --git a/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-area.component.html b/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-area.component.html index ab08e19db0339b7d1aa5352adca61c0778cb7f95..1566dc396538005f2ebe1e510fcdf04f1eaa13ac 100644 --- a/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-area.component.html +++ b/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-area.component.html @@ -26,62 +26,82 @@ <ozgcloud-spinner [stateResource]="vorgangStateResource" class="header-spinner"></ozgcloud-spinner> <ng-container *ngIf="vorgangStateResource.resource as vorgangResource"> - <div class="container"> - <alfa-vorgang-detail-header - [vorgangWithEingang]="vorgangResource" - class="mat-typography" - data-test-id="vorgang-detail-header" - ></alfa-vorgang-detail-header> - - <div class="section one-column"> - <alfa-vorgang-detail-formular-daten + <div + class="flex min-h-full grow flex-col divide-y divide-gray-200 border-l border-r border-gray-200 dark:divide-background-100 dark:border-transparent lg:flex-row lg:divide-x lg:divide-y-0" + > + <div class="grow"> + <alfa-vorgang-detail-header [vorgangWithEingang]="vorgangResource" - ></alfa-vorgang-detail-formular-daten> + data-test-id="vorgang-detail-header" + ></alfa-vorgang-detail-header> - <div *ngIf="vorgangResource | hasLink: vorgangWithEingangLinkRel.BESCHEIDE"> - <ozgcloud-expansion-panel headline="Bescheid" data-test-id="bescheid-expansion-panel"> - <alfa-bescheid-list-in-vorgang-container data-test-id="bescheid-container-in-vorgang"> - </alfa-bescheid-list-in-vorgang-container> - </ozgcloud-expansion-panel> - </div> + <div class="section one-column"> + <alfa-vorgang-detail-formular-daten + [vorgangWithEingang]="vorgangResource" + ></alfa-vorgang-detail-formular-daten> - <alfa-vorgang-detail-formular-buttons - [vorgangWithEingang]="vorgangResource" - ></alfa-vorgang-detail-formular-buttons> - </div> + <div *ngIf="vorgangResource | hasLink: vorgangWithEingangLinkRel.BESCHEIDE"> + <ozgcloud-expansion-panel headline="Bescheid" data-test-id="bescheid-expansion-panel"> + <alfa-bescheid-list-in-vorgang-container data-test-id="bescheid-container-in-vorgang"> + </alfa-bescheid-list-in-vorgang-container> + </ozgcloud-expansion-panel> + </div> - <div class="two-column"> - <div class="section" *ngIf="vorgangResource | hasLink: vorgangWithEingangLinkRel.FORWARDING"> - <alfa-vorgang-forwarding-container - [vorgang]="vorgangResource" - data-test-id="forwarding-container-in-vorgang" - ></alfa-vorgang-forwarding-container> + <alfa-vorgang-detail-formular-buttons + [vorgangWithEingang]="vorgangResource" + ></alfa-vorgang-detail-formular-buttons> </div> - <div class="section" *ngIf="vorgangResource | hasLink: vorgangHeaderLinkRel.WIEDERVORLAGEN"> - <alfa-wiedervorlage-list-in-vorgang-container - [vorgangStateResource]="vorgangStateResource" - data-test-id="wiedervorlagen-container-in-vorgang" - ></alfa-wiedervorlage-list-in-vorgang-container> + <div + class="section one-column" + *ngIf="vorgangResource | hasLink: vorgangWithEingangLinkRel.CREATE_COLLABORATION_REQUEST" + > + <ozgcloud-expansion-panel + headline="Zusammenarbeit" + data-test-id="collaboration-expansion-panel" + > + <alfa-collaboration-in-vorgang-container + data-test-id="collaboration-in-voragng-container" + ></alfa-collaboration-in-vorgang-container> + </ozgcloud-expansion-panel> </div> - <div class="section" *ngIf="vorgangResource | hasLink: vorgangHeaderLinkRel.KOMMENTARE"> - <alfa-kommentar-list-in-vorgang-container - [vorgangStateResource]="vorgangStateResource" - data-test-id="kommentar-container-in-vorgang" - ></alfa-kommentar-list-in-vorgang-container> + <div class="two-column"> + <div + class="section" + *ngIf="vorgangResource | hasLink: vorgangWithEingangLinkRel.FORWARDING" + > + <alfa-vorgang-forwarding-container + [vorgang]="vorgangResource" + data-test-id="forwarding-container-in-vorgang" + ></alfa-vorgang-forwarding-container> + </div> + + <div class="section" *ngIf="vorgangResource | hasLink: vorgangHeaderLinkRel.WIEDERVORLAGEN"> + <alfa-wiedervorlage-list-in-vorgang-container + [vorgangStateResource]="vorgangStateResource" + data-test-id="wiedervorlagen-container-in-vorgang" + ></alfa-wiedervorlage-list-in-vorgang-container> + </div> + + <div class="section" *ngIf="vorgangResource | hasLink: vorgangHeaderLinkRel.KOMMENTARE"> + <alfa-kommentar-list-in-vorgang-container + [vorgangStateResource]="vorgangStateResource" + data-test-id="kommentar-container-in-vorgang" + ></alfa-kommentar-list-in-vorgang-container> + </div> </div> </div> - </div> - <div class="right"> - <alfa-vorgang-detail-antragsteller - [antragsteller]="vorgangResource.eingang.antragsteller" - data-test-id="vorgang-detail-antragsteller" - ></alfa-vorgang-detail-antragsteller> - <alfa-postfach-mail-list-container - *ngIf="vorgangResource | hasLink: vorgangHeaderLinkRel.POSTFACH_MAILS" - [vorgangStateResource]="vorgangStateResource" - data-test-id="postfach-nachrichten-container-in-vorgang" - ></alfa-postfach-mail-list-container> + <div class="flex h-full min-w-80 flex-1 flex-col px-4 py-3"> + <alfa-vorgang-detail-antragsteller + [antragsteller]="vorgangResource.eingang.antragsteller" + data-test-id="vorgang-detail-antragsteller" + ></alfa-vorgang-detail-antragsteller> + <alfa-postfach-mail-list-container + *ngIf="vorgangResource | hasLink: vorgangHeaderLinkRel.POSTFACH_MAILS" + [vorgangStateResource]="vorgangStateResource" + data-test-id="postfach-nachrichten-container-in-vorgang" + ></alfa-postfach-mail-list-container> + </div> </div> </ng-container> diff --git a/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-area.component.scss b/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-area.component.scss index b27537ba49e70f26ea5350c7ca2b70cd2163f2f6..c3a493baffe343fe0fddd809db8c86361a29229c 100644 --- a/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-area.component.scss +++ b/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-area.component.scss @@ -25,27 +25,6 @@ @import 'include-media/dist/include-media'; @import 'variables'; -:host { - position: relative; - display: flex; - background-color: inherit; - width: 100%; -} - -.container { - width: calc(100% - 300px); - border-left: 1px solid rgba(0, 0, 0, 0.08); - border-right: 1px solid rgba(0, 0, 0, 0.08); - min-height: calc(100vh - $header-height - $navigation-height); -} - -.right { - right: 21px; - width: 300px; - padding: 12px 16px; - display: flex; - flex-direction: column; -} .header-spinner { position: absolute; diff --git a/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-area.component.spec.ts b/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-area.component.spec.ts index 34870cb494be2e9160e6101076214483ad95ff0b..239dd66734f6d2a249fcad3d17b6f41c388c412a 100644 --- a/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-area.component.spec.ts +++ b/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-area.component.spec.ts @@ -21,6 +21,7 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ +import { CollaborationInVorgangContainerComponent } from '@alfa-client/collaboration'; import { VorgangForwardingContainerComponent } from '@alfa-client/forwarding'; import { KommentarListInVorgangContainerComponent } from '@alfa-client/kommentar'; import { PostfachMailListContainerComponent } from '@alfa-client/postfach'; @@ -30,6 +31,7 @@ import { createEmptyStateResource, createStateResource, } from '@alfa-client/tech-shared'; +import { existsAsHtmlElement, notExistsAsHtmlElement } from '@alfa-client/test-utils'; import { ExpansionPanelComponent, OzgcloudStrokedButtonWithSpinnerComponent, @@ -57,6 +59,7 @@ describe('VorgangDetailAreaComponent', () => { let component: VorgangDetailAreaComponent; let fixture: ComponentFixture<VorgangDetailAreaComponent>; + const collaborationContainer: string = getDataTestIdOf('collaboration-in-voragng-container'); const wiedervorlagenContainer: string = getDataTestIdOf('wiedervorlagen-container-in-vorgang'); const kommentarContainer: string = getDataTestIdOf('kommentar-container-in-vorgang'); const postfachNachrichtenContainer: string = getDataTestIdOf( @@ -86,6 +89,7 @@ describe('VorgangDetailAreaComponent', () => { MockComponent(VorgangForwardingContainerComponent), MockComponent(BescheidListInVorgangContainerComponent), MockComponent(ExpansionPanelComponent), + MockComponent(CollaborationInVorgangContainerComponent), ], }).compileComponents(); }); @@ -101,6 +105,26 @@ describe('VorgangDetailAreaComponent', () => { expect(component).toBeTruthy(); }); + describe('Collaboration', () => { + it('should be visibile if link is present', () => { + component.vorgangStateResource = createStateResource( + createVorgangWithEingangResource([VorgangWithEingangLinkRel.CREATE_COLLABORATION_REQUEST]), + ); + + fixture.detectChanges(); + + existsAsHtmlElement(fixture, collaborationContainer); + }); + + it('should be hidden if link is missing', () => { + component.vorgangStateResource = createStateResource(vorgang); + + fixture.detectChanges(); + + notExistsAsHtmlElement(fixture, collaborationContainer); + }); + }); + describe('wiedervorlagen', () => { it('should be visible', () => { component.vorgangStateResource = createStateResource( diff --git a/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-area.component.ts b/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-area.component.ts index 09cee300973dd96c97e72416ae51f1c5659b914f..84620549535e2efd0a84a992fd9f239f18093fa5 100644 --- a/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-area.component.ts +++ b/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-area.component.ts @@ -33,6 +33,7 @@ import { Component, Input } from '@angular/core'; selector: 'alfa-vorgang-detail-area', templateUrl: './vorgang-detail-area.component.html', styleUrls: ['./vorgang-detail-area.component.scss'], + styles: [':host {@apply relative flex flex-row grow}'], }) export class VorgangDetailAreaComponent { @Input() vorgangStateResource: StateResource<VorgangWithEingangResource>; diff --git a/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-formular-daten/vorgang-detail-antrag-data/vorgang-detail-eingang-header/vorgang-detail-eingang-header.component.html b/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-formular-daten/vorgang-detail-antrag-data/vorgang-detail-eingang-header/vorgang-detail-eingang-header.component.html index f380c834f477c400ea3f433c010d5042d310dfe8..f98881642b25040932ffba2562c09236cac0d56b 100644 --- a/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-formular-daten/vorgang-detail-antrag-data/vorgang-detail-eingang-header/vorgang-detail-eingang-header.component.html +++ b/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-formular-daten/vorgang-detail-antrag-data/vorgang-detail-eingang-header/vorgang-detail-eingang-header.component.html @@ -23,7 +23,7 @@ unter der Lizenz sind dem Lizenztext zu entnehmen. --> -<table aria-label="Eingangsdaten"> +<table aria-label="Eingangsdaten" class="text-sm"> <tr> <th scope="col">Formularfeld</th> <th scope="col">Formulareingabe</th> diff --git a/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-formular-daten/vorgang-detail-form-data-table/vorgang-detail-form-data-table.component.html b/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-formular-daten/vorgang-detail-form-data-table/vorgang-detail-form-data-table.component.html index 0fe8366d794c27918ea8b9379d8f62177093f1fd..d1278b5dd728a544987d02dad56a18edb886505a 100644 --- a/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-formular-daten/vorgang-detail-form-data-table/vorgang-detail-form-data-table.component.html +++ b/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-formular-daten/vorgang-detail-form-data-table/vorgang-detail-form-data-table.component.html @@ -23,7 +23,7 @@ unter der Lizenz sind dem Lizenztext zu entnehmen. --> -<table aria-label="Formulardaten" *ngIf="isObject(formData) && !isArray(formData)"> +<table class="text-sm" aria-label="Formulardaten" *ngIf="isObject(formData) && !isArray(formData)"> <tr> <th scope="col">Formularfeld</th> <th scope="col">Formulareingabe</th> @@ -43,7 +43,7 @@ </tr> </table> -<table aria-label="Formulardaten" *ngIf="isArray(formData)"> +<table class="text-sm" aria-label="Formulardaten" *ngIf="isArray(formData)"> <tr> <th scope="col">Formulareingabe</th> </tr> diff --git a/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-formular-daten/vorgang-detail-formular-daten.component.scss b/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-formular-daten/vorgang-detail-formular-daten.component.scss index 0398e355577626ab5eb2c15f4a48ef0779fcc557..20502e39cc84dcfed0c934c467c3cb7cd7dec769 100644 --- a/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-formular-daten/vorgang-detail-formular-daten.component.scss +++ b/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-formular-daten/vorgang-detail-formular-daten.component.scss @@ -24,3 +24,7 @@ mat-tab-group { padding-right: 1rem; } + +::ng-deep .mat-tab-header { + overflow-x: scroll !important; +} diff --git a/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-header/vorgang-detail-header.component.html b/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-header/vorgang-detail-header.component.html index 976a5a346670cbc0d0b44b77143781bd2b5a2472..53bde5e294bc362da2e488e9be63a802e33b57ab 100644 --- a/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-header/vorgang-detail-header.component.html +++ b/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-header/vorgang-detail-header.component.html @@ -23,39 +23,50 @@ unter der Lizenz sind dem Lizenztext zu entnehmen. --> -<alfa-vorgang-status-dot [status]="vorgang.status" class="status-dot"></alfa-vorgang-status-dot> -<div class="flex flex-row items-center gap-7"> - <alfa-vorgang-status-text - [status]="vorgang.status" - data-test-id="status-text" - class="status-text" - ></alfa-vorgang-status-text> - <alfa-beschieden-date-in-vorgang-container></alfa-beschieden-date-in-vorgang-container> +<div class="flex flex-row"> + <div class="flex w-12 flex-shrink-0 flex-grow-0 items-center"> + <alfa-vorgang-status-dot [status]="vorgang.status" class="status-dot"></alfa-vorgang-status-dot> + </div> + <div class="flex flex-shrink flex-grow flex-wrap gap-x-3 gap-y-1"> + <div class="flex flex-grow gap-7"> + <alfa-vorgang-status-text + [status]="vorgang.status" + data-test-id="status-text" + class="status-text" + ></alfa-vorgang-status-text> + <alfa-beschieden-date-in-vorgang-container></alfa-beschieden-date-in-vorgang-container> + </div> + <div class="initial-date text-sm" data-test-id="created-at"> + {{ vorgang.createdAt | date: 'EEEE, d. LLLL y, H:mm' }} + </div> + </div> </div> -<div class="initial-date" data-test-id="created-at"> - {{ vorgang.createdAt | date: 'EEEE, d. LLLL y, H:mm' }} +<div class="ml-12 flex flex-row gap-6"> + <div class="flex flex-grow flex-col gap-x-3 gap-y-1"> + <h2 data-test-id="name" class="mb-1.5 mt-4 break-all text-base font-medium"> + {{ vorgang.name }} + </h2> + <alfa-vorgang-nummer class="vorgang-nummer" [vorgang]="vorgang"></alfa-vorgang-nummer> + <div class="flex flex-1 flex-row gap-1"> + <div class="flex flex-shrink-0" [class.text-gray-400]="!hasAktenzeichen"> + <mat-icon svgIcon="az" style="width: 1.5rem; height: 1.5rem"></mat-icon> + </div> + <alfa-aktenzeichen-editable + data-test-id="alfa-aktenzeichen-editable-button" + class="flex flex-grow" + [class.text-gray-400]="!hasAktenzeichen" + [vorgang]="vorgang" + ></alfa-aktenzeichen-editable> + </div> + </div> + <div class="mt-4 w-10 flex-shrink-0 flex-grow-0"> + <alfa-user-profile-in-vorgang-container + *ngIf="vorgang | hasLink: linkRel.ASSIGN" + data-test-id="vorgang-header-user-icon" + [vorgang]="vorgang" + class="user" + ></alfa-user-profile-in-vorgang-container> + </div> </div> - -<alfa-vorgang-nummer class="vorgang-nummer big" [vorgang]="vorgang"></alfa-vorgang-nummer> - -<div class="aktenzeichen" [class.aktenzeichen--active]="hasAktenzeichen"> - <mat-icon svgIcon="az"></mat-icon> - <alfa-aktenzeichen-editable - class="aktenzeichen-editable" - [vorgang]="vorgang" - ></alfa-aktenzeichen-editable> -</div> - -<div data-test-id="name" class="name"> - {{ vorgang.name }} -</div> - -<alfa-user-profile-in-vorgang-container - *ngIf="vorgang | hasLink: linkRel.ASSIGN" - data-test-id="vorgang-header-user-icon" - [vorgang]="vorgang" - class="user" -> -</alfa-user-profile-in-vorgang-container> diff --git a/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-header/vorgang-detail-header.component.scss b/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-header/vorgang-detail-header.component.scss index 62adcc21dd7dcfdda38c96f774b16d8af49b5c39..90cd66da90d80e78e540c55a81c54c45b18ba321 100644 --- a/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-header/vorgang-detail-header.component.scss +++ b/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-header/vorgang-detail-header.component.scss @@ -21,92 +21,6 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -@import 'breakpoints'; -@import 'include-media/dist/include-media'; -@import 'variables'; - :host { - display: grid; - row-gap: 10px; - align-items: center; - grid-template-columns: 48px 1fr 8%; - grid-template-areas: - 'status-dot status-text initial-date' - '. name user' - '. vorgang-nummer user' - '. aktenzeichen aktenzeichen'; - - padding: 1rem 1.5rem; -} - -.name { - line-height: 1.4; - margin: 8px 0 4px 0; - font-size: 16px; - font-weight: 500; -} - -.ellipsis { - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; -} - -mat-icon { - margin: 0 0.5rem 0 -0rem; - height: 1.875rem; - min-height: 1.875rem; - width: 1.875rem; - min-width: 1.875rem; -} - -.status-dot { - grid-area: status-dot; -} - -.status-text { - grid-area: status-text; -} - -.initial-date { - grid-area: initial-date; - justify-self: end; - white-space: nowrap; -} - -.aktenzeichen { - grid-area: aktenzeichen; - margin-left: -2px; - display: flex; - align-items: center; - color: #c2c2c2; - - &--active { - color: unset; - } - - .aktenzeichen-editable { - width: calc(100% - 82px); - } -} - -.vorgang-nummer { - grid-area: vorgang-nummer; - margin-left: -2px; -} - -.name { - grid-area: name; - align-self: start; -} - -.user { - grid-area: user; - align-self: start; - justify-self: end; -} - -.status { - display: flex; - flex-direction: column; -} + @apply flex flex-col px-6 py-4; +} \ No newline at end of file diff --git a/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-header/vorgang-detail-header.component.spec.ts b/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-header/vorgang-detail-header.component.spec.ts index 85e1fd655bb0390a80cd39b79e3acae785ca68d6..292996ceb1f44de52245d522725c0243cc9c9b55 100644 --- a/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-header/vorgang-detail-header.component.spec.ts +++ b/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-header/vorgang-detail-header.component.spec.ts @@ -23,13 +23,9 @@ */ import { BeschiedenDateInVorgangContainerComponent } from '@alfa-client/bescheid'; import { EMPTY_STRING, EnumToLabelPipe, HasLinkPipe } from '@alfa-client/tech-shared'; -import { getDebugElementFromFixtureByCss } from '@alfa-client/test-utils'; +import { getDebugElementFromFixtureByCss, getElementFromFixture } from '@alfa-client/test-utils'; import { VorgangHeaderLinkRel } from '@alfa-client/vorgang-shared'; -import { - VorgangNummerComponent, - VorgangStatusDotComponent, - VorgangStatusTextComponent, -} from '@alfa-client/vorgang-shared-ui'; +import { VorgangNummerComponent, VorgangStatusDotComponent, VorgangStatusTextComponent } from '@alfa-client/vorgang-shared-ui'; import { DebugElement } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { MatIcon } from '@angular/material/icon'; @@ -46,6 +42,9 @@ describe('VorgangDetailHeaderComponent', () => { let fixture: ComponentFixture<VorgangDetailHeaderComponent>; const user: string = getDataTestIdOf('vorgang-header-user-icon'); + const aktenzeichenEditableComponentDataTestId: string = getDataTestIdOf( + 'alfa-aktenzeichen-editable-button', + ); beforeEach(async () => { await TestBed.configureTestingModule({ @@ -111,16 +110,16 @@ describe('VorgangDetailHeaderComponent', () => { expect(component.hasAktenzeichen).toBeFalsy(); }); - it('should set class aktenzeichen--active for aktenzeichen', () => { + it('should NOT set class text-gray-400 for aktenzeichen', () => { fixture.detectChanges(); - const element = getDebugElementFromFixtureByCss(fixture, 'div.aktenzeichen--active'); + const element = getDebugElementFromFixtureByCss(fixture, 'div.text-gray-400'); - expect(element).toBeInstanceOf(DebugElement); + expect(element).not.toBeInstanceOf(DebugElement); }); it.each([null, EMPTY_STRING])( - 'should NOT set class aktenzeichen--active for aktenzeichen %s', + 'should set class text-gray-400 for aktenzeichen %s', (aktenzeichen: string) => { const vorgangWithEingang = createVorgangWithEingangResource(); vorgangWithEingang.aktenzeichen = aktenzeichen; @@ -128,10 +127,33 @@ describe('VorgangDetailHeaderComponent', () => { fixture.detectChanges(); - const element = getDebugElementFromFixtureByCss(fixture, 'div.aktenzeichen--active'); + const element = getDebugElementFromFixtureByCss(fixture, 'div.text-gray-400'); - expect(element).not.toBeInstanceOf(DebugElement); + expect(element).toBeInstanceOf(DebugElement); }, ); + + it.each([null, EMPTY_STRING])( + 'should set class text-gray-400 for alfa-aktenzeichen-editable %s', + (aktenzeichen: string) => { + const vorgangWithEingang = createVorgangWithEingangResource(); + vorgangWithEingang.aktenzeichen = aktenzeichen; + component.vorgangWithEingang = vorgangWithEingang; + + fixture.detectChanges(); + + const element = getElementFromFixture(fixture, aktenzeichenEditableComponentDataTestId); + + expect(element).toHaveClass('text-gray-400'); + }, + ); + + it('should NOT set class text-gray-400 for alfa-aktenzeichen-editable', () => { + fixture.detectChanges(); + + const element = getElementFromFixture(fixture, aktenzeichenEditableComponentDataTestId); + + expect(element).not.toHaveClass('text-gray-400'); + }); }); }); diff --git a/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-header/vorgang-detail-header.component.ts b/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-header/vorgang-detail-header.component.ts index e0a5d6854b61a58401ea6c9830a9b76ea8a95cbf..4748530c41f1b37ca65281ccaaba44b08c47eebe 100644 --- a/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-header/vorgang-detail-header.component.ts +++ b/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-area/vorgang-detail-header/vorgang-detail-header.component.ts @@ -21,15 +21,15 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { Component, Input } from '@angular/core'; -import { VorgangWithEingangLinkRel, VorgangWithEingangResource } from '@alfa-client/vorgang-shared'; import { isNotEmpty } from '@alfa-client/tech-shared'; +import { VorgangWithEingangLinkRel, VorgangWithEingangResource } from '@alfa-client/vorgang-shared'; +import { Component, Input } from '@angular/core'; import { createVorgangWithEingangResource } from '../../../../../../vorgang-shared/test/vorgang'; @Component({ selector: 'alfa-vorgang-detail-header', templateUrl: './vorgang-detail-header.component.html', - styleUrls: ['./vorgang-detail-header.component.scss'], + styles: [':host {@apply flex flex-col px-6 py-4}'], }) export class VorgangDetailHeaderComponent { readonly linkRel = VorgangWithEingangLinkRel; diff --git a/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-bescheiden/vorgang-detail-bescheiden-result/vorgang-detail-bescheiden-result-attachments/vorgang-detail-bescheiden-result-attachments.component.html b/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-bescheiden/vorgang-detail-bescheiden-result/vorgang-detail-bescheiden-result-attachments/vorgang-detail-bescheiden-result-attachments.component.html index ff4e542e0d9181e9ee7bc98b468e62b2a1d23482..78e9fba0ee241ee0b39e7a9e4d771e48cb1b84fd 100644 --- a/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-bescheiden/vorgang-detail-bescheiden-result/vorgang-detail-bescheiden-result-attachments/vorgang-detail-bescheiden-result-attachments.component.html +++ b/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-bescheiden/vorgang-detail-bescheiden-result/vorgang-detail-bescheiden-result-attachments/vorgang-detail-bescheiden-result-attachments.component.html @@ -22,7 +22,7 @@ *ngIf="uploadFileInProgress.loading || uploadFileInProgress.error" [loadingCaption]="uploadFileInProgress.fileName" errorCaption="Fehler beim Hochladen" - [errorMessages]="uploadFileInProgress.error | convertApiErrorToErrorMessages" + [errorMessages]="uploadFileInProgress.error | convertProblemDetailToErrorMessages" description="Anhang wird hochgeladen" [isLoading]="uploadFileInProgress.loading" ></ods-attachment> diff --git a/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-bescheiden/vorgang-detail-bescheiden-result/vorgang-detail-bescheiden-result-attachments/vorgang-detail-bescheiden-result-attachments.component.spec.ts b/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-bescheiden/vorgang-detail-bescheiden-result/vorgang-detail-bescheiden-result-attachments/vorgang-detail-bescheiden-result-attachments.component.spec.ts index 63758c537759f45fbe750b6c286b6b2e5778864d..caed9958a4f9dc70ae170976ae44c01c77e8e235 100644 --- a/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-bescheiden/vorgang-detail-bescheiden-result/vorgang-detail-bescheiden-result-attachments/vorgang-detail-bescheiden-result-attachments.component.spec.ts +++ b/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-bescheiden/vorgang-detail-bescheiden-result/vorgang-detail-bescheiden-result-attachments/vorgang-detail-bescheiden-result-attachments.component.spec.ts @@ -2,7 +2,6 @@ import { BescheidService } from '@alfa-client/bescheid-shared'; import { BinaryFile2ContainerComponent } from '@alfa-client/binary-file'; import { BinaryFileResource } from '@alfa-client/binary-file-shared'; import { - ConvertApiErrorToErrorMessagesPipe, convertForDataTest, ConvertForDataTestPipe, createErrorStateResource, @@ -15,6 +14,7 @@ import { OzgcloudSvgIconComponent, SpinnerComponent } from '@alfa-client/ui'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { MatIcon } from '@angular/material/icon'; import { AttachmentComponent, AttachmentWrapperComponent, SpinnerIconComponent } from '@ods/system'; +import { ConvertProblemDetailToErrorMessagesPipe } from 'libs/tech-shared/src/lib/pipe/convert-problem-detail-to-error-messages.pipe'; import { MockComponent, MockPipe } from 'ng-mocks'; import { BehaviorSubject, EMPTY, Observable, of, Subscription } from 'rxjs'; import { createUploadFileInProgress } from '../../../../../../../bescheid-shared/src/test/bescheid'; @@ -51,7 +51,7 @@ describe('VorgangDetailBescheidenResultAttachmentsComponent', () => { ConvertForDataTestPipe, MatIcon, MockPipe(FileSizePipe), - MockPipe(ConvertApiErrorToErrorMessagesPipe), + MockPipe(ConvertProblemDetailToErrorMessagesPipe), MockComponent(OzgcloudSvgIconComponent), MockComponent(SpinnerComponent), MockComponent(AttachmentWrapperComponent), diff --git a/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-bescheiden/vorgang-detail-bescheiden-result/vorgang-detail-bescheiden-result-dokument/vorgang-detail-bescheiden-result-dokument.component.html b/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-bescheiden/vorgang-detail-bescheiden-result/vorgang-detail-bescheiden-result-dokument/vorgang-detail-bescheiden-result-dokument.component.html index bb762af88d7b449d222804be6eccd2be60690f5e..d94edab530d6fb8fcdce754bee1d5083f0e7e6b7 100644 --- a/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-bescheiden/vorgang-detail-bescheiden-result/vorgang-detail-bescheiden-result-dokument/vorgang-detail-bescheiden-result-dokument.component.html +++ b/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-bescheiden/vorgang-detail-bescheiden-result/vorgang-detail-bescheiden-result-dokument/vorgang-detail-bescheiden-result-dokument.component.html @@ -27,7 +27,7 @@ 'upload-bescheid-document-error-' + !!uploadBescheidDocumentInProgress.error " [isLoading]="uploadBescheidDocumentInProgress.loading" - [errorMessages]="uploadBescheidDocumentInProgress.error | convertApiErrorToErrorMessages" + [errorMessages]="uploadBescheidDocumentInProgress.error | convertProblemDetailToErrorMessages" description="Bescheiddokument wird hochgeladen" ></ods-attachment> <ods-attachment diff --git a/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-bescheiden/vorgang-detail-bescheiden-result/vorgang-detail-bescheiden-result-dokument/vorgang-detail-bescheiden-result-dokument.component.spec.ts b/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-bescheiden/vorgang-detail-bescheiden-result/vorgang-detail-bescheiden-result-dokument/vorgang-detail-bescheiden-result-dokument.component.spec.ts index c61b90e368bb525ef644dfd2e95d156c06338d22..7dceb5ce5ba5e75c88ddb80fdd529a89b3e5ed41 100644 --- a/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-bescheiden/vorgang-detail-bescheiden-result/vorgang-detail-bescheiden-result-dokument/vorgang-detail-bescheiden-result-dokument.component.spec.ts +++ b/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-bescheiden/vorgang-detail-bescheiden-result/vorgang-detail-bescheiden-result-dokument/vorgang-detail-bescheiden-result-dokument.component.spec.ts @@ -2,7 +2,6 @@ import { BescheidLinkRel, BescheidResource, BescheidService } from '@alfa-client import { BinaryFile2ContainerComponent } from '@alfa-client/binary-file'; import { CommandResource } from '@alfa-client/command-shared'; import { - ConvertApiErrorToErrorMessagesPipe, StateResource, createEmptyStateResource, createStateResource, @@ -13,6 +12,7 @@ import { getUrl } from '@ngxp/rest'; import { AttachmentComponent, AttachmentWrapperComponent } from '@ods/system'; import { createBescheidResource } from 'libs/bescheid-shared/src/test/bescheid'; import { createBinaryFileResource } from 'libs/binary-file-shared/test/binary-file'; +import { ConvertProblemDetailToErrorMessagesPipe } from 'libs/tech-shared/src/lib/pipe/convert-problem-detail-to-error-messages.pipe'; import { getDataTestIdOf } from 'libs/tech-shared/test/data-test'; import { createApiError } from 'libs/tech-shared/test/error'; import { MockComponent, MockPipe } from 'ng-mocks'; @@ -48,7 +48,7 @@ describe('VorgangDetailBescheidenResultDokumentComponent', () => { MockComponent(BinaryFile2ContainerComponent), MockComponent(AttachmentComponent), MockComponent(AttachmentWrapperComponent), - MockPipe(ConvertApiErrorToErrorMessagesPipe), + MockPipe(ConvertProblemDetailToErrorMessagesPipe), ], providers: [ { diff --git a/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-bescheiden/vorgang-detail-bescheiden-steps/vorgang-detail-bescheiden-steps-content/vorgang-detail-bescheiden-antrag-bescheiden/vorgang-detail-bescheiden-antrag-bescheiden.component.html b/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-bescheiden/vorgang-detail-bescheiden-steps/vorgang-detail-bescheiden-steps-content/vorgang-detail-bescheiden-antrag-bescheiden/vorgang-detail-bescheiden-antrag-bescheiden.component.html index ad1704f9c19a82c2a3fe30cb866eb98730f4ff69..22c3e14619ff5c4636838a058b8e614684b64a83 100644 --- a/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-bescheiden/vorgang-detail-bescheiden-steps/vorgang-detail-bescheiden-steps-content/vorgang-detail-bescheiden-antrag-bescheiden/vorgang-detail-bescheiden-antrag-bescheiden.component.html +++ b/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-bescheiden/vorgang-detail-bescheiden-steps/vorgang-detail-bescheiden-steps-content/vorgang-detail-bescheiden-antrag-bescheiden/vorgang-detail-bescheiden-antrag-bescheiden.component.html @@ -14,7 +14,7 @@ value="false" data-test-id="button-abgelehnt" variant="bescheid_abgelehnt" - ><ods-close-icon size="medium" class="fill-abgelehnt"></ods-close-icon> + ><ods-close-icon size="large" class="fill-abgelehnt"></ods-close-icon> </ods-radio-button-card> </div> <div class="flex w-full"> diff --git a/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-page.component.html b/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-page.component.html index b985566e1869ee4b1de3b2588dd7188845a72768..db812801dad788276e27e6c98981813d383bfc0d 100644 --- a/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-page.component.html +++ b/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail-page/vorgang-detail-page.component.html @@ -23,7 +23,8 @@ unter der Lizenz sind dem Lizenztext zu entnehmen. --> -<ng-container *ngIf="vorgangStateResource$ | async as vorgangStateResource"> +<main *ngIf="vorgangStateResource$ | async as vorgangStateResource"> + <h1 class="sr-only">Details zum Vorgang</h1> <ozgcloud-subnavigation class="mat-typography mat-app-background" data-test-id="subnavigation"> <alfa-vorgang-detail-back-button-container></alfa-vorgang-detail-back-button-container> <ng-container *ngIf="vorgangStateResource.resource"> @@ -38,14 +39,16 @@ </ng-container> </ozgcloud-subnavigation> - <div class="l-scroll-area--full"> + <div + class="l-scroll-area--full flex border-l border-r border-grayborder/30 dark:border-transparent" + > <alfa-vorgang-detail-area *ngIf="vorgangStateResource" [vorgangStateResource]="vorgangStateResource" data-test-id="detail-area" ></alfa-vorgang-detail-area> </div> -</ng-container> +</main> <ozgcloud-progress-bar [stateResource]="revokeCommandStateResource$ | async" diff --git a/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail.module.ts b/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail.module.ts index 5a6d84231e9b41aa3bc51655315edd6ff5e4e531..4ab436286109e6af58e8fa0d851edf0d074ee1f7 100644 --- a/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail.module.ts +++ b/alfa-client/libs/vorgang-detail/src/lib/vorgang-detail.module.ts @@ -23,6 +23,7 @@ */ import { BescheidModule } from '@alfa-client/bescheid'; import { BinaryFileModule } from '@alfa-client/binary-file'; +import { CollaborationModule } from '@alfa-client/collaboration'; import { ForwardingModule } from '@alfa-client/forwarding'; import { HistorieModule } from '@alfa-client/historie'; import { KommentarModule } from '@alfa-client/kommentar'; @@ -161,6 +162,7 @@ const routes: Routes = [ TextareaEditorComponent, BescheidStatusTextComponent, ErrorMessageComponent, + CollaborationModule, ], declarations: [ VorgangDetailPageComponent, diff --git a/alfa-client/libs/vorgang-shared-ui/src/index.ts b/alfa-client/libs/vorgang-shared-ui/src/index.ts index 8184426d005669520ee4aecfb7c9089f7500fbda..af3be465ac6cfdacf0f35612a2ed3b20d453b6ab 100644 --- a/alfa-client/libs/vorgang-shared-ui/src/index.ts +++ b/alfa-client/libs/vorgang-shared-ui/src/index.ts @@ -22,7 +22,6 @@ * unter der Lizenz sind dem Lizenztext zu entnehmen. */ export * from './lib/aktenzeichen/aktenzeichen.component'; -export * from './lib/vorgang-in-postfach-breadcrumb-container/vorgang-in-postfach-breadcrumb-container.component'; export * from './lib/vorgang-nummer/vorgang-nummer.component'; export * from './lib/vorgang-search-container/vorgang-search-container.component'; export * from './lib/vorgang-shared-ui.module'; diff --git a/alfa-client/libs/vorgang-shared-ui/src/lib/aktenzeichen/aktenzeichen.component.html b/alfa-client/libs/vorgang-shared-ui/src/lib/aktenzeichen/aktenzeichen.component.html index 5721feaf55413b70b412e07ccafb3856599e39a4..de19cd3d1c9c817c155c02dbce1f0a4caef91339 100644 --- a/alfa-client/libs/vorgang-shared-ui/src/lib/aktenzeichen/aktenzeichen.component.html +++ b/alfa-client/libs/vorgang-shared-ui/src/lib/aktenzeichen/aktenzeichen.component.html @@ -23,6 +23,10 @@ unter der Lizenz sind dem Lizenztext zu entnehmen. --> -<span [matTooltip]="aktenzeichen" data-test-id="aktenzeichen" class="truncate">{{ - aktenzeichen -}}</span> +<div + class="line-clamp-1 flex-shrink overflow-hidden break-all text-base font-normal lg:line-clamp-none lg:flex" + data-test-id="aktenzeichen" + [matTooltip]="aktenzeichen" +> + {{ aktenzeichen }} +</div> diff --git a/alfa-client/libs/vorgang-shared-ui/src/lib/aktenzeichen/aktenzeichen.component.spec.ts b/alfa-client/libs/vorgang-shared-ui/src/lib/aktenzeichen/aktenzeichen.component.spec.ts index 32bad0caeef5fb45a8b21978294d17a529f4d1fb..05840895fe5579580d83309bb74166ec39bb0cde 100644 --- a/alfa-client/libs/vorgang-shared-ui/src/lib/aktenzeichen/aktenzeichen.component.spec.ts +++ b/alfa-client/libs/vorgang-shared-ui/src/lib/aktenzeichen/aktenzeichen.component.spec.ts @@ -21,10 +21,11 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { ComponentFixture, TestBed } from '@angular/core/testing'; import { VorgangResource } from '@alfa-client/vorgang-shared'; -import { MatTooltipDirective } from 'libs/ui/src/lib/ui/mattooltip/mattooltip.directive'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MatTooltipModule } from '@angular/material/tooltip'; import { createVorgangResource } from 'libs/vorgang-shared/test/vorgang'; +import { MockModule } from 'ng-mocks'; import { VORGANG_KEIN_AKTENZEICHEN_ZUGEWIESEN } from '../vorgang-util'; import { AktenzeichenComponent } from './aktenzeichen.component'; @@ -38,7 +39,7 @@ describe('AktenzeichenComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [AktenzeichenComponent, MatTooltipDirective], + declarations: [AktenzeichenComponent, MockModule(MatTooltipModule)], }).compileComponents(); }); @@ -60,7 +61,7 @@ describe('AktenzeichenComponent', () => { fixture.detectChanges(); const aktenzeichenMessage: HTMLElement = fixture.nativeElement.querySelector(aktenzeichen); - expect(aktenzeichenMessage.innerHTML).toBe(VORGANG_KEIN_AKTENZEICHEN_ZUGEWIESEN); + expect(aktenzeichenMessage.innerHTML.trim()).toBe(VORGANG_KEIN_AKTENZEICHEN_ZUGEWIESEN); }); it('should show aktenzeichen', () => { @@ -69,7 +70,7 @@ describe('AktenzeichenComponent', () => { fixture.detectChanges(); const aktenzeichenMessage: HTMLElement = fixture.nativeElement.querySelector(aktenzeichen); - expect(aktenzeichenMessage.textContent).toBe(vorgang.aktenzeichen); + expect(aktenzeichenMessage.textContent.trim()).toBe(vorgang.aktenzeichen); }); }); }); diff --git a/alfa-client/libs/vorgang-shared-ui/src/lib/vorgang-in-postfach-breadcrumb-container/vorgang-in-postfach-breadcrumb-container.component.html b/alfa-client/libs/vorgang-shared-ui/src/lib/vorgang-in-postfach-breadcrumb-container/vorgang-in-postfach-breadcrumb-container.component.html deleted file mode 100644 index c8de63c7fdfba1ac8c851fe64f6c50b40100ec20..0000000000000000000000000000000000000000 --- a/alfa-client/libs/vorgang-shared-ui/src/lib/vorgang-in-postfach-breadcrumb-container/vorgang-in-postfach-breadcrumb-container.component.html +++ /dev/null @@ -1,29 +0,0 @@ -<!-- - - Copyright (C) 2022 Das Land Schleswig-Holstein vertreten durch den - Ministerpräsidenten des Landes Schleswig-Holstein - Staatskanzlei - Abteilung Digitalisierung und zentrales IT-Management der Landesregierung - - Lizenziert unter der EUPL, Version 1.2 oder - sobald - diese von der Europäischen Kommission genehmigt wurden - - Folgeversionen der EUPL ("Lizenz"); - Sie dürfen dieses Werk ausschließlich gemäß - dieser Lizenz nutzen. - Eine Kopie der Lizenz finden Sie hier: - - https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - - Sofern nicht durch anwendbare Rechtsvorschriften - gefordert oder in schriftlicher Form vereinbart, wird - die unter der Lizenz verbreitete Software "so wie sie - ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - - ausdrücklich oder stillschweigend - verbreitet. - Die sprachspezifischen Genehmigungen und Beschränkungen - unter der Lizenz sind dem Lizenztext zu entnehmen. - ---> -<alfa-vorgang-in-postfach-breadcrumb - [vorgangStateResource]="vorgangStateResource$ | async" - data-test-id="postfach-breadcrump" -></alfa-vorgang-in-postfach-breadcrumb> diff --git a/alfa-client/libs/vorgang-shared-ui/src/lib/vorgang-in-postfach-breadcrumb-container/vorgang-in-postfach-breadcrumb-container.component.scss b/alfa-client/libs/vorgang-shared-ui/src/lib/vorgang-in-postfach-breadcrumb-container/vorgang-in-postfach-breadcrumb-container.component.scss deleted file mode 100644 index 9a08a5aabce6cc4cdbb268c4190a8d67f82f19e5..0000000000000000000000000000000000000000 --- a/alfa-client/libs/vorgang-shared-ui/src/lib/vorgang-in-postfach-breadcrumb-container/vorgang-in-postfach-breadcrumb-container.component.scss +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright (C) 2022 Das Land Schleswig-Holstein vertreten durch den - * Ministerpräsidenten des Landes Schleswig-Holstein - * Staatskanzlei - * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung - * - * Lizenziert unter der EUPL, Version 1.2 oder - sobald - * diese von der Europäischen Kommission genehmigt wurden - - * Folgeversionen der EUPL ("Lizenz"); - * Sie dürfen dieses Werk ausschließlich gemäß - * dieser Lizenz nutzen. - * Eine Kopie der Lizenz finden Sie hier: - * - * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * - * Sofern nicht durch anwendbare Rechtsvorschriften - * gefordert oder in schriftlicher Form vereinbart, wird - * die unter der Lizenz verbreitete Software "so wie sie - * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - - * ausdrücklich oder stillschweigend - verbreitet. - * Die sprachspezifischen Genehmigungen und Beschränkungen - * unter der Lizenz sind dem Lizenztext zu entnehmen. - */ diff --git a/alfa-client/libs/vorgang-shared-ui/src/lib/vorgang-in-postfach-breadcrumb-container/vorgang-in-postfach-breadcrumb-container.component.spec.ts b/alfa-client/libs/vorgang-shared-ui/src/lib/vorgang-in-postfach-breadcrumb-container/vorgang-in-postfach-breadcrumb-container.component.spec.ts deleted file mode 100644 index 369a11caaf327063d67485a3b978ab158b167ffa..0000000000000000000000000000000000000000 --- a/alfa-client/libs/vorgang-shared-ui/src/lib/vorgang-in-postfach-breadcrumb-container/vorgang-in-postfach-breadcrumb-container.component.spec.ts +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2022 Das Land Schleswig-Holstein vertreten durch den - * Ministerpräsidenten des Landes Schleswig-Holstein - * Staatskanzlei - * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung - * - * Lizenziert unter der EUPL, Version 1.2 oder - sobald - * diese von der Europäischen Kommission genehmigt wurden - - * Folgeversionen der EUPL ("Lizenz"); - * Sie dürfen dieses Werk ausschließlich gemäß - * dieser Lizenz nutzen. - * Eine Kopie der Lizenz finden Sie hier: - * - * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * - * Sofern nicht durch anwendbare Rechtsvorschriften - * gefordert oder in schriftlicher Form vereinbart, wird - * die unter der Lizenz verbreitete Software "so wie sie - * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - - * ausdrücklich oder stillschweigend - verbreitet. - * Die sprachspezifischen Genehmigungen und Beschränkungen - * unter der Lizenz sind dem Lizenztext zu entnehmen. - */ -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { Mock, mock } from '@alfa-client/test-utils'; -import { VorgangService } from '@alfa-client/vorgang-shared'; -import { MockComponent } from 'ng-mocks'; -import { VorgangInPostfachBreadcrumbContainerComponent } from './vorgang-in-postfach-breadcrumb-container.component'; -import { VorgangInPostfachBreadcrumbComponent } from './vorgang-in-postfach-breadcrumb/vorgang-in-postfach-breadcrumb.component'; - -describe('VorgangInPostfachBreadcrumbContainerComponent', () => { - let component: VorgangInPostfachBreadcrumbContainerComponent; - let fixture: ComponentFixture<VorgangInPostfachBreadcrumbContainerComponent>; - - const vorgangService: Mock<VorgangService> = mock(VorgangService); - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ - VorgangInPostfachBreadcrumbContainerComponent, - MockComponent(VorgangInPostfachBreadcrumbComponent), - ], - providers: [ - { - provide: VorgangService, - useValue: vorgangService, - }, - ], - }).compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(VorgangInPostfachBreadcrumbContainerComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); - - describe('ngOnInit', () => { - it('should call vorgang service', () => { - component.ngOnInit(); - - expect(vorgangService.getVorgangWithEingang).toHaveBeenCalled(); - }); - }); -}); diff --git a/alfa-client/libs/vorgang-shared-ui/src/lib/vorgang-in-postfach-breadcrumb-container/vorgang-in-postfach-breadcrumb-container.component.ts b/alfa-client/libs/vorgang-shared-ui/src/lib/vorgang-in-postfach-breadcrumb-container/vorgang-in-postfach-breadcrumb-container.component.ts deleted file mode 100644 index bde0eba9d92b2abdac24b3d3270d9d49e89475e2..0000000000000000000000000000000000000000 --- a/alfa-client/libs/vorgang-shared-ui/src/lib/vorgang-in-postfach-breadcrumb-container/vorgang-in-postfach-breadcrumb-container.component.ts +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2022 Das Land Schleswig-Holstein vertreten durch den - * Ministerpräsidenten des Landes Schleswig-Holstein - * Staatskanzlei - * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung - * - * Lizenziert unter der EUPL, Version 1.2 oder - sobald - * diese von der Europäischen Kommission genehmigt wurden - - * Folgeversionen der EUPL ("Lizenz"); - * Sie dürfen dieses Werk ausschließlich gemäß - * dieser Lizenz nutzen. - * Eine Kopie der Lizenz finden Sie hier: - * - * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * - * Sofern nicht durch anwendbare Rechtsvorschriften - * gefordert oder in schriftlicher Form vereinbart, wird - * die unter der Lizenz verbreitete Software "so wie sie - * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - - * ausdrücklich oder stillschweigend - verbreitet. - * Die sprachspezifischen Genehmigungen und Beschränkungen - * unter der Lizenz sind dem Lizenztext zu entnehmen. - */ -import { Component, OnInit } from '@angular/core'; -import { StateResource } from '@alfa-client/tech-shared'; -import { VorgangService, VorgangWithEingangResource } from '@alfa-client/vorgang-shared'; -import { Observable } from 'rxjs'; - -@Component({ - selector: 'alfa-vorgang-in-postfach-breadcrumb-container', - templateUrl: './vorgang-in-postfach-breadcrumb-container.component.html', - styleUrls: ['./vorgang-in-postfach-breadcrumb-container.component.scss'], -}) -export class VorgangInPostfachBreadcrumbContainerComponent implements OnInit { - vorgangStateResource$: Observable<StateResource<VorgangWithEingangResource>>; - - constructor(private vorgangService: VorgangService) {} - - ngOnInit(): void { - this.vorgangStateResource$ = this.vorgangService.getVorgangWithEingang(); - } -} diff --git a/alfa-client/libs/vorgang-shared-ui/src/lib/vorgang-in-postfach-breadcrumb-container/vorgang-in-postfach-breadcrumb/vorgang-in-postfach-breadcrumb.component.html b/alfa-client/libs/vorgang-shared-ui/src/lib/vorgang-in-postfach-breadcrumb-container/vorgang-in-postfach-breadcrumb/vorgang-in-postfach-breadcrumb.component.html deleted file mode 100644 index b51eccd1277977c1a109c3a87718fd11a4091901..0000000000000000000000000000000000000000 --- a/alfa-client/libs/vorgang-shared-ui/src/lib/vorgang-in-postfach-breadcrumb-container/vorgang-in-postfach-breadcrumb/vorgang-in-postfach-breadcrumb.component.html +++ /dev/null @@ -1,30 +0,0 @@ -<!-- - - Copyright (C) 2022 Das Land Schleswig-Holstein vertreten durch den - Ministerpräsidenten des Landes Schleswig-Holstein - Staatskanzlei - Abteilung Digitalisierung und zentrales IT-Management der Landesregierung - - Lizenziert unter der EUPL, Version 1.2 oder - sobald - diese von der Europäischen Kommission genehmigt wurden - - Folgeversionen der EUPL ("Lizenz"); - Sie dürfen dieses Werk ausschließlich gemäß - dieser Lizenz nutzen. - Eine Kopie der Lizenz finden Sie hier: - - https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - - Sofern nicht durch anwendbare Rechtsvorschriften - gefordert oder in schriftlicher Form vereinbart, wird - die unter der Lizenz verbreitete Software "so wie sie - ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - - ausdrücklich oder stillschweigend - verbreitet. - Die sprachspezifischen Genehmigungen und Beschränkungen - unter der Lizenz sind dem Lizenztext zu entnehmen. - ---> -<span *ngIf="vorgangStateResource.resource as vorgang"> - <a routerLink="/vorgang/{{ vorgang | toResourceUri }}" alfa-aktenzeichen [vorgang]="vorgang"></a> - <span>/</span> - <span>Nachrichten</span> -</span> diff --git a/alfa-client/libs/vorgang-shared-ui/src/lib/vorgang-in-postfach-breadcrumb-container/vorgang-in-postfach-breadcrumb/vorgang-in-postfach-breadcrumb.component.scss b/alfa-client/libs/vorgang-shared-ui/src/lib/vorgang-in-postfach-breadcrumb-container/vorgang-in-postfach-breadcrumb/vorgang-in-postfach-breadcrumb.component.scss deleted file mode 100644 index 4df2ebcfa6f39b46618043b2ddb18650cea2f619..0000000000000000000000000000000000000000 --- a/alfa-client/libs/vorgang-shared-ui/src/lib/vorgang-in-postfach-breadcrumb-container/vorgang-in-postfach-breadcrumb/vorgang-in-postfach-breadcrumb.component.scss +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Copyright (C) 2022 Das Land Schleswig-Holstein vertreten durch den - * Ministerpräsidenten des Landes Schleswig-Holstein - * Staatskanzlei - * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung - * - * Lizenziert unter der EUPL, Version 1.2 oder - sobald - * diese von der Europäischen Kommission genehmigt wurden - - * Folgeversionen der EUPL ("Lizenz"); - * Sie dürfen dieses Werk ausschließlich gemäß - * dieser Lizenz nutzen. - * Eine Kopie der Lizenz finden Sie hier: - * - * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * - * Sofern nicht durch anwendbare Rechtsvorschriften - * gefordert oder in schriftlicher Form vereinbart, wird - * die unter der Lizenz verbreitete Software "so wie sie - * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - - * ausdrücklich oder stillschweigend - verbreitet. - * Die sprachspezifischen Genehmigungen und Beschränkungen - * unter der Lizenz sind dem Lizenztext zu entnehmen. - */ -@import 'variables'; - -:host { - display: block; - font-size: 18px; - padding: 16px 24px 4px 26px; - - ::ng-deep span { - margin: 0 4px; - } - - a { - text-decoration: none; - outline: 0; - margin: -3px; - padding: 3px; - - &:focus { - border-radius: 12px; - background-color: rgba(#777, 0.16); - } - } -} diff --git a/alfa-client/libs/vorgang-shared-ui/src/lib/vorgang-in-postfach-breadcrumb-container/vorgang-in-postfach-breadcrumb/vorgang-in-postfach-breadcrumb.component.spec.ts b/alfa-client/libs/vorgang-shared-ui/src/lib/vorgang-in-postfach-breadcrumb-container/vorgang-in-postfach-breadcrumb/vorgang-in-postfach-breadcrumb.component.spec.ts deleted file mode 100644 index f5bdd3e8a9e65ad7adcd8f08453e914f3b06a6de..0000000000000000000000000000000000000000 --- a/alfa-client/libs/vorgang-shared-ui/src/lib/vorgang-in-postfach-breadcrumb-container/vorgang-in-postfach-breadcrumb/vorgang-in-postfach-breadcrumb.component.spec.ts +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2022 Das Land Schleswig-Holstein vertreten durch den - * Ministerpräsidenten des Landes Schleswig-Holstein - * Staatskanzlei - * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung - * - * Lizenziert unter der EUPL, Version 1.2 oder - sobald - * diese von der Europäischen Kommission genehmigt wurden - - * Folgeversionen der EUPL ("Lizenz"); - * Sie dürfen dieses Werk ausschließlich gemäß - * dieser Lizenz nutzen. - * Eine Kopie der Lizenz finden Sie hier: - * - * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * - * Sofern nicht durch anwendbare Rechtsvorschriften - * gefordert oder in schriftlicher Form vereinbart, wird - * die unter der Lizenz verbreitete Software "so wie sie - * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - - * ausdrücklich oder stillschweigend - verbreitet. - * Die sprachspezifischen Genehmigungen und Beschränkungen - * unter der Lizenz sind dem Lizenztext zu entnehmen. - */ -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { RouterTestingModule } from '@angular/router/testing'; -import { createStateResource, ToResourceUriPipe } from '@alfa-client/tech-shared'; -import { createVorgangWithEingangResource } from 'libs/vorgang-shared/test/vorgang'; -import { MockComponent } from 'ng-mocks'; -import { AktenzeichenComponent } from '../../aktenzeichen/aktenzeichen.component'; -import { VorgangInPostfachBreadcrumbComponent } from './vorgang-in-postfach-breadcrumb.component'; - -describe('VorgangInPostfachBreadcrumbComponent', () => { - let component: VorgangInPostfachBreadcrumbComponent; - let fixture: ComponentFixture<VorgangInPostfachBreadcrumbComponent>; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ - VorgangInPostfachBreadcrumbComponent, - ToResourceUriPipe, - MockComponent(AktenzeichenComponent), - ], - imports: [RouterTestingModule], - }).compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(VorgangInPostfachBreadcrumbComponent); - component = fixture.componentInstance; - component.vorgangStateResource = createStateResource(createVorgangWithEingangResource()); - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/alfa-client/libs/vorgang-shared-ui/src/lib/vorgang-in-postfach-breadcrumb-container/vorgang-in-postfach-breadcrumb/vorgang-in-postfach-breadcrumb.component.ts b/alfa-client/libs/vorgang-shared-ui/src/lib/vorgang-in-postfach-breadcrumb-container/vorgang-in-postfach-breadcrumb/vorgang-in-postfach-breadcrumb.component.ts deleted file mode 100644 index 0268ce2c96d75db36678288fd07fd6022af3b069..0000000000000000000000000000000000000000 --- a/alfa-client/libs/vorgang-shared-ui/src/lib/vorgang-in-postfach-breadcrumb-container/vorgang-in-postfach-breadcrumb/vorgang-in-postfach-breadcrumb.component.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2022 Das Land Schleswig-Holstein vertreten durch den - * Ministerpräsidenten des Landes Schleswig-Holstein - * Staatskanzlei - * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung - * - * Lizenziert unter der EUPL, Version 1.2 oder - sobald - * diese von der Europäischen Kommission genehmigt wurden - - * Folgeversionen der EUPL ("Lizenz"); - * Sie dürfen dieses Werk ausschließlich gemäß - * dieser Lizenz nutzen. - * Eine Kopie der Lizenz finden Sie hier: - * - * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * - * Sofern nicht durch anwendbare Rechtsvorschriften - * gefordert oder in schriftlicher Form vereinbart, wird - * die unter der Lizenz verbreitete Software "so wie sie - * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - - * ausdrücklich oder stillschweigend - verbreitet. - * Die sprachspezifischen Genehmigungen und Beschränkungen - * unter der Lizenz sind dem Lizenztext zu entnehmen. - */ -import { Component, Input } from '@angular/core'; -import { StateResource } from '@alfa-client/tech-shared'; -import { VorgangWithEingangResource } from '@alfa-client/vorgang-shared'; - -@Component({ - selector: 'alfa-vorgang-in-postfach-breadcrumb', - templateUrl: './vorgang-in-postfach-breadcrumb.component.html', - styleUrls: ['./vorgang-in-postfach-breadcrumb.component.scss'], -}) -export class VorgangInPostfachBreadcrumbComponent { - @Input() vorgangStateResource: StateResource<VorgangWithEingangResource>; -} diff --git a/alfa-client/libs/vorgang-shared-ui/src/lib/vorgang-nummer/_vorgang-nummer.component.theme.scss b/alfa-client/libs/vorgang-shared-ui/src/lib/vorgang-nummer/_vorgang-nummer.component.theme.scss deleted file mode 100644 index 00e4305df64ad8278be645380fe84dc15f92cd4b..0000000000000000000000000000000000000000 --- a/alfa-client/libs/vorgang-shared-ui/src/lib/vorgang-nummer/_vorgang-nummer.component.theme.scss +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Copyright (C) 2022 Das Land Schleswig-Holstein vertreten durch den - * Ministerpräsidenten des Landes Schleswig-Holstein - * Staatskanzlei - * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung - * - * Lizenziert unter der EUPL, Version 1.2 oder - sobald - * diese von der Europäischen Kommission genehmigt wurden - - * Folgeversionen der EUPL ("Lizenz"); - * Sie dürfen dieses Werk ausschließlich gemäß - * dieser Lizenz nutzen. - * Eine Kopie der Lizenz finden Sie hier: - * - * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * - * Sofern nicht durch anwendbare Rechtsvorschriften - * gefordert oder in schriftlicher Form vereinbart, wird - * die unter der Lizenz verbreitete Software "so wie sie - * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - - * ausdrücklich oder stillschweigend - verbreitet. - * Die sprachspezifischen Genehmigungen und Beschränkungen - * unter der Lizenz sind dem Lizenztext zu entnehmen. - */ -alfa-vorgang-nummer { - div { - display: flex; - align-items: center; - margin: 0; - } - - mat-icon { - margin-right: 0.25rem; - height: 1.5rem; - min-height: 1.5rem; - width: 1.5rem; - min-width: 1.5rem; - } - - span { - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - } - - &.big { - div { - font-size: 16px; - font-weight: 300; - } - mat-icon { - margin-right: 0.5rem; - height: 1.875rem; - min-height: 1.875rem; - width: 1.875rem; - min-width: 1.875rem; - } - } -} diff --git a/alfa-client/libs/vorgang-shared-ui/src/lib/vorgang-nummer/vorgang-nummer.component.html b/alfa-client/libs/vorgang-shared-ui/src/lib/vorgang-nummer/vorgang-nummer.component.html index 79e54f9224cda253b209638ff7a340ee55b2c555..66d983892157124277169515a44384b7fb2e789a 100644 --- a/alfa-client/libs/vorgang-shared-ui/src/lib/vorgang-nummer/vorgang-nummer.component.html +++ b/alfa-client/libs/vorgang-shared-ui/src/lib/vorgang-nummer/vorgang-nummer.component.html @@ -23,7 +23,14 @@ unter der Lizenz sind dem Lizenztext zu entnehmen. --> -<div> - <mat-icon svgIcon="nr"></mat-icon> - <span [matTooltip]="vorgang.nummer" data-test-id="vorgang-nummer">{{ vorgang.nummer }}</span> + +<div class="flex flex-shrink-0"> + <mat-icon svgIcon="nr" style="width: 1.5rem; height: 1.5rem"></mat-icon> +</div> +<div + class="line-clamp-1 flex-shrink overflow-hidden break-all text-base font-normal lg:line-clamp-none lg:flex" + data-test-id="vorgang-nummer" + [matTooltip]="vorgang.nummer" +> + {{ vorgang.nummer }} </div> diff --git a/alfa-client/libs/vorgang-shared-ui/src/lib/vorgang-nummer/vorgang-nummer.component.spec.ts b/alfa-client/libs/vorgang-shared-ui/src/lib/vorgang-nummer/vorgang-nummer.component.spec.ts index 2f16944ecba83b737f8a187b9797991ea9810b93..a622e364e43db4f9fb65e938f384f6a55234d952 100644 --- a/alfa-client/libs/vorgang-shared-ui/src/lib/vorgang-nummer/vorgang-nummer.component.spec.ts +++ b/alfa-client/libs/vorgang-shared-ui/src/lib/vorgang-nummer/vorgang-nummer.component.spec.ts @@ -26,9 +26,10 @@ import { VorgangResource } from '@alfa-client/vorgang-shared'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { MatIcon } from '@angular/material/icon'; import { MatIconTestingModule } from '@angular/material/icon/testing'; +import { MatTooltipModule } from '@angular/material/tooltip'; import { getDataTestIdOf } from 'libs/tech-shared/test/data-test'; -import { MatTooltipDirective } from 'libs/ui/src/lib/ui/mattooltip/mattooltip.directive'; import { createVorgangResource } from 'libs/vorgang-shared/test/vorgang'; +import { MockModule } from 'ng-mocks'; import { VorgangNummerComponent } from './vorgang-nummer.component'; describe('VorgangNummerComponent', () => { @@ -40,7 +41,7 @@ describe('VorgangNummerComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [MatTooltipDirective, VorgangNummerComponent], + declarations: [MockModule(MatTooltipModule), VorgangNummerComponent], imports: [MatIcon, MatIconTestingModule], }).compileComponents(); }); @@ -60,7 +61,7 @@ describe('VorgangNummerComponent', () => { it('should show vorgang.nummer', () => { const text: HTMLElement = getElementFromFixture(fixture, vorgangnummer); - expect(text.textContent).toBe(vorgang.nummer); + expect(text.textContent.trim()).toBe(vorgang.nummer); }); }); }); diff --git a/alfa-client/libs/vorgang-shared-ui/src/lib/vorgang-nummer/vorgang-nummer.component.ts b/alfa-client/libs/vorgang-shared-ui/src/lib/vorgang-nummer/vorgang-nummer.component.ts index 610e020a9d73ba64a29ef19537ecba26a6c0e3dc..3acb48b3de595cafc0f0ff76fc579f951d51f560 100644 --- a/alfa-client/libs/vorgang-shared-ui/src/lib/vorgang-nummer/vorgang-nummer.component.ts +++ b/alfa-client/libs/vorgang-shared-ui/src/lib/vorgang-nummer/vorgang-nummer.component.ts @@ -21,12 +21,13 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { Component, Input } from '@angular/core'; import { Vorgang } from '@alfa-client/vorgang-shared'; +import { Component, Input } from '@angular/core'; @Component({ selector: 'alfa-vorgang-nummer', templateUrl: './vorgang-nummer.component.html', + styles: [':host {@apply flex flex-1 flex-row gap-1}'], }) export class VorgangNummerComponent { @Input() vorgang: Vorgang; diff --git a/alfa-client/libs/vorgang-shared-ui/src/lib/vorgang-search-container/vorgang-search/vorgang-search.component.scss b/alfa-client/libs/vorgang-shared-ui/src/lib/vorgang-search-container/vorgang-search/vorgang-search.component.scss index 006026859a6c1c9552ccbb9928f909d9d447078d..37365d6a66d8d35bd8ebb1864f069fc74fad0e03 100644 --- a/alfa-client/libs/vorgang-shared-ui/src/lib/vorgang-search-container/vorgang-search/vorgang-search.component.scss +++ b/alfa-client/libs/vorgang-shared-ui/src/lib/vorgang-search-container/vorgang-search/vorgang-search.component.scss @@ -31,7 +31,7 @@ input { border: 0; background-color: transparent; outline: 0; - font-size: 18px !important; + font-size: 1.125rem !important; color: inherit; min-width: 0; // Firefox workaround for close icon width: calc(100% - 40px); diff --git a/alfa-client/libs/vorgang-shared-ui/src/lib/vorgang-shared-ui.module.ts b/alfa-client/libs/vorgang-shared-ui/src/lib/vorgang-shared-ui.module.ts index 542c39b469e4eff2171701519b31a5950e68ed18..21b8ef623c86deb6ed8b8e8c7430bff6bd20971b 100644 --- a/alfa-client/libs/vorgang-shared-ui/src/lib/vorgang-shared-ui.module.ts +++ b/alfa-client/libs/vorgang-shared-ui/src/lib/vorgang-shared-ui.module.ts @@ -21,15 +21,13 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { CommonModule } from '@angular/common'; -import { NgModule } from '@angular/core'; -import { RouterModule } from '@angular/router'; import { TechSharedModule } from '@alfa-client/tech-shared'; import { UiModule } from '@alfa-client/ui'; import { VorgangSharedModule } from '@alfa-client/vorgang-shared'; +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; import { AktenzeichenComponent } from './aktenzeichen/aktenzeichen.component'; -import { VorgangInPostfachBreadcrumbContainerComponent } from './vorgang-in-postfach-breadcrumb-container/vorgang-in-postfach-breadcrumb-container.component'; -import { VorgangInPostfachBreadcrumbComponent } from './vorgang-in-postfach-breadcrumb-container/vorgang-in-postfach-breadcrumb/vorgang-in-postfach-breadcrumb.component'; import { VorgangNummerComponent } from './vorgang-nummer/vorgang-nummer.component'; import { VorgangSearchContainerComponent } from './vorgang-search-container/vorgang-search-container.component'; import { VorgangSearchAutocompleteOptionsContentComponent } from './vorgang-search-container/vorgang-search/vorgang-search-autocomplete-options-content/vorgang-search-autocomplete-options-content.component'; @@ -46,8 +44,6 @@ import { WiedervorlageIconComponent } from './wiedervorlage-icon/wiedervorlage-i VorgangSearchComponent, AktenzeichenComponent, VorgangStatusDotComponent, - VorgangInPostfachBreadcrumbContainerComponent, - VorgangInPostfachBreadcrumbComponent, VorgangSearchAutocompleteOptionsContentComponent, VorgangSearchClearButtonComponent, VorgangNummerComponent, @@ -58,7 +54,6 @@ import { WiedervorlageIconComponent } from './wiedervorlage-icon/wiedervorlage-i VorgangSearchContainerComponent, AktenzeichenComponent, VorgangStatusDotComponent, - VorgangInPostfachBreadcrumbContainerComponent, VorgangSearchAutocompleteOptionsContentComponent, VorgangNummerComponent, VorgangStatusTextComponent, diff --git a/alfa-client/libs/vorgang-shared-ui/src/lib/wiedervorlage-icon/wiedervorlage-icon.component.html b/alfa-client/libs/vorgang-shared-ui/src/lib/wiedervorlage-icon/wiedervorlage-icon.component.html index 9bf2265a277651fe8d40eff1a80ad736136077ea..929a086df43bd18a6e9a4b0097d02c877d3d51e1 100644 --- a/alfa-client/libs/vorgang-shared-ui/src/lib/wiedervorlage-icon/wiedervorlage-icon.component.html +++ b/alfa-client/libs/vorgang-shared-ui/src/lib/wiedervorlage-icon/wiedervorlage-icon.component.html @@ -1,4 +1,7 @@ -<div [class.red]="isOverdue" data-test-class="wiedervorlage-icon"> +<div + [ngClass]="{ 'text-error': isOverdue, 'text-text': !isOverdue }" + data-test-class="wiedervorlage-icon" +> <ng-container *ngIf="isOverdue; else defaultFrist"> <ozgcloud-svgicon svgIcon="resubmission_expired" diff --git a/alfa-client/libs/vorgang-shared-ui/src/lib/wiedervorlage-icon/wiedervorlage-icon.component.scss b/alfa-client/libs/vorgang-shared-ui/src/lib/wiedervorlage-icon/wiedervorlage-icon.component.scss index 9315d4d7758a4c1fcfe23122a58d1d7059d8b257..5d71aeb617f12da1e3cf893fb102314114d1e7df 100644 --- a/alfa-client/libs/vorgang-shared-ui/src/lib/wiedervorlage-icon/wiedervorlage-icon.component.scss +++ b/alfa-client/libs/vorgang-shared-ui/src/lib/wiedervorlage-icon/wiedervorlage-icon.component.scss @@ -6,11 +6,3 @@ margin-right: 6px; height: $iconHeight; } - -.red { - color: mat.get-color-from-palette($warnPalette, darker); -} - -body.dark :host .red { - color: red; -} diff --git a/alfa-client/libs/vorgang-shared/src/lib/vorgang.linkrel.ts b/alfa-client/libs/vorgang-shared/src/lib/vorgang.linkrel.ts index 57cfeeab38cb013afb230703102924d436b2acd0..21dc4295af4576f3d4e3439ada7935dcd45a5275 100644 --- a/alfa-client/libs/vorgang-shared/src/lib/vorgang.linkrel.ts +++ b/alfa-client/libs/vorgang-shared/src/lib/vorgang.linkrel.ts @@ -56,7 +56,6 @@ export enum VorgangWithEingangLinkRel { HISTORIE = 'historie', SEARCH_USER_PROFILES = 'search-user-profiles', EXPORT = 'export', - CREATE_BESCHEID = 'createBescheid', CREATE_BESCHEID_DRAFT = 'createBescheidDraft', PROCESS_VORGANG = 'processVorgang', @@ -65,6 +64,8 @@ export enum VorgangWithEingangLinkRel { BESCHEIDE = 'bescheide', UEBERSPRINGEN_UND_ABSCHLIESSEN = 'ueberspringen_und_abschliessen', DOWNLOAD_ATTACHMENTS = 'downloadAttachments', + + CREATE_COLLABORATION_REQUEST = 'createCollaborationRequest', } export enum LoeschAnforderungLinkRel { diff --git a/alfa-client/libs/vorgang/src/lib/vorgang-list-container/vorgang-list-container.component.html b/alfa-client/libs/vorgang/src/lib/vorgang-list-container/vorgang-list-container.component.html index cf927e2bacf4d6005e3200b3bbcd87a79736ba74..21f6df0c5a7913b49e25ca281f68add1f7fd2f77 100644 --- a/alfa-client/libs/vorgang/src/lib/vorgang-list-container/vorgang-list-container.component.html +++ b/alfa-client/libs/vorgang/src/lib/vorgang-list-container/vorgang-list-container.component.html @@ -27,11 +27,11 @@ class="list" *ngIf="{ vorgaenge: vorgaenge$ | async, - vorgangListPageResource: vorgangListPageResource$ | async + vorgangListPageResource: vorgangListPageResource$ | async, } as vorgangListData" > <alfa-vorgang-list - class="l-scroll-area--full" + class="l-scroll-area--full flex-col" [vorgangListPageResource]="vorgangListData.vorgangListPageResource" [vorgaenge]="vorgangListData.vorgaenge" [searchString]="searchString$ | async" diff --git a/alfa-client/libs/vorgang/src/lib/vorgang-list-container/vorgang-list/empty-list/empty-list.component.html b/alfa-client/libs/vorgang/src/lib/vorgang-list-container/vorgang-list/empty-list/empty-list.component.html index 73e479922ecc7ce9a6a1512263e624b4325ad828..9e299474628d6d580806b68d4cc97510749f7c12 100644 --- a/alfa-client/libs/vorgang/src/lib/vorgang-list-container/vorgang-list/empty-list/empty-list.component.html +++ b/alfa-client/libs/vorgang/src/lib/vorgang-list-container/vorgang-list/empty-list/empty-list.component.html @@ -24,10 +24,12 @@ --> <div role="status" aria-live="assertive" *ngIf="searchString; else emptyDatabase"> - <h3 data-test-id="empty-list-text">Es wurden keine Treffer für „{{ searchString }}“ gefunden</h3> + <h3 class="mb-1 text-lg" data-test-id="empty-list-text"> + Es wurden keine Treffer für „{{ searchString }}“ gefunden + </h3> <p>Versuchen Sie es mit einem anderen Suchbegriff oder prüfen Sie die Schreibweise.</p> </div> <ng-template #emptyDatabase> - <h3 data-test-id="empty-database-text">Es sind keine Vorgänge vorhanden</h3> + <h3 class="mb-1 text-lg" data-test-id="empty-database-text">Es sind keine Vorgänge vorhanden</h3> </ng-template> diff --git a/alfa-client/libs/vorgang/src/lib/vorgang-list-container/vorgang-list/empty-list/empty-list.component.scss b/alfa-client/libs/vorgang/src/lib/vorgang-list-container/vorgang-list/empty-list/empty-list.component.scss index 536fd860a7e00090aac1b7868250154e178dbbea..f5257d8da6cdb6bf4d867ef90955dee710f72b40 100644 --- a/alfa-client/libs/vorgang/src/lib/vorgang-list-container/vorgang-list/empty-list/empty-list.component.scss +++ b/alfa-client/libs/vorgang/src/lib/vorgang-list-container/vorgang-list/empty-list/empty-list.component.scss @@ -25,8 +25,3 @@ display: block; padding: 24px 32px; } - -h3 { - font-size: 18px; - margin-bottom: 4px; -} diff --git a/alfa-client/libs/vorgang/src/lib/vorgang-list-container/vorgang-list/vorgang-list-item/vorgang-created-at/vorgang-created-at.component.spec.ts b/alfa-client/libs/vorgang/src/lib/vorgang-list-container/vorgang-list/vorgang-list-item/vorgang-created-at/vorgang-created-at.component.spec.ts index 15a3f647fef8fbe83919d8c363bc3d5db5698cca..84d5e3e901e2b3dea809dc8fb2f3ebbb847baf46 100644 --- a/alfa-client/libs/vorgang/src/lib/vorgang-list-container/vorgang-list/vorgang-list-item/vorgang-created-at/vorgang-created-at.component.spec.ts +++ b/alfa-client/libs/vorgang/src/lib/vorgang-list-container/vorgang-list/vorgang-list-item/vorgang-created-at/vorgang-created-at.component.spec.ts @@ -33,9 +33,10 @@ import localeDe from '@angular/common/locales/de'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { MatIcon } from '@angular/material/icon'; import { MatIconTestingModule } from '@angular/material/icon/testing'; +import { MatTooltipModule } from '@angular/material/tooltip'; import { getDataTestIdOf } from 'libs/tech-shared/test/data-test'; -import { MatTooltipDirective } from 'libs/ui/src/lib/ui/mattooltip/mattooltip.directive'; import { createVorgangResource } from 'libs/vorgang-shared/test/vorgang'; +import { MockModule } from 'ng-mocks'; import { VorgangCreatedAtComponent } from './vorgang-created-at.component'; registerLocaleData(localeDe); @@ -50,10 +51,10 @@ describe('VorgangCreatedAtComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [ - MatTooltipDirective, FormatDateWithoutYearWithTimePipe, FormatDateWithTimePipe, VorgangCreatedAtComponent, + MockModule(MatTooltipModule), ], imports: [MatIcon, MatIconTestingModule], }).compileComponents(); diff --git a/alfa-client/libs/vorgang/src/lib/vorgang-list-container/vorgang-list/vorgang-list-item/vorgang-list-item.component.html b/alfa-client/libs/vorgang/src/lib/vorgang-list-container/vorgang-list/vorgang-list-item/vorgang-list-item.component.html index 4ae91cb40e746eef978cc1682f431ec3ab17e9f1..228c9808d45c6f5b81e47b178e21cfe28e4fe01c 100644 --- a/alfa-client/libs/vorgang/src/lib/vorgang-list-container/vorgang-list/vorgang-list-item/vorgang-list-item.component.html +++ b/alfa-client/libs/vorgang/src/lib/vorgang-list-container/vorgang-list/vorgang-list-item/vorgang-list-item.component.html @@ -28,9 +28,9 @@ [attr.aria-label]="ariaLabel" routerLink="/vorgang/{{ vorgang | toResourceUri: vorgangLinkRel.VORGANG_WITH_EINGANG }}" [attr.data-test-id]="'vorgang-list-item-' + vorgang.name | convertForDataTest" - class="flex flex-row gap-4 border-b-2 border-slate-100 p-4 hover:shadow-[inset_1px_-1px_0_0_rgba(0,0,0,0.16)]" + class="flex flex-row gap-4 border-b-2 border-gray-200 p-4 hover:shadow-[inset_1px_-1px_0_0_rgba(0,0,0,0.16)] dark:border-b dark:border-gray-300 dark:hover:shadow-[inset_1px_-1px_0_0_rgba(255,255,255,0.60)]" > - <div class="flex w-36 flex-none flex-col"> + <div class="flex w-32 flex-none flex-col"> <div class="flex items-center gap-3"> <alfa-vorgang-status-dot [status]="vorgang.status" @@ -51,14 +51,20 @@ ></alfa-vorgang-bescheid-status> </div> </div> - <div class="flex min-w-0 flex-1 flex-col gap-2"> - <div data-test-id="name" class="text-base font-medium">{{ vorgang.name }}</div> - <div> + <div class="flex flex-1 flex-col gap-1"> + <div data-test-id="name" class="break-all text-base font-medium"> + {{ vorgang.name }} + </div> + <div class="mt-1"> <alfa-vorgang-nummer class="vorgang-nummer" [vorgang]="vorgang"></alfa-vorgang-nummer> </div> - <div class="flex min-w-0 grow flex-row gap-1"> - <mat-icon class="flex flex-none" svgIcon="az"></mat-icon> - <div alfa-aktenzeichen class="flex-initial truncate" [vorgang]="vorgang"></div> + <div> + <div class="flex flex-row gap-1"> + <div class="flex flex-shrink-0"> + <mat-icon svgIcon="az" style="width: 1.5rem; height: 1.5rem"></mat-icon> + </div> + <div alfa-aktenzeichen [vorgang]="vorgang"></div> + </div> </div> </div> <div class="flex w-36 flex-none flex-col gap-2"> @@ -83,7 +89,6 @@ </ozgcloud-postfach-icon> </div> </div> - <div class="w-28 flex-none"></div> <div class="w-10 flex-none"> <alfa-user-profile-in-vorgang-list-item-container *ngIf="vorgang | hasLink: vorgangLinkRel.ASSIGN" diff --git a/alfa-client/libs/vorgang/src/lib/vorgang-list-container/vorgang-list/vorgang-list-item/vorgang-list-item.component.spec.ts b/alfa-client/libs/vorgang/src/lib/vorgang-list-container/vorgang-list/vorgang-list-item/vorgang-list-item.component.spec.ts index e046a32eab12a4064c49d7a06c47725633814dfd..4a06bce8341af9c60b88bb268fa5c6e437466e40 100644 --- a/alfa-client/libs/vorgang/src/lib/vorgang-list-container/vorgang-list/vorgang-list-item/vorgang-list-item.component.spec.ts +++ b/alfa-client/libs/vorgang/src/lib/vorgang-list-container/vorgang-list/vorgang-list-item/vorgang-list-item.component.spec.ts @@ -47,11 +47,11 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { MAT_DATE_LOCALE } from '@angular/material/core'; import { MatIcon } from '@angular/material/icon'; import { MatIconTestingModule } from '@angular/material/icon/testing'; +import { MatTooltipModule } from '@angular/material/tooltip'; import { RouterTestingModule } from '@angular/router/testing'; import { getDataTestClassOf, getDataTestIdOf } from 'libs/tech-shared/test/data-test'; -import { MatTooltipDirective } from 'libs/ui/src/lib/ui/mattooltip/mattooltip.directive'; import { createVorgangResource } from 'libs/vorgang-shared/test/vorgang'; -import { MockComponent } from 'ng-mocks'; +import { MockComponent, MockModule } from 'ng-mocks'; import { VorgangBescheidStatusComponent } from './vorgang-bescheid-status/vorgang-bescheid-status.component'; import { VorgangCreatedAtComponent } from './vorgang-created-at/vorgang-created-at.component'; import { VorgangListItemComponent } from './vorgang-list-item.component'; @@ -73,7 +73,6 @@ describe('VorgangListItemComponent', () => { declarations: [ VorgangListItemComponent, MatIcon, - MatTooltipDirective, EnumToLabelPipe, ToResourceUriPipe, HasLinkPipe, @@ -88,6 +87,7 @@ describe('VorgangListItemComponent', () => { MockComponent(UserProfileInVorgangListItemContainerComponent), MockComponent(VorgangCreatedAtComponent), MockComponent(VorgangBescheidStatusComponent), + MockModule(MatTooltipModule), ], providers: [ { provide: LOCALE_ID, useValue: 'de' }, diff --git a/alfa-client/libs/vorgang/src/lib/vorgang-list-container/vorgang-list/vorgang-list-item/vorgang-next-frist-button/vorgang-next-frist-button.component.html b/alfa-client/libs/vorgang/src/lib/vorgang-list-container/vorgang-list/vorgang-list-item/vorgang-next-frist-button/vorgang-next-frist-button.component.html index 37b136c5f1d31d7d6f21f87df330bec6cce80a9c..206b3aa1c4c6986f0b4f18fdcb876810bf81d610 100644 --- a/alfa-client/libs/vorgang/src/lib/vorgang-list-container/vorgang-list/vorgang-list-item/vorgang-next-frist-button/vorgang-next-frist-button.component.html +++ b/alfa-client/libs/vorgang/src/lib/vorgang-list-container/vorgang-list/vorgang-list-item/vorgang-next-frist-button/vorgang-next-frist-button.component.html @@ -31,7 +31,11 @@ (mouseleave)="showWiedervorlagen = false" (focusout)="showWiedervorlagen = false" > - <div class="date" [class.red]="isOverdue" data-test-class="wiedervorlage-next-frist"> + <div + class="date" + [ngClass]="{ 'text-error': isOverdue, 'text-text': !isOverdue }" + data-test-class="wiedervorlage-next-frist" + > <alfa-wiedervorlage-icon [isOverdue]="isOverdue"></alfa-wiedervorlage-icon> <span>{{ vorgang.nextFrist | formatToPrettyDate }}</span> </div> diff --git a/alfa-client/libs/vorgang/src/lib/vorgang-list-container/vorgang-list/vorgang-list-item/vorgang-next-frist-button/vorgang-next-frist-button.component.scss b/alfa-client/libs/vorgang/src/lib/vorgang-list-container/vorgang-list/vorgang-list-item/vorgang-next-frist-button/vorgang-next-frist-button.component.scss index aa806fcb134fd31ffe3294c79892fccf159157fa..458f0a6eb3a1c8807675fab809d27e13ca79503b 100644 --- a/alfa-client/libs/vorgang/src/lib/vorgang-list-container/vorgang-list/vorgang-list-item/vorgang-next-frist-button/vorgang-next-frist-button.component.scss +++ b/alfa-client/libs/vorgang/src/lib/vorgang-list-container/vorgang-list/vorgang-list-item/vorgang-next-frist-button/vorgang-next-frist-button.component.scss @@ -29,7 +29,6 @@ background-color: inherit; border: 0; padding: 0; - color: inherit; position: relative; } @@ -38,11 +37,3 @@ align-items: center; white-space: nowrap; } - -.red { - color: mat.get-color-from-palette($warnPalette, darker); -} - -body.dark :host .red { - color: red; -} diff --git a/alfa-client/libs/vorgang/src/lib/vorgang-list-page-container/vorgang-list-page/vorgang-filter-menu-container/vorgang-filter-menu/_vorgang-filter-item.theme.scss b/alfa-client/libs/vorgang/src/lib/vorgang-list-page-container/vorgang-list-page/vorgang-filter-menu-container/vorgang-filter-menu/_vorgang-filter-item.theme.scss index b30526d2948a0fcf1e0ddfa018ae3e1f7469eae7..63637945bf7b4a4a09251c073daeba6ae7ba00bd 100644 --- a/alfa-client/libs/vorgang/src/lib/vorgang-list-page-container/vorgang-list-page/vorgang-filter-menu-container/vorgang-filter-menu/_vorgang-filter-item.theme.scss +++ b/alfa-client/libs/vorgang/src/lib/vorgang-list-page-container/vorgang-list-page/vorgang-filter-menu-container/vorgang-filter-menu/_vorgang-filter-item.theme.scss @@ -30,7 +30,7 @@ alfa-vorgang-filter-menu { justify-content: space-between; gap: 0.5rem; height: 2rem; - font-size: 14px; + font-size: 0.875rem; mat-icon { display: none; diff --git a/alfa-client/libs/vorgang/src/lib/vorgang-list-page-container/vorgang-list-page/vorgang-list-page.component.html b/alfa-client/libs/vorgang/src/lib/vorgang-list-page-container/vorgang-list-page/vorgang-list-page.component.html index 43ee22e674f7a4c0b4e62185d5c0ca2cba652d82..c947a1e4cb95a6d6817a0e3573f93a94d523943b 100644 --- a/alfa-client/libs/vorgang/src/lib/vorgang-list-page-container/vorgang-list-page/vorgang-list-page.component.html +++ b/alfa-client/libs/vorgang/src/lib/vorgang-list-page-container/vorgang-list-page/vorgang-list-page.component.html @@ -23,9 +23,10 @@ unter der Lizenz sind dem Lizenztext zu entnehmen. --> +<h1 class="sr-only">Vorgangsliste</h1> <alfa-vorgang-filter-menu-container class="mat-app-background"></alfa-vorgang-filter-menu-container> -<div class="content"> +<div class="flex flex-row"> <alfa-vorgang-views-menu-container *ngIf="apiRootStateResource.resource" [apiRootResource]="apiRootStateResource.resource" @@ -38,20 +39,21 @@ else showNoRoleMessage " data-test-id="vorgaenge-list" - class="flex flex-1 flex-col overflow-hidden" + class="flex flex-1 flex-col overflow-hidden border border-gray-200 dark:border-black" > <router-outlet></router-outlet> </main> <ng-template #showNoRoleMessage> - <div class="l-scroll-area--full no-role-message" data-test-id="user-no-role-message"> - <h2>Es sind keine Vorgänge in Alfa verfügbar.</h2> - <p>Prüfen Sie, ob folgendes zutrifft:</p> - <ul> - <li>Es wurden keine Rollen zugewiesen.</li> - </ul> - - <p>Bitte bei der verantwortlichen Person des User-Managements bzw. des Keycloaks melden.</p> + <div class="flex-1 border border-gray-200 px-7 py-6" data-test-id="user-no-role-message"> + <div> + <h2>Es sind keine Vorgänge in Alfa verfügbar.</h2> + <p>Prüfen Sie, ob folgendes zutrifft:</p> + <ul> + <li>Es wurden keine Rollen zugewiesen.</li> + </ul> + <p>Bitte bei der verantwortlichen Person des User-Managements bzw. des Keycloaks melden.</p> + </div> </div> </ng-template> </div> diff --git a/alfa-client/libs/vorgang/src/lib/vorgang-list-page-container/vorgang-list-page/vorgang-list-page.component.scss b/alfa-client/libs/vorgang/src/lib/vorgang-list-page-container/vorgang-list-page/vorgang-list-page.component.scss index 07a6f9291052df7ec173c86ffd766c1b85df934d..0ed70776b23972cc2f11d42feee865af9cc28525 100644 --- a/alfa-client/libs/vorgang/src/lib/vorgang-list-page-container/vorgang-list-page/vorgang-list-page.component.scss +++ b/alfa-client/libs/vorgang/src/lib/vorgang-list-page-container/vorgang-list-page/vorgang-list-page.component.scss @@ -33,16 +33,6 @@ h1 { display: flex; flex-direction: row; background-color: $background; - - main, - .no-role-message { - flex: 1 1 100%; - border: 1px solid $greyLight; - } - - .no-role-message { - padding: 1.5rem 2rem; - } } :host-context(.dark) { diff --git a/alfa-client/libs/vorgang/src/lib/vorgang-list-page-container/vorgang-list-page/vorgang-views-menu/vorgang-search-view-item-container/vorgang-search-view-item/vorgang-search-view-item.component.html b/alfa-client/libs/vorgang/src/lib/vorgang-list-page-container/vorgang-list-page/vorgang-views-menu/vorgang-search-view-item-container/vorgang-search-view-item/vorgang-search-view-item.component.html index 9f578e5efcee92394cc676b46eb6cd5e0ee27cca..2952d25ef91664647393b0bbc585841e47c2c4c3 100644 --- a/alfa-client/libs/vorgang/src/lib/vorgang-list-page-container/vorgang-list-page/vorgang-views-menu/vorgang-search-view-item-container/vorgang-search-view-item/vorgang-search-view-item.component.html +++ b/alfa-client/libs/vorgang/src/lib/vorgang-list-page-container/vorgang-list-page/vorgang-views-menu/vorgang-search-view-item-container/vorgang-search-view-item/vorgang-search-view-item.component.html @@ -4,5 +4,5 @@ data-test-id="views-menu-item-Suche" > <ozgcloud-icon icon="search"></ozgcloud-icon> - Suche + <span class="text-sm">Suche</span> </ozgcloud-routing-button> diff --git a/alfa-client/libs/vorgang/src/lib/vorgang-list-page-container/vorgang-list-page/vorgang-views-menu/vorgang-view-item-container/vorgang-view-item/vorgang-view-item.component.html b/alfa-client/libs/vorgang/src/lib/vorgang-list-page-container/vorgang-list-page/vorgang-views-menu/vorgang-view-item-container/vorgang-view-item/vorgang-view-item.component.html index 85a09071dd9f3890a988ce787c06accb65d54a16..5983cd2ccd84b22bf04bae1536d6dcb362409ba5 100644 --- a/alfa-client/libs/vorgang/src/lib/vorgang-list-page-container/vorgang-list-page/vorgang-views-menu/vorgang-view-item-container/vorgang-view-item/vorgang-view-item.component.html +++ b/alfa-client/libs/vorgang/src/lib/vorgang-list-page-container/vorgang-list-page/vorgang-views-menu/vorgang-view-item-container/vorgang-view-item/vorgang-view-item.component.html @@ -4,10 +4,11 @@ [attr.data-test-id]="'views-menu-item-' + (label | convertForDataTest)" > <ng-content></ng-content> - <div class="label">{{ label }}</div> + <div class="label text-sm">{{ label }}</div> <div *ngIf="count != null" [attr.data-test-id]="'views-menu-item-count-' + (label | convertForDataTest)" + class="text-sm" > {{ count }} </div> diff --git a/alfa-client/libs/vorgang/src/lib/vorgang-list-page-container/vorgang-list-page/vorgang-views-menu/vorgang-views-menu.component.html b/alfa-client/libs/vorgang/src/lib/vorgang-list-page-container/vorgang-list-page/vorgang-views-menu/vorgang-views-menu.component.html index 6c874f1b35f4c2e6ea7955504a4dc69a5937fa6b..1e827d60d406dbba0cb91511e6258e5e43924386 100644 --- a/alfa-client/libs/vorgang/src/lib/vorgang-list-page-container/vorgang-list-page/vorgang-views-menu/vorgang-views-menu.component.html +++ b/alfa-client/libs/vorgang/src/lib/vorgang-list-page-container/vorgang-list-page/vorgang-views-menu/vorgang-views-menu.component.html @@ -24,7 +24,7 @@ --> <ng-container *ngIf="vorgangStatisticStateResource?.resource as statistic"> - <div class="views"> + <nav class="views" aria-label="Statusansichten"> <alfa-vorgang-view-item-container *ngIf=" apiRootResource @@ -190,5 +190,5 @@ class="top-border" > </alfa-vorgang-search-view-item-container> - </div> + </nav> </ng-container> diff --git a/alfa-client/libs/vorgang/src/lib/vorgang-list-search-container/vorgang-list-search/vorgang-list-search.component.scss b/alfa-client/libs/vorgang/src/lib/vorgang-list-search-container/vorgang-list-search/vorgang-list-search.component.scss index df5f0bc9484e063d9de082c2293e383a0827f3e2..464e85fde3aa33bf73740006a64cdb20d69453a3 100644 --- a/alfa-client/libs/vorgang/src/lib/vorgang-list-search-container/vorgang-list-search/vorgang-list-search.component.scss +++ b/alfa-client/libs/vorgang/src/lib/vorgang-list-search-container/vorgang-list-search/vorgang-list-search.component.scss @@ -7,6 +7,6 @@ div { } h3 { - font-size: 18px; + font-size: 1.125rem; margin-bottom: 4px; } diff --git a/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-list-in-vorgang-container/wiedervorlage-list-in-vorgang/wiedervorlage-in-vorgang/wiedervorlage-in-vorgang.component.html b/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-list-in-vorgang-container/wiedervorlage-list-in-vorgang/wiedervorlage-in-vorgang/wiedervorlage-in-vorgang.component.html index 8f22382ce7fa032dcc18833d833fbac8d705de24..63809ff94287361c07b4b4bbb35f83c19a87f663 100644 --- a/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-list-in-vorgang-container/wiedervorlage-list-in-vorgang/wiedervorlage-in-vorgang/wiedervorlage-in-vorgang.component.html +++ b/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-list-in-vorgang-container/wiedervorlage-list-in-vorgang/wiedervorlage-in-vorgang/wiedervorlage-in-vorgang.component.html @@ -23,7 +23,10 @@ unter der Lizenz sind dem Lizenztext zu entnehmen. --> -<div [attr.data-test-id]="wiedervorlageResource.betreff | convertForDataTest" class="container"> +<div + [attr.data-test-id]="wiedervorlageResource.betreff | convertForDataTest" + class="container text-sm" +> <div class="row"> <alfa-wiedervorlage-status data-test-class="status" diff --git a/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-list-in-vorgang-container/wiedervorlage-list-in-vorgang/wiedervorlage-in-vorgang/wiedervorlage-in-vorgang.component.spec.ts b/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-list-in-vorgang-container/wiedervorlage-list-in-vorgang/wiedervorlage-in-vorgang/wiedervorlage-in-vorgang.component.spec.ts index f6442e1fe17610a68eb694d0d3ab5676244ba8db..2905c68dcec2713e72866dffb2902e04e570d518 100644 --- a/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-list-in-vorgang-container/wiedervorlage-list-in-vorgang/wiedervorlage-in-vorgang/wiedervorlage-in-vorgang.component.spec.ts +++ b/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-list-in-vorgang-container/wiedervorlage-list-in-vorgang/wiedervorlage-in-vorgang/wiedervorlage-in-vorgang.component.spec.ts @@ -21,14 +21,6 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { registerLocaleData } from '@angular/common'; -import localeDe from '@angular/common/locales/de'; -import localeDeExtra from '@angular/common/locales/extra/de'; -import { LOCALE_ID } from '@angular/core'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { MAT_DATE_LOCALE } from '@angular/material/core'; -import { MatIcon } from '@angular/material/icon'; -import { RouterTestingModule } from '@angular/router/testing'; import { ConvertForDataTestPipe, FormatToPrettyDatePipe, @@ -44,9 +36,17 @@ import { TextWithUserNameTooltipContainerComponent, } from '@alfa-client/user-profile'; import { WiedervorlageResource } from '@alfa-client/wiedervorlage-shared'; -import { MatTooltipDirective } from 'libs/ui/src/lib/ui/mattooltip/mattooltip.directive'; +import { registerLocaleData } from '@angular/common'; +import localeDe from '@angular/common/locales/de'; +import localeDeExtra from '@angular/common/locales/extra/de'; +import { LOCALE_ID } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MAT_DATE_LOCALE } from '@angular/material/core'; +import { MatIcon } from '@angular/material/icon'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { RouterTestingModule } from '@angular/router/testing'; import { createWiedervorlageResource } from 'libs/wiedervorlage-shared/test/wiedervorlage'; -import { MockComponent } from 'ng-mocks'; +import { MockComponent, MockModule } from 'ng-mocks'; import { WiedervorlageStatusComponent } from '../../../wiedervorlage-status/wiedervorlage-status.component'; import { WiedervorlageAttachmentListContainerComponent } from './wiedervorlage-attachment-list-container/wiedervorlage-attachment-list-container.component'; import { WiedervorlageInVorgangExpandButtonComponent } from './wiedervorlage-in-vorgang-expand-button/wiedervorlage-in-vorgang-expand-button.component'; @@ -71,7 +71,6 @@ describe('WiedervorlageInVorgangComponent', () => { ToTrafficLightTooltipPipe, ConvertForDataTestPipe, MatIcon, - MatTooltipDirective, HasLinkPipe, MockComponent(ExpansionPanelComponent), MockComponent(WiedervorlageStatusComponent), @@ -79,6 +78,7 @@ describe('WiedervorlageInVorgangComponent', () => { MockComponent(WiedervorlageInVorgangExpandButtonComponent), MockComponent(LinkWithUserNameTooltipContainerComponent), MockComponent(TextWithUserNameTooltipContainerComponent), + MockModule(MatTooltipModule), ], imports: [RouterTestingModule], providers: [ diff --git a/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-list-in-vorgang-list-container/wiedervorlage-list-in-vorgang-list-container.component.spec.ts b/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-list-in-vorgang-list-container/wiedervorlage-list-in-vorgang-list-container.component.spec.ts index 75ab10717c2841090bd7ac8ff5ae5f504fff248f..74236224ee08d62dd4221888ae3cfc50122659d3 100644 --- a/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-list-in-vorgang-list-container/wiedervorlage-list-in-vorgang-list-container.component.spec.ts +++ b/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-list-in-vorgang-list-container/wiedervorlage-list-in-vorgang-list-container.component.spec.ts @@ -21,17 +21,17 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { ComponentFixture, TestBed } from '@angular/core/testing'; import { FormatToPrettyDatePipe, ToTrafficLightTooltipPipe, createStateResource, } from '@alfa-client/tech-shared'; import { mock } from '@alfa-client/test-utils'; -import { MatTooltipDirective } from '@alfa-client/ui'; import { WiedervorlageService } from '@alfa-client/wiedervorlage-shared'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MatTooltipModule } from '@angular/material/tooltip'; import { createWiedervorlageListResource } from 'libs/wiedervorlage-shared/test/wiedervorlage'; -import { MockComponent } from 'ng-mocks'; +import { MockComponent, MockModule } from 'ng-mocks'; import { of } from 'rxjs'; import { WiedervorlageStatusComponent } from '../wiedervorlage-status/wiedervorlage-status.component'; import { WiedervorlageListInVorgangListContainerComponent } from './wiedervorlage-list-in-vorgang-list-container.component'; @@ -49,10 +49,10 @@ describe('WiedervorlageListInVorgangListContainerComponent', () => { await TestBed.configureTestingModule({ declarations: [ WiedervorlageListInVorgangListContainerComponent, - MatTooltipDirective, FormatToPrettyDatePipe, ToTrafficLightTooltipPipe, MockComponent(WiedervorlageStatusComponent), + MockModule(MatTooltipModule), ], providers: [ { diff --git a/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-page-container/wiedervorlage-page/wiedervorlage-breadcrumb-container/wiedervorlage-breadcrumb/wiedervorlage-breadcrumb.component.html b/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-page-container/wiedervorlage-page/wiedervorlage-breadcrumb-container/wiedervorlage-breadcrumb/wiedervorlage-breadcrumb.component.html index c9ba19672327f65810cbf341f1509d9d35efb768..bd029889ffc001a81cfc4018f1c9303de2a51348 100644 --- a/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-page-container/wiedervorlage-page/wiedervorlage-breadcrumb-container/wiedervorlage-breadcrumb/wiedervorlage-breadcrumb.component.html +++ b/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-page-container/wiedervorlage-page/wiedervorlage-breadcrumb-container/wiedervorlage-breadcrumb/wiedervorlage-breadcrumb.component.html @@ -23,8 +23,4 @@ unter der Lizenz sind dem Lizenztext zu entnehmen. --> -<span alfa-aktenzeichen [vorgang]="vorgang"></span> -<span>/</span> -<span>Wiedervorlagen</span> -<span>/</span> -<span>{{ betreff }}</span> +<h2 class="mb-2 mt-1 text-base font-medium">{{ betreff }}</h2> diff --git a/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-page-container/wiedervorlage-page/wiedervorlage-breadcrumb-container/wiedervorlage-breadcrumb/wiedervorlage-breadcrumb.component.scss b/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-page-container/wiedervorlage-page/wiedervorlage-breadcrumb-container/wiedervorlage-breadcrumb/wiedervorlage-breadcrumb.component.scss deleted file mode 100644 index aec918f0e6ec45b7cb161e4fd7a59f18b7d9a4c5..0000000000000000000000000000000000000000 --- a/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-page-container/wiedervorlage-page/wiedervorlage-breadcrumb-container/wiedervorlage-breadcrumb/wiedervorlage-breadcrumb.component.scss +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Copyright (C) 2022 Das Land Schleswig-Holstein vertreten durch den - * Ministerpräsidenten des Landes Schleswig-Holstein - * Staatskanzlei - * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung - * - * Lizenziert unter der EUPL, Version 1.2 oder - sobald - * diese von der Europäischen Kommission genehmigt wurden - - * Folgeversionen der EUPL ("Lizenz"); - * Sie dürfen dieses Werk ausschließlich gemäß - * dieser Lizenz nutzen. - * Eine Kopie der Lizenz finden Sie hier: - * - * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * - * Sofern nicht durch anwendbare Rechtsvorschriften - * gefordert oder in schriftlicher Form vereinbart, wird - * die unter der Lizenz verbreitete Software "so wie sie - * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - - * ausdrücklich oder stillschweigend - verbreitet. - * Die sprachspezifischen Genehmigungen und Beschränkungen - * unter der Lizenz sind dem Lizenztext zu entnehmen. - */ -@import 'variables'; - -:host { - display: block; - font-size: 18px; - margin: 0 -4px 24px -4px; - - ::ng-deep span { - margin: 0 4px; - } - - a { - text-decoration: none; - outline: 0; - margin: -3px; - padding: 3px; - - &:focus { - border-radius: 12px; - background-color: rgba(#777, 0.16); - } - } -} diff --git a/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-page-container/wiedervorlage-page/wiedervorlage-breadcrumb-container/wiedervorlage-breadcrumb/wiedervorlage-breadcrumb.component.ts b/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-page-container/wiedervorlage-page/wiedervorlage-breadcrumb-container/wiedervorlage-breadcrumb/wiedervorlage-breadcrumb.component.ts index d4f5108551db4bc9e246f30efb74d8ff36137003..7de3be70b8613d9de494fb7b5ef8b7e70c5a0813 100644 --- a/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-page-container/wiedervorlage-page/wiedervorlage-breadcrumb-container/wiedervorlage-breadcrumb/wiedervorlage-breadcrumb.component.ts +++ b/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-page-container/wiedervorlage-page/wiedervorlage-breadcrumb-container/wiedervorlage-breadcrumb/wiedervorlage-breadcrumb.component.ts @@ -21,14 +21,13 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { Component, Input, OnChanges, SimpleChanges } from '@angular/core'; import { VorgangResource } from '@alfa-client/vorgang-shared'; import { WiedervorlageMessages, WiedervorlageResource } from '@alfa-client/wiedervorlage-shared'; +import { Component, Input, OnChanges, SimpleChanges } from '@angular/core'; @Component({ selector: 'alfa-wiedervorlage-breadcrumb', templateUrl: './wiedervorlage-breadcrumb.component.html', - styleUrls: ['./wiedervorlage-breadcrumb.component.scss'], }) export class WiedervorlageBreadcrumbComponent implements OnChanges { @Input() vorgang: VorgangResource; diff --git a/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-page-container/wiedervorlage-page/wiedervorlage-page.component.html b/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-page-container/wiedervorlage-page/wiedervorlage-page.component.html index 612c4524067db2d4d7d4d0f312029a33e79603ed..bfca634528875b5bc619c54bf6cf960a268a38d8 100644 --- a/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-page-container/wiedervorlage-page/wiedervorlage-page.component.html +++ b/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-page-container/wiedervorlage-page/wiedervorlage-page.component.html @@ -33,7 +33,8 @@ </ozgcloud-subnavigation> <div class="l-scroll-area--full"> - <div class="wrapper"> + <div class="wrapper grow"> + <h1 class="text-lg font-medium">Wiedervorlage</h1> <alfa-wiedervorlage-breadcrumb-container [wiedervorlage]="wiedervorlageStateResource.resource" ></alfa-wiedervorlage-breadcrumb-container> diff --git a/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-status/wiedervorlage-status.component.spec.ts b/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-status/wiedervorlage-status.component.spec.ts index e256cd43b662deb73056374fee28c375a879a7c6..bf2eefe11e7a37462651bd714ef0c86887b81900 100644 --- a/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-status/wiedervorlage-status.component.spec.ts +++ b/alfa-client/libs/wiedervorlage/src/lib/wiedervorlage-status/wiedervorlage-status.component.spec.ts @@ -21,12 +21,12 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ +import { ToTrafficLightPipe } from '@alfa-client/tech-shared'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { MatIcon } from '@angular/material/icon'; -import { ToTrafficLightPipe } from '@alfa-client/tech-shared'; -import { MatTooltipDirective } from '@alfa-client/ui'; +import { MatTooltipModule } from '@angular/material/tooltip'; import { createWiedervorlageResource } from 'libs/wiedervorlage-shared/test/wiedervorlage'; -import { MockDirective } from 'ng-mocks'; +import { MockModule } from 'ng-mocks'; import { WiedervorlageStatusComponent } from './wiedervorlage-status.component'; const doneIcon: string = '[data-test-class="done-icon"]'; @@ -40,8 +40,8 @@ describe('WiedervorlageStatusComponent', () => { declarations: [ WiedervorlageStatusComponent, ToTrafficLightPipe, - MockDirective(MatTooltipDirective), MatIcon, + MockModule(MatTooltipModule), ], }).compileComponents(); }); diff --git a/alfa-client/package-lock.json b/alfa-client/package-lock.json index a7e9f6cfe77a4da3de7f151bdcbd299486c59c20..986f8539663f4c42b3f2e4cf04bc7cb7d945515a 100644 --- a/alfa-client/package-lock.json +++ b/alfa-client/package-lock.json @@ -1,12 +1,12 @@ { "name": "alfa", - "version": "0.7.0-SNAPSHOT", + "version": "0.8.0-SNAPSHOT", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "alfa", - "version": "0.7.0-SNAPSHOT", + "version": "0.8.0-SNAPSHOT", "license": "MIT", "dependencies": { "@angular/animations": "17.3.10", @@ -79,6 +79,7 @@ "@storybook/core-server": "^8.1.4", "@swc-node/register": "1.9.1", "@swc/core": "~1.5.7", + "@swc/helpers": "~0.5.2", "@testing-library/jest-dom": "6.4.5", "@types/file-saver": "2.0.7", "@types/jest": "29.4.4", @@ -202,13 +203,13 @@ } }, "node_modules/@angular-devkit/architect": { - "version": "0.1800.3", - "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@angular-devkit/architect/-/architect-0.1800.3.tgz", - "integrity": "sha512-ZoQuvCN/Ft4XJ+/XouYFKGoyEYTfZ8I5yI1M4t19lkRb3MwpQribWcZu4PP+SNnS6/9qnW7guxiQGS+CVlqnDg==", + "version": "0.1801.4", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@angular-devkit/architect/-/architect-0.1801.4.tgz", + "integrity": "sha512-Ch1ZwRh1N/vcCKHm4ErLcgZly3tlwdLUDGBaAIlhE3YFGq543Swv6a5IcDw0veD6iGFceJAmbrp+z5hmzI8p5A==", "dev": true, "peer": true, "dependencies": { - "@angular-devkit/core": "18.0.3", + "@angular-devkit/core": "18.1.4", "rxjs": "7.8.1" }, "engines": { @@ -218,15 +219,15 @@ } }, "node_modules/@angular-devkit/architect/node_modules/@angular-devkit/core": { - "version": "18.0.3", - "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@angular-devkit/core/-/core-18.0.3.tgz", - "integrity": "sha512-nTs1KbNSVCVooPdDaeTh1YbggNVaqexbQjXNIvJJzRB8qPkWNPxm0pQeFjU7kWUBg2+aBXN4/CNwU1YHwxfiSQ==", + "version": "18.1.4", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@angular-devkit/core/-/core-18.1.4.tgz", + "integrity": "sha512-lKBsvbqW2QFL8terzNuSDSmKBo8//QNRO4qU5mVJ1fFf4xBJanXKoiAMuADhx+/owVIptnYT59IZ8jUAna+Srg==", "dev": true, "peer": true, "dependencies": { - "ajv": "8.13.0", + "ajv": "8.16.0", "ajv-formats": "3.0.1", - "jsonc-parser": "3.2.1", + "jsonc-parser": "3.3.1", "picomatch": "4.0.2", "rxjs": "7.8.1", "source-map": "0.7.4" @@ -246,9 +247,9 @@ } }, "node_modules/@angular-devkit/architect/node_modules/ajv": { - "version": "8.13.0", - "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/ajv/-/ajv-8.13.0.tgz", - "integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==", + "version": "8.16.0", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/ajv/-/ajv-8.16.0.tgz", + "integrity": "sha512-F0twR8U1ZU67JIEtekUcLkXkoO5mMMmgGD8sK/xUFzJ805jxHQl92hImFAqqXMyMYjSPOyUPAwHYhB72g5sTXw==", "dev": true, "peer": true, "dependencies": { @@ -280,6 +281,13 @@ } } }, + "node_modules/@angular-devkit/architect/node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true, + "peer": true + }, "node_modules/@angular-devkit/architect/node_modules/picomatch": { "version": "4.0.2", "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/picomatch/-/picomatch-4.0.2.tgz", @@ -6276,30 +6284,30 @@ } }, "node_modules/@floating-ui/core": { - "version": "1.6.2", - "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@floating-ui/core/-/core-1.6.2.tgz", - "integrity": "sha512-+2XpQV9LLZeanU4ZevzRnGFg2neDeKHgFLjP6YLW+tly0IvrhqT4u8enLGjLH3qeh85g19xY5rsAusfwTdn5lg==", + "version": "1.6.7", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@floating-ui/core/-/core-1.6.7.tgz", + "integrity": "sha512-yDzVT/Lm101nQ5TCVeK65LtdN7Tj4Qpr9RTXJ2vPFLqtLxwOrpoxAHAJI8J3yYWUc40J0BDBheaitK5SJmno2g==", "dev": true, "peer": true, "dependencies": { - "@floating-ui/utils": "^0.2.0" + "@floating-ui/utils": "^0.2.7" } }, "node_modules/@floating-ui/dom": { - "version": "1.6.5", - "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@floating-ui/dom/-/dom-1.6.5.tgz", - "integrity": "sha512-Nsdud2X65Dz+1RHjAIP0t8z5e2ff/IRbei6BqFrl1urT8sDVzM1HMQ+R0XcU5ceRfyO3I6ayeqIfh+6Wb8LGTw==", + "version": "1.6.10", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@floating-ui/dom/-/dom-1.6.10.tgz", + "integrity": "sha512-fskgCFv8J8OamCmyun8MfjB1Olfn+uZKjOKZ0vhYF3gRmEUXcGOjxWL8bBr7i4kIuPZ2KD2S3EUIOxnjC8kl2A==", "dev": true, "peer": true, "dependencies": { - "@floating-ui/core": "^1.0.0", - "@floating-ui/utils": "^0.2.0" + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.7" } }, "node_modules/@floating-ui/react-dom": { - "version": "2.1.0", - "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@floating-ui/react-dom/-/react-dom-2.1.0.tgz", - "integrity": "sha512-lNzj5EQmEKn5FFKc04+zasr09h/uX8RtJRNj5gUXsSQIXHVWTVh+hVAg1vOMCexkX8EgvemMvIFpQfkosnVNyA==", + "version": "2.1.1", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@floating-ui/react-dom/-/react-dom-2.1.1.tgz", + "integrity": "sha512-4h84MJt3CHrtG18mGsXuLCHMrug49d7DFkU0RMIyshRveBeyV2hmV/pDaF2Uxtu8kgq5r46llp5E5FQiR0K2Yg==", "dev": true, "peer": true, "dependencies": { @@ -6311,9 +6319,9 @@ } }, "node_modules/@floating-ui/utils": { - "version": "0.2.2", - "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@floating-ui/utils/-/utils-0.2.2.tgz", - "integrity": "sha512-J4yDIIthosAsRZ5CPYP/jQvUAQtlZTTD/4suA08/FEnlxqW3sKS9iAhgsa9VYLZ6vDHn/ixJgIqRQPotoBjxIw==", + "version": "0.2.7", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@floating-ui/utils/-/utils-0.2.7.tgz", + "integrity": "sha512-X8R8Oj771YRl/w+c1HqAC1szL8zWQRwFvgDwT129k9ACdBoud/+/rX9V0qiMl6LWUdP9voC2nDVZYPMQQsb6eA==", "dev": true, "peer": true }, @@ -8465,13 +8473,13 @@ } }, "node_modules/@nrwl/devkit": { - "version": "19.2.3", - "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@nrwl/devkit/-/devkit-19.2.3.tgz", - "integrity": "sha512-OL6sc70gR/USasvbYzyYY44Hd5ZCde2UfiA5h8VeAYAJbq+JmtscpvjcnZ7OIsXyYEOxe1rypULElqu/8qpKzQ==", + "version": "19.5.7", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@nrwl/devkit/-/devkit-19.5.7.tgz", + "integrity": "sha512-sTEwqsAT6bMturU14o/0O6v509OkwGOglxpbiL/zIYO/fDkMoNgnhlHBIT87i4YVuofMz2Z+hTfjDskzDPRSYw==", "dev": true, "peer": true, "dependencies": { - "@nx/devkit": "19.2.3" + "@nx/devkit": "19.5.7" } }, "node_modules/@nrwl/eslint-plugin-nx": { @@ -8985,13 +8993,13 @@ } }, "node_modules/@nx/devkit": { - "version": "19.2.3", - "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@nx/devkit/-/devkit-19.2.3.tgz", - "integrity": "sha512-if1WwRVexrQBBADObEcxVIivq4QRZWY/nYRhCQy/qfFI6Cu2jBSI6ZQ1uy7to2L2sQPLgn8v2beQZiAeZdIktg==", + "version": "19.5.7", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@nx/devkit/-/devkit-19.5.7.tgz", + "integrity": "sha512-mUtZQcdqbF0Q9HfyG14jmpPCtZ1GnVaLNIADZv5SLpFyfh4ZjaBw6wdjPj7Sp3imLoyqMrcd9nCRNO2hlem8bw==", "dev": true, "peer": true, "dependencies": { - "@nrwl/devkit": "19.2.3", + "@nrwl/devkit": "19.5.7", "ejs": "^3.1.7", "enquirer": "~2.3.6", "ignore": "^5.0.4", @@ -11055,28 +11063,61 @@ } }, "node_modules/@radix-ui/react-roving-focus": { - "version": "1.0.4", - "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.4.tgz", - "integrity": "sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ==", + "version": "1.1.0", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.0.tgz", + "integrity": "sha512-EA6AMGeq9AEeQDeSH0aZgG198qkfHSbvWTf1HvoDmOB5bBG/qTxjYMWUKMnYiV6J/iP/J8MEFSuB2zRU2n7ODA==", "dev": true, "peer": true, "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/primitive": "1.0.1", - "@radix-ui/react-collection": "1.0.3", - "@radix-ui/react-compose-refs": "1.0.1", - "@radix-ui/react-context": "1.0.1", - "@radix-ui/react-direction": "1.0.1", - "@radix-ui/react-id": "1.0.1", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-use-callback-ref": "1.0.1", - "@radix-ui/react-use-controllable-state": "1.0.1" + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-collection": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/primitive": { + "version": "1.1.0", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@radix-ui/primitive/-/primitive-1.1.0.tgz", + "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==", + "dev": true, + "peer": true + }, + "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-collection": { + "version": "1.1.0", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@radix-ui/react-collection/-/react-collection-1.1.0.tgz", + "integrity": "sha512-GZsZslMJEyo1VKm5L1ZJY8tGDxZNPAoUeQUIbKeJfoi7Q4kmig5AsgLMYYuyYbfjd8fBmFORAIwYAkXMnXZgZw==", + "dev": true, + "peer": true, + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { @@ -11087,6 +11128,167 @@ } } }, + "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.0", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", + "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", + "dev": true, + "peer": true, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-context": { + "version": "1.1.0", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@radix-ui/react-context/-/react-context-1.1.0.tgz", + "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", + "dev": true, + "peer": true, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-direction": { + "version": "1.1.0", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@radix-ui/react-direction/-/react-direction-1.1.0.tgz", + "integrity": "sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==", + "dev": true, + "peer": true, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-id": { + "version": "1.1.0", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@radix-ui/react-id/-/react-id-1.1.0.tgz", + "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==", + "dev": true, + "peer": true, + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-primitive": { + "version": "2.0.0", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", + "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", + "dev": true, + "peer": true, + "dependencies": { + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-slot": { + "version": "1.1.0", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", + "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", + "dev": true, + "peer": true, + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.0", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", + "integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==", + "dev": true, + "peer": true, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.1.0", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz", + "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==", + "dev": true, + "peer": true, + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.0", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz", + "integrity": "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==", + "dev": true, + "peer": true, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-select": { "version": "1.2.2", "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@radix-ui/react-select/-/react-select-1.2.2.tgz", @@ -11133,20 +11335,19 @@ } }, "node_modules/@radix-ui/react-separator": { - "version": "1.0.3", - "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@radix-ui/react-separator/-/react-separator-1.0.3.tgz", - "integrity": "sha512-itYmTy/kokS21aiV5+Z56MZB54KrhPgn6eHDKkFeOLR34HMN2s8PaN47qZZAGnvupcjxHaFZnW4pQEh0BvvVuw==", + "version": "1.1.0", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@radix-ui/react-separator/-/react-separator-1.1.0.tgz", + "integrity": "sha512-3uBAs+egzvJBDZAzvb/n4NxxOYpnspmWxO2u5NbZ8Y6FM/NdrGSF9bop3Cf6F6C71z1rTSn8KV0Fo2ZVd79lGA==", "dev": true, "peer": true, "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-primitive": "1.0.3" + "@radix-ui/react-primitive": "2.0.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { @@ -11157,6 +11358,65 @@ } } }, + "node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.0", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", + "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", + "dev": true, + "peer": true, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive": { + "version": "2.0.0", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", + "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", + "dev": true, + "peer": true, + "dependencies": { + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-slot": { + "version": "1.1.0", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", + "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", + "dev": true, + "peer": true, + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-slot": { "version": "1.0.2", "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@radix-ui/react-slot/-/react-slot-1.0.2.tgz", @@ -11177,22 +11437,21 @@ } }, "node_modules/@radix-ui/react-toggle": { - "version": "1.0.3", - "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@radix-ui/react-toggle/-/react-toggle-1.0.3.tgz", - "integrity": "sha512-Pkqg3+Bc98ftZGsl60CLANXQBBQ4W3mTFS9EJvNxKMZ7magklKV69/id1mlAlOFDDfHvlCms0fx8fA4CMKDJHg==", + "version": "1.1.0", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@radix-ui/react-toggle/-/react-toggle-1.1.0.tgz", + "integrity": "sha512-gwoxaKZ0oJ4vIgzsfESBuSgJNdc0rv12VhHgcqN0TEJmmZixXG/2XpsLK8kzNWYcnaoRIEEQc0bEi3dIvdUpjw==", "dev": true, "peer": true, "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/primitive": "1.0.1", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-use-controllable-state": "1.0.1" + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-controllable-state": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { @@ -11204,26 +11463,104 @@ } }, "node_modules/@radix-ui/react-toggle-group": { - "version": "1.0.4", - "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@radix-ui/react-toggle-group/-/react-toggle-group-1.0.4.tgz", - "integrity": "sha512-Uaj/M/cMyiyT9Bx6fOZO0SAG4Cls0GptBWiBmBxofmDbNVnYYoyRWj/2M/6VCi/7qcXFWnHhRUfdfZFvvkuu8A==", + "version": "1.1.0", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.0.tgz", + "integrity": "sha512-PpTJV68dZU2oqqgq75Uzto5o/XfOVgkrJ9rulVmfTKxWp3HfUjHE6CP/WLRR4AzPX9HWxw7vFow2me85Yu+Naw==", "dev": true, "peer": true, "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/primitive": "1.0.1", - "@radix-ui/react-context": "1.0.1", - "@radix-ui/react-direction": "1.0.1", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-roving-focus": "1.0.4", - "@radix-ui/react-toggle": "1.0.3", - "@radix-ui/react-use-controllable-state": "1.0.1" + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-roving-focus": "1.1.0", + "@radix-ui/react-toggle": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle-group/node_modules/@radix-ui/primitive": { + "version": "1.1.0", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@radix-ui/primitive/-/primitive-1.1.0.tgz", + "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==", + "dev": true, + "peer": true + }, + "node_modules/@radix-ui/react-toggle-group/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.0", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", + "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", + "dev": true, + "peer": true, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle-group/node_modules/@radix-ui/react-context": { + "version": "1.1.0", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@radix-ui/react-context/-/react-context-1.1.0.tgz", + "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", + "dev": true, + "peer": true, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle-group/node_modules/@radix-ui/react-direction": { + "version": "1.1.0", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@radix-ui/react-direction/-/react-direction-1.1.0.tgz", + "integrity": "sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==", + "dev": true, + "peer": true, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle-group/node_modules/@radix-ui/react-primitive": { + "version": "2.0.0", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", + "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", + "dev": true, + "peer": true, + "dependencies": { + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { @@ -11234,27 +11571,181 @@ } } }, + "node_modules/@radix-ui/react-toggle-group/node_modules/@radix-ui/react-slot": { + "version": "1.1.0", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", + "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", + "dev": true, + "peer": true, + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle-group/node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.0", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", + "integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==", + "dev": true, + "peer": true, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle-group/node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.1.0", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz", + "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==", + "dev": true, + "peer": true, + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle/node_modules/@radix-ui/primitive": { + "version": "1.1.0", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@radix-ui/primitive/-/primitive-1.1.0.tgz", + "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==", + "dev": true, + "peer": true + }, + "node_modules/@radix-ui/react-toggle/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.0", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", + "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", + "dev": true, + "peer": true, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle/node_modules/@radix-ui/react-primitive": { + "version": "2.0.0", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", + "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", + "dev": true, + "peer": true, + "dependencies": { + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle/node_modules/@radix-ui/react-slot": { + "version": "1.1.0", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", + "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", + "dev": true, + "peer": true, + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle/node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.0", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", + "integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==", + "dev": true, + "peer": true, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle/node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.1.0", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz", + "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==", + "dev": true, + "peer": true, + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-toolbar": { - "version": "1.0.4", - "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@radix-ui/react-toolbar/-/react-toolbar-1.0.4.tgz", - "integrity": "sha512-tBgmM/O7a07xbaEkYJWYTXkIdU/1pW4/KZORR43toC/4XWyBCURK0ei9kMUdp+gTPPKBgYLxXmRSH1EVcIDp8Q==", + "version": "1.1.0", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@radix-ui/react-toolbar/-/react-toolbar-1.1.0.tgz", + "integrity": "sha512-ZUKknxhMTL/4hPh+4DuaTot9aO7UD6Kupj4gqXCsBTayX1pD1L+0C2/2VZKXb4tIifQklZ3pf2hG9T+ns+FclQ==", "dev": true, "peer": true, "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/primitive": "1.0.1", - "@radix-ui/react-context": "1.0.1", - "@radix-ui/react-direction": "1.0.1", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-roving-focus": "1.0.4", - "@radix-ui/react-separator": "1.0.3", - "@radix-ui/react-toggle-group": "1.0.4" + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-roving-focus": "1.1.0", + "@radix-ui/react-separator": "1.1.0", + "@radix-ui/react-toggle-group": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { @@ -11265,6 +11756,104 @@ } } }, + "node_modules/@radix-ui/react-toolbar/node_modules/@radix-ui/primitive": { + "version": "1.1.0", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@radix-ui/primitive/-/primitive-1.1.0.tgz", + "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==", + "dev": true, + "peer": true + }, + "node_modules/@radix-ui/react-toolbar/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.0", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", + "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", + "dev": true, + "peer": true, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toolbar/node_modules/@radix-ui/react-context": { + "version": "1.1.0", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@radix-ui/react-context/-/react-context-1.1.0.tgz", + "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", + "dev": true, + "peer": true, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toolbar/node_modules/@radix-ui/react-direction": { + "version": "1.1.0", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@radix-ui/react-direction/-/react-direction-1.1.0.tgz", + "integrity": "sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==", + "dev": true, + "peer": true, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toolbar/node_modules/@radix-ui/react-primitive": { + "version": "2.0.0", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", + "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", + "dev": true, + "peer": true, + "dependencies": { + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toolbar/node_modules/@radix-ui/react-slot": { + "version": "1.1.0", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", + "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", + "dev": true, + "peer": true, + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-use-callback-ref": { "version": "1.0.1", "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz", @@ -13612,19 +14201,19 @@ } }, "node_modules/@storybook/components": { - "version": "7.6.19", - "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@storybook/components/-/components-7.6.19.tgz", - "integrity": "sha512-8Zw/RQ4crzKkUR7ojxvRIj8vktKiBBO8Nq93qv4JfDqDWrcR7cro0hOlZgmZmrzbFunBBt6WlsNNO6nVP7R4Xw==", + "version": "7.6.20", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@storybook/components/-/components-7.6.20.tgz", + "integrity": "sha512-0d8u4m558R+W5V+rseF/+e9JnMciADLXTpsILrG+TBhwECk0MctIWW18bkqkujdCm8kDZr5U2iM/5kS1Noy7Ug==", "dev": true, "peer": true, "dependencies": { "@radix-ui/react-select": "^1.2.2", "@radix-ui/react-toolbar": "^1.0.4", - "@storybook/client-logger": "7.6.19", + "@storybook/client-logger": "7.6.20", "@storybook/csf": "^0.1.2", "@storybook/global": "^5.0.0", - "@storybook/theming": "7.6.19", - "@storybook/types": "7.6.19", + "@storybook/theming": "7.6.20", + "@storybook/types": "7.6.20", "memoizerific": "^1.11.3", "use-resize-observer": "^9.1.0", "util-deprecate": "^1.0.2" @@ -13639,14 +14228,14 @@ } }, "node_modules/@storybook/components/node_modules/@storybook/channels": { - "version": "7.6.19", - "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@storybook/channels/-/channels-7.6.19.tgz", - "integrity": "sha512-2JGh+i95GwjtjqWqhtEh15jM5ifwbRGmXeFqkY7dpdHH50EEWafYHr2mg3opK3heVDwg0rJ/VBptkmshloXuvA==", + "version": "7.6.20", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@storybook/channels/-/channels-7.6.20.tgz", + "integrity": "sha512-4hkgPSH6bJclB2OvLnkZOGZW1WptJs09mhQ6j6qLjgBZzL/ZdD6priWSd7iXrmPiN5TzUobkG4P4Dp7FjkiO7A==", "dev": true, "peer": true, "dependencies": { - "@storybook/client-logger": "7.6.19", - "@storybook/core-events": "7.6.19", + "@storybook/client-logger": "7.6.20", + "@storybook/core-events": "7.6.20", "@storybook/global": "^5.0.0", "qs": "^6.10.0", "telejson": "^7.2.0", @@ -13658,9 +14247,9 @@ } }, "node_modules/@storybook/components/node_modules/@storybook/client-logger": { - "version": "7.6.19", - "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@storybook/client-logger/-/client-logger-7.6.19.tgz", - "integrity": "sha512-oGzOxbmLmciSIfd5gsxDzPmX8DttWhoYdPKxjMuCuWLTO2TWpkCWp1FTUMWO72mm/6V/FswT/aqpJJBBvdZ3RQ==", + "version": "7.6.20", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@storybook/client-logger/-/client-logger-7.6.20.tgz", + "integrity": "sha512-NwG0VIJQCmKrSaN5GBDFyQgTAHLNishUPLW1NrzqTDNAhfZUoef64rPQlinbopa0H4OXmlB+QxbQIb3ubeXmSQ==", "dev": true, "peer": true, "dependencies": { @@ -13672,13 +14261,13 @@ } }, "node_modules/@storybook/components/node_modules/@storybook/types": { - "version": "7.6.19", - "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@storybook/types/-/types-7.6.19.tgz", - "integrity": "sha512-DeGYrRPRMGTVfT7o2rEZtRzyLT2yKTI2exgpnxbwPWEFAduZCSfzBrcBXZ/nb5B0pjA9tUNWls1YzGkJGlkhpg==", + "version": "7.6.20", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@storybook/types/-/types-7.6.20.tgz", + "integrity": "sha512-GncdY3x0LpbhmUAAJwXYtJDUQEwfF175gsjH0/fxPkxPoV7Sef9TM41jQLJW/5+6TnZoCZP/+aJZTJtq3ni23Q==", "dev": true, "peer": true, "dependencies": { - "@storybook/channels": "7.6.19", + "@storybook/channels": "7.6.20", "@types/babel__core": "^7.0.0", "@types/express": "^4.7.0", "file-system-cache": "2.3.0" @@ -13853,9 +14442,9 @@ } }, "node_modules/@storybook/core-events": { - "version": "7.6.19", - "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@storybook/core-events/-/core-events-7.6.19.tgz", - "integrity": "sha512-K/W6Uvum0ocZSgjbi8hiotpe+wDEHDZlvN+KlPqdh9ae9xDK8aBNBq9IelCoqM+uKO1Zj+dDfSQds7CD781DJg==", + "version": "7.6.20", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@storybook/core-events/-/core-events-7.6.20.tgz", + "integrity": "sha512-tlVDuVbDiNkvPDFAu+0ou3xBBYbx9zUURQz4G9fAq0ScgBOs/bpzcRrFb4mLpemUViBAd47tfZKdH4MAX45KVQ==", "dev": true, "peer": true, "dependencies": { @@ -14383,14 +14972,14 @@ } }, "node_modules/@storybook/theming": { - "version": "7.6.19", - "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@storybook/theming/-/theming-7.6.19.tgz", - "integrity": "sha512-sAho13MmtA80ctOaLn8lpkQBsPyiqSdLcOPH5BWFhatQzzBQCpTAKQk+q/xGju8bNiPZ+yQBaBzbN8SfX8ceCg==", + "version": "7.6.20", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@storybook/theming/-/theming-7.6.20.tgz", + "integrity": "sha512-iT1pXHkSkd35JsCte6Qbanmprx5flkqtSHC6Gi6Umqoxlg9IjiLPmpHbaIXzoC06DSW93hPj5Zbi1lPlTvRC7Q==", "dev": true, "peer": true, "dependencies": { "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", - "@storybook/client-logger": "7.6.19", + "@storybook/client-logger": "7.6.20", "@storybook/global": "^5.0.0", "memoizerific": "^1.11.3" }, @@ -14404,9 +14993,9 @@ } }, "node_modules/@storybook/theming/node_modules/@storybook/client-logger": { - "version": "7.6.19", - "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@storybook/client-logger/-/client-logger-7.6.19.tgz", - "integrity": "sha512-oGzOxbmLmciSIfd5gsxDzPmX8DttWhoYdPKxjMuCuWLTO2TWpkCWp1FTUMWO72mm/6V/FswT/aqpJJBBvdZ3RQ==", + "version": "7.6.20", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@storybook/client-logger/-/client-logger-7.6.20.tgz", + "integrity": "sha512-NwG0VIJQCmKrSaN5GBDFyQgTAHLNishUPLW1NrzqTDNAhfZUoef64rPQlinbopa0H4OXmlB+QxbQIb3ubeXmSQ==", "dev": true, "peer": true, "dependencies": { @@ -14675,6 +15264,15 @@ "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", "devOptional": true }, + "node_modules/@swc/helpers": { + "version": "0.5.12", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@swc/helpers/-/helpers-0.5.12.tgz", + "integrity": "sha512-KMZNXiGibsW9kvZAO1Pam2JPTDBm+KSHMMHWdsyI/1DbIZjT2A6Gy3hblVXUMEDvUAKq+e0vL0X0o54owWji7g==", + "devOptional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@swc/types": { "version": "0.1.8", "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/@swc/types/-/types-0.1.8.tgz", @@ -19867,9 +20465,9 @@ "dev": true }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.3.6", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", "dependencies": { "ms": "2.1.2" }, @@ -27768,32 +28366,32 @@ } }, "node_modules/mocha": { - "version": "10.4.0", - "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/mocha/-/mocha-10.4.0.tgz", - "integrity": "sha512-eqhGB8JKapEYcC4ytX/xrzKforgEc3j1pGlAXVy3eRwrtAy5/nIfT1SvgGzfN0XZZxeLq0aQWkOUAmqIJiv+bA==", + "version": "10.7.0", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/mocha/-/mocha-10.7.0.tgz", + "integrity": "sha512-v8/rBWr2VO5YkspYINnvu81inSz2y3ODJrhO175/Exzor1RcEZZkizgE2A+w/CAXXoESS8Kys5E62dOHGHzULA==", "dev": true, "peer": true, "dependencies": { - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.4", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "8.1.0", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "5.0.1", - "ms": "2.1.3", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "workerpool": "6.2.1", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" + "ansi-colors": "^4.1.3", + "browser-stdout": "^1.3.1", + "chokidar": "^3.5.3", + "debug": "^4.3.5", + "diff": "^5.2.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^8.1.0", + "he": "^1.2.0", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^5.1.6", + "ms": "^2.1.3", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^6.5.1", + "yargs": "^16.2.0", + "yargs-parser": "^20.2.9", + "yargs-unparser": "^2.0.0" }, "bin": { "_mocha": "bin/_mocha", @@ -27803,16 +28401,6 @@ "node": ">= 14.0.0" } }, - "node_modules/mocha/node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, "node_modules/mocha/node_modules/ansi-regex": { "version": "5.0.1", "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -27839,34 +28427,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/mocha/node_modules/chokidar": { - "version": "3.5.3", - "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "peer": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, "node_modules/mocha/node_modules/cliui": { "version": "7.0.4", "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/cliui/-/cliui-7.0.4.tgz", @@ -27879,16 +28439,6 @@ "wrap-ansi": "^7.0.0" } }, - "node_modules/mocha/node_modules/diff": { - "version": "5.0.0", - "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/mocha/node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -27934,9 +28484,9 @@ } }, "node_modules/mocha/node_modules/minimatch": { - "version": "5.0.1", - "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/minimatch/-/minimatch-5.0.1.tgz", - "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "version": "5.1.6", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, "peer": true, "dependencies": { @@ -27953,16 +28503,6 @@ "dev": true, "peer": true }, - "node_modules/mocha/node_modules/serialize-javascript": { - "version": "6.0.0", - "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, - "peer": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, "node_modules/mocha/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -28030,9 +28570,9 @@ } }, "node_modules/mocha/node_modules/yargs-parser": { - "version": "20.2.4", - "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "version": "20.2.9", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "dev": true, "peer": true, "engines": { @@ -37868,9 +38408,9 @@ } }, "node_modules/workerpool": { - "version": "6.2.1", - "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/workerpool/-/workerpool-6.2.1.tgz", - "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "version": "6.5.1", + "resolved": "http://nexus.ozg-sh.de/repository/npm-proxy/workerpool/-/workerpool-6.5.1.tgz", + "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", "dev": true, "peer": true }, diff --git a/alfa-client/package.json b/alfa-client/package.json index 6f0148b7f31491fecb49b5b507cf6fe7191ae40e..a02724a20ba7b3e3d31967f73f9e0ec13ad8fce5 100644 --- a/alfa-client/package.json +++ b/alfa-client/package.json @@ -1,6 +1,6 @@ { "name": "alfa", - "version": "0.7.0-SNAPSHOT", + "version": "0.8.0-SNAPSHOT", "license": "MIT", "scripts": { "start": "nx run alfa:serve --port 4300 --disable-host-check", @@ -13,7 +13,7 @@ "ci-build-alfa-client-container": "nx container alfa", "test": "nx affected --target=test --parallel 8 -- --runInBand", "test:cov": "jest --coverage", - "test:lib": "nx test ${npm_config_lib} --watchAll", + "test:lib": "nx test ${npm_config_lib}", "test:debug:lib": "nx test ${npm_config_lib} --detectOpenHandles --watchAll", "ci-build": "nx run alfa:build --outputHashing=all", "ci-build-admin": "nx container admin && cp -r dist/ apps/admin/", diff --git a/alfa-client/pom.xml b/alfa-client/pom.xml index 31c038a64e63e52771d87ecb1085e907e9af2cb8..b36055f368b4404644459bb3f3816469ba738293 100644 --- a/alfa-client/pom.xml +++ b/alfa-client/pom.xml @@ -29,7 +29,7 @@ <parent> <groupId>de.ozgcloud.alfa</groupId> <artifactId>alfa</artifactId> - <version>2.11.0-SNAPSHOT</version> + <version>2.12.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> diff --git a/alfa-client/tsconfig.base.json b/alfa-client/tsconfig.base.json index b8645da774def8753885d8a14cfd87a1e8f887eb..dcd7a091e2e4c3e485af22ec4b92386bfee0394d 100644 --- a/alfa-client/tsconfig.base.json +++ b/alfa-client/tsconfig.base.json @@ -23,6 +23,7 @@ "@alfa-client/bescheid-shared": ["libs/bescheid-shared/src/index.ts"], "@alfa-client/binary-file": ["libs/binary-file/src/index.ts"], "@alfa-client/binary-file-shared": ["libs/binary-file-shared/src/index.ts"], + "@alfa-client/collaboration": ["libs/collaboration/src/index.ts"], "@alfa-client/command-shared": ["libs/command-shared/src/index.ts"], "@alfa-client/environment-shared": ["libs/environment-shared/src/index.ts"], "@alfa-client/forwarding": ["libs/forwarding/src/index.ts"], @@ -55,7 +56,8 @@ "@alfa-client/wiedervorlage-shared": ["libs/wiedervorlage-shared/src/index.ts"], "@ods/component": ["libs/design-component/src/index.ts"], "@ods/system": ["libs/design-system/src/index.ts"], - "authentication": ["libs/authentication/src/index.ts"] + "authentication": ["libs/authentication/src/index.ts"], + "test": ["libs/test/src/index.ts"] } }, "exclude": ["node_modules", "tmp"] diff --git a/alfa-server/pom.xml b/alfa-server/pom.xml index 5124be2c496ebd8623a1445b16fdcb0a2074c4f0..e157390eee8d5e6722072745abd75cb760f831ec 100644 --- a/alfa-server/pom.xml +++ b/alfa-server/pom.xml @@ -5,7 +5,7 @@ <parent> <groupId>de.ozgcloud.alfa</groupId> <artifactId>alfa</artifactId> - <version>2.11.0-SNAPSHOT</version> + <version>2.12.0-SNAPSHOT</version> </parent> <artifactId>alfa-server</artifactId> diff --git a/alfa-server/src/main/resources/application-dev.yml b/alfa-server/src/main/resources/application-dev.yml index 52ca56a22d8fa5ded4dcecfae01b3e22553c2da1..6f11904235f17602539afaca47cd695e69deee8b 100644 --- a/alfa-server/src/main/resources/application-dev.yml +++ b/alfa-server/src/main/resources/application-dev.yml @@ -10,6 +10,7 @@ server: ozgcloud: feature: reply-always-allowed: true + collaboration-enabled: true production: false stage: production: false diff --git a/alfa-server/src/main/resources/application-e2e.yml b/alfa-server/src/main/resources/application-e2e.yml index 7aa16cc6e02921c1fa3157a05b185fcbf08c623d..69d9b46a198e57b5d03f01cc78f2d83e209f9335 100644 --- a/alfa-server/src/main/resources/application-e2e.yml +++ b/alfa-server/src/main/resources/application-e2e.yml @@ -8,8 +8,8 @@ keycloak: ozgcloud: feature: - createBescheid: true reply-always-allowed: true + collaboration-enabled: true forwarding: lninfo: url: classpath:files/LandesnetzInfo.html diff --git a/alfa-server/src/main/resources/application-local.yml b/alfa-server/src/main/resources/application-local.yml index b113a7052d27746ebf67fdd3600b381e7e658dd5..74743fd293aab8aeda88d286548ff33e73ea55d7 100644 --- a/alfa-server/src/main/resources/application-local.yml +++ b/alfa-server/src/main/resources/application-local.yml @@ -18,6 +18,7 @@ grpc: ozgcloud: feature: reply-always-allowed: true + collaboration-enabled: true production: false user-assistance: documentation: diff --git a/alfa-service/javac.20240524_095230.args b/alfa-service/javac.20240524_095230.args deleted file mode 100644 index afb64b5f04472a1efb08d555a627210a5f51a84e..0000000000000000000000000000000000000000 --- a/alfa-service/javac.20240524_095230.args +++ /dev/null @@ -1 +0,0 @@ -@/tmp/org.codehaus.plexus.compiler.javac.JavacCompiler16216354973819705899arguments diff --git a/alfa-service/pom.xml b/alfa-service/pom.xml index f2e9a4141f8dc1c75e3769a1671b1ca511714d8c..4d4e6c9ae8b0eca82e0af3bf544e12e11dc7fa15 100644 --- a/alfa-service/pom.xml +++ b/alfa-service/pom.xml @@ -31,7 +31,7 @@ <parent> <groupId>de.ozgcloud.alfa</groupId> <artifactId>alfa</artifactId> - <version>2.11.0-SNAPSHOT</version> + <version>2.12.0-SNAPSHOT</version> </parent> <artifactId>alfa-service</artifactId> diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/collaboration/CollaborationVorgangProcessor.java b/alfa-service/src/main/java/de/ozgcloud/alfa/collaboration/CollaborationVorgangProcessor.java new file mode 100644 index 0000000000000000000000000000000000000000..d8c0a04560d014f582b19b2aeecf5306d2ed39be --- /dev/null +++ b/alfa-service/src/main/java/de/ozgcloud/alfa/collaboration/CollaborationVorgangProcessor.java @@ -0,0 +1,43 @@ +package de.ozgcloud.alfa.collaboration; + +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*; + +import java.util.Objects; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.hateoas.EntityModel; +import org.springframework.hateoas.LinkRelation; +import org.springframework.hateoas.server.RepresentationModelProcessor; +import org.springframework.stereotype.Component; + +import de.ozgcloud.alfa.common.ModelBuilder; +import de.ozgcloud.alfa.common.command.CommandController; +import de.ozgcloud.alfa.common.user.CurrentUserService; +import de.ozgcloud.alfa.common.user.UserRole; +import de.ozgcloud.alfa.vorgang.VorgangWithEingang; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Component +@ConditionalOnProperty("ozgcloud.feature.collaboration-enabled") +class CollaborationVorgangProcessor implements RepresentationModelProcessor<EntityModel<VorgangWithEingang>> { + + static final LinkRelation REL_CREATE_COLLABORATION_REQUEST = LinkRelation.of("createCollaborationRequest"); + + private final CurrentUserService currentUserService; + + @Override + public EntityModel<VorgangWithEingang> process(EntityModel<VorgangWithEingang> model) { + var vorgang = model.getContent(); + + if (Objects.isNull(vorgang)) { + return model; + } + + return ModelBuilder.fromModel(model) + .ifMatch(() -> currentUserService.hasRole(UserRole.VERWALTUNG_USER)) + .addLink(linkTo(methodOn(CommandController.CommandByRelationController.class).createCommand(vorgang.getId(), vorgang.getId(), + vorgang.getVersion(), null)).withRel(REL_CREATE_COLLABORATION_REQUEST)) + .buildModel(); + } +} diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/common/FeatureToggleProperties.java b/alfa-service/src/main/java/de/ozgcloud/alfa/common/FeatureToggleProperties.java index b06e637da196c9b9d5dfb11a5d51a72fb505da7d..5b60fc67206213d2be23bda2aebddfb539873da5 100644 --- a/alfa-service/src/main/java/de/ozgcloud/alfa/common/FeatureToggleProperties.java +++ b/alfa-service/src/main/java/de/ozgcloud/alfa/common/FeatureToggleProperties.java @@ -13,12 +13,12 @@ import lombok.Setter; public class FeatureToggleProperties { /** - * Enable/Disable bescheid creation feature. + * Enable mail reply option regardless of other configuration. */ - private boolean createBescheid = false; + private boolean replyAlwaysAllowed = false; /** - * Enable mail reply option regardless of other configuration. + * Enable collaboration-feature in Vorgang */ - private boolean replyAlwaysAllowed = false; + private boolean collaborationEnabled = false; } diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/common/command/Command.java b/alfa-service/src/main/java/de/ozgcloud/alfa/common/command/Command.java index 9e862c4756eb06bfa8aa47591b7d390de442d865..1a5d2ac5fda7dc714f9f2b13514dc22705159219 100644 --- a/alfa-service/src/main/java/de/ozgcloud/alfa/common/command/Command.java +++ b/alfa-service/src/main/java/de/ozgcloud/alfa/common/command/Command.java @@ -26,6 +26,8 @@ package de.ozgcloud.alfa.common.command; import java.time.ZonedDateTime; import java.util.Map; +import org.apache.commons.lang3.StringUtils; + import com.fasterxml.jackson.annotation.JsonIgnore; import de.ozgcloud.alfa.common.LinkedUserProfileResource; @@ -74,4 +76,14 @@ public class Command { public CommandOrder getCommandOrder() { return CommandOrder.fromOrder(order); } + + @JsonIgnore + public boolean isFinishedSuccessfully() { + return status == CommandStatus.FINISHED && StringUtils.isEmpty(errorMessage); + } + + @JsonIgnore + public boolean isNotDone() { + return status.isNotDone(); + } } \ No newline at end of file diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/common/command/CommandService.java b/alfa-service/src/main/java/de/ozgcloud/alfa/common/command/CommandService.java index 777ad9b7689d27e87f460276f96ffbd46d99ffe3..da98c4e572fda1d5c7175040bd88eace508555f6 100644 --- a/alfa-service/src/main/java/de/ozgcloud/alfa/common/command/CommandService.java +++ b/alfa-service/src/main/java/de/ozgcloud/alfa/common/command/CommandService.java @@ -23,27 +23,30 @@ */ package de.ozgcloud.alfa.common.command; +import java.util.Calendar; import java.util.Optional; import java.util.stream.Stream; import jakarta.validation.Valid; import org.apache.commons.collections.MapUtils; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; import de.ozgcloud.alfa.loeschanforderung.DeleteLoeschAnforderung; import lombok.NonNull; +import lombok.RequiredArgsConstructor; +@RequiredArgsConstructor @Validated @Service public class CommandService { static final long NO_RELATION_VERSION = -1; + private static final int WAIT_TIME_MS = 500; + private static final int COMMAND_REQUEST_THRESHOLD_MILLIS = 10000; - @Autowired - private CommandRemoteService remoteService; + private final CommandRemoteService remoteService; /** * @deprecated use {@link #createCommand(CreateCommand)} instead @@ -111,4 +114,25 @@ public class CommandService { return remoteService.findCommands(vorgangId, Optional.of(CommandStatus.FINISHED), Optional.empty()); } + public Command waitUntilDone(Command commandToWaitFor) { + var command = commandToWaitFor; + var calendar = Calendar.getInstance(); + var timeout = calendar.getTimeInMillis() + COMMAND_REQUEST_THRESHOLD_MILLIS; + while (command.isNotDone() && calendar.getTimeInMillis() < timeout) { + synchronized (this) { + try { + wait(WAIT_TIME_MS); + command = reloadCommand(command.getId()); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + } + return command; + } + + public Command reloadCommand(String commandId) { + return remoteService.getCommand(commandId); + } + } \ No newline at end of file diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/common/command/CommandStatus.java b/alfa-service/src/main/java/de/ozgcloud/alfa/common/command/CommandStatus.java index 285ebdaae3817652aa5c80b1d9817628fd044e6c..548107b825bca6aaed64a50f6f24611c9d7a8174 100644 --- a/alfa-service/src/main/java/de/ozgcloud/alfa/common/command/CommandStatus.java +++ b/alfa-service/src/main/java/de/ozgcloud/alfa/common/command/CommandStatus.java @@ -23,6 +23,14 @@ */ package de.ozgcloud.alfa.common.command; +import java.util.Set; + public enum CommandStatus { PENDING, FINISHED, ERROR, REVOKE_PENDING, REVOKED; + + private static final Set<CommandStatus> FINAL_STATES = Set.of(FINISHED, ERROR, REVOKED); + + public boolean isNotDone() { + return !FINAL_STATES.contains(this); + } } diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/common/errorhandling/ExceptionController.java b/alfa-service/src/main/java/de/ozgcloud/alfa/common/errorhandling/ExceptionController.java index 5e4f5be4e7d0a8077ab670e49769d3655c8abd47..facaacd37af7212818e9e220331f7c6a24c4c4a4 100644 --- a/alfa-service/src/main/java/de/ozgcloud/alfa/common/errorhandling/ExceptionController.java +++ b/alfa-service/src/main/java/de/ozgcloud/alfa/common/errorhandling/ExceptionController.java @@ -23,49 +23,38 @@ */ package de.ozgcloud.alfa.common.errorhandling; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Map.Entry; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; import java.util.UUID; -import java.util.stream.Stream; -import jakarta.validation.ConstraintViolation; import jakarta.validation.ConstraintViolationException; -import jakarta.validation.Path; -import jakarta.validation.metadata.ConstraintDescriptor; -import org.hibernate.validator.engine.HibernateConstraintViolation; import org.springframework.core.annotation.Order; import org.springframework.http.HttpStatus; +import org.springframework.http.ProblemDetail; import org.springframework.security.access.AccessDeniedException; -import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; -import de.ozgcloud.alfa.common.binaryfile.DynamicViolationParameter; import de.ozgcloud.common.errorhandling.ExceptionUtil; import de.ozgcloud.common.errorhandling.TechnicalException; +import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; -@ControllerAdvice +@RestControllerAdvice +@RequiredArgsConstructor @Log4j2 @Order(98) -public class ExceptionController { - - private static final Set<String> IGNORABLE_CONSTRAINT_VIOLATION_ATTRIBUTES = new HashSet<>(Arrays.asList("groups", "payload", "message")); +public class ExceptionController extends ResponseEntityExceptionHandler { static final String RUNTIME_MESSAGE_CODE = "generale.server_error"; static final String RESOURCE_NOT_FOUNT_MESSAGE_CODE = "resource.not_found"; static final String ACCESS_DENIED_MESSAGE_CODE = "generale.access_denied"; + private final ProblemDetailMapper problemDetailMapper; + @ExceptionHandler(FunctionalException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) - @ResponseBody public ApiError handleFunctionalException(FunctionalException e) { LOG.error("BadRequest", e); return ApiError.builder().issue(buildIssueForFunctionalException(e)).build(); @@ -77,7 +66,6 @@ public class ExceptionController { @ExceptionHandler(AccessDeniedException.class) @ResponseStatus(HttpStatus.FORBIDDEN) - @ResponseBody public ApiError handleAccessDeniedException(AccessDeniedException e) { var exceptionId = createExceptionId(); var messageWithExceptionId = ExceptionUtil.formatMessageWithExceptionId(e.getMessage(), exceptionId); @@ -92,7 +80,6 @@ public class ExceptionController { @ExceptionHandler(ResourceNotFoundException.class) @ResponseStatus(HttpStatus.NOT_FOUND) - @ResponseBody public ApiError handleResourceNotFoundException(ResourceNotFoundException e) { LOG.warn("Resource not found: {}", e.getMessage()); return ApiError.builder().issue(buildIssueForResourceNotFoundException(e)).build(); @@ -103,66 +90,14 @@ public class ExceptionController { } @ExceptionHandler(ConstraintViolationException.class) - @ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY) - @ResponseBody - public ApiError handleConstraintViolationException(ConstraintViolationException e) { - var exceptionId = createExceptionId(); - var messageWithExceptionId = ExceptionUtil.formatMessageWithExceptionId(e.getMessage(), exceptionId); - LOG.warn("Validation Exception: {}", messageWithExceptionId); - return ApiError.builder().issues(buildIssues(e, exceptionId)).build(); - } - - private List<Issue> buildIssues(ConstraintViolationException e, String exceptionId) { - return e.getConstraintViolations().stream() - .map(violation -> buildIssue(violation, exceptionId)) - .toList(); - } - - private Issue buildIssue(ConstraintViolation<?> violation, String exceptionId) { - return Issue.builder()// - .field(buildFieldPath(violation.getPropertyPath()))// - .messageCode(violation.getMessageTemplate().replace("{", "").replace("}", ""))// - .message(violation.getMessage())// - .parameters(buildParameters(violation).toList()) - .exceptionId(exceptionId) - .build(); - } - - private String buildFieldPath(Path propertyPath) { - return propertyPath.toString().substring(propertyPath.toString().indexOf('.') + 1); - } - - Stream<IssueParam> buildParameters(ConstraintViolation<?> violation) { - var dynamicPayload = getDynamicPayload(violation); - return Optional.ofNullable(violation.getConstraintDescriptor()) - .map(ConstraintDescriptor::getAttributes) - .map(descr -> descr.entrySet().stream() - .filter(entry -> !IGNORABLE_CONSTRAINT_VIOLATION_ATTRIBUTES.contains(entry.getKey())) - .map(entryMap -> buildIssueParam(entryMap, dynamicPayload))) - .orElse(Stream.empty()); - } - - private IssueParam buildIssueParam(Entry<String, Object> entry, Optional<DynamicViolationParameter> dynamicValues) { - return IssueParam.builder().name(entry.getKey()).value(getValue(entry, dynamicValues)).build(); - } - - private String getValue(Entry<String, Object> entry, Optional<DynamicViolationParameter> dynamicValues) { - return dynamicValues - .map(DynamicViolationParameter::getMap) - .map(map -> map.get(entry.getKey())) - .filter(Objects::nonNull) - .map(String::valueOf) - .orElse(String.valueOf(entry.getValue())); - } + public ProblemDetail handleConstraintViolationException(ConstraintViolationException e) { + LOG.warn("Validation Exception: {}", problemDetailMapper.buildMessageWithExceptionId(e)); - private Optional<DynamicViolationParameter> getDynamicPayload(ConstraintViolation<?> violation) { - HibernateConstraintViolation<?> hibernateViolation = violation.unwrap(HibernateConstraintViolation.class); - return Optional.ofNullable(hibernateViolation.getDynamicPayload(DynamicViolationParameter.class)); + return problemDetailMapper.fromConstraintViolationException(e); } @ExceptionHandler(TechnicalException.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) - @ResponseBody public ApiError handleTechnicalException(TechnicalException e) { LOG.error("TechnicalException on Request", e); return buildRuntimeApiError(e.getMessage(), e.getExceptionId()); @@ -170,7 +105,6 @@ public class ExceptionController { @ExceptionHandler(RuntimeException.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) - @ResponseBody public ApiError handleRuntimeException(RuntimeException e) { var exceptionId = createExceptionId(); var messageWithExceptionId = ExceptionUtil.formatMessageWithExceptionId(e.getMessage(), exceptionId); diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/common/errorhandling/ProblemDetailMapper.java b/alfa-service/src/main/java/de/ozgcloud/alfa/common/errorhandling/ProblemDetailMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..c4a358f396674e8bcd4f8f4fb36eb677eec7b74b --- /dev/null +++ b/alfa-service/src/main/java/de/ozgcloud/alfa/common/errorhandling/ProblemDetailMapper.java @@ -0,0 +1,90 @@ +package de.ozgcloud.alfa.common.errorhandling; + +import java.util.Collections; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Stream; + +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; +import jakarta.validation.metadata.ConstraintDescriptor; + +import org.hibernate.validator.engine.HibernateConstraintViolation; +import org.springframework.http.HttpStatus; +import org.springframework.http.ProblemDetail; +import org.springframework.stereotype.Component; + +import de.ozgcloud.alfa.common.binaryfile.DynamicViolationParameter; +import de.ozgcloud.common.errorhandling.ExceptionUtil; + +@Component +public class ProblemDetailMapper { + + static final String INVALID_PARAMS_KEY_CONSTRAINT_PARAMETERS = "constraintParameters"; + static final String INVALID_PARAMS_KEY_REASON = "reason"; + static final String INVALID_PARAMS_KEY_VALUE = "value"; + static final String INVALID_PARAMS_KEY_NAME = "name"; + static final String INVALID_PARAMS = "invalidParams"; + static final String PROVIDED_VALUE_WAS_NULL = "Provided value was null!"; + private static final Set<String> IGNORABLE_CONSTRAINT_VIOLATION_ATTRIBUTES = Set.of("groups", "payload", "message"); + + public ProblemDetail fromConstraintViolationException(ConstraintViolationException e) { + var problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.UNPROCESSABLE_ENTITY, buildMessageWithExceptionId(e)); + problemDetail.setProperty(INVALID_PARAMS, buildInvalidParams(e.getConstraintViolations()).toList()); + return problemDetail; + } + + public String buildMessageWithExceptionId(ConstraintViolationException e) { + var exceptionId = createExceptionId(); + return ExceptionUtil.formatMessageWithExceptionId(e.getLocalizedMessage(), + exceptionId); + } + + String createExceptionId() { + return UUID.randomUUID().toString(); + } + + Stream<Map<String, Object>> buildInvalidParams(Set<ConstraintViolation<?>> violations) { + return Objects.requireNonNullElse(violations, Collections.<ConstraintViolation<?>>emptySet()).stream().map(this::buildDetailedViolation); + } + + Map<String, Object> buildDetailedViolation(ConstraintViolation<?> violation) { + return Map.of( + INVALID_PARAMS_KEY_NAME, violation.getPropertyPath().toString(), + INVALID_PARAMS_KEY_VALUE, Objects.requireNonNullElse(violation.getInvalidValue(), PROVIDED_VALUE_WAS_NULL).toString(), + INVALID_PARAMS_KEY_REASON, violation.getMessage(), + INVALID_PARAMS_KEY_CONSTRAINT_PARAMETERS, buildParameters(violation).toList()); + } + + Stream<IssueParam> buildParameters(ConstraintViolation<?> violation) { + var dynamicPayload = getDynamicPayload(violation); + return Optional.ofNullable(violation.getConstraintDescriptor()) + .map(ConstraintDescriptor::getAttributes) + .map(descr -> descr.entrySet().stream() + .filter(entry -> !IGNORABLE_CONSTRAINT_VIOLATION_ATTRIBUTES.contains(entry.getKey())) + .map(entryMap -> buildIssueParam(entryMap, dynamicPayload))) + .orElse(Stream.empty()); + } + + private IssueParam buildIssueParam(Entry<String, Object> entry, Optional<DynamicViolationParameter> dynamicValues) { + return IssueParam.builder().name(entry.getKey()).value(getValue(entry, dynamicValues)).build(); + } + + private String getValue(Entry<String, Object> entry, Optional<DynamicViolationParameter> dynamicValues) { + return dynamicValues + .map(DynamicViolationParameter::getMap) + .map(map -> map.get(entry.getKey())) + .filter(Objects::nonNull) + .map(String::valueOf) + .orElse(String.valueOf(entry.getValue())); + } + + private Optional<DynamicViolationParameter> getDynamicPayload(ConstraintViolation<?> violation) { + HibernateConstraintViolation<?> hibernateViolation = violation.unwrap(HibernateConstraintViolation.class); + return Optional.ofNullable(hibernateViolation.getDynamicPayload(DynamicViolationParameter.class)); + } +} diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/common/user/CurrentUserService.java b/alfa-service/src/main/java/de/ozgcloud/alfa/common/user/CurrentUserService.java index f4a517c4052a3658f8ecfc3d81e5ac5a6593c849..a62746f45d4920e8e787e2beca665ecf137d8a99 100644 --- a/alfa-service/src/main/java/de/ozgcloud/alfa/common/user/CurrentUserService.java +++ b/alfa-service/src/main/java/de/ozgcloud/alfa/common/user/CurrentUserService.java @@ -29,7 +29,6 @@ import java.util.List; import java.util.Objects; import java.util.Optional; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.hierarchicalroles.RoleHierarchy; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.oauth2.jwt.Jwt; @@ -37,7 +36,9 @@ import org.springframework.stereotype.Service; import de.ozgcloud.alfa.common.binaryfile.AlfaUserWithFileId; import de.ozgcloud.common.errorhandling.TechnicalException; +import lombok.RequiredArgsConstructor; +@RequiredArgsConstructor @Service public class CurrentUserService { @@ -51,10 +52,9 @@ public class CurrentUserService { static final String KEYCLOAK_USER_GIVEN_NAME = "given_name"; static final String KEYCLOAK_USER_FAMILY_NAME = "family_name"; - @Autowired - private UserService userService; - @Autowired - private RoleHierarchy roleHierarchy; + private final UserService userService; + + private final RoleHierarchy roleHierarchy; public boolean hasRole(String role) { return CurrentUserHelper.hasRole(role) || hasRoleReachable(role); diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/vorgang/VorgangWithEingangProcessor.java b/alfa-service/src/main/java/de/ozgcloud/alfa/vorgang/VorgangWithEingangProcessor.java index 94edbda19108796dfe1b0518e3cea960fa3aa1d5..6b111ac3c55ed436df4b141e6115ba419d599905 100644 --- a/alfa-service/src/main/java/de/ozgcloud/alfa/vorgang/VorgangWithEingangProcessor.java +++ b/alfa-service/src/main/java/de/ozgcloud/alfa/vorgang/VorgangWithEingangProcessor.java @@ -23,7 +23,6 @@ */ package de.ozgcloud.alfa.vorgang; -import static java.util.Optional.*; import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*; import java.util.List; @@ -39,7 +38,6 @@ import org.springframework.stereotype.Component; import de.ozgcloud.alfa.attachment.AttachmentController; import de.ozgcloud.alfa.attachment.AttachmentController.AttachmentsByVorgangController; -import de.ozgcloud.alfa.common.FeatureToggleProperties; import de.ozgcloud.alfa.common.ModelBuilder; import de.ozgcloud.alfa.common.command.CommandController.CommandByRelationController; import de.ozgcloud.alfa.common.user.CurrentUserService; @@ -64,7 +62,6 @@ class VorgangWithEingangProcessor implements RepresentationModelProcessor<Entity static final LinkRelation REL_POSTFACH_MAILS = LinkRelation.of("postfachMails"); static final LinkRelation REL_VORGANG_FORWARDING = LinkRelation.of("forwarding"); static final LinkRelation REL_HISTORIE = LinkRelation.of("historie"); - static final LinkRelation REL_BESCHEID = LinkRelation.of("createBescheid"); static final LinkRelation REL_PROCESS_VORGANG = LinkRelation.of("processVorgang"); static final String REL_SEARCH_USER = "search-user-profiles"; @@ -75,8 +72,6 @@ class VorgangWithEingangProcessor implements RepresentationModelProcessor<Entity private final CurrentUserService userService; private final UserManagerUrlProvider userManagerUrlProvider; - private final FeatureToggleProperties featureToggleProperties; - private final VorgangProperties vorgangProperties; private final VorgangProcessorProperties vorgangProcessorProperties; private static final Predicate<VorgangWithEingang> HAS_ATTACHMENTS = vorgangWithEingang -> vorgangWithEingang.getEingang() @@ -111,9 +106,6 @@ class VorgangWithEingangProcessor implements RepresentationModelProcessor<Entity .addLink(linkTo(methodOn(HistorieController.class).getAll(vorgang.getId())).withRel(REL_HISTORIE)) .ifMatch(() -> userManagerUrlProvider.isConfiguredForSearchUserProfile() && hasOrganisationsEinheitId(vorgang)) .addLink(this::buildSearchUserProfilesLink) - .ifMatch(this::isCreateBescheidEnabled) - .addLink(linkTo(methodOn(CommandByRelationController.class).createCommand(vorgang.getId(), vorgang.getId(), vorgang.getVersion(), - null)).withRel(REL_BESCHEID)) .ifMatch(this::isProcessable) .addLink( () -> linkTo(methodOn(CommandByRelationController.class).createCommand(vorgang.getId(), vorgang.getId(), vorgang.getVersion(), @@ -144,21 +136,6 @@ class VorgangWithEingangProcessor implements RepresentationModelProcessor<Entity return vorgang.getEingang().getZustaendigeStelle().getOrganisationseinheitenId(); } - boolean isCreateBescheidEnabled(VorgangWithEingang vorgang) { - return featureToggleProperties.isCreateBescheid() && hasVorgangCreateBescheidEnabled(vorgang); - } - - boolean hasVorgangCreateBescheidEnabled(VorgangWithEingang vorgang) { - return ofNullable(vorgang.getEingang()) - .map(Eingang::getHeader) - .map(this::isCreateBescheidEnabled) - .orElse(false); - } - - private boolean isCreateBescheidEnabled(EingangHeader eingangHeader) { - return vorgangProperties.getBescheid().stream().anyMatch(prop -> isFormIdAndFormEngineNameMatching(eingangHeader, prop)); - } - private boolean isProcessable(VorgangWithEingang vorgang) { return isAnyFormIdAndFormEngineNameMatching(vorgang.getEingang().getHeader(), vorgangProcessorProperties.getProcessor()); } diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/wiedervorlage/WiedervorlageCommandController.java b/alfa-service/src/main/java/de/ozgcloud/alfa/wiedervorlage/WiedervorlageCommandController.java index d621a01dd99a332de8fe0dd6b2b754ccdc9a351a..2cc413979da3622e47fc9d0d5677b804f001cf23 100644 --- a/alfa-service/src/main/java/de/ozgcloud/alfa/wiedervorlage/WiedervorlageCommandController.java +++ b/alfa-service/src/main/java/de/ozgcloud/alfa/wiedervorlage/WiedervorlageCommandController.java @@ -53,27 +53,26 @@ public class WiedervorlageCommandController { @PathVariable long wiedervorlageVersion) { var wiedervorlage = service.getById(wiedervorlageId); var createdCommand = createCommand(wiedervorlage, command); + var doneCommand = service.updateNextFrist(createdCommand, wiedervorlage.getVorgangId()); - service.updateNextFrist(wiedervorlage.getVorgangId()); - - return ResponseEntity.created(linkTo(CommandController.class).slash(createdCommand.getId()).toUri()).build(); + return ResponseEntity.created(linkTo(CommandController.class).slash(doneCommand.getId()).toUri()).build(); } Command createCommand(Wiedervorlage wiedervorlage, CreateCommand command) { switch (command.getOrder()) { - case LegacyOrder.WIEDERVORLAGE_ERLEDIGEN: { - return service.erledigen(wiedervorlage); - } - case LegacyOrder.WIEDERVORLAGE_WIEDEREROEFFNEN: { - return service.wiedereroeffnen(wiedervorlage); - } - case LegacyOrder.EDIT_WIEDERVORLAGE: { - return service.editWiedervorlage(updateWiedervorlageByCommand(wiedervorlage, (Wiedervorlage) command.getBody()), - wiedervorlage.getId(), - wiedervorlage.getVersion()); - } - default: - throw new TechnicalException("Unsupported order " + command.getOrder()); + case LegacyOrder.WIEDERVORLAGE_ERLEDIGEN: { + return service.erledigen(wiedervorlage); + } + case LegacyOrder.WIEDERVORLAGE_WIEDEREROEFFNEN: { + return service.wiedereroeffnen(wiedervorlage); + } + case LegacyOrder.EDIT_WIEDERVORLAGE: { + return service.editWiedervorlage(updateWiedervorlageByCommand(wiedervorlage, (Wiedervorlage) command.getBody()), + wiedervorlage.getId(), + wiedervorlage.getVersion()); + } + default: + throw new TechnicalException("Unsupported order " + command.getOrder()); } } @@ -97,10 +96,9 @@ public class WiedervorlageCommandController { @PostMapping public ResponseEntity<Void> createWiedervorlage(@RequestBody CreateCommand command, @PathVariable String vorgangId) { var createdCommand = service.createWiedervorlage((Wiedervorlage) command.getBody(), vorgangId); + var doneCommand = service.updateNextFrist(createdCommand, createdCommand.getVorgangId()); - service.updateNextFrist(vorgangId); - - return ResponseEntity.created(linkTo(CommandController.class).slash(createdCommand.getId()).toUri()).build(); + return ResponseEntity.created(linkTo(CommandController.class).slash(doneCommand.getId()).toUri()).build(); } } } \ No newline at end of file diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/wiedervorlage/WiedervorlageService.java b/alfa-service/src/main/java/de/ozgcloud/alfa/wiedervorlage/WiedervorlageService.java index 660aeefbe8e6a5d1425fd04d4d3b9cedee1a6e8a..28f4795c3695c12aa76981ebcb01c2f807fbbbde 100644 --- a/alfa-service/src/main/java/de/ozgcloud/alfa/wiedervorlage/WiedervorlageService.java +++ b/alfa-service/src/main/java/de/ozgcloud/alfa/wiedervorlage/WiedervorlageService.java @@ -33,27 +33,26 @@ import java.util.stream.Stream; import jakarta.validation.Valid; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; import de.ozgcloud.alfa.common.attacheditem.VorgangAttachedItemService; import de.ozgcloud.alfa.common.command.Command; +import de.ozgcloud.alfa.common.command.CommandService; import de.ozgcloud.alfa.common.user.CurrentUserService; +import lombok.RequiredArgsConstructor; +@RequiredArgsConstructor @Validated @Service class WiedervorlageService { private static final Predicate<Wiedervorlage> IS_NOT_DONE = wiedervorlage -> !wiedervorlage.isDone(); - @Autowired - private WiedervorlageRemoteService remoteService; - @Autowired - private VorgangAttachedItemService vorgangAttachedItemService; - @Autowired - private CurrentUserService currentUserService; + private final WiedervorlageRemoteService remoteService; + private final VorgangAttachedItemService vorgangAttachedItemService; + private final CurrentUserService currentUserService; + private final CommandService commandService; public Command createWiedervorlage(@Valid Wiedervorlage wiedervorlage, String vorgangId) { return vorgangAttachedItemService.createNewWiedervorlage(addCreated(wiedervorlage), vorgangId); @@ -74,8 +73,15 @@ class WiedervorlageService { return remoteService.getById(wiedervorlageId); } - @Async - public void updateNextFrist(String vorgangId) { + public Command updateNextFrist(Command command, String vorgangId) { + var doneCommand = commandService.waitUntilDone(command); + if (doneCommand.isFinishedSuccessfully()) { + doUpdateNextFrist(vorgangId); + } + return doneCommand; + } + + void doUpdateNextFrist(String vorgangId) { var allWiedervorlagen = findByVorgangId(vorgangId); remoteService.updateNextFrist(vorgangId, calculateNextFrist(allWiedervorlagen)); diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/bescheid/BescheidCommandITCase.java b/alfa-service/src/test/java/de/ozgcloud/alfa/bescheid/BescheidCommandITCase.java index d4c71d099ea22b5a27736889e0fb0aebff784a44..b85d323ef153890af71025bd9b886ab230a6a67f 100644 --- a/alfa-service/src/test/java/de/ozgcloud/alfa/bescheid/BescheidCommandITCase.java +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/bescheid/BescheidCommandITCase.java @@ -52,9 +52,9 @@ public class BescheidCommandITCase { String content = createInvalidRequestContent(BescheidTestFactory.createBuilder().beschiedenAm(null).build()); doRequest(content).andExpect(status().isUnprocessableEntity()) - .andExpect(jsonPath("$.issues.length()").value(1)) - .andExpect(jsonPath("$.issues.[0].field").value("command.body.beschiedenAm")) - .andExpect(jsonPath("$.issues.[0].messageCode").value(ValidationMessageCodes.FIELD_DATE_FORMAT_INVALID)); + .andExpect(jsonPath("$.invalidParams.length()").value(1)) + .andExpect(jsonPath("$.invalidParams[0].name").value("createCommand.command.body.beschiedenAm")) + .andExpect(jsonPath("$.invalidParams[0].reason").value(ValidationMessageCodes.FIELD_DATE_FORMAT_INVALID)); } @ParameterizedTest @@ -82,9 +82,9 @@ public class BescheidCommandITCase { return mockMvc.perform( post(CommandByRelationController.COMMAND_BY_RELATION_PATH, VorgangHeaderTestFactory.ID, VorgangHeaderTestFactory.ID, VorgangHeaderTestFactory.VERSION) - .with(csrf()) - .contentType(MediaType.APPLICATION_JSON) - .content(content)); + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(content)); } } } diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/CollaborationVorgangProcessorITCase.java b/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/CollaborationVorgangProcessorITCase.java new file mode 100644 index 0000000000000000000000000000000000000000..fe189548817a2ec6f40b4eb9536d0e2f8cef3cdb --- /dev/null +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/CollaborationVorgangProcessorITCase.java @@ -0,0 +1,32 @@ +package de.ozgcloud.alfa.collaboration; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.ApplicationContext; + +class CollaborationVorgangProcessorITCase { + + @SpringBootTest(properties = {"ozgcloud.feature.collaboration-enabled=true"}) + @Nested + class OnFeatureEnabled { + + @Test + void shouldExistInApplicationContext(ApplicationContext context) { + assertThat(context.getBean(CollaborationVorgangProcessor.class)).isNotNull(); + } + } + + @SpringBootTest + @Nested + class OnFeatureDisabled { + + @Test + void shouldNotExistInApplicationContext(ApplicationContext context) { + assertThatThrownBy(() -> context.getBean(CollaborationVorgangProcessor.class)).isInstanceOf(NoSuchBeanDefinitionException.class); + } + } +} diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/CollaborationVorgangProcessorTest.java b/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/CollaborationVorgangProcessorTest.java new file mode 100644 index 0000000000000000000000000000000000000000..5a51e828cc7b1eec1fbe93d2125722ee038e3954 --- /dev/null +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/CollaborationVorgangProcessorTest.java @@ -0,0 +1,80 @@ +package de.ozgcloud.alfa.collaboration; + +import static de.ozgcloud.alfa.common.UserProfileUrlProviderTestFactory.*; +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; +import org.springframework.hateoas.EntityModel; +import org.springframework.hateoas.Link; +import org.springframework.hateoas.UriTemplate; + +import de.ozgcloud.alfa.common.UserProfileUrlProvider; +import de.ozgcloud.alfa.common.command.CommandController; +import de.ozgcloud.alfa.common.user.CurrentUserService; +import de.ozgcloud.alfa.common.user.UserRole; +import de.ozgcloud.alfa.vorgang.VorgangHeaderTestFactory; +import de.ozgcloud.alfa.vorgang.VorgangWithEingangTestFactory; + +class CollaborationVorgangProcessorTest { + + @Spy + @InjectMocks + private CollaborationVorgangProcessor processor; + + @Mock + private CurrentUserService currentUserService; + + private final UserProfileUrlProvider urlProvider = new UserProfileUrlProvider(); + + @Nested + class TestProcess { + + @Nested + class OnNullVorgang { + + @Test + void shouldNotAddLinksIfVorgangIsNull() { + var model = processor.process(new EntityModel<>() { + }); + + assertThat(model.hasLinks()).isFalse(); + } + } + + @Nested + class OnNonNullVorgang { + + @BeforeEach + void prepareBuilder() { + initUserProfileUrlProvider(urlProvider); + } + + @Test + void shouldAddCreateCollaborationRequestRelation() { + when(currentUserService.hasRole(UserRole.VERWALTUNG_USER)).thenReturn(true); + + var model = processor.process(EntityModel.of(VorgangWithEingangTestFactory.create())); + + assertThat(model.getLink(CollaborationVorgangProcessor.REL_CREATE_COLLABORATION_REQUEST)).isPresent().get() + .extracting(Link::getHref) + .isEqualTo(UriTemplate.of(CommandController.CommandByRelationController.COMMAND_BY_RELATION_PATH) + .expand(VorgangHeaderTestFactory.ID, VorgangHeaderTestFactory.ID, VorgangHeaderTestFactory.VERSION).toString()); + } + + @Test + void shouldNotAddCreateCollaborationRequestRelation() { + when(currentUserService.hasRole(UserRole.VERWALTUNG_USER)).thenReturn(false); + + var model = processor.process(EntityModel.of(VorgangWithEingangTestFactory.create())); + + assertThat(model.getLink(CollaborationVorgangProcessor.REL_CREATE_COLLABORATION_REQUEST)).isEmpty(); + } + } + } +} diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/common/binaryfile/BinaryFileControllerITCase.java b/alfa-service/src/test/java/de/ozgcloud/alfa/common/binaryfile/BinaryFileControllerITCase.java index 16cd0b895439464f3feba0aeda52642d63985cc4..18ddf8ce29170a549e2bd63d0c080a5c6ebd3451 100644 --- a/alfa-service/src/test/java/de/ozgcloud/alfa/common/binaryfile/BinaryFileControllerITCase.java +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/common/binaryfile/BinaryFileControllerITCase.java @@ -122,7 +122,7 @@ class BinaryFileControllerITCase { void setTokenToSecuriyContext(String token) throws Exception { mockMvc.perform(get(DownloadTokenController.DOWNLOAD_TOKEN_PATH + "?" + DownloadTokenController.PARAM_TOKEN + "=" + token) - .with(csrf())) + .with(csrf())) .andExpect(status().isOk()); } } @@ -168,8 +168,8 @@ class BinaryFileControllerITCase { @Test void shouldReturnValidationMessage() throws Exception { performRequest(createFile(MediaType.TEXT_PLAIN)).andExpect(status().isUnprocessableEntity()) - .andExpect(jsonPath("$.issues.length()").value(1)) - .andExpect(jsonPath("$.issues.[0].messageCode").value(ValidationMessageCodes.FIELD_FILE_CONTENT_TYPE_INVALID)); + .andExpect(jsonPath("$.invalidParams.length()").value(1)) + .andExpect(jsonPath("$.invalidParams[0].reason").value(ValidationMessageCodes.FIELD_FILE_CONTENT_TYPE_INVALID)); } @Test @@ -183,7 +183,7 @@ class BinaryFileControllerITCase { return mockMvc.perform(multipart( String.format("%s/%s/%s/file", BinaryFileController.PATH, VorgangHeaderTestFactory.ID, UploadBinaryFileTestFactory.FIELD)).file( - file).with(csrf())); + file).with(csrf())); } private MockMultipartFile createFile(MediaType contentType) { diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/common/binaryfile/BinaryFileITCase.java b/alfa-service/src/test/java/de/ozgcloud/alfa/common/binaryfile/BinaryFileITCase.java index dcc9f9afcf7b622d9ac9e5d2f12a1a60b3bb34c2..6ab03aafe02d6aa07594f3622255b66b47e107d5 100644 --- a/alfa-service/src/test/java/de/ozgcloud/alfa/common/binaryfile/BinaryFileITCase.java +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/common/binaryfile/BinaryFileITCase.java @@ -85,33 +85,33 @@ class BinaryFileITCase { void shouldReturnConstraintViolationExceptionData() throws Exception { var result = callEndpoint(TEST_FILE, BinaryFileTestFactory.FIELD); - result.andExpect(jsonPath("issues[0].field").value("uploadBinaryFileRequest")) - .andExpect(jsonPath("issues[0].messageCode").value("validation_field_file_size_exceeded")) - .andExpect(jsonPath("issues[0].message").value("validation_field_file_size_exceeded")) - .andExpect(jsonPath("issues[0].parameters[0].name").value("unit")) - .andExpect(jsonPath("issues[0].parameters[0].value").value(UploadBinaryFileSizeValidator.DEFAULT_UNIT_STRING)); + result.andExpect(jsonPath("invalidParams[0].name").value("uploadFile.uploadBinaryFileRequest")) + .andExpect(jsonPath("invalidParams[0].reason").value("validation_field_file_size_exceeded")) + .andExpect(jsonPath("invalidParams[0].constraintParameters[0].name").value("unit")) + .andExpect( + jsonPath("invalidParams[0].constraintParameters[0].value").value(UploadBinaryFileSizeValidator.DEFAULT_UNIT_STRING)); } @Test void shouldContainsExceptionDataOnDefaultMaxSize() throws Exception { var result = callEndpoint(TEST_FILE, BinaryFileTestFactory.FIELD); - result.andExpect(jsonPath("issues[0].parameters[1].name").value("max")) - .andExpect(jsonPath("issues[0].parameters[1].value").value("40")); + result.andExpect(jsonPath("invalidParams[0].constraintParameters[1].name").value("max")) + .andExpect(jsonPath("invalidParams[0].constraintParameters[1].value").value("40")); } } @Nested class TestForPostfachNachricht { - private final static String FIELD = "postfachNachrichtAttachment"; + private static final String FIELD = "postfachNachrichtAttachment"; @Test void shouldContainsExceptionDataOnPostfachNachrichtMaxSize() throws Exception { var result = callEndpoint(TEST_FILE, FIELD); - result.andExpect(jsonPath("issues[0].parameters[1].name").value("max")) - .andExpect(jsonPath("issues[0].parameters[1].value").value("3")); + result.andExpect(jsonPath("invalidParams[0].constraintParameters[1].name").value("max")) + .andExpect(jsonPath("invalidParams[0].constraintParameters[1].value").value("3")); } } diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/common/command/CommandControllerTest.java b/alfa-service/src/test/java/de/ozgcloud/alfa/common/command/CommandControllerTest.java index 53f6ec125efb2914bb7754ff51211c646f904bbc..47da8200c2c2a6ab2465778f1110f3213e8c139b 100644 --- a/alfa-service/src/test/java/de/ozgcloud/alfa/common/command/CommandControllerTest.java +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/common/command/CommandControllerTest.java @@ -43,6 +43,7 @@ import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import de.ozgcloud.alfa.common.errorhandling.ExceptionController; +import de.ozgcloud.alfa.common.errorhandling.ProblemDetailMapper; import de.ozgcloud.alfa.vorgang.VorgangHeaderTestFactory; import de.ozgcloud.common.test.TestUtils; @@ -54,12 +55,14 @@ class CommandControllerTest { private CommandService service; @Mock private CommandModelAssembler modelAssembler; + @Mock + private ProblemDetailMapper problemDetailMapper; private MockMvc mockMvc; @BeforeEach void initTest() { - mockMvc = MockMvcBuilders.standaloneSetup(controller).setControllerAdvice(new ExceptionController()).build(); + mockMvc = MockMvcBuilders.standaloneSetup(controller).setControllerAdvice(new ExceptionController(problemDetailMapper)).build(); } @Nested @@ -171,8 +174,8 @@ class CommandControllerTest { private ResultActions doRequest() throws Exception { return mockMvc.perform(get(CommandController.COMMANDS_PATH) - .param(CommandController.PARAM_PENDING, Boolean.toString(true)) - .param(CommandController.PARAM_VORGANG_ID, VorgangHeaderTestFactory.ID)) + .param(CommandController.PARAM_PENDING, Boolean.toString(true)) + .param(CommandController.PARAM_VORGANG_ID, VorgangHeaderTestFactory.ID)) .andExpect(status().is2xxSuccessful()); } } diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/common/command/CommandITCase.java b/alfa-service/src/test/java/de/ozgcloud/alfa/common/command/CommandITCase.java index 7ee0005a679ae552bd6e78cdc24ae00e6ab99213..3f396641e5d9aca17161d0276e9a0fe8d8521a44 100644 --- a/alfa-service/src/test/java/de/ozgcloud/alfa/common/command/CommandITCase.java +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/common/command/CommandITCase.java @@ -126,9 +126,9 @@ public class CommandITCase { .email(null).build()); doRequest(requestContent).andExpect(status().isUnprocessableEntity()) - .andExpect(jsonPath("$.issues.length()").value(1)) - .andExpect(jsonPath("$.issues.[0].field").value("command.redirectRequest.email")) - .andExpect(jsonPath("$.issues.[0].messageCode").value(ValidationMessageCodes.FIELD_IS_EMPTY)); + .andExpect(jsonPath("$.invalidParams.length()").value(1)) + .andExpect(jsonPath("$.invalidParams[0].name").value("createCommand.command.redirectRequest.email")) + .andExpect(jsonPath("$.invalidParams[0].reason").value(ValidationMessageCodes.FIELD_IS_EMPTY)); } } @@ -145,7 +145,7 @@ public class CommandITCase { .build()); doRequest(requestContent).andExpect(status().isUnprocessableEntity()) - .andExpect(jsonPath("$.issues.length()").value(2)); + .andExpect(jsonPath("$.invalidParams.length()").value(2)); } } @@ -153,16 +153,16 @@ public class CommandITCase { @Nested class TestEmail { - private final String FIELD = "email"; + private static final String FIELD = "email"; @Test void shouldReturnErrorOnNullEMail() throws Exception { var requestContent = buildRedirectRequestWithEmail(null); doRequest(requestContent).andExpect(status().isUnprocessableEntity()) - .andExpect(jsonPath("$.issues.length()").value(1)) - .andExpect(jsonPath("$.issues.[0].field").value("command.redirectRequest." + FIELD)) - .andExpect(jsonPath("$.issues.[0].messageCode").value(ValidationMessageCodes.FIELD_IS_EMPTY)); + .andExpect(jsonPath("$.invalidParams.length()").value(1)) + .andExpect(jsonPath("$.invalidParams[0].name").value("createCommand.command.redirectRequest." + FIELD)) + .andExpect(jsonPath("$.invalidParams[0].reason").value(ValidationMessageCodes.FIELD_IS_EMPTY)); } @Test @@ -170,9 +170,9 @@ public class CommandITCase { var requestContent = buildRedirectRequestWithEmail("local@@domain.com"); doRequest(requestContent).andExpect(status().isUnprocessableEntity()) - .andExpect(jsonPath("$.issues.length()").value(1)) - .andExpect(jsonPath("$.issues.[0].field").value("command.redirectRequest." + FIELD)) - .andExpect(jsonPath("$.issues.[0].messageCode").value(ValidationMessageCodes.FIELD_INVALID)); + .andExpect(jsonPath("$.invalidParams.length()").value(1)) + .andExpect(jsonPath("$.invalidParams[0].name").value("createCommand.command.redirectRequest." + FIELD)) + .andExpect(jsonPath("$.invalidParams[0].reason").value(ValidationMessageCodes.FIELD_INVALID)); } private String buildRedirectRequestWithEmail(String eMail) { @@ -191,13 +191,13 @@ public class CommandITCase { var requestContent = buildRedirectRequestWithPassword(RandomStringUtils.randomAlphabetic(7)); doRequest(requestContent).andExpect(status().isUnprocessableEntity()) - .andExpect(jsonPath("$.issues.length()").value(1)) - .andExpect(jsonPath("$.issues.[0].field").value("command.redirectRequest." + FIELD)) - .andExpect(jsonPath("$.issues.[0].messageCode").value(ValidationMessageCodes.FIELD_SIZE)) - .andExpect(jsonPath("$.issues.[0].parameters[0].name").value("min")) - .andExpect(jsonPath("$.issues.[0].parameters[0].value").value(8)) - .andExpect(jsonPath("$.issues.[0].parameters[1].name").value("max")) - .andExpect(jsonPath("$.issues.[0].parameters[1].value").value(40)); + .andExpect(jsonPath("$.invalidParams.length()").value(1)) + .andExpect(jsonPath("$.invalidParams[0].name").value("createCommand.command.redirectRequest." + FIELD)) + .andExpect(jsonPath("$.invalidParams[0].reason").value(ValidationMessageCodes.FIELD_SIZE)) + .andExpect(jsonPath("$.invalidParams[0].constraintParameters[0].name").value("min")) + .andExpect(jsonPath("$.invalidParams[0].constraintParameters[0].value").value(8)) + .andExpect(jsonPath("$.invalidParams[0].constraintParameters[1].name").value("max")) + .andExpect(jsonPath("$.invalidParams[0].constraintParameters[1].value").value(40)); } @Test @@ -205,9 +205,9 @@ public class CommandITCase { var requestContent = buildRedirectRequestWithPassword(RandomStringUtils.randomAlphabetic(41)); doRequest(requestContent).andExpect(status().isUnprocessableEntity()) - .andExpect(jsonPath("$.issues.length()").value(1)) - .andExpect(jsonPath("$.issues.[0].field").value("command.redirectRequest." + FIELD)) - .andExpect(jsonPath("$.issues.[0].messageCode").value(ValidationMessageCodes.FIELD_SIZE)); + .andExpect(jsonPath("$.invalidParams.length()").value(1)) + .andExpect(jsonPath("$.invalidParams[0].name").value("createCommand.command.redirectRequest." + FIELD)) + .andExpect(jsonPath("$.invalidParams[0].reason").value(ValidationMessageCodes.FIELD_SIZE)); } private String buildRedirectRequestWithPassword(String password) { @@ -245,9 +245,9 @@ public class CommandITCase { .buildSendPostfachMailContent(PostfachMailTestFactory.createBuilder().subject(null).build()); doRequest(requestContent).andExpect(status().isUnprocessableEntity()) - .andExpect(jsonPath("$.issues.length()").value(1)) - .andExpect(jsonPath("$.issues.[0].field").value("command.body.subject")) - .andExpect(jsonPath("$.issues.[0].messageCode").value(ValidationMessageCodes.FIELD_IS_EMPTY)); + .andExpect(jsonPath("$.invalidParams.length()").value(1)) + .andExpect(jsonPath("$.invalidParams[0].name").value("createCommand.command.body.subject")) + .andExpect(jsonPath("$.invalidParams[0].reason").value(ValidationMessageCodes.FIELD_IS_EMPTY)); } @Test @@ -257,9 +257,9 @@ public class CommandITCase { PostfachMailTestFactory.createBuilder().subject(RandomStringUtils.randomAlphanumeric(71)).build()); doRequest(requestContent).andExpect(status().isUnprocessableEntity()) - .andExpect(jsonPath("$.issues.length()").value(1)) - .andExpect(jsonPath("$.issues.[0].field").value("command.body.subject")) - .andExpect(jsonPath("$.issues.[0].messageCode").value(ValidationMessageCodes.FIELD_MAX_SIZE)); + .andExpect(jsonPath("$.invalidParams.length()").value(1)) + .andExpect(jsonPath("$.invalidParams[0].name").value("createCommand.command.body.subject")) + .andExpect(jsonPath("$.invalidParams[0].reason").value(ValidationMessageCodes.FIELD_MAX_SIZE)); } @Test @@ -280,9 +280,9 @@ public class CommandITCase { .buildSendPostfachMailContent(PostfachMailTestFactory.createBuilder().mailBody(null).build()); doRequest(request).andExpect(status().isUnprocessableEntity()) - .andExpect(jsonPath("$.issues.length()").value(1)) - .andExpect(jsonPath("$.issues.[0].field").value("command.body.mailBody")) - .andExpect(jsonPath("$.issues.[0].messageCode").value(ValidationMessageCodes.FIELD_IS_EMPTY)); + .andExpect(jsonPath("$.invalidParams.length()").value(1)) + .andExpect(jsonPath("$.invalidParams[0].name").value("createCommand.command.body.mailBody")) + .andExpect(jsonPath("$.invalidParams[0].reason").value(ValidationMessageCodes.FIELD_IS_EMPTY)); } @Test @@ -299,9 +299,9 @@ public class CommandITCase { ResultActions doRequest(String content) throws Exception { return mockMvc.perform(post("/api/vorgangs/" + CommandTestFactory.VORGANG_ID + "/relations/" + CommandTestFactory.RELATION_ID + "/" + CommandTestFactory.RELATION_VERSION + "/commands") - .with(csrf()) - .contentType(MediaType.APPLICATION_JSON) - .content(content)); + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(content)); } } } \ No newline at end of file diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/common/command/CommandServiceTest.java b/alfa-service/src/test/java/de/ozgcloud/alfa/common/command/CommandServiceTest.java index 95b37ba1901cbd62a954642ee61d4daf09fe4954..2bf962ce6cb343babed90d058cbe4549f6acb1b5 100644 --- a/alfa-service/src/test/java/de/ozgcloud/alfa/common/command/CommandServiceTest.java +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/common/command/CommandServiceTest.java @@ -27,6 +27,7 @@ import static org.assertj.core.api.Assertions.*; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; +import java.util.Calendar; import java.util.Map; import java.util.Optional; import java.util.UUID; @@ -39,6 +40,7 @@ import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.MockedStatic; import org.mockito.Spy; import de.ozgcloud.alfa.loeschanforderung.DeleteLoeschAnforderung; @@ -297,4 +299,99 @@ class CommandServiceTest { verify(remoteService).findCommands(VorgangHeaderTestFactory.ID, Optional.of(CommandStatus.FINISHED), Optional.empty()); } } + + @Nested + class TestWaitUntilDone { + + private final Command pendingCommand = CommandTestFactory.createBuilder().status(CommandStatus.PENDING).build(); + private final Command finishedCommand = pendingCommand.toBuilder().status(CommandStatus.FINISHED).build(); + + @Nested + class OnFinishedCommand { + + @Test + void shouldReturnFinishedCommand() { + var resultCommand = service.waitUntilDone(finishedCommand); + + assertThat(resultCommand).isEqualTo(finishedCommand); + } + + @Test + void shouldNotReloadCommand() { + service.waitUntilDone(finishedCommand); + + verify(service, never()).reloadCommand(any()); + } + } + + @Nested + class OnPendingCommand { + + @BeforeEach + void setUp() { + doReturn(finishedCommand).when(service).reloadCommand(pendingCommand.getId()); + } + + @Test + void shouldReloadCommand() { + service.waitUntilDone(pendingCommand); + + verify(service).reloadCommand(pendingCommand.getId()); + } + + @Test + void shouldReturnDoneCommand() { + var resultCommand = service.waitUntilDone(pendingCommand); + + assertThat(resultCommand).isEqualTo(finishedCommand); + } + } + + @Nested + class OnTimeoutExceeded { + + @Mock + private Calendar calendar; + + @Test + void shouldReturnPendingCommand() { + try (MockedStatic<Calendar> calendarMockedStatic = mockStatic(Calendar.class)) { + calendarMockedStatic.when(Calendar::getInstance).thenReturn(calendar); + when(calendar.getTimeInMillis()).thenReturn(0L, 15000L); + + var resultCommand = service.waitUntilDone(pendingCommand); + + assertThat(resultCommand).isEqualTo(pendingCommand); + } + } + + @Test + void shouldReloadPendingCommand() { + doReturn(pendingCommand).when(service).reloadCommand(pendingCommand.getId()); + + try (MockedStatic<Calendar> calendarMockedStatic = mockStatic(Calendar.class)) { + calendarMockedStatic.when(Calendar::getInstance).thenReturn(calendar); + when(calendar.getTimeInMillis()).thenReturn(0L, 0L, 15000L); + + service.waitUntilDone(pendingCommand); + + verify(service).reloadCommand(pendingCommand.getId()); + } + } + + @Test + void shouldReturnPendingCommandAfterReload() { + doReturn(pendingCommand).when(service).reloadCommand(pendingCommand.getId()); + + try (MockedStatic<Calendar> calendarMockedStatic = mockStatic(Calendar.class)) { + calendarMockedStatic.when(Calendar::getInstance).thenReturn(calendar); + when(calendar.getTimeInMillis()).thenReturn(0L, 0L, 15000L); + + var resultCommand = service.waitUntilDone(pendingCommand); + + assertThat(resultCommand).isEqualTo(pendingCommand); + } + } + } + } } \ No newline at end of file diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/common/command/CommandStatusTest.java b/alfa-service/src/test/java/de/ozgcloud/alfa/common/command/CommandStatusTest.java new file mode 100644 index 0000000000000000000000000000000000000000..f91dc7028af4ca954d1dfc9fae1e70affbdcc066 --- /dev/null +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/common/command/CommandStatusTest.java @@ -0,0 +1,34 @@ +package de.ozgcloud.alfa.common.command; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.EnumSource.Mode; + +class CommandStatusTest { + + @Nested + class TestIsNotDone { + + @ParameterizedTest + @EnumSource(names = { "PENDING", "REVOKE_PENDING" }) + void shouldReturnTrue(CommandStatus status) { + var istNotDone = status.isNotDone(); + + assertThat(istNotDone).isTrue(); + + } + + @ParameterizedTest + @EnumSource(names = { "PENDING", "REVOKE_PENDING" }, mode = Mode.EXCLUDE) + void shouldReturnFalse(CommandStatus status) { + var istNotDone = status.isNotDone(); + + assertThat(istNotDone).isFalse(); + } + + } + +} \ No newline at end of file diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/common/command/CommandTest.java b/alfa-service/src/test/java/de/ozgcloud/alfa/common/command/CommandTest.java new file mode 100644 index 0000000000000000000000000000000000000000..c838d402226703a6133dc5f8a65b79e7d911373e --- /dev/null +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/common/command/CommandTest.java @@ -0,0 +1,84 @@ +package de.ozgcloud.alfa.common.command; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.EnumSource.Mode; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.Mock; + +import com.thedeanda.lorem.LoremIpsum; + +class CommandTest { + + @Nested + class TestIsFinishedSuccessfully { + + @Test + void shouldReturnTrue() { + var command = CommandTestFactory.createBuilder().status(CommandStatus.FINISHED).build(); + + var isDoneSuccessfully = command.isFinishedSuccessfully(); + + assertThat(isDoneSuccessfully).isTrue(); + } + + @ParameterizedTest + @EnumSource(names = "FINISHED", mode = Mode.EXCLUDE) + void shouldReturnFalseOnStatusNotFinished(CommandStatus commandStatus) { + var command = CommandTestFactory.createBuilder().status(commandStatus).build(); + + var isDoneSuccessfully = command.isFinishedSuccessfully(); + + assertThat(isDoneSuccessfully).isFalse(); + } + + @ParameterizedTest + @EnumSource + void shouldReturnFalseOnErrorMessage(CommandStatus commandStatus) { + var command = CommandTestFactory.createBuilder().status(commandStatus).errorMessage(LoremIpsum.getInstance().getWords(1)).build(); + + var isDoneSuccessfully = command.isFinishedSuccessfully(); + + assertThat(isDoneSuccessfully).isFalse(); + } + + } + + @Nested + class TestIsNotDone { + + @Mock + private CommandStatus commandStatus; + private Command command; + + @BeforeEach + void setUp() { + command = CommandTestFactory.createBuilder().status(commandStatus).build(); + } + + @Test + void shouldCallIsNotDone() { + command.isNotDone(); + + verify(commandStatus).isNotDone(); + } + + @ParameterizedTest + @ValueSource(booleans = { true, false }) + void shouldReturnIsNotDone(boolean isNotDone) { + when(commandStatus.isNotDone()).thenReturn(isNotDone); + + var result = command.isNotDone(); + + assertThat(result).isEqualTo(isNotDone); + } + + } + +} \ No newline at end of file diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/common/errorhandling/ExceptionControllerTest.java b/alfa-service/src/test/java/de/ozgcloud/alfa/common/errorhandling/ExceptionControllerTest.java index 489f0a8f4f2504e203d81504db92d12efc76f777..50a0b2d1af3cce26dfd38934071b5cf48264c048 100644 --- a/alfa-service/src/test/java/de/ozgcloud/alfa/common/errorhandling/ExceptionControllerTest.java +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/common/errorhandling/ExceptionControllerTest.java @@ -27,7 +27,6 @@ import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; import java.util.Collections; -import java.util.Map; import jakarta.validation.ConstraintViolationException; @@ -36,10 +35,14 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; +import org.mockito.Mock; import org.mockito.Spy; +import org.springframework.http.HttpStatus; +import org.springframework.http.ProblemDetail; import org.springframework.security.access.AccessDeniedException; -import de.ozgcloud.alfa.common.binaryfile.DynamicViolationParameter; +import com.thedeanda.lorem.LoremIpsum; + import de.ozgcloud.alfa.common.command.LegacyOrder; import de.ozgcloud.common.errorhandling.TechnicalException; @@ -49,6 +52,9 @@ class ExceptionControllerTest { @InjectMocks private ExceptionController exceptionController; + @Mock + private ProblemDetailMapper problemDetailMapper; + @Nested class TestHandleFunctionalException { @@ -162,79 +168,42 @@ class ExceptionControllerTest { @Nested class TestContraintValidationException { - private final ConstraintViolationException exception = new ConstraintViolationException(ExceptionTestFactory.MESSAGE, - Collections.singleton(ExceptionTestFactory.buildMockedConstraintViolation())); - - @BeforeEach - void mockExceptionId() { - doReturn(ExceptionTestFactory.EXCEPTION_ID).when(exceptionController).createExceptionId(); - } - - @Test - void shouldHaveField() { - var error = handleException(); - - assertThat(error.getIssues()).hasSize(1); - assertThat(error.getIssues().get(0).getField()).isEqualTo(ExceptionTestFactory.PATH_FIELD); - } - - @Test - void shouldHaveMessageCode() { - var error = handleException(); - - assertThat(error.getIssues()).hasSize(1); - assertThat(error.getIssues().get(0).getMessageCode()).isEqualTo(ExceptionTestFactory.MESSAGE_CODE); - } - - @Test - void shouldHaveMessage() { - var error = handleException(); - - assertThat(error.getIssues()).hasSize(1); - assertThat(error.getIssues().get(0).getMessage()).isEqualTo(ExceptionTestFactory.MESSAGE); - } - - @Test - void shouldHaveExceptionId() { - var error = handleException(); + @Nested + class TestHandleConstraintViolationException { + private final String exceptionMessage = LoremIpsum.getInstance().getWords(5); - assertThat(error.getIssues().get(0).getExceptionId()).isEqualTo(ExceptionTestFactory.EXCEPTION_ID); - } + private final ConstraintViolationException exception = new ConstraintViolationException(exceptionMessage, + Collections.singleton(ExceptionTestFactory.buildMockedConstraintViolation())); - @Test - void shouldHaveIssueParameter() { - var error = handleException(); + @Test + void shouldGetMessageWithExceptionId() { + handleException(); - var issueParameter = error.getIssues().get(0).getParameters().get(0); - assertThat(issueParameter.getName()).isEqualTo(ExceptionTestFactory.PARAM_NAME); - assertThat(issueParameter.getValue()).isEqualTo(ExceptionTestFactory.PARAM_DYNAMIC_VALUE); - } + verify(problemDetailMapper).buildMessageWithExceptionId(exception); + } - @Nested - class TestWithDynamicPayload { + @Test + void shouldBuildConstraintViolationProblemDetail() { + handleException(); - private final DynamicViolationParameter dynamicViolationParameter = DynamicViolationParameter.builder() - .map(Map.of(ExceptionTestFactory.PARAM_NAME, ExceptionTestFactory.PARAM_DYNAMIC_VALUE)).build(); - private final ConstraintViolationException exceptionWithDynamicPayload = new ConstraintViolationException(ExceptionTestFactory.MESSAGE, - Collections.singleton(ExceptionTestFactory.buildMockedConstraintViolationWithDynamicPayload(dynamicViolationParameter))); + verify(problemDetailMapper).fromConstraintViolationException(exception); + } @Test - void shouldHaveReplacedParams() { - var error = handleExceptionWithDynamicPayload(); + void shouldReturnBuiltProblemDetail() { + var expectedProblemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.UNPROCESSABLE_ENTITY, exceptionMessage); + when(problemDetailMapper.fromConstraintViolationException(exception)).thenReturn(expectedProblemDetail); + + var problemDetail = handleException(); - var issueParameter = error.getIssues().get(0).getParameters().get(0); - assertThat(issueParameter.getName()).isEqualTo(ExceptionTestFactory.PARAM_NAME); - assertThat(issueParameter.getValue()).isEqualTo(ExceptionTestFactory.PARAM_DYNAMIC_VALUE); + assertThat(problemDetail).isEqualTo(expectedProblemDetail); } - private ApiError handleExceptionWithDynamicPayload() { - return exceptionController.handleConstraintViolationException(exceptionWithDynamicPayload); + private ProblemDetail handleException() { + return exceptionController.handleConstraintViolationException(exception); } } - private ApiError handleException() { - return exceptionController.handleConstraintViolationException(exception); - } } @Nested diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/common/errorhandling/ExceptionTestFactory.java b/alfa-service/src/test/java/de/ozgcloud/alfa/common/errorhandling/ExceptionTestFactory.java index d96c42787390b1b99df6bd412efce278c3dd7335..19cdbe389dc959ade05e069b6b820513c980d3c2 100644 --- a/alfa-service/src/test/java/de/ozgcloud/alfa/common/errorhandling/ExceptionTestFactory.java +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/common/errorhandling/ExceptionTestFactory.java @@ -48,26 +48,26 @@ public class ExceptionTestFactory { static final String PARAM_DYNAMIC_VALUE = "20"; static final String MESSAGE = LoremIpsum.getInstance().getWords(5); static final String MESSAGE_CODE = "message.code"; - private static final String PATH_SUFFIX = "createCommandByRelation"; + private static final String PATH_PREFIX = "createCommandByRelation"; static final String PATH_FIELD = "command.wiedervorlage.betreff"; - private static final String PATH = PATH_SUFFIX + "." + PATH_FIELD; + public static final String PATH = PATH_PREFIX + "." + PATH_FIELD; public static ConstraintViolation<?> buildMockedConstraintViolation() { return ExceptionTestFactory.buildMockedConstraintViolationWithDynamicPayload(null); } - @SuppressWarnings({ "rawtypes", "unchecked" }) + @SuppressWarnings({ "unchecked" }) public static <T> ConstraintViolation<T> buildMockedConstraintViolationWithDynamicPayload(DynamicViolationParameter dynamicViolationParameter) { - ConstraintViolation violation = mock(ConstraintViolation.class); - HibernateConstraintViolation hibernateViolation = mock(HibernateConstraintViolation.class); - ConstraintDescriptor constraintDescriptor = mock(ConstraintDescriptor.class); + var violation = mock(ConstraintViolation.class); + var hibernateViolation = mock(HibernateConstraintViolation.class); + var constraintDescriptor = mock(ConstraintDescriptor.class); var path = mock(Path.class); when(path.toString()).thenReturn(PATH); when(violation.getPropertyPath()).thenReturn(path); - when(violation.getMessageTemplate()).thenReturn("{" + MESSAGE_CODE + "}"); when(violation.getMessage()).thenReturn(MESSAGE); when(violation.getConstraintDescriptor()).thenReturn(constraintDescriptor); + when(violation.getInvalidValue()).thenReturn(PARAM_VALUE); when(constraintDescriptor.getAttributes()).thenReturn(Map.of(PARAM_NAME, PARAM_DYNAMIC_VALUE)); when(violation.unwrap(any())).thenReturn(hibernateViolation); when(hibernateViolation.getDynamicPayload(any())).thenReturn(dynamicViolationParameter); diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/common/errorhandling/IssueParamTestFactory.java b/alfa-service/src/test/java/de/ozgcloud/alfa/common/errorhandling/IssueParamTestFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..2a9a035c179a40230996e0d97cc72ebbb0df7366 --- /dev/null +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/common/errorhandling/IssueParamTestFactory.java @@ -0,0 +1,17 @@ +package de.ozgcloud.alfa.common.errorhandling; + +import de.ozgcloud.alfa.common.errorhandling.IssueParam.IssueParamBuilder; + +public class IssueParamTestFactory { + public static final String PARAM_NAME = ExceptionTestFactory.PARAM_NAME; + public static final String PARAM_DYNAMIC_VALUE = ExceptionTestFactory.PARAM_DYNAMIC_VALUE; + + public static IssueParam create() { + return createBuilder() + .build(); + } + + public static IssueParamBuilder createBuilder() { + return IssueParam.builder().name(PARAM_NAME).value(PARAM_DYNAMIC_VALUE); + } +} diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/common/errorhandling/ProblemDetailMapperTest.java b/alfa-service/src/test/java/de/ozgcloud/alfa/common/errorhandling/ProblemDetailMapperTest.java new file mode 100644 index 0000000000000000000000000000000000000000..b55e6f4ee9a7f15c902446f1846893ca4614f3b0 --- /dev/null +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/common/errorhandling/ProblemDetailMapperTest.java @@ -0,0 +1,313 @@ +package de.ozgcloud.alfa.common.errorhandling; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import java.util.AbstractMap.SimpleEntry; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Stream; + +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; +import jakarta.validation.metadata.ConstraintDescriptor; + +import org.hibernate.validator.engine.HibernateConstraintViolation; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.Spy; +import org.springframework.http.HttpStatus; +import org.springframework.http.ProblemDetail; + +import com.thedeanda.lorem.LoremIpsum; + +import de.ozgcloud.alfa.common.binaryfile.DynamicViolationParameter; +import de.ozgcloud.common.errorhandling.ExceptionUtil; + +class ProblemDetailMapperTest { + + @Spy + private ProblemDetailMapper mapper; + + @Nested + class TestFromConstraintViolationException { + private final String exceptionMessage = LoremIpsum.getInstance().getWords(5); + private final Set<ConstraintViolation<?>> violations = Collections.singleton(ExceptionTestFactory.buildMockedConstraintViolation()); + private final ConstraintViolationException exception = new ConstraintViolationException(exceptionMessage, violations); + private final String message = LoremIpsum.getInstance().getWords(5); + + @BeforeEach + void mockMapper() { + doReturn(message).when(mapper).buildMessageWithExceptionId(exception); + } + + @Test + void shouldGetMessageWithExcpetionId() { + callMapper(); + + verify(mapper).buildMessageWithExceptionId(exception); + } + + @Test + void shouldHaveStatusUnprocessableEntity() { + var problemDetail = callMapper(); + + assertThat(problemDetail.getStatus()).isEqualTo(HttpStatus.UNPROCESSABLE_ENTITY.value()); + } + + @Test + void shouldHaveMessageInDetail() { + var problemDetail = callMapper(); + + assertThat(problemDetail.getDetail()).contains(message); + } + + @Test + void shouldGetDetailedViolationList() { + callMapper(); + + verify(mapper).buildInvalidParams(violations); + } + + @Test + void shouldSetPropertyInvalidParams() { + var expectedInvalidParamsValue = Map.of(); + doReturn(Stream.of(expectedInvalidParamsValue)).when(mapper).buildInvalidParams(violations); + + var problemDetail = callMapper(); + + assertThat(problemDetail.getProperties()) + .containsExactly(new SimpleEntry<String, Object>(ProblemDetailMapper.INVALID_PARAMS, List.of(expectedInvalidParamsValue))); + } + + private ProblemDetail callMapper() { + return mapper.fromConstraintViolationException(exception); + } + } + + @Nested + class TestBuildMessageWithExceptionId { + + private final String exceptionMessage = LoremIpsum.getInstance().getWords(5); + private final String exceptionId = UUID.randomUUID().toString(); + private final ConstraintViolationException exception = new ConstraintViolationException(exceptionMessage, null); + + @BeforeEach + void mockCreateExcpetionId() { + doReturn(exceptionId).when(mapper).createExceptionId(); + } + + @Test + void shouldCreateExceptionId() { + callMapper(); + + verify(mapper).createExceptionId(); + } + + @Test + void shouldFormatMessageWithExceptionId() { + var messageWithId = callMapper(); + + assertThat(messageWithId).isEqualTo(ExceptionUtil.formatMessageWithExceptionId(exceptionMessage, exceptionId)); + } + + private String callMapper() { + return mapper.buildMessageWithExceptionId(exception); + } + } + + @Nested + class TestBuildInvalidParams { + + @Nested + class OnViolations { + private final Set<ConstraintViolation<?>> violations = Set.of(ExceptionTestFactory.buildMockedConstraintViolation(), + ExceptionTestFactory.buildMockedConstraintViolation()); + + @Test + void shouldCallBuildDetailedViolation() { + callMapper().toList(); + + violations.forEach(violation -> verify(mapper).buildDetailedViolation(violation)); + } + + @Test + void shouldReturnListWithDetailedViolations() { + Map<String, Object> detailsMap = Map.of(LoremIpsum.getInstance().getWords(1), LoremIpsum.getInstance().getWords(1)); + violations.forEach(violation -> doReturn(detailsMap).when(mapper).buildDetailedViolation(violation)); + + var detailedViolations = callMapper(); + + assertThat(detailedViolations).containsExactly(detailsMap, detailsMap); + } + + private Stream<Map<String, Object>> callMapper() { + return mapper.buildInvalidParams(violations); + } + } + + @Nested + class OnEmptyViolations { + private final Set<ConstraintViolation<?>> violations = Collections.emptySet(); + + @Test + void shouldCallNotBuildDetailedViolation() { + callMapper(); + + verify(mapper, never()).buildDetailedViolation(any()); + } + + @Test + void shouldReturnListWithDetailedViolations() { + var detailedViolations = callMapper(); + + assertThat(detailedViolations).isEmpty(); + } + + private Stream<Map<String, Object>> callMapper() { + return mapper.buildInvalidParams(violations); + } + } + } + + @Nested + class TestBuildDetailedViolation { + private final ConstraintViolation<?> violation = ExceptionTestFactory.buildMockedConstraintViolation(); + + @Test + void shouldContainFieldName() { + var expectedEntry = new SimpleEntry<>(ProblemDetailMapper.INVALID_PARAMS_KEY_NAME, ExceptionTestFactory.PATH); + + var detailedViolation = callMapper(); + + assertThat(detailedViolation).contains(expectedEntry); + } + + @Test + void shouldContainValue() { + var expectedEntry = new SimpleEntry<>(ProblemDetailMapper.INVALID_PARAMS_KEY_VALUE, ExceptionTestFactory.PARAM_VALUE); + + var detailedViolation = callMapper(); + + assertThat(detailedViolation).contains(expectedEntry); + } + + @Test + void shouldHandleNullValue() { + when(violation.getInvalidValue()).thenReturn(null); + var expectedEntry = new SimpleEntry<>(ProblemDetailMapper.INVALID_PARAMS_KEY_VALUE, + ProblemDetailMapper.PROVIDED_VALUE_WAS_NULL); + + var detailedViolation = callMapper(); + + assertThat(detailedViolation).contains(expectedEntry); + } + + @Test + void shouldContainReason() { + var expectedEntry = new SimpleEntry<>(ProblemDetailMapper.INVALID_PARAMS_KEY_REASON, ExceptionTestFactory.MESSAGE); + + var detailedViolation = callMapper(); + + assertThat(detailedViolation).contains(expectedEntry); + } + + @Test + void shouldBuildParameters() { + callMapper(); + + verify(mapper).buildParameters(violation); + } + + @Test + void shouldContainConstraintParameters() { + var issueParameter = IssueParamTestFactory.create(); + doReturn(Stream.of(issueParameter)).when(mapper).buildParameters(violation); + var expectedEntry = new SimpleEntry<String, Object>(ProblemDetailMapper.INVALID_PARAMS_KEY_CONSTRAINT_PARAMETERS, + List.of(issueParameter)); + + var detailedViolation = callMapper(); + + assertThat(detailedViolation).contains(expectedEntry); + } + + private Map<String, Object> callMapper() { + return mapper.buildDetailedViolation(violation); + } + } + + @Nested + class TestBuildParameters { + + @Mock + private ConstraintViolation<?> violation; + + @Mock + @SuppressWarnings("rawtypes") + private ConstraintDescriptor constraintDescriptor; + + @Mock + @SuppressWarnings("rawtypes") + private HibernateConstraintViolation hibernateConstraintViolation; + + @SuppressWarnings("unchecked") + @BeforeEach + void setUpViolationMocks() { + when(violation.getConstraintDescriptor()).thenReturn(constraintDescriptor); + when(violation.unwrap(HibernateConstraintViolation.class)).thenReturn(hibernateConstraintViolation); + when(constraintDescriptor.getAttributes()) + .thenReturn(Map.of(ExceptionTestFactory.PARAM_NAME, ExceptionTestFactory.PARAM_DYNAMIC_VALUE)); + } + + @Nested + class OnNonDynamicPayload { + + @Test + void shouldBuildIssueParam() { + var issueParams = mapper.buildParameters(violation); + + assertThat(issueParams).usingRecursiveFieldByFieldElementComparator().contains(IssueParamTestFactory.create()); + } + + } + + @Nested + class TestWithDynamicPayload { + + @SuppressWarnings("unchecked") + @Test + void shouldHaveReplacedIssueParameterName() { + var dynamicValue = LoremIpsum.getInstance().getWords(1); + var dynamicViolationParameter = DynamicViolationParameter.builder() + .map(Map.of(ExceptionTestFactory.PARAM_NAME, dynamicValue)) + .build(); + when(hibernateConstraintViolation.getDynamicPayload(DynamicViolationParameter.class)).thenReturn(dynamicViolationParameter); + var expectedIssueParam = IssueParamTestFactory.createBuilder().value(dynamicValue).build(); + + var issueParams = mapper.buildParameters(violation); + + assertThat(issueParams).usingRecursiveFieldByFieldElementComparator().contains(expectedIssueParam); + } + + @SuppressWarnings("unchecked") + @Test + void shouldIgnoreKeyNotPresentInEntry() { + var dynamicViolationParameter = DynamicViolationParameter.builder() + .map(Map.of(LoremIpsum.getInstance().getWords(1), LoremIpsum.getInstance().getWords(1))) + .build(); + when(hibernateConstraintViolation.getDynamicPayload(DynamicViolationParameter.class)).thenReturn(dynamicViolationParameter); + var expectedIssueParam = IssueParamTestFactory.create(); + + var issueParams = mapper.buildParameters(violation); + + assertThat(issueParams).usingRecursiveFieldByFieldElementComparator().contains(expectedIssueParam); + } + } + } +} diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/common/user/CurrentUserServiceTest.java b/alfa-service/src/test/java/de/ozgcloud/alfa/common/user/CurrentUserServiceTest.java index e363dc185c06592f6e7e11e35f25c131bd6a83d6..a478cf2de0cf24b12bec1c1279be4b26c3d9a2d5 100644 --- a/alfa-service/src/test/java/de/ozgcloud/alfa/common/user/CurrentUserServiceTest.java +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/common/user/CurrentUserServiceTest.java @@ -35,8 +35,10 @@ import java.util.Optional; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Spy; +import org.springframework.security.access.hierarchicalroles.RoleHierarchy; import org.springframework.security.oauth2.jwt.Jwt; import de.ozgcloud.alfa.JwtTestFactory; @@ -44,7 +46,12 @@ import de.ozgcloud.alfa.JwtTestFactory; class CurrentUserServiceTest { @Spy + @InjectMocks private CurrentUserService service; + @Mock + private UserService userService; + @Mock + private RoleHierarchy roleHierarchy; @Nested class TestGetOrganisationseinheit { diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/kommentar/KommentarCommandITCase.java b/alfa-service/src/test/java/de/ozgcloud/alfa/kommentar/KommentarCommandITCase.java index 87d5be0da2eae911f865f47380307fe8a7fa61b5..0eed2bf6a4c58bdce7d81ccb29f1de9d40ad372d 100644 --- a/alfa-service/src/test/java/de/ozgcloud/alfa/kommentar/KommentarCommandITCase.java +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/kommentar/KommentarCommandITCase.java @@ -107,9 +107,9 @@ class KommentarCommandITCase { String content = buildContentWithText(null); doRequestByKommentarId(content).andExpect(status().isUnprocessableEntity()) - .andExpect(jsonPath("$.issues.length()").value(1)) - .andExpect(jsonPath("$.issues.[0].field").value("kommentar.text")) - .andExpect(jsonPath("$.issues.[0].messageCode").value(ValidationMessageCodes.FIELD_IS_EMPTY)); + .andExpect(jsonPath("$.invalidParams.length()").value(1)) + .andExpect(jsonPath("$.invalidParams[0].name").value("editKommentar.kommentar.text")) + .andExpect(jsonPath("$.invalidParams[0].reason").value(ValidationMessageCodes.FIELD_IS_EMPTY)); } @SneakyThrows @@ -119,7 +119,7 @@ class KommentarCommandITCase { String content = buildContentWithText(StringUtils.EMPTY); doRequestByKommentarId(content).andExpect(status().isUnprocessableEntity()) - .andExpect(jsonPath("$.issues.[0].field").value("kommentar.text")); + .andExpect(jsonPath("$.invalidParams[0].name").value("editKommentar.kommentar.text")); } @@ -130,7 +130,7 @@ class KommentarCommandITCase { String content = buildContentWithText(StringUtils.EMPTY); doRequestByKommentarId(content).andExpect(status().isUnprocessableEntity()) - .andExpect(jsonPath("$.issues[0].parameters.length()").value(2)); + .andExpect(jsonPath("$.invalidParams[0].constraintParameters.length()").value(2)); } private String buildContentWithText(String text) { @@ -190,9 +190,9 @@ class KommentarCommandITCase { String content = buildContentWithText(null); doRequestByVorgangId(content).andExpect(status().isUnprocessableEntity()) - .andExpect(jsonPath("$.issues.length()").value(1)) - .andExpect(jsonPath("$.issues.[0].field").value("kommentar.text")) - .andExpect(jsonPath("$.issues.[0].messageCode").value(ValidationMessageCodes.FIELD_IS_EMPTY)); + .andExpect(jsonPath("$.invalidParams.length()").value(1)) + .andExpect(jsonPath("$.invalidParams.[0].name").value("createKommentar.kommentar.text")) + .andExpect(jsonPath("$.invalidParams.[0].reason").value(ValidationMessageCodes.FIELD_IS_EMPTY)); } @SneakyThrows @@ -202,7 +202,7 @@ class KommentarCommandITCase { String content = buildContentWithText(StringUtils.EMPTY); doRequestByVorgangId(content).andExpect(status().isUnprocessableEntity()) - .andExpect(jsonPath("$.issues.[0].field").value("kommentar.text")); + .andExpect(jsonPath("$.invalidParams.[0].name").value("createKommentar.kommentar.text")); } @@ -213,7 +213,7 @@ class KommentarCommandITCase { String content = buildContentWithText(StringUtils.EMPTY); doRequestByVorgangId(content).andExpect(status().isUnprocessableEntity()) - .andExpect(jsonPath("$.issues[0].parameters.length()").value(2)); + .andExpect(jsonPath("$.invalidParams[0].constraintParameters.length()").value(2)); } private String buildContentWithText(String text) { diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/loeschanforderung/LoeschAnforderungByVorgangControllerTest.java b/alfa-service/src/test/java/de/ozgcloud/alfa/loeschanforderung/LoeschAnforderungByVorgangControllerTest.java index 63101495e8422e6f26a9be4389fe19c995f30853..c27019e2998e2ef073b0caf0c15a8daf07f15e8f 100644 --- a/alfa-service/src/test/java/de/ozgcloud/alfa/loeschanforderung/LoeschAnforderungByVorgangControllerTest.java +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/loeschanforderung/LoeschAnforderungByVorgangControllerTest.java @@ -26,6 +26,7 @@ import de.ozgcloud.alfa.common.command.CommandOrder; import de.ozgcloud.alfa.common.command.CommandTestFactory; import de.ozgcloud.alfa.common.command.CreateCommand; import de.ozgcloud.alfa.common.errorhandling.ExceptionController; +import de.ozgcloud.alfa.common.errorhandling.ProblemDetailMapper; import de.ozgcloud.alfa.loeschanforderung.LoeschAnforderungController.LoeschAnforderungByVorgangController; import de.ozgcloud.alfa.vorgang.VorgangController; import de.ozgcloud.alfa.vorgang.VorgangHeaderTestFactory; @@ -44,11 +45,14 @@ class LoeschAnforderungByVorgangControllerTest { @Mock private VorgangController vorgangController; + @Mock + private ProblemDetailMapper problemDetailMapper; + private MockMvc mockMvc; @BeforeEach void init() { - mockMvc = MockMvcBuilders.standaloneSetup(controller).setControllerAdvice(new ExceptionController()).build(); + mockMvc = MockMvcBuilders.standaloneSetup(controller).setControllerAdvice(new ExceptionController(problemDetailMapper)).build(); } @DisplayName("Create LoeschAnforderung") @@ -93,7 +97,7 @@ class LoeschAnforderungByVorgangControllerTest { private ResultActions doRequest() throws Exception { var requestBody = CommandTestFactory.buildCreateVorgangCommandContent(CommandOrder.VORGANG_ZUM_LOESCHEN_MARKIEREN.name()); return mockMvc.perform(post(LoeschAnforderungByVorgangController.BASE_PATH, - VorgangHeaderTestFactory.ID, VorgangHeaderTestFactory.VERSION) + VorgangHeaderTestFactory.ID, VorgangHeaderTestFactory.VERSION) .content(requestBody).contentType(MediaType.APPLICATION_JSON).characterEncoding(StandardCharsets.UTF_8.name())) .andExpect(status().isCreated()); } diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/loeschanforderung/LoeschAnforderungCommandControllerTest.java b/alfa-service/src/test/java/de/ozgcloud/alfa/loeschanforderung/LoeschAnforderungCommandControllerTest.java index 392b53911f57a0836a38022192c77c7d6b72411c..ad87a8ced732c2ec48ef91262ce188a9647aabf3 100644 --- a/alfa-service/src/test/java/de/ozgcloud/alfa/loeschanforderung/LoeschAnforderungCommandControllerTest.java +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/loeschanforderung/LoeschAnforderungCommandControllerTest.java @@ -3,8 +3,8 @@ package de.ozgcloud.alfa.loeschanforderung; import static de.ozgcloud.alfa.common.command.CommandController.*; import static org.assertj.core.api.Assertions.*; import static org.hamcrest.CoreMatchers.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @@ -40,6 +40,7 @@ import de.ozgcloud.alfa.common.command.CreateCommand; import de.ozgcloud.alfa.common.command.LegacyOrder; import de.ozgcloud.alfa.common.command.StatusPatch; import de.ozgcloud.alfa.common.errorhandling.ExceptionController; +import de.ozgcloud.alfa.common.errorhandling.ProblemDetailMapper; import de.ozgcloud.alfa.vorgang.VorgangController; import de.ozgcloud.alfa.vorgang.VorgangHeaderTestFactory; import de.ozgcloud.alfa.vorgang.VorgangWithEingang; @@ -64,11 +65,14 @@ class LoeschAnforderungCommandControllerTest { @Mock private CommandController commandController; + @Mock + private ProblemDetailMapper problemDetailMapper; + private MockMvc mockMvc; @BeforeEach void init() { - mockMvc = MockMvcBuilders.standaloneSetup(controller).setControllerAdvice(new ExceptionController()).build(); + mockMvc = MockMvcBuilders.standaloneSetup(controller).setControllerAdvice(new ExceptionController(problemDetailMapper)).build(); } @DisplayName("Create command") diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/loeschanforderung/LoeschAnforderungControllerTest.java b/alfa-service/src/test/java/de/ozgcloud/alfa/loeschanforderung/LoeschAnforderungControllerTest.java index a0ea03964f79264344bb89b24f16bddcc4cf0ed1..0ccd8259172c85ae64c5b55356d9eadc30e2b9a1 100644 --- a/alfa-service/src/test/java/de/ozgcloud/alfa/loeschanforderung/LoeschAnforderungControllerTest.java +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/loeschanforderung/LoeschAnforderungControllerTest.java @@ -17,6 +17,7 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders; import de.ozgcloud.alfa.common.UserProfileUrlProvider; import de.ozgcloud.alfa.common.errorhandling.ExceptionController; +import de.ozgcloud.alfa.common.errorhandling.ProblemDetailMapper; import lombok.SneakyThrows; class LoeschAnforderungControllerTest { @@ -30,11 +31,14 @@ class LoeschAnforderungControllerTest { @Mock private LoeschAnforderungModelAssembler modelAssembler; + @Mock + private ProblemDetailMapper problemDetailMapper; + private MockMvc mockMvc; @BeforeEach void init() { - mockMvc = MockMvcBuilders.standaloneSetup(controller).setControllerAdvice(new ExceptionController()).build(); + mockMvc = MockMvcBuilders.standaloneSetup(controller).setControllerAdvice(new ExceptionController(problemDetailMapper)).build(); } @Nested diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/vorgang/VorgangWithEingangProcessorTest.java b/alfa-service/src/test/java/de/ozgcloud/alfa/vorgang/VorgangWithEingangProcessorTest.java index 05306f1e29f313ec547dee7e7a8fc5ec1d652614..988b8cd04b8df6ae58cb6923785235828ebd5447 100644 --- a/alfa-service/src/test/java/de/ozgcloud/alfa/vorgang/VorgangWithEingangProcessorTest.java +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/vorgang/VorgangWithEingangProcessorTest.java @@ -27,7 +27,6 @@ import static de.ozgcloud.alfa.common.UserProfileUrlProviderTestFactory.*; import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; -import java.util.Collections; import java.util.List; import org.junit.jupiter.api.BeforeEach; @@ -45,7 +44,6 @@ import org.springframework.hateoas.LinkRelation; import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UriTemplate; -import de.ozgcloud.alfa.common.FeatureToggleProperties; import de.ozgcloud.alfa.common.UserProfileUrlProvider; import de.ozgcloud.alfa.common.command.CommandController.CommandByRelationController; import de.ozgcloud.alfa.common.user.CurrentUserService; @@ -66,12 +64,6 @@ class VorgangWithEingangProcessorTest { @Mock private UserManagerUrlProvider userManagerUrlProvider; - @Mock - private FeatureToggleProperties featureToggleProperties; - - @Mock - private VorgangProperties vorgangProperties; - @Mock private VorgangProcessorProperties vorgangProcessorProperties; @@ -332,218 +324,6 @@ class VorgangWithEingangProcessorTest { } } - @Nested - class TestHasVorgangCreateBescheidEnabled { - - @Nested - class TestOnEmptyBescheidProperties { - - @BeforeEach - void setUp() { - when(vorgangProperties.getBescheid()).thenReturn(Collections.emptyList()); - } - - @Test - void shouldReturnFalse() { - var hasEnabled = callProcessor(VorgangWithEingangTestFactory.create()); - - assertThat(hasEnabled).isFalse(); - } - - } - - @Nested - class TestOnBescheidPropertiesSet { - - @BeforeEach - void setUp() { - when(vorgangProperties.getBescheid()).thenReturn(List.of(VorgangPropertyTestFactory.create())); - } - - @Test - void shouldReturnFalseIfFormEngineNameNotEquals() { - var vorgang = createVorgang(EingangTestFactory.createBuilder() - .header(EingangHeaderTestFactory.createBuilder() - .formEngineName("different").build()) - .build()); - - var hasEnabled = callProcessor(vorgang); - - assertThat(hasEnabled).isFalse(); - } - - @Test - void shouldReturnTrue() { - var hasEnabled = callProcessor(VorgangWithEingangTestFactory.create()); - - assertThat(hasEnabled).isTrue(); - } - } - - @Test - void shouldReturnFalseOnEmptyEingang() { - var vorgangWithEmptyEingang = createVorgang(null); - - var hasEnabled = callProcessor(vorgangWithEmptyEingang); - - assertThat(hasEnabled).isFalse(); - } - - @Test - void shouldReturnFalseOnEmptyEingangHeader() { - var vorgangWithEmptyEingangHeader = EingangTestFactory.createBuilder().header(null).build(); - - var hasEnabled = callProcessor(createVorgang(vorgangWithEmptyEingangHeader)); - - assertThat(hasEnabled).isFalse(); - } - - @Test - void shouldReturnFalseOnEmptyFormEngineName() { - var vorgangWithEmptyFormEngineName = createVorgang( - EingangTestFactory.createBuilder().header(EingangHeaderTestFactory.createBuilder().formEngineName(null).build()).build()); - - var hasEnabled = callProcessor(vorgangWithEmptyFormEngineName); - - assertThat(hasEnabled).isFalse(); - } - - @Test - void shouldReturnFalseOnEmptyFormId() { - var vorgangWithEmptyFormId = createVorgang( - EingangTestFactory.createBuilder().header(EingangHeaderTestFactory.createBuilder().formId(null).build()).build()); - - var hasEnabled = callProcessor(vorgangWithEmptyFormId); - - assertThat(hasEnabled).isFalse(); - } - - private boolean callProcessor(VorgangWithEingang vorgang) { - return processor.hasVorgangCreateBescheidEnabled(vorgang); - } - - private VorgangWithEingang createVorgang(Eingang eingang) { - return VorgangWithEingangTestFactory.createBuilder().eingang(eingang).build(); - } - } - - @Nested - class TestIsCreateBescheidEnabled { - - @Nested - class TestFeatureToggleDisabled { - - @BeforeEach - void setUp() { - when(featureToggleProperties.isCreateBescheid()).thenReturn(false); - } - - @Test - void shouldCallFeatureToggleProperties() { - callProcessor(VorgangWithEingangTestFactory.create()); - - verify(featureToggleProperties).isCreateBescheid(); - } - - @Test - void shouldNotCallHasVorgangCreateBescheidEnabled() { - var vorgang = VorgangWithEingangTestFactory.create(); - - callProcessor(vorgang); - - verify(processor, never()).hasVorgangCreateBescheidEnabled(vorgang); - } - - @Test - void shouldReturnFalse() { - var isEnabled = callProcessor(VorgangWithEingangTestFactory.create()); - - assertThat(isEnabled).isFalse(); - } - } - - @Nested - class TestFeatureToggleEnabled { - - private final VorgangWithEingang vorgang = VorgangWithEingangTestFactory.create(); - - @BeforeEach - void setUp() { - when(featureToggleProperties.isCreateBescheid()).thenReturn(true); - } - - @Test - void shouldCallFeatureToggleProperties() { - callProcessor(vorgang); - - verify(featureToggleProperties).isCreateBescheid(); - } - - @Test - void shouldCallHasVorgangCreateBescheidEnabled() { - callProcessor(vorgang); - - verify(processor).hasVorgangCreateBescheidEnabled(vorgang); - } - - @Test - void shouldReturnTrue() { - doReturn(true).when(processor).hasVorgangCreateBescheidEnabled(vorgang); - - var isEnabled = callProcessor(vorgang); - - assertThat(isEnabled).isTrue(); - } - - @Test - void shouldReturnFalse() { - doReturn(false).when(processor).hasVorgangCreateBescheidEnabled(vorgang); - - var isEnabled = callProcessor(vorgang); - - assertThat(isEnabled).isFalse(); - } - } - - private boolean callProcessor(VorgangWithEingang vorgang) { - return processor.isCreateBescheidEnabled(vorgang); - } - - } - - @Nested - class TestCreateBescheidLink { - - private final VorgangWithEingang vorgang = VorgangWithEingangTestFactory.create(); - private final EntityModel<VorgangWithEingang> vorgangEntityModel = EntityModel.of(vorgang); - - @BeforeEach - void activateFeature() { - initUserProfileUrlProvider(urlProvider); - } - - @Test - void shouldHaveCreateBescheidLink() { - doReturn(true).when(processor).isCreateBescheidEnabled(vorgang); - - var model = processor.process(vorgangEntityModel); - - assertThat(model.getLink(VorgangWithEingangProcessor.REL_BESCHEID)).isPresent().get() - .extracting(Link::getHref) - .isEqualTo("/api/vorgangs/" + VorgangHeaderTestFactory.ID + "/relations/" + VorgangHeaderTestFactory.ID + "/" - + VorgangHeaderTestFactory.VERSION + "/commands"); - } - - @Test - void shouldHaveNoLinkIfDisabled() { - doReturn(false).when(processor).isCreateBescheidEnabled(vorgang); - - var model = processor.process(vorgangEntityModel); - - assertThat(model.getLink(VorgangWithEingangProcessor.REL_BESCHEID)).isEmpty(); - } - } - @DisplayName("Process vorgang") @Nested class TestProcessVorgang { diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/wiedervorlage/WiedervorlageCommandByVorgangControllerTest.java b/alfa-service/src/test/java/de/ozgcloud/alfa/wiedervorlage/WiedervorlageCommandByVorgangControllerTest.java index 74d31babe720c7877812b0422b00cb1d76b377c2..ef4a43f95f4ed8458d9be2f37bb190e32bd87e84 100644 --- a/alfa-service/src/test/java/de/ozgcloud/alfa/wiedervorlage/WiedervorlageCommandByVorgangControllerTest.java +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/wiedervorlage/WiedervorlageCommandByVorgangControllerTest.java @@ -44,7 +44,9 @@ import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import de.ozgcloud.alfa.common.binaryfile.BinaryFileTestFactory; +import de.ozgcloud.alfa.common.command.Command; import de.ozgcloud.alfa.common.command.CommandController.CommandByRelationController; +import de.ozgcloud.alfa.common.command.CommandStatus; import de.ozgcloud.alfa.common.command.CommandTestFactory; import de.ozgcloud.alfa.common.command.CreateCommand; import de.ozgcloud.alfa.common.command.LegacyOrder; @@ -80,10 +82,15 @@ class WiedervorlageCommandByVorgangControllerTest { @Captor private ArgumentCaptor<Wiedervorlage> wiedervorlageCaptor; + private Command createCommand; + private Command doneCommand; @BeforeEach void mockUserService() { - when(service.createWiedervorlage(any(), any())).thenReturn(CommandTestFactory.create()); + createCommand = CommandTestFactory.create(); + doneCommand = createCommand.toBuilder().status(CommandStatus.FINISHED).build(); + when(service.createWiedervorlage(any(), any())).thenReturn(createCommand); + when(service.updateNextFrist(createCommand, VorgangHeaderTestFactory.ID)).thenReturn(doneCommand); } @Nested @@ -98,10 +105,10 @@ class WiedervorlageCommandByVorgangControllerTest { } @Test - void shouldCallServiceToUpdateNextFrist() { + void shouldUpdateNextFristOnSuccessfullyDoneCommand() { doRequest(); - verify(service).updateNextFrist(VorgangHeaderTestFactory.ID); + verify(service).updateNextFrist(createCommand, VorgangHeaderTestFactory.ID); } @SneakyThrows @@ -115,8 +122,8 @@ class WiedervorlageCommandByVorgangControllerTest { @SneakyThrows private ResultActions doRequest() { return mockMvc.perform( - post(WiedervorlageCommandByVorgangController.WIEDERVORLAGE_COMMANDS_BY_VORGANG, - VorgangHeaderTestFactory.ID) + post(WiedervorlageCommandByVorgangController.WIEDERVORLAGE_COMMANDS_BY_VORGANG, + VorgangHeaderTestFactory.ID) .content(createRequestContent()) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().is2xxSuccessful()); diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/wiedervorlage/WiedervorlageCommandControllerTest.java b/alfa-service/src/test/java/de/ozgcloud/alfa/wiedervorlage/WiedervorlageCommandControllerTest.java index 74bc2df86a8f6f8d98dc1fe34365654ef44c2e0e..198f7a00535cfc7dbcc73c53fa65f3d826a2225d 100644 --- a/alfa-service/src/test/java/de/ozgcloud/alfa/wiedervorlage/WiedervorlageCommandControllerTest.java +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/wiedervorlage/WiedervorlageCommandControllerTest.java @@ -46,7 +46,7 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders; import de.ozgcloud.alfa.common.binaryfile.FileId; import de.ozgcloud.alfa.common.command.Command; import de.ozgcloud.alfa.common.command.CommandOrder; -import de.ozgcloud.alfa.common.command.CommandService; +import de.ozgcloud.alfa.common.command.CommandStatus; import de.ozgcloud.alfa.common.command.CommandTestFactory; import de.ozgcloud.alfa.common.command.CreateCommand; import de.ozgcloud.alfa.common.command.LegacyOrder; @@ -60,8 +60,6 @@ class WiedervorlageCommandControllerTest { @InjectMocks private WiedervorlageCommandController controller; @Mock - private CommandService commandService; - @Mock private WiedervorlageService service; private MockMvc mockMvc; @@ -80,12 +78,18 @@ class WiedervorlageCommandControllerTest { @Nested class ControllerMethods { + private Command createCommand; + private Command doneCommand; + @BeforeEach void init() { when(service.getById(any())).thenReturn(WiedervorlageTestFactory.create()); - when(service.editWiedervorlage(any(), any(), anyLong())).thenReturn(CommandTestFactory.createBuilder() + createCommand = CommandTestFactory.createBuilder() .order(CommandOrder.UPDATE_ATTACHED_ITEM.name()) - .body(WiedervorlageTestFactory.createAsMap()).build()); + .body(WiedervorlageTestFactory.createAsMap()).build(); + doneCommand = createCommand.toBuilder().status(CommandStatus.FINISHED).build(); + when(service.editWiedervorlage(any(), any(), anyLong())).thenReturn(createCommand); + when(service.updateNextFrist(createCommand, VorgangHeaderTestFactory.ID)).thenReturn(doneCommand); } @SneakyThrows @@ -97,10 +101,10 @@ class WiedervorlageCommandControllerTest { } @Test - void shouldCallServiceUpdateNextFrist() { + void shouldUpdateNextFristOnSuccessfullyDoneCommand() { doRequest(); - verify(service).updateNextFrist(VorgangHeaderTestFactory.ID); + verify(service).updateNextFrist(createCommand, VorgangHeaderTestFactory.ID); } @SneakyThrows @@ -129,8 +133,8 @@ class WiedervorlageCommandControllerTest { @SneakyThrows private ResultActions doRequest() { return mockMvc.perform( - post(WiedervorlageCommandController.WIEDERVORLAGE_COMMANDS, WiedervorlageTestFactory.ID, WiedervorlageTestFactory.VERSION) - .content(createRequestContent()).contentType(MediaType.APPLICATION_JSON)) + post(WiedervorlageCommandController.WIEDERVORLAGE_COMMANDS, WiedervorlageTestFactory.ID, WiedervorlageTestFactory.VERSION) + .content(createRequestContent()).contentType(MediaType.APPLICATION_JSON)) .andExpect(status().is2xxSuccessful()); } diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/wiedervorlage/WiedervorlageCommandITCase.java b/alfa-service/src/test/java/de/ozgcloud/alfa/wiedervorlage/WiedervorlageCommandITCase.java index 88a17b72ba185204edb3514e4a2bfcd90bcf53a6..d0328ea3258d380bf799e934d07e4f20227c4528 100644 --- a/alfa-service/src/test/java/de/ozgcloud/alfa/wiedervorlage/WiedervorlageCommandITCase.java +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/wiedervorlage/WiedervorlageCommandITCase.java @@ -103,9 +103,9 @@ class WiedervorlageCommandITCase { String content = buildContentWithBetreff(null); doRequest(content).andExpect(status().isUnprocessableEntity()) - .andExpect(jsonPath("$.issues.length()").value(1)) - .andExpect(jsonPath("$.issues.[0].field").value("wiedervorlage.betreff")) - .andExpect(jsonPath("$.issues.[0].messageCode").value(ValidationMessageCodes.FIELD_IS_EMPTY)); + .andExpect(jsonPath("$.invalidParams.length()").value(1)) + .andExpect(jsonPath("$.invalidParams[0].name").value("editWiedervorlage.wiedervorlage.betreff")) + .andExpect(jsonPath("$.invalidParams[0].reason").value(ValidationMessageCodes.FIELD_IS_EMPTY)); } @SneakyThrows @@ -115,7 +115,7 @@ class WiedervorlageCommandITCase { String content = buildContentWithBetreff("a"); doRequest(content).andExpect(status().isUnprocessableEntity()) - .andExpect(jsonPath("$.issues.[0].field").value("wiedervorlage.betreff")); + .andExpect(jsonPath("$.invalidParams[0].name").value("editWiedervorlage.wiedervorlage.betreff")); } @@ -125,7 +125,8 @@ class WiedervorlageCommandITCase { void minMaxParameter() { String content = buildContentWithBetreff("a"); - doRequest(content).andExpect(status().isUnprocessableEntity()).andExpect(jsonPath("$.issues[0].parameters.length()").value(2)); + doRequest(content).andExpect(status().isUnprocessableEntity()) + .andExpect(jsonPath("$.invalidParams[0].constraintParameters.length()").value(2)); } @SneakyThrows @@ -148,9 +149,9 @@ class WiedervorlageCommandITCase { String content = createEditContent(WiedervorlageTestFactory.createBuilder().frist(LocalDate.parse("2020-01-01")).build()); doRequest(content).andExpect(status().isUnprocessableEntity()) - .andExpect(jsonPath("$.issues.length()").value(1)) - .andExpect(jsonPath("$.issues.[0].field").value("wiedervorlage.frist")) - .andExpect(jsonPath("$.issues.[0].messageCode").value(ValidationMessageCodes.FIELD_DATE_PAST)); + .andExpect(jsonPath("$.invalidParams.length()").value(1)) + .andExpect(jsonPath("$.invalidParams[0].name").value("editWiedervorlage.wiedervorlage.frist")) + .andExpect(jsonPath("$.invalidParams[0].reason").value(ValidationMessageCodes.FIELD_DATE_PAST)); } @SneakyThrows @@ -160,9 +161,9 @@ class WiedervorlageCommandITCase { String content = createEditContent(WiedervorlageTestFactory.createBuilder().frist(null).build()); doRequest(content).andExpect(status().isUnprocessableEntity()) - .andExpect(jsonPath("$.issues.length()").value(1)) - .andExpect(jsonPath("$.issues.[0].field").value("wiedervorlage.frist")) - .andExpect(jsonPath("$.issues.[0].messageCode").value(ValidationMessageCodes.FIELD_IS_EMPTY)); + .andExpect(jsonPath("$.invalidParams.length()").value(1)) + .andExpect(jsonPath("$.invalidParams[0].name").value("editWiedervorlage.wiedervorlage.frist")) + .andExpect(jsonPath("$.invalidParams[0].reason").value(ValidationMessageCodes.FIELD_IS_EMPTY)); } private String createEditContent(Wiedervorlage wiedervorlage) { @@ -225,9 +226,9 @@ class WiedervorlageCommandITCase { String content = buildContentWithBetreff(null); doRequest(content).andExpect(status().isUnprocessableEntity()) - .andExpect(jsonPath("$.issues.length()").value(1)) - .andExpect(jsonPath("$.issues.[0].field").value("wiedervorlage.betreff")) - .andExpect(jsonPath("$.issues.[0].messageCode").value(ValidationMessageCodes.FIELD_IS_EMPTY)); + .andExpect(jsonPath("$.invalidParams.length()").value(1)) + .andExpect(jsonPath("$.invalidParams[0].name").value("createWiedervorlage.wiedervorlage.betreff")) + .andExpect(jsonPath("$.invalidParams[0].reason").value(ValidationMessageCodes.FIELD_IS_EMPTY)); } @SneakyThrows @@ -237,7 +238,7 @@ class WiedervorlageCommandITCase { String content = buildContentWithBetreff("a"); doRequest(content).andExpect(status().isUnprocessableEntity()) - .andExpect(jsonPath("$.issues.[0].field").value("wiedervorlage.betreff")); + .andExpect(jsonPath("$.invalidParams[0].name").value("createWiedervorlage.wiedervorlage.betreff")); } @@ -247,7 +248,8 @@ class WiedervorlageCommandITCase { void minMaxParameter() { String content = buildContentWithBetreff("a"); - doRequest(content).andExpect(status().isUnprocessableEntity()).andExpect(jsonPath("$.issues[0].parameters.length()").value(2)); + doRequest(content).andExpect(status().isUnprocessableEntity()) + .andExpect(jsonPath("$.invalidParams[0].constraintParameters.length()").value(2)); } @SneakyThrows @@ -272,9 +274,9 @@ class WiedervorlageCommandITCase { createWithWiedervorlage(WiedervorlageTestFactory.createBuilder().frist(LocalDate.parse("2020-01-01")).build())); doRequest(content).andExpect(status().isUnprocessableEntity()) - .andExpect(jsonPath("$.issues.length()").value(1)) - .andExpect(jsonPath("$.issues.[0].field").value("wiedervorlage.frist")) - .andExpect(jsonPath("$.issues.[0].messageCode").value(ValidationMessageCodes.FIELD_DATE_PAST)); + .andExpect(jsonPath("$.invalidParams.length()").value(1)) + .andExpect(jsonPath("$.invalidParams[0].name").value("createWiedervorlage.wiedervorlage.frist")) + .andExpect(jsonPath("$.invalidParams[0].reason").value(ValidationMessageCodes.FIELD_DATE_PAST)); } @SneakyThrows @@ -285,9 +287,9 @@ class WiedervorlageCommandITCase { createWithWiedervorlage(WiedervorlageTestFactory.createBuilder().frist(null).build())); doRequest(content).andExpect(status().isUnprocessableEntity()) - .andExpect(jsonPath("$.issues.length()").value(1)) - .andExpect(jsonPath("$.issues.[0].field").value("wiedervorlage.frist")) - .andExpect(jsonPath("$.issues.[0].messageCode").value(ValidationMessageCodes.FIELD_IS_EMPTY)); + .andExpect(jsonPath("$.invalidParams.length()").value(1)) + .andExpect(jsonPath("$.invalidParams[0].name").value("createWiedervorlage.wiedervorlage.frist")) + .andExpect(jsonPath("$.invalidParams[0].reason").value(ValidationMessageCodes.FIELD_IS_EMPTY)); } } diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/wiedervorlage/WiedervorlageServiceTest.java b/alfa-service/src/test/java/de/ozgcloud/alfa/wiedervorlage/WiedervorlageServiceTest.java index 3af67e13526b1f213bcbd417e2c5b2a45ae78a85..ae7f2e55675bd0ee63bb76f7a1b175b3bec39534 100644 --- a/alfa-service/src/test/java/de/ozgcloud/alfa/wiedervorlage/WiedervorlageServiceTest.java +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/wiedervorlage/WiedervorlageServiceTest.java @@ -47,6 +47,7 @@ import org.mockito.Spy; import de.ozgcloud.alfa.common.attacheditem.VorgangAttachedItemService; import de.ozgcloud.alfa.common.command.Command; import de.ozgcloud.alfa.common.command.CommandService; +import de.ozgcloud.alfa.common.command.CommandStatus; import de.ozgcloud.alfa.common.command.CommandTestFactory; import de.ozgcloud.alfa.common.user.CurrentUserService; import de.ozgcloud.alfa.common.user.UserProfileTestFactory; @@ -107,14 +108,14 @@ class WiedervorlageServiceTest { } @Test - void shouldSetCreatedAt() throws Exception { + void shouldSetCreatedAt() { var wiedervorlage = callAddCreated(); assertThat(wiedervorlage.getCreatedAt()).isNotNull().isCloseTo(ZonedDateTime.now(), within(2, ChronoUnit.SECONDS)); } @Test - void shouldSetCreatedBy() throws Exception { + void shouldSetCreatedBy() { var wiedervorlage = callAddCreated(); assertThat(wiedervorlage.getCreatedBy()).isEqualTo(UserProfileTestFactory.ID.toString()); @@ -172,7 +173,7 @@ class WiedervorlageServiceTest { } @Nested - class TestUpdateNextFrist { + class TestDoUpdateNextFrist { @Nested class ServiceMethod { @@ -206,7 +207,7 @@ class WiedervorlageServiceTest { } private void callUpdateNextFrist() { - service.updateNextFrist(VorgangHeaderTestFactory.ID); + service.doUpdateNextFrist(VorgangHeaderTestFactory.ID); } } @@ -254,4 +255,68 @@ class WiedervorlageServiceTest { } } } + + @Nested + class TestUpdateNextFrist { + + @Test + void shouldWaitUntilCommandDone() { + var pendingCommand = CommandTestFactory.createBuilder().status(CommandStatus.PENDING).build(); + var command = CommandTestFactory.create(); + when(commandService.waitUntilDone(command)).thenReturn(pendingCommand); + + service.updateNextFrist(command, VorgangHeaderTestFactory.ID); + + verify(commandService).waitUntilDone(command); + } + + @Test + void shouldReturnDoneCommand() { + var doneCommand = CommandTestFactory.createBuilder().status(CommandStatus.FINISHED).build(); + var command = CommandTestFactory.create(); + when(commandService.waitUntilDone(command)).thenReturn(doneCommand); + + var result = service.updateNextFrist(command, VorgangHeaderTestFactory.ID); + + assertThat(result).isEqualTo(doneCommand); + } + + @Nested + class OnDoneSuccessfullyCommand { + private final Command command = CommandTestFactory.create(); + private final Command doneCommand = CommandTestFactory.createBuilder().status(CommandStatus.FINISHED).build(); + + @BeforeEach + void setUp() { + when(commandService.waitUntilDone(command)).thenReturn(doneCommand); + } + + @Test + void shouldUpdateNextFrist() { + service.updateNextFrist(command, VorgangHeaderTestFactory.ID); + + verify(service).doUpdateNextFrist(VorgangHeaderTestFactory.ID); + } + } + + @Nested + class OnNotDoneSuccessfullyCommand { + private final Command command = CommandTestFactory.create(); + private final Command pendingCommand = CommandTestFactory.createBuilder().status(CommandStatus.PENDING).build(); + + @BeforeEach + void setUp() { + when(commandService.waitUntilDone(command)).thenReturn(pendingCommand); + } + + @Test + void shouldNotUpdateNextFrist() { + service.updateNextFrist(command, VorgangHeaderTestFactory.ID); + + verify(service, never()).doUpdateNextFrist(VorgangHeaderTestFactory.ID); + } + } + + } + } \ No newline at end of file diff --git a/alfa-xdomea/pom.xml b/alfa-xdomea/pom.xml index 6f92cd2bc38e598b1dc2ab97a546ab4c331c7139..c141f0be18aa411909614eadcb8a85bc679f4837 100644 --- a/alfa-xdomea/pom.xml +++ b/alfa-xdomea/pom.xml @@ -31,7 +31,7 @@ <parent> <groupId>de.ozgcloud.alfa</groupId> <artifactId>alfa</artifactId> - <version>2.11.0-SNAPSHOT</version> + <version>2.12.0-SNAPSHOT</version> </parent> <artifactId>alfa-xdomea</artifactId> diff --git a/pom.xml b/pom.xml index 9f392394780ae740b80a1a4c48cc613ae8061467..334f0fbc2c8cf186962349557be289bcb2724de3 100644 --- a/pom.xml +++ b/pom.xml @@ -30,12 +30,12 @@ <parent> <groupId>de.ozgcloud.common</groupId> <artifactId>ozgcloud-common-parent</artifactId> - <version>4.0.1</version> + <version>4.3.1</version> </parent> <groupId>de.ozgcloud.alfa</groupId> <artifactId>alfa</artifactId> - <version>2.11.0-SNAPSHOT</version> + <version>2.12.0-SNAPSHOT</version> <name>Alfa Parent</name> <packaging>pom</packaging>