diff --git a/src/assets/icons/dossier-info.svg b/src/assets/icons/dossier-info.svg
new file mode 100644
index 0000000..6c0057d
--- /dev/null
+++ b/src/assets/icons/dossier-info.svg
@@ -0,0 +1,7 @@
+
+
+
+
\ No newline at end of file
diff --git a/src/assets/icons/settings.svg b/src/assets/icons/settings.svg
new file mode 100644
index 0000000..c00977c
--- /dev/null
+++ b/src/assets/icons/settings.svg
@@ -0,0 +1,13 @@
+
+
\ No newline at end of file
diff --git a/src/assets/styles/common-layout.scss b/src/assets/styles/common-layout.scss
index f68cf13..38605e6 100644
--- a/src/assets/styles/common-layout.scss
+++ b/src/assets/styles/common-layout.scss
@@ -127,16 +127,16 @@ section.settings {
}
}
}
+}
- @media only screen and (max-width: 1600px) {
- .user-column {
- justify-content: center;
- align-items: center;
+@media only screen and (max-width: 1600px) {
+ .user-column {
+ justify-content: center;
+ align-items: center;
- // TODO
- redaction-initials-avatar .username {
- display: none;
- }
+ // TODO: Shouldn't use `redaction-` here
+ redaction-initials-avatar .username {
+ display: none;
}
}
}
@@ -214,78 +214,6 @@ section.settings {
flex: 2;
}
-.mt-0 {
- margin-top: 0 !important;
-}
-
-.mt-5 {
- margin-top: 5px;
-}
-
-.mt-8 {
- margin-top: 8px;
-}
-
-.mt-12 {
- margin-top: 12px;
-}
-
-.mt-16 {
- margin-top: 16px !important;
-}
-
-.mt-20 {
- margin-top: 20px;
-}
-
-.mt-24 {
- margin-top: 24px;
-}
-
-.mt-32 {
- margin-top: 32px;
-}
-
-.mb-0 {
- margin-bottom: 0 !important;
-}
-
-.mb-6 {
- margin-bottom: 6px;
-}
-
-.mb-8 {
- margin-bottom: 8px !important;
-}
-
-.mb-12 {
- margin-bottom: 12px !important;
-}
-
-.ml-8 {
- margin-left: 8px;
-}
-
-.ml-14 {
- margin-left: 14px;
-}
-
-.ml-16 {
- margin-left: 16px;
-}
-
-.mr-24 {
- margin-right: 24px;
-}
-
-.pb-24 {
- padding-bottom: 24px;
-}
-
-.pb-32 {
- padding-bottom: 32px;
-}
-
.w-100 {
min-width: 100px !important;
width: 100px !important;
@@ -309,22 +237,6 @@ section.settings {
cursor: pointer;
}
-.mr-4 {
- margin-right: 4px !important;
-}
-
-.mr-8 {
- margin-right: 8px !important;
-}
-
-.mr-16 {
- margin-right: 16px;
-}
-
-.mr-34 {
- margin-right: 34px;
-}
-
.fit-content {
width: fit-content;
}
diff --git a/src/assets/styles/common-styles.scss b/src/assets/styles/common-styles.scss
index 722c27e..ed6c081 100644
--- a/src/assets/styles/common-styles.scss
+++ b/src/assets/styles/common-styles.scss
@@ -1,3 +1,4 @@
+@use 'common-utilities';
@use 'common-fonts';
@use 'common-inputs';
@use 'common-buttons';
diff --git a/src/assets/styles/common-utilities.scss b/src/assets/styles/common-utilities.scss
new file mode 100644
index 0000000..46ab464
--- /dev/null
+++ b/src/assets/styles/common-utilities.scss
@@ -0,0 +1,26 @@
+/* Margins, paddings */
+
+$start: 0;
+$end: 100;
+
+$values: "";
+$sides: (top, bottom, left, right);
+
+@for $i from $start + 1 through $end {
+ $values: append($values, $i, comma);
+ $values: set-nth($values, 1, $start);
+}
+
+// TODO: Check if !important can be avoided
+
+@each $space in $values {
+ @each $side in $sides {
+ .m#{str-slice($side, 0, 1)}-#{$space} {
+ margin-#{$side}: #{$space}px !important;
+ }
+
+ .p#{str-slice($side, 0, 1)}-#{$space} {
+ padding-#{$side}: #{$space}px !important;
+ }
+ }
+}
diff --git a/src/lib/dialog/base-dialog.component.ts b/src/lib/dialog/base-dialog.component.ts
index 017173d..13d1e7b 100644
--- a/src/lib/dialog/base-dialog.component.ts
+++ b/src/lib/dialog/base-dialog.component.ts
@@ -9,7 +9,7 @@ import { IqserEventTarget } from '../utils';
* However, some components (e.g. redaction-select, color picker) don't set focus on the input after choosing a value.
* Also, other components (e.g. dropdown select) trigger a different action on enter, instead of submit.
*
- * Make sure to remove property type="submit" from the save button and the (submit)="save()" property from the form
+ * Make sure to remove the (submit)="save()" property from the form and to set type="button" on the save button
* (otherwise the save request will be triggered twice).
* */
export abstract class BaseDialogComponent {
diff --git a/src/lib/icons/icons.module.ts b/src/lib/icons/icons.module.ts
index 6869045..67f60a2 100644
--- a/src/lib/icons/icons.module.ts
+++ b/src/lib/icons/icons.module.ts
@@ -19,6 +19,7 @@ export class IqserIconsModule {
'collapse',
'csv',
'document',
+ 'dossier-info',
'download',
'edit',
'expand',
@@ -37,6 +38,7 @@ export class IqserIconsModule {
'radio-selected',
'refresh',
'search',
+ 'settings',
'sort-asc',
'sort-desc',
'status-collapse',
diff --git a/src/lib/listing/page-header/page-header.component.scss b/src/lib/listing/page-header/page-header.component.scss
index acbdfdf..e69de29 100644
--- a/src/lib/listing/page-header/page-header.component.scss
+++ b/src/lib/listing/page-header/page-header.component.scss
@@ -1,3 +0,0 @@
-.ml-6 {
- margin-left: 6px;
-}
diff --git a/src/lib/listing/workflow/column-header/column-header.component.scss b/src/lib/listing/workflow/column-header/column-header.component.scss
index 3ce2e65..cd944a3 100644
--- a/src/lib/listing/workflow/column-header/column-header.component.scss
+++ b/src/lib/listing/workflow/column-header/column-header.component.scss
@@ -4,7 +4,3 @@
align-items: center;
justify-content: space-between;
}
-
-.mr-10 {
- margin-right: 10px;
-}
diff --git a/src/lib/services/index.ts b/src/lib/services/index.ts
index 214c413..026ff9d 100644
--- a/src/lib/services/index.ts
+++ b/src/lib/services/index.ts
@@ -3,3 +3,4 @@ export * from './toaster.service';
export * from './error-message.service';
export * from './generic.service';
export * from './composite-route.guard';
+export * from './stats.service';
diff --git a/src/lib/services/stats.service.ts b/src/lib/services/stats.service.ts
new file mode 100644
index 0000000..3be767c
--- /dev/null
+++ b/src/lib/services/stats.service.ts
@@ -0,0 +1,62 @@
+import { Inject, Injectable, Injector } from '@angular/core';
+import { BehaviorSubject, Observable } from 'rxjs';
+import { HttpClient } from '@angular/common/http';
+import { tap } from 'rxjs/operators';
+import { HeadersConfiguration, mapEach, RequiredParam, Validate } from '../utils';
+
+@Injectable()
+export abstract class StatsService {
+ private readonly _http = this._injector.get(HttpClient);
+ private readonly _map = new Map>();
+
+ protected constructor(
+ protected readonly _injector: Injector,
+ @Inject('ENTITY_PRIMARY_KEY') protected readonly _primaryKey: string,
+ @Inject('ENTITY_CLASS') private readonly _entityClass: new (entityInterface: I, ...args: unknown[]) => E,
+ @Inject('ENTITY_PATH') protected readonly _defaultModelPath: string,
+ ) {}
+
+ @Validate()
+ getFor(@RequiredParam() ids: string[]): Observable {
+ const request = this._http.post(`/${encodeURI(this._defaultModelPath)}`, ids, {
+ headers: HeadersConfiguration.getHeaders(),
+ observe: 'body',
+ });
+
+ return request.pipe(
+ mapEach(entity => new this._entityClass(entity)),
+ tap(entities => entities.forEach(entity => this.set(entity))),
+ );
+ }
+
+ get(key: string): E {
+ return this._getBehaviourSubject(key).value;
+ }
+
+ set(stats: E): void {
+ if (!this._map.has(this._pluckPrimaryKey(stats))) {
+ this._map.set(this._pluckPrimaryKey(stats), new BehaviorSubject(stats));
+ return;
+ }
+
+ const old = this.get(this._pluckPrimaryKey(stats));
+ if (JSON.stringify(old) !== JSON.stringify(stats)) {
+ this._getBehaviourSubject(this._pluckPrimaryKey(stats)).next(stats);
+ }
+ }
+
+ watch$(key: string): Observable {
+ return this._getBehaviourSubject(key).asObservable();
+ }
+
+ private _pluckPrimaryKey(stats: E): string {
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore
+ return stats[this._primaryKey] as string;
+ }
+
+ private _getBehaviourSubject(key: string): BehaviorSubject {
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ return this._map.get(key)!;
+ }
+}