Compare commits

..

No commits in common. "master" and "4.776.0" have entirely different histories.

833 changed files with 11460 additions and 16259 deletions

View File

@ -16,7 +16,6 @@
}, },
"rules": { "rules": {
"rxjs/no-ignored-subscription": "warn", "rxjs/no-ignored-subscription": "warn",
"@angular-eslint/prefer-standalone": "off",
"@angular-eslint/no-conflicting-lifecycle": "error", "@angular-eslint/no-conflicting-lifecycle": "error",
"@angular-eslint/no-host-metadata-property": "error", "@angular-eslint/no-host-metadata-property": "error",
"@angular-eslint/no-input-rename": "error", "@angular-eslint/no-input-rename": "error",

View File

@ -4,60 +4,38 @@ variables:
PROJECT: red-ui PROJECT: red-ui
DOCKERFILELOCATION: 'docker/$PROJECT/Dockerfile' DOCKERFILELOCATION: 'docker/$PROJECT/Dockerfile'
include: include:
- project: 'gitlab/gitlab' - project: 'gitlab/gitlab'
ref: 'main' ref: 'main'
file: 'ci-templates/docker_build_nexus_v2.yml' file: 'ci-templates/docker_build_nexus_v2.yml'
rules: rules:
- if: $CI_PIPELINE_SOURCE != "schedule" - if: $CI_PIPELINE_SOURCE != "schedule"
sonarqube:
stage: test
image:
name: sonarsource/sonar-scanner-cli:11.1
entrypoint:
- ''
variables:
SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar"
GIT_DEPTH: '0'
cache:
key: "${CI_JOB_NAME}"
paths:
- ".sonar/cache"
script:
- sonar-scanner
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH"
- if: "$CI_COMMIT_BRANCH =~ /^release/"
localazy update: localazy update:
image: node:20.5 image: node:20.5
cache: cache:
- key: - key:
files: files:
- yarn.lock - yarn.lock
paths: paths:
- .yarn-cache/ - .yarn-cache/
script: script:
# - git config user.email "${CI_EMAIL}" - git config user.email "${CI_EMAIL}"
# - git config user.name "${CI_USERNAME}" - git config user.name "${CI_USERNAME}"
# - git remote add gitlab_origin https://${CI_USERNAME}:${CI_ACCESS_TOKEN}@gitlab.knecon.com/redactmanager/red-ui.git - git remote add gitlab_origin https://${CI_USERNAME}:${CI_ACCESS_TOKEN}@gitlab.knecon.com/redactmanager/red-ui.git
- git push https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.knecon.com/redactmanager/red-ui.git - cd tools/localazy
- cd tools/localazy - yarn install --cache-folder .yarn-cache
- yarn install --cache-folder .yarn-cache - yarn start
- yarn start - cd ../..
- cd ../.. - git add .
- git add . - |-
- |- CHANGES=$(git status --porcelain | wc -l)
CHANGES=$(git status --porcelain | wc -l) if [ "$CHANGES" -gt "0" ]
if [ "$CHANGES" -gt "0" ] then
then git status
git status git commit -m "push back localazy update"
git commit -m "push back localazy update" git push gitlab_origin HEAD:${CI_COMMIT_REF_NAME}
git push gitlab_origin HEAD:${CI_COMMIT_REF_NAME} fi
# git push https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.knecon.com/redactmanager/red-ui.git rules:
# git push - if: $CI_PIPELINE_SOURCE == "schedule"
fi
rules:
- if: $CI_PIPELINE_SOURCE == "schedule"

View File

@ -2,12 +2,13 @@
### To Create a new Stack in rancher check [this Wiki page](https://wiki.iqser.com/pages/viewpage.action?spaceKey=RED&title=Work+with+kubectl) ### To Create a new Stack in rancher check [this Wiki page](https://wiki.iqser.com/pages/viewpage.action?spaceKey=RED&title=Work+with+kubectl)
# Dependencies update guide
* When updating @pdftron/webviewer, make sure to change the version also in the angular.json and everywhere where the path to /assets/wv-recources is used
* Make sure the keycloak.js version is the same with the keycloak version from helm chart
## Code style ## Code style
* Don't use `setInterval` without calling `clearInterval` in `ngOnDestroy` or in `destroyRef.onDestroy(() => clearInterval(intervalId))`
* Always use `trackBy` in `*ngFor` loops (see shorthand below)
```typescript
readonly trackBy = trackByFactory();
```
* Don't use `setInterval` without calling `clearInterval` in `ngOnDestroy`
* Never call getters in HTML templates * Never call getters in HTML templates
## Keycloak Staging Config ## Keycloak Staging Config

View File

@ -50,7 +50,7 @@
{ {
"glob": "**/*", "glob": "**/*",
"input": "node_modules/@pdftron/webviewer/public/", "input": "node_modules/@pdftron/webviewer/public/",
"output": "/assets/wv-resources/11.1.0/" "output": "/assets/wv-resources/"
}, },
{ {
"glob": "**/*", "glob": "**/*",
@ -73,7 +73,7 @@
"stylePreprocessorOptions": { "stylePreprocessorOptions": {
"includePaths": ["./apps/red-ui/src/assets/styles", "./libs/common-ui/src/assets/styles"] "includePaths": ["./apps/red-ui/src/assets/styles", "./libs/common-ui/src/assets/styles"]
}, },
"scripts": ["node_modules/chart.js/auto/auto.cjs"], "scripts": ["node_modules/@pdftron/webviewer/webviewer.min.js", "node_modules/chart.js/auto/auto.cjs"],
"extractLicenses": false, "extractLicenses": false,
"sourceMap": true, "sourceMap": true,
"optimization": false, "optimization": false,

View File

@ -8,7 +8,7 @@ import { DossierFilesGuard } from '@guards/dossier-files-guard';
import { templateExistsWhenEnteringDossierList } from '@guards/dossier-template-exists.guard'; import { templateExistsWhenEnteringDossierList } from '@guards/dossier-template-exists.guard';
import { DossierTemplatesGuard } from '@guards/dossier-templates.guard'; import { DossierTemplatesGuard } from '@guards/dossier-templates.guard';
import { loadActiveDossiersGuard, loadAllDossiersGuard, loadArchivedDossiersGuard } from '@guards/dossiers.guard'; import { loadActiveDossiersGuard, loadAllDossiersGuard, loadArchivedDossiersGuard } from '@guards/dossiers.guard';
import { isNotEditingFileAttributeGuard } from '@guards/file-attribute.guard'; import { editAttributeGuard } from '@guards/edit-attribute.guard';
import { FeaturesGuard } from '@guards/features-guard.service'; import { FeaturesGuard } from '@guards/features-guard.service';
import { ifLoggedIn } from '@guards/if-logged-in.guard'; import { ifLoggedIn } from '@guards/if-logged-in.guard';
import { TrashGuard } from '@guards/trash.guard'; import { TrashGuard } from '@guards/trash.guard';
@ -16,7 +16,7 @@ import { CompositeRouteGuard, DEFAULT_REDIRECT_KEY, IqserPermissionsGuard, Iqser
import { TenantSelectComponent } from '@iqser/common-ui/lib/tenants'; import { TenantSelectComponent } from '@iqser/common-ui/lib/tenants';
import { doesNotHaveAnyRole, hasAnyRole, IqserAuthGuard } from '@iqser/common-ui/lib/users'; import { doesNotHaveAnyRole, hasAnyRole, IqserAuthGuard } from '@iqser/common-ui/lib/users';
import { CustomRouteReuseStrategy } from '@iqser/common-ui/lib/utils'; import { CustomRouteReuseStrategy } from '@iqser/common-ui/lib/utils';
import { ARCHIVE_ROUTE, BreadcrumbTypes, DOSSIER_ID, DOSSIER_TEMPLATE_ID, DOSSIERS_ROUTE, FILE_ID } from '@red/domain'; import { ARCHIVE_ROUTE, BreadcrumbTypes, DOSSIER_ID, DOSSIER_TEMPLATE_ID, DOSSIERS_ARCHIVE, DOSSIERS_ROUTE, FILE_ID } from '@red/domain';
import { RedRoleGuard } from '@users/red-role.guard'; import { RedRoleGuard } from '@users/red-role.guard';
import { Roles } from '@users/roles'; import { Roles } from '@users/roles';
import { mainGuard } from '@utils/main.guard'; import { mainGuard } from '@utils/main.guard';
@ -38,7 +38,7 @@ const dossierTemplateIdRoutes: IqserRoutes = [
{ {
path: `:${DOSSIER_ID}`, path: `:${DOSSIER_ID}`,
canActivate: [CompositeRouteGuard, IqserPermissionsGuard], canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
canDeactivate: [isNotEditingFileAttributeGuard], canDeactivate: [editAttributeGuard],
data: { data: {
routeGuards: [DossierFilesGuard], routeGuards: [DossierFilesGuard],
breadcrumbs: [BreadcrumbTypes.dossierTemplate, BreadcrumbTypes.dossier], breadcrumbs: [BreadcrumbTypes.dossierTemplate, BreadcrumbTypes.dossier],
@ -49,7 +49,7 @@ const dossierTemplateIdRoutes: IqserRoutes = [
}, },
skeleton: 'dossier', skeleton: 'dossier',
}, },
loadChildren: () => import('./modules/dossier-overview/dossier-overview.routes'), loadChildren: () => import('./modules/dossier-overview/dossier-overview.module').then(m => m.DossierOverviewModule),
}, },
{ {
path: `:${DOSSIER_ID}/file/:${FILE_ID}`, path: `:${DOSSIER_ID}/file/:${FILE_ID}`,
@ -63,12 +63,12 @@ const dossierTemplateIdRoutes: IqserRoutes = [
redirectTo: '/auth-error', redirectTo: '/auth-error',
}, },
}, },
loadChildren: () => import('./modules/file-preview/file-preview.routes'), loadChildren: () => import('./modules/file-preview/file-preview.module').then(m => m.FilePreviewModule),
}, },
{ {
path: '', path: '',
pathMatch: 'full', pathMatch: 'full',
loadChildren: () => import('./modules/dossiers-listing/dossiers-listing.routes'), loadChildren: () => import('./modules/dossiers-listing/dossiers-listing.module').then(m => m.DossiersListingModule),
data: { data: {
breadcrumbs: [BreadcrumbTypes.dossierTemplate], breadcrumbs: [BreadcrumbTypes.dossierTemplate],
}, },
@ -77,10 +77,11 @@ const dossierTemplateIdRoutes: IqserRoutes = [
}, },
{ {
path: `${ARCHIVE_ROUTE}`, path: `${ARCHIVE_ROUTE}`,
loadChildren: () => import('./modules/archive/archive.routes'), loadChildren: () => import('./modules/archive/archive.module').then(m => m.ArchiveModule),
canActivate: [CompositeRouteGuard, loadArchivedDossiersGuard()], canActivate: [CompositeRouteGuard, webViewerLoadedGuard(), loadArchivedDossiersGuard()],
data: { data: {
routeGuards: [FeaturesGuard], routeGuards: [FeaturesGuard],
features: [DOSSIERS_ARCHIVE],
}, },
}, },
{ {
@ -98,16 +99,16 @@ const mainRoutes: IqserRoutes = [
}, },
{ {
path: 'account', path: 'account',
loadChildren: () => import('./modules/account/account.routes'), loadChildren: () => import('./modules/account/account.module').then(m => m.AccountModule),
}, },
{ {
path: 'admin', path: 'admin',
loadChildren: () => import('./modules/admin/admin.routes'), loadChildren: () => import('./modules/admin/admin.module').then(m => m.AdminModule),
canActivate: [RedRoleGuard], canActivate: [RedRoleGuard],
}, },
{ {
path: 'dashboard', path: 'dashboard',
loadChildren: () => import('./modules/dashboard/dashboard.routes'), loadChildren: () => import('./modules/dashboard/dashboard.module').then(m => m.DashboardModule),
canActivate: [CompositeRouteGuard], canActivate: [CompositeRouteGuard],
data: { data: {
routeGuards: [IqserAuthGuard, RedRoleGuard, IqserPermissionsGuard, DossierTemplatesGuard, DashboardGuard], routeGuards: [IqserAuthGuard, RedRoleGuard, IqserPermissionsGuard, DossierTemplatesGuard, DashboardGuard],
@ -146,7 +147,7 @@ const mainRoutes: IqserRoutes = [
}, },
{ {
path: 'search', path: 'search',
loadComponent: () => import('./modules/search/search-screen/search-screen.component'), loadChildren: () => import('./modules/search/search.module').then(m => m.SearchModule),
canActivate: [CompositeRouteGuard, IqserPermissionsGuard, loadAllDossiersGuard()], canActivate: [CompositeRouteGuard, IqserPermissionsGuard, loadAllDossiersGuard()],
data: { data: {
routeGuards: [IqserAuthGuard, RedRoleGuard], routeGuards: [IqserAuthGuard, RedRoleGuard],
@ -158,7 +159,7 @@ const mainRoutes: IqserRoutes = [
}, },
{ {
path: 'trash', path: 'trash',
loadChildren: () => import('./modules/trash/trash.routes'), loadChildren: () => import('./modules/trash/trash.module').then(m => m.TrashModule),
canActivate: [CompositeRouteGuard, IqserPermissionsGuard], canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
data: { data: {
routeGuards: [IqserAuthGuard, RedRoleGuard, TrashGuard], routeGuards: [IqserAuthGuard, RedRoleGuard, TrashGuard],

View File

@ -4,27 +4,26 @@ import { UserPreferenceService } from '@users/user-preference.service';
import { getConfig } from '@iqser/common-ui'; import { getConfig } from '@iqser/common-ui';
import { AppConfig } from '@red/domain'; import { AppConfig } from '@red/domain';
import { NavigationEnd, Router } from '@angular/router'; import { NavigationEnd, Router } from '@angular/router';
import { filter, map, switchMap, take, tap } from 'rxjs/operators'; import { filter, map, switchMap, take } from 'rxjs/operators';
import { APP_TYPE_PATHS } from '@common-ui/utils/constants';
import { MatIconRegistry } from '@angular/material/icon';
import { DomSanitizer } from '@angular/platform-browser';
import { TenantsService } from '@common-ui/tenants';
export function loadCustomTheme(cssFileName: string) { function loadCustomTheme() {
const head = document.getElementsByTagName('head')[0]; const cssFileName = getConfig<AppConfig>().THEME;
const link = document.createElement('link');
link.id = cssFileName; if (cssFileName) {
link.rel = 'stylesheet'; const head = document.getElementsByTagName('head')[0];
link.type = 'text/css'; const link = document.createElement('link');
link.href = 'assets/styles/themes/' + cssFileName + '.css'; link.id = cssFileName;
link.media = 'all'; link.rel = 'stylesheet';
head.appendChild(link); link.type = 'text/css';
link.href = 'assets/styles/themes/' + cssFileName + '.css';
link.media = 'all';
head.appendChild(link);
}
} }
@Component({ @Component({
selector: 'redaction-root', selector: 'redaction-root',
templateUrl: './app.component.html', templateUrl: './app.component.html',
standalone: false,
}) })
export class AppComponent { export class AppComponent {
constructor( constructor(
@ -35,12 +34,9 @@ export class AppComponent {
userPreferenceService: UserPreferenceService, userPreferenceService: UserPreferenceService,
renderer: Renderer2, renderer: Renderer2,
private readonly _router: Router, private readonly _router: Router,
private readonly _iconRegistry: MatIconRegistry,
private readonly _sanitizer: DomSanitizer,
private readonly _tenantsService: TenantsService,
) { ) {
const config = getConfig<AppConfig>();
renderer.addClass(document.body, userPreferenceService.getTheme()); renderer.addClass(document.body, userPreferenceService.getTheme());
loadCustomTheme();
const removeQueryParams = _router.events.pipe( const removeQueryParams = _router.events.pipe(
filter((event): event is NavigationEnd => event instanceof NavigationEnd), filter((event): event is NavigationEnd => event instanceof NavigationEnd),
@ -51,25 +47,9 @@ export class AppComponent {
); );
removeQueryParams.subscribe(); removeQueryParams.subscribe();
this._tenantsService if (getConfig().IS_DOCUMINE) {
.waitForSettingTenant() document.getElementById('favicon').setAttribute('href', 'assets/icons/documine-logo.ico');
.pipe( }
tap(() => {
const isDocumine = this._tenantsService.activeTenant.documine;
const logo = isDocumine ? 'documine' : 'redaction';
_iconRegistry.addSvgIconInNamespace(
'iqser',
'logo',
_sanitizer.bypassSecurityTrustResourceUrl(`/assets/icons/general/${logo}-logo.svg`),
);
if (isDocumine) {
document.getElementById('favicon').setAttribute('href', 'assets/icons/documine-logo.ico');
}
loadCustomTheme(isDocumine ? APP_TYPE_PATHS.SCM : APP_TYPE_PATHS.REDACT);
}),
take(1),
)
.subscribe();
} }
#removeKeycloakQueryParams() { #removeKeycloakQueryParams() {

View File

@ -1,21 +1,12 @@
import { APP_BASE_HREF, DatePipe as BaseDatePipe } from '@angular/common'; import { APP_BASE_HREF, DatePipe as BaseDatePipe } from '@angular/common';
import { HTTP_INTERCEPTORS } from '@angular/common/http'; import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { ErrorHandler, inject, NgModule, provideEnvironmentInitializer } from '@angular/core'; import { ENVIRONMENT_INITIALIZER, ErrorHandler, inject, NgModule } from '@angular/core';
import { MatDividerModule } from '@angular/material/divider'; import { MatDividerModule } from '@angular/material/divider';
import { MatIcon } from '@angular/material/icon'; import { MAT_TOOLTIP_DEFAULT_OPTIONS } from '@angular/material/tooltip';
import { MatMenu, MatMenuContent, MatMenuItem, MatMenuTrigger } from '@angular/material/menu';
import { MatProgressSpinner } from '@angular/material/progress-spinner';
import { MAT_TOOLTIP_DEFAULT_OPTIONS, MatTooltip } from '@angular/material/tooltip';
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ServiceWorkerModule } from '@angular/service-worker'; import { ServiceWorkerModule } from '@angular/service-worker';
import { ChevronButtonComponent } from '@common-ui/buttons/chevron-button';
import { EmptyStateComponent } from '@common-ui/empty-state';
import { HelpModeKey } from '@common-ui/help-mode/types';
import { InputWithActionComponent } from '@common-ui/inputs/input-with-action/input-with-action.component';
import { RoundCheckboxComponent } from '@common-ui/inputs/round-checkbox/round-checkbox.component';
import { GET_TENANT_FROM_PATH_FN, UI_ROOT } from '@common-ui/utils'; import { GET_TENANT_FROM_PATH_FN, UI_ROOT } from '@common-ui/utils';
import { AuthErrorComponent } from '@components/auth-error/auth-error.component';
import { BaseScreenComponent } from '@components/base-screen/base-screen.component'; import { BaseScreenComponent } from '@components/base-screen/base-screen.component';
import { BreadcrumbsComponent } from '@components/breadcrumbs/breadcrumbs.component'; import { BreadcrumbsComponent } from '@components/breadcrumbs/breadcrumbs.component';
import { DownloadsListScreenComponent } from '@components/downloads-list-screen/downloads-list-screen.component'; import { DownloadsListScreenComponent } from '@components/downloads-list-screen/downloads-list-screen.component';
@ -30,51 +21,54 @@ import { UserMenuComponent } from '@components/user-menu/user-menu.component';
import { environment } from '@environments/environment'; import { environment } from '@environments/environment';
import { import {
CachingModule, CachingModule,
ChevronButtonComponent,
CircleButtonComponent, CircleButtonComponent,
HelpButtonComponent, EmptyStateComponent,
HelpModeComponent, HelpModeKey,
HiddenActionDirective, HiddenActionDirective,
IconButtonComponent, IconButtonComponent,
InputWithActionComponent,
IqserAllowDirective, IqserAllowDirective,
IqserDenyDirective, IqserDenyDirective,
IqserHelpModeModule,
IqserListingModule, IqserListingModule,
IqserLoadingModule, IqserLoadingModule,
IqserTranslateModule, IqserTranslateModule,
LanguageService, LanguageService,
MAX_RETRIES_ON_SERVER_ERROR, MAX_RETRIES_ON_SERVER_ERROR,
RoundCheckboxComponent,
SERVER_ERROR_SKIP_PATHS, SERVER_ERROR_SKIP_PATHS,
ServerErrorInterceptor, ServerErrorInterceptor,
StopPropagationDirective, StopPropagationDirective,
} from '@iqser/common-ui'; } from '@iqser/common-ui';
import { CommonUiModule } from '@iqser/common-ui/lib/common-ui.module'; import { CommonUiModule } from '@iqser/common-ui/lib/common-ui.module';
import { provideHelpMode } from '@iqser/common-ui/lib/help-mode/utils/help-mode.provider';
import { LogoComponent, SkeletonComponent, ToastComponent } from '@iqser/common-ui/lib/shared'; import { LogoComponent, SkeletonComponent, ToastComponent } from '@iqser/common-ui/lib/shared';
import { TenantsModule } from '@iqser/common-ui/lib/tenants'; import { TenantsModule } from '@iqser/common-ui/lib/tenants';
import { InitialsAvatarComponent, IqserUsersModule } from '@iqser/common-ui/lib/users'; import { IqserUsersModule } from '@iqser/common-ui/lib/users';
import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor'; import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor';
import { TranslateModule } from '@ngx-translate/core'; import { MissingTranslationHandler } from '@ngx-translate/core';
import { AppConfig, ILoggerConfig } from '@red/domain'; import { AppConfig, ILoggerConfig } from '@red/domain';
import { ConfigService } from '@services/config.service'; import { ConfigService } from '@services/config.service';
import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service'; import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service';
import { ArchivedDossiersService } from '@services/dossiers/archived-dossiers.service'; import { ArchivedDossiersService } from '@services/dossiers/archived-dossiers.service';
import { GlobalErrorHandler } from '@services/global-error-handler.service'; import { GlobalErrorHandler } from '@services/global-error-handler.service';
import { LoggerRulesService } from '@services/logger-rules.service'; import { LoggerRulesService } from '@services/logger-rules.service';
import { provideCustomDateFormatter } from '@shared/custom-date-formatting.provider';
import { NavigateLastDossiersScreenDirective } from '@shared/directives/navigate-last-dossiers-screen.directive';
import { DatePipe } from '@shared/pipes/date.pipe'; import { DatePipe } from '@shared/pipes/date.pipe';
import { SharedModule } from '@shared/shared.module';
import { FileUploadDownloadModule } from '@upload-download/file-upload-download.module';
import { RedRoleGuard } from '@users/red-role.guard'; import { RedRoleGuard } from '@users/red-role.guard';
import { UserPreferenceService } from '@users/user-preference.service'; import { UserPreferenceService } from '@users/user-preference.service';
import { UserService } from '@users/user.service'; import { UserService } from '@users/user.service';
import { UI_CACHES } from '@utils/constants'; import { UI_CACHES } from '@utils/constants';
import { ColorPickerService } from 'ngx-color-picker'; import { REDMissingTranslationHandler } from '@utils/missing-translations-handler';
import { LoggerModule, NGXLogger, NgxLoggerLevel, TOKEN_LOGGER_CONFIG, TOKEN_LOGGER_RULES_SERVICE } from 'ngx-logger'; import { LoggerModule, NGXLogger, NgxLoggerLevel, TOKEN_LOGGER_CONFIG, TOKEN_LOGGER_RULES_SERVICE } from 'ngx-logger';
import { ToastrModule } from 'ngx-toastr'; import { ToastrModule } from 'ngx-toastr';
import helpModeKeys from '../assets/help-mode/help-mode-keys.json'; import * as helpModeKeys from '../assets/help-mode/help-mode-keys.json';
import { AppRoutingModule } from './app-routing.module'; import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { IconsModule } from './modules/icons/icons.module';
import { PdfViewerModule } from './modules/pdf-viewer/pdf-viewer.module'; import { PdfViewerModule } from './modules/pdf-viewer/pdf-viewer.module';
import { ACTIVE_DOSSIERS_SERVICE, ARCHIVED_DOSSIERS_SERVICE } from './tokens'; import { ACTIVE_DOSSIERS_SERVICE, ARCHIVED_DOSSIERS_SERVICE } from './tokens';
import { AuthErrorComponent } from '@components/auth-error/auth-error.component';
export const appModuleFactory = (config: AppConfig) => { export const appModuleFactory = (config: AppConfig) => {
@NgModule({ @NgModule({
@ -96,6 +90,8 @@ export const appModuleFactory = (config: AppConfig) => {
imports: [ imports: [
BrowserModule, BrowserModule,
BrowserAnimationsModule, BrowserAnimationsModule,
SharedModule,
FileUploadDownloadModule,
AppRoutingModule, AppRoutingModule,
MonacoEditorModule, MonacoEditorModule,
CommonUiModule.forRoot({ CommonUiModule.forRoot({
@ -108,6 +104,7 @@ export const appModuleFactory = (config: AppConfig) => {
existingRoleGuard: RedRoleGuard, existingRoleGuard: RedRoleGuard,
}), }),
CachingModule.forRoot(UI_CACHES), CachingModule.forRoot(UI_CACHES),
IqserHelpModeModule.forRoot(helpModeKeys as HelpModeKey[]),
PdfViewerModule, PdfViewerModule,
ToastrModule.forRoot({ ToastrModule.forRoot({
closeButton: true, closeButton: true,
@ -117,7 +114,7 @@ export const appModuleFactory = (config: AppConfig) => {
resetTimeoutOnDuplicate: true, resetTimeoutOnDuplicate: true,
}), }),
TenantsModule.forRoot(), TenantsModule.forRoot(),
IqserTranslateModule.forRoot({ pathPrefix: config.BASE_TRANSLATIONS_DIRECTORY }), IqserTranslateModule.forRoot({ pathPrefix: config.BASE_TRANSLATIONS_DIRECTORY || '/assets/i18n/redact/' }),
IqserLoadingModule.forRoot(), IqserLoadingModule.forRoot(),
ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production }), ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production }),
LoggerModule.forRoot(undefined, { LoggerModule.forRoot(undefined, {
@ -135,20 +132,20 @@ export const appModuleFactory = (config: AppConfig) => {
features: { features: {
ANNOTATIONS: { ANNOTATIONS: {
color: 'aqua', color: 'aqua',
enabled: false, enabled: true,
level: NgxLoggerLevel.DEBUG, level: NgxLoggerLevel.DEBUG,
}, },
FILTERS: { FILTERS: {
enabled: false, enabled: false,
}, },
TENANTS: { TENANTS: {
enabled: false, enabled: true,
}, },
ROUTES: { ROUTES: {
enabled: false, enabled: true,
}, },
PDF: { PDF: {
enabled: false, enabled: true,
}, },
FILE: { FILE: {
enabled: false, enabled: false,
@ -171,9 +168,6 @@ export const appModuleFactory = (config: AppConfig) => {
DOSSIERS_CHANGES: { DOSSIERS_CHANGES: {
enabled: false, enabled: false,
}, },
GUARDS: {
enabled: false,
},
}, },
} as ILoggerConfig, } as ILoggerConfig,
}, },
@ -192,20 +186,6 @@ export const appModuleFactory = (config: AppConfig) => {
IconButtonComponent, IconButtonComponent,
MatDividerModule, MatDividerModule,
ChevronButtonComponent, ChevronButtonComponent,
InitialsAvatarComponent,
HelpModeComponent,
HelpButtonComponent,
MatMenuTrigger,
MatMenuItem,
MatIcon,
MatMenu,
MatMenuContent,
MatTooltip,
MatProgressSpinner,
IconsModule,
NavigateLastDossiersScreenDirective,
DatePipe,
TranslateModule,
], ],
providers: [ providers: [
{ {
@ -233,10 +213,18 @@ export const appModuleFactory = (config: AppConfig) => {
provide: ErrorHandler, provide: ErrorHandler,
useClass: GlobalErrorHandler, useClass: GlobalErrorHandler,
}, },
provideEnvironmentInitializer(async () => { {
const languageService = inject(LanguageService); provide: ENVIRONMENT_INITIALIZER,
return languageService.setInitialLanguage(); multi: true,
}), useValue: async () => {
const languageService = inject(LanguageService);
return languageService.setInitialLanguage();
},
},
{
provide: MissingTranslationHandler,
useClass: REDMissingTranslationHandler,
},
{ {
provide: MAX_RETRIES_ON_SERVER_ERROR, provide: MAX_RETRIES_ON_SERVER_ERROR,
useFactory: () => config.MAX_RETRIES_ON_SERVER_ERROR, useFactory: () => config.MAX_RETRIES_ON_SERVER_ERROR,
@ -257,14 +245,10 @@ export const appModuleFactory = (config: AppConfig) => {
provide: MAT_TOOLTIP_DEFAULT_OPTIONS, provide: MAT_TOOLTIP_DEFAULT_OPTIONS,
useValue: { useValue: {
disableTooltipInteractivity: true, disableTooltipInteractivity: true,
showDelay: 1,
}, },
}, },
BaseDatePipe, BaseDatePipe,
DatePipe, DatePipe,
...provideCustomDateFormatter(),
...provideHelpMode(helpModeKeys as HelpModeKey[]),
ColorPickerService,
], ],
bootstrap: [AppComponent], bootstrap: [AppComponent],
}) })

View File

@ -7,7 +7,6 @@ import { AppConfig } from '@red/domain';
selector: 'redaction-auth-error', selector: 'redaction-auth-error',
templateUrl: './auth-error.component.html', templateUrl: './auth-error.component.html',
styleUrls: ['./auth-error.component.scss'], styleUrls: ['./auth-error.component.scss'],
standalone: false,
}) })
export class AuthErrorComponent { export class AuthErrorComponent {
readonly #config = getConfig<AppConfig>(); readonly #config = getConfig<AppConfig>();

View File

@ -9,7 +9,7 @@
<redaction-breadcrumbs></redaction-breadcrumbs> <redaction-breadcrumbs></redaction-breadcrumbs>
</div> </div>
<a [matTooltip]="'top-bar.navigation-items.back-to-dashboard' | translate" [routerLink]="['/main']" class="logo"> <a [matTooltip]="'top-bar.navigation-items.back-to-dashboard' | translate" [routerLink]="['/']" class="logo">
<div [attr.help-mode-key]="'home'" class="actions"> <div [attr.help-mode-key]="'home'" class="actions">
<iqser-logo (iqserHiddenAction)="userPreferenceService.toggleDevFeatures()" icon="iqser:logo"></iqser-logo> <iqser-logo (iqserHiddenAction)="userPreferenceService.toggleDevFeatures()" icon="iqser:logo"></iqser-logo>
<div class="app-name">{{ titleService.getTitle() }}</div> <div class="app-name">{{ titleService.getTitle() }}</div>

View File

@ -8,7 +8,8 @@ import { SpotlightSearchAction } from '@components/spotlight-search/spotlight-se
import { filter, map, startWith } from 'rxjs/operators'; import { filter, map, startWith } from 'rxjs/operators';
import { getConfig, IqserPermissionsService } from '@iqser/common-ui'; import { getConfig, IqserPermissionsService } from '@iqser/common-ui';
import { BreadcrumbsService } from '@services/breadcrumbs.service'; import { BreadcrumbsService } from '@services/breadcrumbs.service';
import { ARCHIVE_ROUTE, DOSSIERS_ROUTE } from '@red/domain'; import { FeaturesService } from '@services/features.service';
import { ARCHIVE_ROUTE, DOSSIERS_ARCHIVE, DOSSIERS_ROUTE } from '@red/domain';
import { Roles } from '@users/roles'; import { Roles } from '@users/roles';
import { REDDocumentViewer } from '../../modules/pdf-viewer/services/document-viewer.service'; import { REDDocumentViewer } from '../../modules/pdf-viewer/services/document-viewer.service';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@ -20,7 +21,6 @@ const isSearchScreen: (url: string) => boolean = url => url.includes('/search');
@Component({ @Component({
templateUrl: './base-screen.component.html', templateUrl: './base-screen.component.html',
styleUrls: ['./base-screen.component.scss'], styleUrls: ['./base-screen.component.scss'],
standalone: false,
}) })
export class BaseScreenComponent { export class BaseScreenComponent {
readonly roles = Roles; readonly roles = Roles;
@ -36,6 +36,7 @@ export class BaseScreenComponent {
{ {
text: this._translateService.instant('search.active-dossiers'), text: this._translateService.instant('search.active-dossiers'),
icon: 'red:enter', icon: 'red:enter',
hide: () => !this._featuresService.isEnabled(DOSSIERS_ARCHIVE),
action: (query): void => this.#search(query, [], true), action: (query): void => this.#search(query, [], true),
}, },
{ {
@ -57,6 +58,7 @@ export class BaseScreenComponent {
private readonly _router: Router, private readonly _router: Router,
activatedRoute: ActivatedRoute, activatedRoute: ActivatedRoute,
private readonly _translateService: TranslateService, private readonly _translateService: TranslateService,
private readonly _featuresService: FeaturesService,
readonly permissionsService: IqserPermissionsService, readonly permissionsService: IqserPermissionsService,
readonly userService: UserService, readonly userService: UserService,
readonly userPreferenceService: UserPreferenceService, readonly userPreferenceService: UserPreferenceService,

View File

@ -7,7 +7,6 @@ import { Breadcrumb, BreadcrumbDisplayType, BreadcrumbsService } from '@services
selector: 'redaction-breadcrumbs', selector: 'redaction-breadcrumbs',
templateUrl: './breadcrumbs.component.html', templateUrl: './breadcrumbs.component.html',
styleUrls: ['./breadcrumbs.component.scss'], styleUrls: ['./breadcrumbs.component.scss'],
standalone: false,
}) })
export class BreadcrumbsComponent { export class BreadcrumbsComponent {
constructor(readonly breadcrumbsService: BreadcrumbsService) {} constructor(readonly breadcrumbsService: BreadcrumbsService) {}

View File

@ -13,7 +13,6 @@ import { firstValueFrom } from 'rxjs';
entitiesService: FileDownloadService, entitiesService: FileDownloadService,
component: DownloadsListScreenComponent, component: DownloadsListScreenComponent,
}), }),
standalone: false,
}) })
export class DownloadsListScreenComponent extends ListingComponent<DownloadStatus> implements OnDestroy { export class DownloadsListScreenComponent extends ListingComponent<DownloadStatus> implements OnDestroy {
readonly #interval: number; readonly #interval: number;

View File

@ -32,18 +32,19 @@
</div> </div>
<div <div
(click)="handleMarkReadEvent($event, [notification], true)" (click)="markRead([notification], true)"
*ngFor="let notification of group.notifications; trackBy: trackBy" *ngFor="let notification of group.notifications; trackBy: trackBy"
[class.unread]="!notification.readDate" [class.unread]="!notification.readDate"
[id]="'notifications-mark-as-read-' + notification.id + '-btn'" [id]="'notifications-mark-as-read-' + notification.id + '-btn'"
class="notification" class="notification"
iqserStopPropagation
mat-menu-item mat-menu-item
> >
<iqser-initials-avatar [user]="notification.userId"></iqser-initials-avatar> <iqser-initials-avatar [user]="notification.userId"></iqser-initials-avatar>
<div class="notification-content"> <div class="notification-content">
<div [innerHTML]="notification.message"></div> <div [innerHTML]="notification.message"></div>
<div class="small-label mt-2">{{ notification.creationDate | date: 'exactDate' }}</div> <div class="small-label mt-2">{{ notification.creationDate | date : 'exactDate' }}</div>
</div> </div>
<div <div
@ -51,7 +52,7 @@
[id]="'notifications-mark-' + notification.id" [id]="'notifications-mark-' + notification.id"
class="dot" class="dot"
iqserStopPropagation iqserStopPropagation
matTooltip="{{ 'notifications.mark-as' | translate: { type: notification.readDate ? 'unread' : 'read' } }}" matTooltip="{{ 'notifications.mark-as' | translate : { type: notification.readDate ? 'unread' : 'read' } }}"
matTooltipPosition="before" matTooltipPosition="before"
></div> ></div>
</div> </div>

View File

@ -22,7 +22,7 @@
} }
.mat-mdc-menu-item.notification { .mat-mdc-menu-item.notification {
padding: 8px 26px 10px 8px !important; padding: 8px 26px 10px 8px;
margin: 2px 0 0 0; margin: 2px 0 0 0;
height: fit-content; height: fit-content;
position: relative; position: relative;

View File

@ -25,7 +25,6 @@ function chronologically(first: string, second: string) {
selector: 'redaction-notifications', selector: 'redaction-notifications',
templateUrl: './notifications.component.html', templateUrl: './notifications.component.html',
styleUrls: ['./notifications.component.scss'], styleUrls: ['./notifications.component.scss'],
standalone: false,
}) })
export class NotificationsComponent { export class NotificationsComponent {
readonly hasUnreadNotifications$: Observable<boolean>; readonly hasUnreadNotifications$: Observable<boolean>;
@ -59,15 +58,6 @@ export class NotificationsComponent {
await this._notificationsService.toggleNotificationRead(notificationsIds, isRead); await this._notificationsService.toggleNotificationRead(notificationsIds, isRead);
} }
async handleMarkReadEvent(event: MouseEvent, notifications: Notification[] = this._notificationsService.all, isRead = true) {
if (!(event.target as HTMLBaseElement).href) {
event.stopPropagation();
event.preventDefault();
}
await this.markRead(notifications, isRead);
}
#groupNotifications(notifications: Notification[]): NotificationsGroup[] { #groupNotifications(notifications: Notification[]): NotificationsGroup[] {
const todayTranslation = this._translateService.instant('today'); const todayTranslation = this._translateService.instant('today');
const groupedMap = notifications.groupBy(n => { const groupedMap = notifications.groupBy(n => {

View File

@ -9,7 +9,7 @@
.container { .container {
padding: 32px; padding: 32px;
width: 1000px; width: 900px;
max-width: 100%; max-width: 100%;
box-sizing: border-box; box-sizing: border-box;
} }

View File

@ -5,6 +5,5 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
templateUrl: './dashboard-skeleton.component.html', templateUrl: './dashboard-skeleton.component.html',
styleUrls: ['./dashboard-skeleton.component.scss'], styleUrls: ['./dashboard-skeleton.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
standalone: false,
}) })
export class DashboardSkeletonComponent {} export class DashboardSkeletonComponent {}

View File

@ -5,7 +5,6 @@ import { ChangeDetectionStrategy, Component, OnInit, TemplateRef, ViewChild } fr
templateUrl: './dossier-skeleton.component.html', templateUrl: './dossier-skeleton.component.html',
styleUrls: ['./dossier-skeleton.component.scss'], styleUrls: ['./dossier-skeleton.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
standalone: false,
}) })
export class DossierSkeletonComponent implements OnInit { export class DossierSkeletonComponent implements OnInit {
@ViewChild('workload1', { static: true }) workload1: TemplateRef<unknown>; @ViewChild('workload1', { static: true }) workload1: TemplateRef<unknown>;

View File

@ -5,6 +5,5 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
templateUrl: './skeleton-stats.component.html', templateUrl: './skeleton-stats.component.html',
styleUrls: ['./skeleton-stats.component.scss'], styleUrls: ['./skeleton-stats.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
standalone: false,
}) })
export class SkeletonStatsComponent {} export class SkeletonStatsComponent {}

View File

@ -6,7 +6,6 @@ import { Title } from '@angular/platform-browser';
templateUrl: './skeleton-top-bar.component.html', templateUrl: './skeleton-top-bar.component.html',
styleUrls: ['./skeleton-top-bar.component.scss'], styleUrls: ['./skeleton-top-bar.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
standalone: false,
}) })
export class SkeletonTopBarComponent { export class SkeletonTopBarComponent {
constructor(readonly titleService: Title) {} constructor(readonly titleService: Title) {}

View File

@ -9,7 +9,6 @@ import { MatMenuTrigger } from '@angular/material/menu';
templateUrl: './spotlight-search.component.html', templateUrl: './spotlight-search.component.html',
styleUrls: ['./spotlight-search.component.scss'], styleUrls: ['./spotlight-search.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
standalone: false,
}) })
export class SpotlightSearchComponent { export class SpotlightSearchComponent {
@Input() actions: readonly SpotlightSearchAction[]; @Input() actions: readonly SpotlightSearchAction[];

View File

@ -9,7 +9,6 @@ interface ITenant extends IStoredTenantId {
selector: 'app-tenants-menu', selector: 'app-tenants-menu',
templateUrl: './tenants-menu.component.html', templateUrl: './tenants-menu.component.html',
styleUrls: ['./tenants-menu.component.scss'], styleUrls: ['./tenants-menu.component.scss'],
standalone: false,
}) })
export class TenantsMenuComponent { export class TenantsMenuComponent {
readonly tenantsService = inject(TenantsService); readonly tenantsService = inject(TenantsService);

View File

@ -20,7 +20,6 @@ interface MenuItem {
selector: 'app-user-menu', selector: 'app-user-menu',
templateUrl: './user-menu.component.html', templateUrl: './user-menu.component.html',
styleUrls: ['./user-menu.component.scss'], styleUrls: ['./user-menu.component.scss'],
standalone: false,
}) })
export class UserMenuComponent { export class UserMenuComponent {
readonly currentUser = getCurrentUser<User>(); readonly currentUser = getCurrentUser<User>();

View File

@ -11,9 +11,7 @@ import { DictionaryService } from '@services/entity-services/dictionary.service'
import { DefaultColorsService } from '@services/entity-services/default-colors.service'; import { DefaultColorsService } from '@services/entity-services/default-colors.service';
import { WatermarkService } from '@services/entity-services/watermark.service'; import { WatermarkService } from '@services/entity-services/watermark.service';
import { FileAttributesService } from '@services/entity-services/file-attributes.service'; import { FileAttributesService } from '@services/entity-services/file-attributes.service';
import { getConfig, Toaster } from '@iqser/common-ui'; import { getConfig } from '@iqser/common-ui';
import { RulesService } from '../modules/admin/services/rules.service';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
export function templateExistsWhenEnteringAdmin(): CanActivateFn { export function templateExistsWhenEnteringAdmin(): CanActivateFn {
return async function (route: ActivatedRouteSnapshot): Promise<boolean> { return async function (route: ActivatedRouteSnapshot): Promise<boolean> {
@ -23,14 +21,12 @@ export function templateExistsWhenEnteringAdmin(): CanActivateFn {
const defaultColorsService = inject(DefaultColorsService); const defaultColorsService = inject(DefaultColorsService);
const watermarksService = inject(WatermarkService); const watermarksService = inject(WatermarkService);
const router = inject(Router); const router = inject(Router);
const rulesService = inject(RulesService);
const isDocumine = getConfig().IS_DOCUMINE; const isDocumine = getConfig().IS_DOCUMINE;
const dossierTemplate = inject(DossierTemplateStatsService).get(dossierTemplateId); const dossierTemplate = inject(DossierTemplateStatsService).get(dossierTemplateId);
await firstValueFrom(fileAttributesService.loadFileAttributesConfig(dossierTemplateId)); await firstValueFrom(fileAttributesService.loadFileAttributesConfig(dossierTemplateId));
await firstValueFrom(dictionaryService.loadDictionaryDataForDossierTemplate(dossierTemplateId)); await firstValueFrom(dictionaryService.loadDictionaryDataForDossierTemplate(dossierTemplateId));
await firstValueFrom(defaultColorsService.loadForDossierTemplate(dossierTemplateId)); await firstValueFrom(defaultColorsService.loadForDossierTemplate(dossierTemplateId));
await firstValueFrom(rulesService.getFor(dossierTemplateId));
if (!isDocumine) { if (!isDocumine) {
await firstValueFrom(watermarksService.loadForDossierTemplate(dossierTemplateId)); await firstValueFrom(watermarksService.loadForDossierTemplate(dossierTemplateId));
} }
@ -54,8 +50,6 @@ export function templateExistsWhenEnteringDossierList(): CanActivateFn {
const dictionaryService = inject(DictionaryService); const dictionaryService = inject(DictionaryService);
const defaultColorsService = inject(DefaultColorsService); const defaultColorsService = inject(DefaultColorsService);
const watermarksService = inject(WatermarkService); const watermarksService = inject(WatermarkService);
const rulesService = inject(RulesService);
const toaster = inject(Toaster);
const isDocumine = getConfig().IS_DOCUMINE; const isDocumine = getConfig().IS_DOCUMINE;
await firstValueFrom(dashboardStatsService.loadForTemplate(dossierTemplateId)); await firstValueFrom(dashboardStatsService.loadForTemplate(dossierTemplateId));
@ -70,10 +64,6 @@ export function templateExistsWhenEnteringDossierList(): CanActivateFn {
await firstValueFrom(fileAttributesService.loadFileAttributesConfig(dossierTemplateId)); await firstValueFrom(fileAttributesService.loadFileAttributesConfig(dossierTemplateId));
await firstValueFrom(dictionaryService.loadDictionaryDataForDossierTemplate(dossierTemplateId)); await firstValueFrom(dictionaryService.loadDictionaryDataForDossierTemplate(dossierTemplateId));
await firstValueFrom(defaultColorsService.loadForDossierTemplate(dossierTemplateId)); await firstValueFrom(defaultColorsService.loadForDossierTemplate(dossierTemplateId));
const rules = await firstValueFrom(rulesService.getFor(dossierTemplateId));
if (rules.timeoutDetected) {
toaster.error(_('dossier-listing.rules.timeoutError'));
}
if (!isDocumine) { if (!isDocumine) {
await firstValueFrom(watermarksService.loadForDossierTemplate(dossierTemplateId)); await firstValueFrom(watermarksService.loadForDossierTemplate(dossierTemplateId));
} }

View File

@ -0,0 +1,8 @@
import { inject } from '@angular/core';
import { CanDeactivateFn } from '@angular/router';
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
import { DossierOverviewScreenComponent } from '../modules/dossier-overview/screen/dossier-overview-screen.component';
// TODO: rename to something like "isNotEditingFileAttributeGuard"
export const editAttributeGuard: CanDeactivateFn<DossierOverviewScreenComponent> = () =>
!inject(FileAttributesService).isEditingFileAttribute();

View File

@ -1,7 +0,0 @@
import { inject } from '@angular/core';
import { CanDeactivateFn } from '@angular/router';
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
import DossierOverviewScreenComponent from '../modules/dossier-overview/screen/dossier-overview-screen.component';
export const isNotEditingFileAttributeGuard: CanDeactivateFn<DossierOverviewScreenComponent> = () =>
!inject(FileAttributesService).isEditingFileAttribute();

View File

@ -40,7 +40,8 @@ export function ifLoggedIn(): AsyncGuard {
logger.info('[KEYCLOAK] Keycloak init...'); logger.info('[KEYCLOAK] Keycloak init...');
await keycloakInitializer(tenant); await keycloakInitializer(tenant);
logger.info('[KEYCLOAK] Keycloak init done for tenant: ', { tenant }); logger.info('[KEYCLOAK] Keycloak init done!');
console.log({ tenant });
await tenantsService.selectTenant(tenant); await tenantsService.selectTenant(tenant);
await usersService.initialize(); await usersService.initialize();
await licenseService.loadLicenses(); await licenseService.loadLicenses();
@ -50,7 +51,6 @@ export function ifLoggedIn(): AsyncGuard {
const jwtToken = jwtDecode(token) as JwtToken; const jwtToken = jwtDecode(token) as JwtToken;
const authTime = (jwtToken.auth_time || jwtToken.iat).toString(); const authTime = (jwtToken.auth_time || jwtToken.iat).toString();
localStorage.setItem('authTime', authTime); localStorage.setItem('authTime', authTime);
localStorage.setItem('token', token);
} }
} }

View File

@ -1,7 +1,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate } from '@angular/router'; import { ActivatedRouteSnapshot, CanActivate } from '@angular/router';
import { EntityPermissionsService } from '@services/entity-permissions/entity-permissions.service';
import { firstValueFrom } from 'rxjs'; import { firstValueFrom } from 'rxjs';
import { EntityPermissionsService } from '@services/entity-permissions/entity-permissions.service';
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class PermissionsGuard implements CanActivate { export class PermissionsGuard implements CanActivate {

View File

@ -1,6 +1,6 @@
import { AnnotationPermissions } from '@models/file/annotation.permissions'; import { AnnotationPermissions } from '@models/file/annotation.permissions';
import { AnnotationWrapper } from '@models/file/annotation.wrapper'; import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { Dictionary, SuperTypes } from '@red/domain'; import { Dictionary } from '@red/domain';
export const canUndo = (annotation: AnnotationWrapper, isApprover: boolean) => !isApprover && annotation.pending; export const canUndo = (annotation: AnnotationWrapper, isApprover: boolean) => !isApprover && annotation.pending;
@ -9,14 +9,17 @@ export const canForceHint = (annotation: AnnotationWrapper, canAddRedaction: boo
export const canForceRedaction = (annotation: AnnotationWrapper, canAddRedaction: boolean) => export const canForceRedaction = (annotation: AnnotationWrapper, canAddRedaction: boolean) =>
canAddRedaction && canAddRedaction &&
(annotation.isSkipped || (annotation.IMAGE_HINT && annotation.superType === SuperTypes.Hint)) && (annotation.isSkipped || (annotation.IMAGE_HINT && !annotation.hasBeenForcedRedaction)) &&
!annotation.isFalsePositive && !annotation.isFalsePositive &&
!annotation.pending; !annotation.pending;
export const canAcceptRecommendation = (annotation: AnnotationWrapper) => annotation.isRecommendation && !annotation.pending; export const canAcceptRecommendation = (annotation: AnnotationWrapper) => annotation.isRecommendation && !annotation.pending;
export const canMarkAsFalsePositive = (annotation: AnnotationWrapper, annotationEntity: Dictionary) => export const canMarkAsFalsePositive = (annotation: AnnotationWrapper, annotationEntity: Dictionary) =>
annotation.canBeMarkedAsFalsePositive && !annotation.hasBeenResizedLocally && annotationEntity?.hasDictionary; annotation.canBeMarkedAsFalsePositive &&
!annotation.hasBeenForcedRedaction &&
!annotation.hasBeenResizedLocally &&
annotationEntity?.hasDictionary;
export const canRemoveOnlyHere = (annotation: AnnotationWrapper, canAddRedaction: boolean, autoAnalysisDisabled: boolean) => export const canRemoveOnlyHere = (annotation: AnnotationWrapper, canAddRedaction: boolean, autoAnalysisDisabled: boolean) =>
canAddRedaction && canAddRedaction &&
@ -24,10 +27,11 @@ export const canRemoveOnlyHere = (annotation: AnnotationWrapper, canAddRedaction
(annotation.isRedacted || (annotation.isHint && !annotation.isImage)); (annotation.isRedacted || (annotation.isHint && !annotation.isImage));
export const canRemoveFromDictionary = (annotation: AnnotationWrapper, autoAnalysisDisabled: boolean) => export const canRemoveFromDictionary = (annotation: AnnotationWrapper, autoAnalysisDisabled: boolean) =>
(annotation.isModifyDictionary || annotation.engines.includes('DICTIONARY') || annotation.engines.includes('DOSSIER_DICTIONARY')) && annotation.isModifyDictionary &&
(annotation.isRedacted || annotation.isSkipped || annotation.isHint || (annotation.isIgnoredHint && !annotation.isRuleBased)) && (annotation.isRedacted || annotation.isSkipped || annotation.isHint || (annotation.isIgnoredHint && !annotation.isRuleBased)) &&
(autoAnalysisDisabled || !annotation.pending) && (autoAnalysisDisabled || !annotation.pending) &&
annotation.isDictBased; !annotation.hasBeenResizedLocally &&
!(annotation.hasBeenForcedHint || annotation.hasBeenForcedRedaction);
export const canRemoveRedaction = (annotation: AnnotationWrapper, permissions: AnnotationPermissions) => export const canRemoveRedaction = (annotation: AnnotationWrapper, permissions: AnnotationPermissions) =>
(!annotation.isIgnoredHint || !annotation.isRuleBased) && (!annotation.isIgnoredHint || !annotation.isRuleBased) &&
@ -69,5 +73,3 @@ export const canEditHint = (annotation: AnnotationWrapper) =>
((annotation.isHint && !annotation.isRuleBased) || annotation.isIgnoredHint) && !annotation.isImage; ((annotation.isHint && !annotation.isRuleBased) || annotation.isIgnoredHint) && !annotation.isImage;
export const canEditImage = (annotation: AnnotationWrapper) => annotation.isImage; export const canEditImage = (annotation: AnnotationWrapper) => annotation.isImage;
export const canRevertChanges = (annotation: AnnotationWrapper) => annotation.hasRedactionChanges;

View File

@ -17,7 +17,6 @@ import {
canRemoveRedaction, canRemoveRedaction,
canResizeAnnotation, canResizeAnnotation,
canResizeInDictionary, canResizeInDictionary,
canRevertChanges,
canUndo, canUndo,
} from './annotation-permissions.utils'; } from './annotation-permissions.utils';
import { AnnotationWrapper } from './annotation.wrapper'; import { AnnotationWrapper } from './annotation.wrapper';
@ -38,7 +37,6 @@ export class AnnotationPermissions {
canEditAnnotations = true; canEditAnnotations = true;
canEditHints = true; canEditHints = true;
canEditImages = true; canEditImages = true;
canRevertChanges = true;
static forUser( static forUser(
isApprover: boolean, isApprover: boolean,
@ -77,7 +75,6 @@ export class AnnotationPermissions {
permissions.canEditAnnotations = canEditAnnotation(annotation); permissions.canEditAnnotations = canEditAnnotation(annotation);
permissions.canEditHints = canEditHint(annotation); permissions.canEditHints = canEditHint(annotation);
permissions.canEditImages = canEditImage(annotation); permissions.canEditImages = canEditImage(annotation);
permissions.canRevertChanges = canRevertChanges(annotation);
summedPermissions._merge(permissions); summedPermissions._merge(permissions);
} }
return summedPermissions; return summedPermissions;
@ -100,7 +97,6 @@ export class AnnotationPermissions {
result.canEditAnnotations = permissions.reduce((acc, next) => acc && next.canEditAnnotations, true); result.canEditAnnotations = permissions.reduce((acc, next) => acc && next.canEditAnnotations, true);
result.canEditHints = permissions.reduce((acc, next) => acc && next.canEditHints, true); result.canEditHints = permissions.reduce((acc, next) => acc && next.canEditHints, true);
result.canEditImages = permissions.reduce((acc, next) => acc && next.canEditImages, true); result.canEditImages = permissions.reduce((acc, next) => acc && next.canEditImages, true);
result.canRevertChanges = permissions.reduce((acc, next) => acc && next.canRevertChanges, true);
return result; return result;
} }

View File

@ -26,12 +26,6 @@ import {
} from '@red/domain'; } from '@red/domain';
import { annotationTypesTranslations } from '@translations/annotation-types-translations'; import { annotationTypesTranslations } from '@translations/annotation-types-translations';
interface AnnotationContent {
translation: string;
params: { [key: string]: string };
untranslatedContent: string;
}
export class AnnotationWrapper implements IListable { export class AnnotationWrapper implements IListable {
id: string; id: string;
superType: SuperType; superType: SuperType;
@ -40,9 +34,9 @@ export class AnnotationWrapper implements IListable {
typeLabel?: string; typeLabel?: string;
color: string; color: string;
numberOfComments = 0; numberOfComments = 0;
firstBottomLeftPoint: IPoint; firstTopLeftPoint: IPoint;
shortContent: string; shortContent: string;
content: AnnotationContent; content: string;
value: string; value: string;
pageNumber: number; pageNumber: number;
dictionaryOperation = false; dictionaryOperation = false;
@ -80,18 +74,7 @@ export class AnnotationWrapper implements IListable {
} }
get isDictBased() { get isDictBased() {
return [LogEntryEngines.DICTIONARY, LogEntryEngines.DOSSIER_DICTIONARY].some(engine => this.engines.includes(engine)); return this.engines.includes(LogEntryEngines.DICTIONARY);
}
get isRedactedImageHint() {
return (
(this.IMAGE_HINT && this.superType === SuperTypes.Redaction) ||
(this.IMAGE_HINT && this.superType === SuperTypes.ManualRedaction)
);
}
get isSkippedImageHint() {
return this.IMAGE_HINT && this.superType === SuperTypes.Hint;
} }
get searchKey(): string { get searchKey(): string {
@ -112,10 +95,7 @@ export class AnnotationWrapper implements IListable {
get canBeMarkedAsFalsePositive() { get canBeMarkedAsFalsePositive() {
return ( return (
(this.isRecommendation || (this.isRecommendation || this.superType === SuperTypes.Redaction || (this.isSkipped && this.isDictBased)) &&
this.superType === SuperTypes.Redaction ||
(this.isSkipped && this.isDictBased) ||
(this.isRemovedLocally && this.isDictBased)) &&
!this.isImage && !this.isImage &&
!this.imported && !this.imported &&
!this.pending && !this.pending &&
@ -199,11 +179,11 @@ export class AnnotationWrapper implements IListable {
} }
get x() { get x() {
return this.firstBottomLeftPoint.x; return this.firstTopLeftPoint.x;
} }
get y() { get y() {
return this.firstBottomLeftPoint.y; return this.firstTopLeftPoint.y;
} }
get legalBasis() { get legalBasis() {
@ -228,7 +208,7 @@ export class AnnotationWrapper implements IListable {
annotationWrapper.value = 'Imported'; annotationWrapper.value = 'Imported';
annotationWrapper.color = earmark.hexColor; annotationWrapper.color = earmark.hexColor;
annotationWrapper.positions = earmark.positions; annotationWrapper.positions = earmark.positions;
annotationWrapper.firstBottomLeftPoint = earmark.positions[0]?.topLeft; annotationWrapper.firstTopLeftPoint = earmark.positions[0]?.topLeft;
annotationWrapper.superTypeLabel = annotationTypesTranslations[annotationWrapper.superType]; annotationWrapper.superTypeLabel = annotationTypesTranslations[annotationWrapper.superType];
return annotationWrapper; return annotationWrapper;
@ -249,7 +229,7 @@ export class AnnotationWrapper implements IListable {
annotationWrapper.isChangeLogEntry = logEntry.state === EntryStates.REMOVED || !!changeLogType; annotationWrapper.isChangeLogEntry = logEntry.state === EntryStates.REMOVED || !!changeLogType;
annotationWrapper.type = logEntry.type; annotationWrapper.type = logEntry.type;
annotationWrapper.value = logEntry.value; annotationWrapper.value = logEntry.value;
annotationWrapper.firstBottomLeftPoint = { x: logEntry.positions[0].rectangle[0], y: logEntry.positions[0].rectangle[1] }; annotationWrapper.firstTopLeftPoint = { x: logEntry.positions[0].rectangle[0], y: logEntry.positions[0].rectangle[1] };
annotationWrapper.pageNumber = logEntry.positions[0].pageNumber; annotationWrapper.pageNumber = logEntry.positions[0].pageNumber;
annotationWrapper.positions = logEntry.positions.map(p => ({ annotationWrapper.positions = logEntry.positions.map(p => ({
page: p.pageNumber, page: p.pageNumber,
@ -282,16 +262,16 @@ export class AnnotationWrapper implements IListable {
annotationWrapper.hasLegalBasisChanged = !!logEntry.manualChanges?.find( annotationWrapper.hasLegalBasisChanged = !!logEntry.manualChanges?.find(
c => c.manualRedactionType === ManualRedactionTypes.LEGAL_BASIS_CHANGE, c => c.manualRedactionType === ManualRedactionTypes.LEGAL_BASIS_CHANGE,
); );
annotationWrapper.hasBeenForcedHint = annotationWrapper.hasBeenForcedHint = !!logEntry.manualChanges?.find(c => c.manualRedactionType === ManualRedactionTypes.FORCE);
!!logEntry.manualChanges?.find(c => c.manualRedactionType === ManualRedactionTypes.FORCE) && annotationWrapper.HINT; annotationWrapper.hasBeenForcedRedaction = !!logEntry.manualChanges?.find(
annotationWrapper.hasBeenForcedRedaction = c => c.manualRedactionType === ManualRedactionTypes.FORCE,
!!logEntry.manualChanges?.find(c => c.manualRedactionType === ManualRedactionTypes.FORCE) && !annotationWrapper.HINT; );
annotationWrapper.hasBeenRemovedByManualOverride = !!logEntry.manualChanges?.find( annotationWrapper.hasBeenRemovedByManualOverride = !!logEntry.manualChanges?.find(
c => c.manualRedactionType === ManualRedactionTypes.REMOVE, c => c.manualRedactionType === ManualRedactionTypes.REMOVE,
); );
const content = this.#createContent(annotationWrapper, logEntry, isDocumine); const content = this.#createContent(annotationWrapper, logEntry, isDocumine);
annotationWrapper.shortContent = this.#getShortContent(annotationWrapper, legalBasisList) || content.untranslatedContent; annotationWrapper.shortContent = this.#getShortContent(annotationWrapper, legalBasisList) || content;
annotationWrapper.content = content; annotationWrapper.content = content;
const lastRelevantManualChange = logEntry.manualChanges?.at(-1); const lastRelevantManualChange = logEntry.manualChanges?.at(-1);
@ -311,10 +291,7 @@ export class AnnotationWrapper implements IListable {
} else { } else {
const colorKey = annotationEntityColorConfig[annotationWrapper.superType]; const colorKey = annotationEntityColorConfig[annotationWrapper.superType];
const defaultColor = annotationDefaultColorConfig[annotationWrapper.superType]; const defaultColor = annotationDefaultColorConfig[annotationWrapper.superType];
annotationWrapper.color = annotationWrapper.color = dictionary ? (dictionary[colorKey] as string) : (defaultColors[defaultColor] as string);
dictionary && !annotationWrapper.isRedactedImageHint
? (dictionary[colorKey] as string)
: (defaultColors[defaultColor] as string);
} }
annotationWrapper.entry = logEntry; annotationWrapper.entry = logEntry;
@ -323,62 +300,44 @@ export class AnnotationWrapper implements IListable {
} }
static #createContent(annotationWrapper: AnnotationWrapper, logEntry: IEntityLogEntry, isDocumine: boolean) { static #createContent(annotationWrapper: AnnotationWrapper, logEntry: IEntityLogEntry, isDocumine: boolean) {
let untranslatedContent = ''; let content = '';
const params: { [key: string]: string } = {};
if (logEntry.matchedRule) { if (logEntry.matchedRule) {
params['hasRule'] = 'true'; content += `Rule ${logEntry.matchedRule} matched${isDocumine ? ':' : ''} \n\n`;
params['matchedRule'] = logEntry.matchedRule.replace(/(^[, ]*)|([, ]*$)/g, '');
params['ruleSymbol'] = isDocumine ? ':' : '';
untranslatedContent += `Rule ${logEntry.matchedRule} matched${isDocumine ? ':' : ''} \n\n`;
} }
if (logEntry.reason) { if (logEntry.reason) {
params['hasReason'] = 'true';
if (isDocumine && logEntry.reason.slice(-1) === '.') { if (isDocumine && logEntry.reason.slice(-1) === '.') {
logEntry.reason = logEntry.reason.slice(0, -1); logEntry.reason = logEntry.reason.slice(0, -1);
} }
if (!params['hasRule']) {
params['reason'] = logEntry.reason.substring(0, 1).toUpperCase() + logEntry.reason.substring(1); content += logEntry.reason + '\n\n';
} else {
params['reason'] = logEntry.reason;
}
params['reason'] = params['reason'].replace(/(^[, ]*)|([, ]*$)/g, '');
untranslatedContent += logEntry.reason + '\n\n';
//remove leading and trailing commas and whitespaces //remove leading and trailing commas and whitespaces
untranslatedContent = untranslatedContent.replace(/(^[, ]*)|([, ]*$)/g, ''); content = content.replace(/(^[, ]*)|([, ]*$)/g, '');
untranslatedContent = untranslatedContent.substring(0, 1).toUpperCase() + untranslatedContent.substring(1); content = content.substring(0, 1).toUpperCase() + content.substring(1);
} }
if (annotationWrapper.legalBasis && !isDocumine) { if (annotationWrapper.legalBasis && !isDocumine) {
params['hasLb'] = 'true'; content += 'Legal basis: ' + annotationWrapper.legalBasis + '\n\n';
params['legalBasis'] = annotationWrapper.legalBasis;
untranslatedContent += 'Legal basis: ' + annotationWrapper.legalBasis + '\n\n';
} }
if (annotationWrapper.hasBeenRemovedByManualOverride) { if (annotationWrapper.hasBeenRemovedByManualOverride) {
params['hasOverride'] = 'true'; content += 'Removed by manual override';
untranslatedContent += 'Removed by manual override';
} }
if (logEntry.section) { if (logEntry.section) {
params['hasSection'] = 'true';
params['sectionSymbol'] = isDocumine ? '' : ':';
params['shouldLower'] = untranslatedContent.length.toString();
params['section'] = logEntry.section;
let prefix = `In section${isDocumine ? '' : ':'} `; let prefix = `In section${isDocumine ? '' : ':'} `;
if (untranslatedContent.length) { if (content.length) {
prefix = ` ${prefix.toLowerCase()}`; prefix = ` ${prefix.toLowerCase()}`;
} }
untranslatedContent += `${prefix} "${logEntry.section}"`; content += `${prefix} "${logEntry.section}"`;
} }
return { translation: _('annotation-content'), params: params, untranslatedContent: untranslatedContent }; return content;
} }
static #getShortContent(annotationWrapper: AnnotationWrapper, legalBasisList: ILegalBasis[]) { static #getShortContent(annotationWrapper: AnnotationWrapper, legalBasisList: ILegalBasis[]) {
if (annotationWrapper.legalBasis) { if (annotationWrapper.legalBasis) {
const lb = legalBasisList.find(lbm => lbm.technicalName?.toLowerCase().includes(annotationWrapper.legalBasis.toLowerCase())); const lb = legalBasisList.find(lbm => lbm.reason?.toLowerCase().includes(annotationWrapper.legalBasis.toLowerCase()));
if (lb) { if (lb) {
return lb.name; return lb.name;
} }

View File

@ -1,13 +1,13 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { CompositeRouteGuard, IqserPermissionsGuard, IqserRoutes } from '@iqser/common-ui'; import { CompositeRouteGuard, IqserPermissionsGuard, IqserRoutes } from '@iqser/common-ui';
import { RedRoleGuard } from '@users/red-role.guard'; import { RedRoleGuard } from '@users/red-role.guard';
import { BaseAccountScreenComponent } from './base-account-screen/base-account-screen-component'; import { BaseAccountScreenComponent } from './base-account-screen/base-account-screen-component';
import { PreferencesComponent } from './screens/preferences/preferences.component'; import { PreferencesComponent } from './screens/preferences/preferences.component';
import { Roles } from '@users/roles'; import { Roles } from '@users/roles';
import { IqserAuthGuard } from '@iqser/common-ui/lib/users'; import { IqserAuthGuard } from '@iqser/common-ui/lib/users';
import { PendingChangesGuard } from '@guards/can-deactivate.guard';
import { NotificationPreferencesService } from './services/notification-preferences.service';
export default [ const routes: IqserRoutes = [
{ path: '', redirectTo: 'user-profile', pathMatch: 'full' }, { path: '', redirectTo: 'user-profile', pathMatch: 'full' },
{ {
path: 'user-profile', path: 'user-profile',
@ -16,13 +16,12 @@ export default [
data: { data: {
routeGuards: [IqserAuthGuard, RedRoleGuard], routeGuards: [IqserAuthGuard, RedRoleGuard],
}, },
loadChildren: () => import('./screens/user-profile/user-profile.routes'), loadChildren: () => import('./screens/user-profile/user-profile.module').then(m => m.UserProfileModule),
}, },
{ {
path: 'notifications', path: 'notifications',
component: BaseAccountScreenComponent, component: BaseAccountScreenComponent,
canActivate: [CompositeRouteGuard, IqserPermissionsGuard], canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
canDeactivate: [PendingChangesGuard],
data: { data: {
routeGuards: [IqserAuthGuard, RedRoleGuard], routeGuards: [IqserAuthGuard, RedRoleGuard],
permissions: { permissions: {
@ -30,8 +29,7 @@ export default [
redirectTo: '/', redirectTo: '/',
}, },
}, },
providers: [NotificationPreferencesService], loadChildren: () => import('./screens/notifications/notifications.module').then(m => m.NotificationsModule),
loadChildren: () => import('./screens/notifications/notifications.routes'),
}, },
{ {
path: 'preferences', path: 'preferences',
@ -63,4 +61,10 @@ export default [
}, },
], ],
}, },
] satisfies IqserRoutes; ];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class AccountRoutingModule {}

View File

@ -4,10 +4,6 @@ import { IqserPermissionsService } from '@iqser/common-ui';
import { Roles } from '@users/roles'; import { Roles } from '@users/roles';
import { User } from '@red/domain'; import { User } from '@red/domain';
import { getCurrentUser } from '@iqser/common-ui/lib/users'; import { getCurrentUser } from '@iqser/common-ui/lib/users';
import { SideNavComponent } from '@common-ui/shared';
import { TranslateModule } from '@ngx-translate/core';
import { NgForOf, NgIf } from '@angular/common';
import { RouterLink, RouterLinkActive } from '@angular/router';
interface NavItem { interface NavItem {
readonly label: string; readonly label: string;
@ -21,7 +17,6 @@ interface NavItem {
templateUrl: './account-side-nav.component.html', templateUrl: './account-side-nav.component.html',
styleUrls: ['./account-side-nav.component.scss'], styleUrls: ['./account-side-nav.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [SideNavComponent, TranslateModule, NgForOf, NgIf, RouterLinkActive, RouterLink],
}) })
export class AccountSideNavComponent { export class AccountSideNavComponent {
readonly currentUser = getCurrentUser<User>(); readonly currentUser = getCurrentUser<User>();

View File

@ -0,0 +1,28 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SharedModule } from '@shared/shared.module';
import { AccountRoutingModule } from './account-routing.module';
import { AccountSideNavComponent } from './account-side-nav/account-side-nav.component';
import { BaseAccountScreenComponent } from './base-account-screen/base-account-screen-component';
import { NotificationPreferencesService } from './services/notification-preferences.service';
import { TranslateModule } from '@ngx-translate/core';
import { IconButtonComponent, IqserAllowDirective, IqserHelpModeModule } from '@iqser/common-ui';
import { PreferencesComponent } from './screens/preferences/preferences.component';
import { SideNavComponent } from '@iqser/common-ui/lib/shared';
import { DialogDefaultsComponent } from './screens/preferences/dialog-defaults/dialog-defaults.component';
@NgModule({
declarations: [AccountSideNavComponent, BaseAccountScreenComponent, PreferencesComponent, DialogDefaultsComponent],
imports: [
CommonModule,
SharedModule,
AccountRoutingModule,
TranslateModule,
IqserHelpModeModule,
IconButtonComponent,
SideNavComponent,
IqserAllowDirective,
],
providers: [NotificationPreferencesService],
})
export class AccountModule {}

View File

@ -8,11 +8,10 @@
<div class="content-container full-height"> <div class="content-container full-height">
<div class="overlay-shadow"></div> <div class="overlay-shadow"></div>
<div [ngClass]="!isWarningsScreen && 'dialog'"> <div [ngClass]="!isWarningsScreen && 'dialog'">
@if (!isWarningsScreen) { <div *ngIf="!isWarningsScreen" class="dialog-header">
<div class="dialog-header"> <div class="heading-l" [translate]="translations[path]"></div>
<div class="heading-l" [translate]="translations[path]"></div> </div>
</div>
}
<router-outlet></router-outlet> <router-outlet></router-outlet>
</div> </div>
</div> </div>

View File

@ -1,16 +1,12 @@
import { ChangeDetectionStrategy, Component, OnInit, ViewContainerRef } from '@angular/core'; import { ChangeDetectionStrategy, Component, OnInit, ViewContainerRef } from '@angular/core';
import { Router, RouterOutlet } from '@angular/router'; import { Router } from '@angular/router';
import { accountTranslations } from '@translations/account-translations'; import { accountTranslations } from '@translations/account-translations';
import { NgClass } from '@angular/common';
import { AccountSideNavComponent } from '../account-side-nav/account-side-nav.component';
import { TranslateModule } from '@ngx-translate/core';
@Component({ @Component({
selector: 'redaction-base-account-screen', selector: 'redaction-base-account-screen',
templateUrl: './base-account-screen-component.html', templateUrl: './base-account-screen-component.html',
styleUrls: ['./base-account-screen-component.scss'], styleUrls: ['./base-account-screen-component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [NgClass, RouterOutlet, AccountSideNavComponent, TranslateModule],
}) })
export class BaseAccountScreenComponent implements OnInit { export class BaseAccountScreenComponent implements OnInit {
readonly translations = accountTranslations; readonly translations = accountTranslations;

View File

@ -1,7 +1,7 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, OnInit } from '@angular/core'; import { ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, OnInit } from '@angular/core';
import { ReactiveFormsModule, UntypedFormBuilder } from '@angular/forms'; import { UntypedFormBuilder } from '@angular/forms';
import { NotificationPreferencesService } from '../../../services/notification-preferences.service'; import { NotificationPreferencesService } from '../../../services/notification-preferences.service';
import { BaseFormComponent, getConfig, IconButtonComponent, LoadingService, Toaster } from '@iqser/common-ui'; import { BaseFormComponent, getConfig, LoadingService, Toaster } from '@iqser/common-ui';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { import {
DocumentNotificationsTypes, DocumentNotificationsTypes,
@ -14,10 +14,6 @@ import {
import { firstValueFrom } from 'rxjs'; import { firstValueFrom } from 'rxjs';
import { notificationsSettingsTranslations } from '@translations/notifications-settings-translations'; import { notificationsSettingsTranslations } from '@translations/notifications-settings-translations';
import { getCurrentUser } from '@iqser/common-ui/lib/users'; import { getCurrentUser } from '@iqser/common-ui/lib/users';
import { NgForOf, NgIf } from '@angular/common';
import { MatSlideToggle } from '@angular/material/slide-toggle';
import { TranslateModule } from '@ngx-translate/core';
import { MatCheckbox } from '@angular/material/checkbox';
const RSS_EXCLUDED_SETTINGS = ['USER_PROMOTED_TO_APPROVER', 'USER_DEGRADED_TO_REVIEWER', 'ASSIGN_REVIEWER']; const RSS_EXCLUDED_SETTINGS = ['USER_PROMOTED_TO_APPROVER', 'USER_DEGRADED_TO_REVIEWER', 'ASSIGN_REVIEWER'];
@ -25,7 +21,6 @@ const RSS_EXCLUDED_SETTINGS = ['USER_PROMOTED_TO_APPROVER', 'USER_DEGRADED_TO_RE
templateUrl: './notifications-screen.component.html', templateUrl: './notifications-screen.component.html',
styleUrls: ['./notifications-screen.component.scss'], styleUrls: ['./notifications-screen.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [ReactiveFormsModule, NgForOf, MatSlideToggle, TranslateModule, NgIf, MatCheckbox, IconButtonComponent],
}) })
export class NotificationsScreenComponent extends BaseFormComponent implements OnInit { export class NotificationsScreenComponent extends BaseFormComponent implements OnInit {
readonly #toaster = inject(Toaster); readonly #toaster = inject(Toaster);

View File

@ -0,0 +1,16 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { CommonModule } from '@angular/common';
import { SharedModule } from '@shared/shared.module';
import { NotificationsScreenComponent } from './notifications-screen/notifications-screen.component';
import { PendingChangesGuard } from '@guards/can-deactivate.guard';
import { TranslateModule } from '@ngx-translate/core';
import { IconButtonComponent } from '@iqser/common-ui';
const routes = [{ path: '', component: NotificationsScreenComponent, canDeactivate: [PendingChangesGuard] }];
@NgModule({
declarations: [NotificationsScreenComponent],
imports: [RouterModule.forChild(routes), CommonModule, SharedModule, TranslateModule, IconButtonComponent],
})
export class NotificationsModule {}

View File

@ -1,11 +0,0 @@
import { NotificationsScreenComponent } from './notifications-screen/notifications-screen.component';
import { PendingChangesGuard } from '@guards/can-deactivate.guard';
import { IqserRoutes } from '@iqser/common-ui';
export default [
{
path: '',
component: NotificationsScreenComponent,
canDeactivate: [PendingChangesGuard],
},
] satisfies IqserRoutes;

View File

@ -1,20 +1,14 @@
import { NgForOf, NgIf } from '@angular/common';
import { ChangeDetectorRef, Component, inject } from '@angular/core'; import { ChangeDetectorRef, Component, inject } from '@angular/core';
import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { MatCheckbox } from '@angular/material/checkbox';
import { MatFormField } from '@angular/material/form-field';
import { MatOption, MatSelect } from '@angular/material/select';
import { BaseFormComponent } from '@common-ui/form'; import { BaseFormComponent } from '@common-ui/form';
import { AsControl } from '@common-ui/utils'; import { FormBuilder, FormGroup } from '@angular/forms';
import { IconButtonComponent } from '@iqser/common-ui';
import { TranslateModule } from '@ngx-translate/core';
import { PreferencesKeys, UserPreferenceService } from '@users/user-preference.service';
import { import {
RedactOrHintOption, RedactOrHintOption,
RedactOrHintOptions, RedactOrHintOptions,
RemoveRedactionOption, RemoveRedactionOption,
RemoveRedactionOptions, RemoveRedactionOptions,
} from '../../../../file-preview/utils/dialog-types'; } from '../../../../file-preview/utils/dialog-options';
import { PreferencesKeys, UserPreferenceService } from '@users/user-preference.service';
import { AsControl } from '@common-ui/utils';
import { import {
hintAddOptions, hintAddOptions,
recommendationRemoveOptions, recommendationRemoveOptions,
@ -41,7 +35,6 @@ interface DefaultOptionsForm {
selector: 'redaction-dialog-defaults', selector: 'redaction-dialog-defaults',
templateUrl: './dialog-defaults.component.html', templateUrl: './dialog-defaults.component.html',
styleUrl: './dialog-defaults.component.scss', styleUrl: './dialog-defaults.component.scss',
imports: [ReactiveFormsModule, TranslateModule, MatFormField, MatSelect, MatOption, NgForOf, MatCheckbox, NgIf, IconButtonComponent],
}) })
export class DialogDefaultsComponent extends BaseFormComponent { export class DialogDefaultsComponent extends BaseFormComponent {
readonly #formBuilder = inject(FormBuilder); readonly #formBuilder = inject(FormBuilder);
@ -67,10 +60,6 @@ export class DialogDefaultsComponent extends BaseFormComponent {
readonly redactionRemoveOptions = redactionRemoveOptions; readonly redactionRemoveOptions = redactionRemoveOptions;
readonly recommendationRemoveOptions = recommendationRemoveOptions; readonly recommendationRemoveOptions = recommendationRemoveOptions;
constructor() {
super();
}
get displayExtraOptionAddRedaction() { get displayExtraOptionAddRedaction() {
return RedactOrHintOptions.IN_DOSSIER === this.form.controls.addRedaction.value; return RedactOrHintOptions.IN_DOSSIER === this.form.controls.addRedaction.value;
} }
@ -95,6 +84,10 @@ export class DialogDefaultsComponent extends BaseFormComponent {
return RemoveRedactionOptions.DO_NOT_RECOMMEND === this.form.controls.removeRecommendation.value; return RemoveRedactionOptions.DO_NOT_RECOMMEND === this.form.controls.removeRecommendation.value;
} }
constructor() {
super();
}
async save(): Promise<any> { async save(): Promise<any> {
const formValue = this.form.value; const formValue = this.form.value;

View File

@ -11,6 +11,12 @@
</mat-slide-toggle> </mat-slide-toggle>
</div> </div>
<div *ngIf="config.IS_DOCUMINE" class="iqser-input-group">
<mat-slide-toggle color="primary" formControlName="openScmDialogByDefault">
{{ 'preferences-screen.form.open-structured-view-by-default' | translate }}
</mat-slide-toggle>
</div>
<div *allow="roles.getTables" class="iqser-input-group"> <div *allow="roles.getTables" class="iqser-input-group">
<label [translate]="'preferences-screen.form.table-extraction-type'"></label> <label [translate]="'preferences-screen.form.table-extraction-type'"></label>
<input formControlName="tableExtractionType" /> <input formControlName="tableExtractionType" />
@ -31,11 +37,6 @@
{{ 'preferences-screen.form.help-mode-dialog' | translate }} {{ 'preferences-screen.form.help-mode-dialog' | translate }}
</mat-checkbox> </mat-checkbox>
</div> </div>
<div class="iqser-input-group">
<mat-checkbox color="primary" formControlName="overwriteFileOption">
{{ 'preferences-screen.form.overwrite-file-option' | translate }}
</mat-checkbox>
</div>
</ng-container> </ng-container>
</div> </div>
</div> </div>

View File

@ -1,33 +1,19 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, OnInit } from '@angular/core'; import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms'; import { FormBuilder, FormGroup } from '@angular/forms';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { import { BaseFormComponent, getConfig, IqserPermissionsService, isIqserDevMode, KEYS, LoadingService } from '@iqser/common-ui';
BaseFormComponent,
getConfig,
IconButtonComponent,
IqserAllowDirective,
IqserPermissionsService,
isIqserDevMode,
KEYS,
LoadingService,
} from '@iqser/common-ui';
import { AsControl } from '@iqser/common-ui/lib/utils'; import { AsControl } from '@iqser/common-ui/lib/utils';
import { Roles } from '@users/roles'; import { Roles } from '@users/roles';
import { PreferencesKeys, UserPreferenceService } from '@users/user-preference.service'; import { PreferencesKeys, UserPreferenceService } from '@users/user-preference.service';
import { DialogDefaultsComponent } from './dialog-defaults/dialog-defaults.component';
import { NgClass, NgIf } from '@angular/common';
import { MatSlideToggle } from '@angular/material/slide-toggle';
import { TranslateModule } from '@ngx-translate/core';
import { MatCheckbox } from '@angular/material/checkbox';
interface PreferencesForm { interface PreferencesForm {
// preferences // preferences
autoExpandFiltersOnActions: boolean; autoExpandFiltersOnActions: boolean;
openScmDialogByDefault: boolean;
tableExtractionType: string; tableExtractionType: string;
// warnings preferences // warnings preferences
loadAllAnnotationsWarning: boolean; loadAllAnnotationsWarning: boolean;
helpModeDialog: boolean; helpModeDialog: boolean;
overwriteFileOption: boolean;
[k: string]: any; [k: string]: any;
} }
@ -44,25 +30,8 @@ const Screens = {
templateUrl: './preferences.component.html', templateUrl: './preferences.component.html',
styleUrls: ['./preferences.component.scss'], styleUrls: ['./preferences.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
DialogDefaultsComponent,
NgClass,
NgIf,
ReactiveFormsModule,
MatSlideToggle,
TranslateModule,
IqserAllowDirective,
MatCheckbox,
IconButtonComponent,
],
}) })
export class PreferencesComponent extends BaseFormComponent implements OnInit { export class PreferencesComponent extends BaseFormComponent implements OnInit {
readonly #formBuilder = inject(FormBuilder);
readonly #permissionsService = inject(IqserPermissionsService);
readonly #changeRef = inject(ChangeDetectorRef);
readonly #loadingService = inject(LoadingService);
readonly #userPreferenceService = inject(UserPreferenceService);
readonly form: FormGroup<AsControl<PreferencesForm>>; readonly form: FormGroup<AsControl<PreferencesForm>>;
readonly currentScreen: Screen; readonly currentScreen: Screen;
readonly screens = Screens; readonly screens = Screens;
@ -71,87 +40,81 @@ export class PreferencesComponent extends BaseFormComponent implements OnInit {
readonly config = getConfig(); readonly config = getConfig();
readonly isIqserDevMode = isIqserDevMode(); readonly isIqserDevMode = isIqserDevMode();
get #isOverwriteFileOptionActive() { constructor(
return !(this.#userPreferenceService.getOverwriteFileOption() === 'undefined'); route: ActivatedRoute,
} readonly userPreferenceService: UserPreferenceService,
private readonly _formBuilder: FormBuilder,
constructor(route: ActivatedRoute) { private readonly _permissionsService: IqserPermissionsService,
private readonly _changeRef: ChangeDetectorRef,
private readonly _loadingService: LoadingService,
) {
super(); super();
this.form = this.#formBuilder.group({ this.form = this._formBuilder.group({
// preferences // preferences
autoExpandFiltersOnActions: [this.#userPreferenceService.getAutoExpandFiltersOnActions()], autoExpandFiltersOnActions: [this.userPreferenceService.getAutoExpandFiltersOnActions()],
tableExtractionType: [this.#userPreferenceService.getTableExtractionType()], openScmDialogByDefault: [this.userPreferenceService.getOpenScmDialogByDefault()],
tableExtractionType: [this.userPreferenceService.getTableExtractionType()],
// warnings preferences // warnings preferences
loadAllAnnotationsWarning: [this.#userPreferenceService.getBool(PreferencesKeys.loadAllAnnotationsWarning)], loadAllAnnotationsWarning: [this.userPreferenceService.getBool(PreferencesKeys.loadAllAnnotationsWarning)],
helpModeDialog: [this.#userPreferenceService.getBool(KEYS.helpModeDialog)], helpModeDialog: [this.userPreferenceService.getBool(KEYS.helpModeDialog)],
overwriteFileOption: [this.#isOverwriteFileOptionActive],
}); });
if (!this.#permissionsService.has(Roles.managePreferences)) { if (!this._permissionsService.has(Roles.managePreferences)) {
this.form.disable(); this.form.disable();
} }
if (!this.#permissionsService.has(Roles.getTables)) { if (!this._permissionsService.has(Roles.getTables)) {
this.form.controls.tableExtractionType.disable(); this.form.controls.tableExtractionType.disable();
} }
if (!this.#isOverwriteFileOptionActive) {
this.form.controls.overwriteFileOption.disable();
}
this.initialFormValue = this.form.getRawValue(); this.initialFormValue = this.form.getRawValue();
this.currentScreen = route.snapshot.data.screen; this.currentScreen = route.snapshot.data.screen;
} }
ngOnInit() { ngOnInit() {
this.#loadingService.stop(); this._loadingService.stop();
} }
async save(): Promise<any> { async save(): Promise<any> {
if (this.form.controls.autoExpandFiltersOnActions.value !== this.#userPreferenceService.getAutoExpandFiltersOnActions()) { if (this.form.controls.autoExpandFiltersOnActions.value !== this.userPreferenceService.getAutoExpandFiltersOnActions()) {
await this.#userPreferenceService.toggleAutoExpandFiltersOnActions(); await this.userPreferenceService.toggleAutoExpandFiltersOnActions();
}
if (this.form.controls.openScmDialogByDefault.value !== this.userPreferenceService.getOpenScmDialogByDefault()) {
await this.userPreferenceService.toggleOpenScmDialogByDefault();
} }
if (this.form.controls.tableExtractionType.value !== this.#userPreferenceService.getTableExtractionType()) { if (this.form.controls.tableExtractionType.value !== this.userPreferenceService.getTableExtractionType()) {
await this.#userPreferenceService.save(PreferencesKeys.tableExtractionType, this.form.controls.tableExtractionType.value); await this.userPreferenceService.save(PreferencesKeys.tableExtractionType, this.form.controls.tableExtractionType.value);
} }
if ( if (
this.form.controls.loadAllAnnotationsWarning.value !== this.form.controls.loadAllAnnotationsWarning.value !==
this.#userPreferenceService.getBool(PreferencesKeys.loadAllAnnotationsWarning) this.userPreferenceService.getBool(PreferencesKeys.loadAllAnnotationsWarning)
) { ) {
await this.#userPreferenceService.save( await this.userPreferenceService.save(
PreferencesKeys.loadAllAnnotationsWarning, PreferencesKeys.loadAllAnnotationsWarning,
String(this.form.controls.loadAllAnnotationsWarning.value), String(this.form.controls.loadAllAnnotationsWarning.value),
); );
} }
if (this.form.controls.helpModeDialog.value !== this.#userPreferenceService.getBool(KEYS.helpModeDialog)) { if (this.form.controls.helpModeDialog.value !== this.userPreferenceService.getBool(KEYS.helpModeDialog)) {
await this.#userPreferenceService.save(KEYS.helpModeDialog, String(this.form.controls.helpModeDialog.value)); await this.userPreferenceService.save(KEYS.helpModeDialog, String(this.form.controls.helpModeDialog.value));
} }
if (this.form.controls.overwriteFileOption.enabled && !this.form.controls.overwriteFileOption.value) { await this.userPreferenceService.reload();
await this.#userPreferenceService.saveOverwriteFileOption('undefined');
}
await this.#userPreferenceService.reload();
this.#patchValues(); this.#patchValues();
this.initialFormValue = this.form.getRawValue(); this.initialFormValue = this.form.getRawValue();
this.#changeRef.markForCheck(); this._changeRef.markForCheck();
} }
#patchValues() { #patchValues() {
this.form.patchValue({ this.form.patchValue({
autoExpandFiltersOnActions: this.#userPreferenceService.getAutoExpandFiltersOnActions(), autoExpandFiltersOnActions: this.userPreferenceService.getAutoExpandFiltersOnActions(),
tableExtractionType: this.#userPreferenceService.getTableExtractionType(), openScmDialogByDefault: this.userPreferenceService.getOpenScmDialogByDefault(),
loadAllAnnotationsWarning: this.#userPreferenceService.getBool(PreferencesKeys.loadAllAnnotationsWarning), tableExtractionType: this.userPreferenceService.getTableExtractionType(),
helpModeDialog: this.#userPreferenceService.getBool(KEYS.helpModeDialog), loadAllAnnotationsWarning: this.userPreferenceService.getBool(PreferencesKeys.loadAllAnnotationsWarning),
overwriteFileOption: this.#isOverwriteFileOptionActive, helpModeDialog: this.userPreferenceService.getBool(KEYS.helpModeDialog),
}); });
if (!this.#isOverwriteFileOptionActive) {
this.form.controls.overwriteFileOption.disable();
}
} }
} }

View File

@ -1,8 +1,7 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { BaseDialogComponent, CircleButtonComponent, IconButtonComponent } from '@iqser/common-ui'; import { BaseDialogComponent } from '@iqser/common-ui';
import { MatDialogRef } from '@angular/material/dialog'; import { MatDialogRef } from '@angular/material/dialog';
import { AbstractControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; import { AbstractControl, FormGroup, Validators } from '@angular/forms';
import { TranslateModule } from '@ngx-translate/core';
interface FormType { interface FormType {
password: AbstractControl<string>; password: AbstractControl<string>;
@ -10,7 +9,6 @@ interface FormType {
@Component({ @Component({
templateUrl: './confirm-password-dialog.component.html', templateUrl: './confirm-password-dialog.component.html',
imports: [ReactiveFormsModule, IconButtonComponent, TranslateModule, CircleButtonComponent],
}) })
export class ConfirmPasswordDialogComponent extends BaseDialogComponent { export class ConfirmPasswordDialogComponent extends BaseDialogComponent {
constructor(protected readonly _dialogRef: MatDialogRef<ConfirmPasswordDialogComponent>) { constructor(protected readonly _dialogRef: MatDialogRef<ConfirmPasswordDialogComponent>) {

View File

@ -16,16 +16,13 @@
<input formControlName="lastName" name="lastName" type="text" /> <input formControlName="lastName" name="lastName" type="text" />
</div> </div>
<div class="iqser-input-group"> <div *ngIf="devMode" class="iqser-input-group">
<label [translate]="'top-bar.navigation-items.my-account.children.language.label'"></label> <label [translate]="'top-bar.navigation-items.my-account.children.language.label'"></label>
<mat-form-field> <mat-form-field>
<mat-select formControlName="language"> <mat-select formControlName="language">
<mat-select-trigger>{{ languageSelectLabel() | translate }}</mat-select-trigger> <mat-option *ngFor="let language of languages" [value]="language">
@for (language of languages; track language) { {{ translations[language] | translate }}
<mat-option [value]="language"> </mat-option>
{{ translations[language] | translate }}
</mat-option>
}
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
</div> </div>
@ -34,19 +31,17 @@
<a (click)="resetPassword()" target="_blank"> {{ 'user-profile-screen.actions.change-password' | translate }}</a> <a (click)="resetPassword()" target="_blank"> {{ 'user-profile-screen.actions.change-password' | translate }}</a>
</div> </div>
@if (devMode) { <div *ngIf="devMode" class="iqser-input-group">
<div class="iqser-input-group"> <mat-slide-toggle color="primary" formControlName="darkTheme">
<mat-slide-toggle color="primary" formControlName="darkTheme"> {{ 'user-profile-screen.form.dark-theme' | translate }}
{{ 'user-profile-screen.form.dark-theme' | translate }} </mat-slide-toggle>
</mat-slide-toggle> </div>
</div>
}
</div> </div>
</div> </div>
<div class="dialog-actions"> <div class="dialog-actions">
<iqser-icon-button <iqser-icon-button
[disabled]="disabled" [disabled]="form.invalid || !(profileChanged || languageChanged || themeChanged)"
[label]="'user-profile-screen.actions.save' | translate" [label]="'user-profile-screen.actions.save' | translate"
[submit]="true" [submit]="true"
[type]="iconButtonTypes.primary" [type]="iconButtonTypes.primary"

View File

@ -1,15 +1,8 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, computed } from '@angular/core'; import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core';
import { FormGroup, ReactiveFormsModule, UntypedFormBuilder, Validators } from '@angular/forms'; import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { import { BaseFormComponent, IqserPermissionsService, LanguageService, LoadingService, Toaster } from '@iqser/common-ui';
BaseFormComponent, import { TranslateService } from '@ngx-translate/core';
IconButtonComponent,
IqserPermissionsService,
LanguageService,
LoadingService,
Toaster,
} from '@iqser/common-ui';
import { TranslateModule, TranslateService } from '@ngx-translate/core';
import { IProfile } from '@red/domain'; import { IProfile } from '@red/domain';
import { languagesTranslations } from '@translations/languages-translations'; import { languagesTranslations } from '@translations/languages-translations';
import { Roles } from '@users/roles'; import { Roles } from '@users/roles';
@ -17,47 +10,17 @@ import { UserPreferenceService } from '@users/user-preference.service';
import { UserService } from '@users/user.service'; import { UserService } from '@users/user.service';
import { firstValueFrom } from 'rxjs'; import { firstValueFrom } from 'rxjs';
import { UserProfileDialogService } from '../services/user-profile-dialog.service'; import { UserProfileDialogService } from '../services/user-profile-dialog.service';
import { MatFormField } from '@angular/material/form-field';
import { MatOption, MatSelect, MatSelectTrigger } from '@angular/material/select';
import { MatSlideToggle } from '@angular/material/slide-toggle';
import { PdfViewer } from '../../../../pdf-viewer/services/pdf-viewer.service';
import { formControlToSignal } from '@utils/functions';
import { AsControl } from '@common-ui/utils';
interface UserProfileForm {
email: string;
firstName: string;
lastName: string;
language: string;
darkTheme: boolean;
}
@Component({ @Component({
templateUrl: './user-profile-screen.component.html', templateUrl: './user-profile-screen.component.html',
styleUrls: ['./user-profile-screen.component.scss'], styleUrls: ['./user-profile-screen.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
ReactiveFormsModule,
MatFormField,
MatSelect,
MatOption,
TranslateModule,
MatSlideToggle,
IconButtonComponent,
MatSelectTrigger,
],
}) })
export class UserProfileScreenComponent extends BaseFormComponent { export class UserProfileScreenComponent extends BaseFormComponent implements OnInit {
readonly form: FormGroup<AsControl<UserProfileForm>> = this.#getForm(); #profileModel: IProfile;
initialFormValue = this.form.getRawValue();
readonly translations = languagesTranslations; readonly translations = languagesTranslations;
readonly devMode = this._userPreferenceService.isIqserDevMode; readonly devMode = this._userPreferenceService.isIqserDevMode;
readonly profileKeys = ['email', 'firstName', 'lastName'];
readonly languages = this._translateService.langs;
readonly language = formControlToSignal<UserProfileForm['language']>(this.form.controls.language);
readonly languageSelectLabel = computed(() => this.translations[this.language()]);
constructor( constructor(
private readonly _userService: UserService, private readonly _userService: UserService,
private readonly _loadingService: LoadingService, private readonly _loadingService: LoadingService,
@ -69,29 +32,43 @@ export class UserProfileScreenComponent extends BaseFormComponent {
protected readonly _userPreferenceService: UserPreferenceService, protected readonly _userPreferenceService: UserPreferenceService,
private readonly _changeRef: ChangeDetectorRef, private readonly _changeRef: ChangeDetectorRef,
private readonly _toaster: Toaster, private readonly _toaster: Toaster,
private readonly _pdfViewer: PdfViewer,
) { ) {
super(); super();
if (!this._permissionsService.has(Roles.updateMyProfile)) { this._loadingService.start();
this.form.disable();
}
this._loadingService.stop();
} }
get languageChanged(): boolean { get languageChanged(): boolean {
return this.initialFormValue['language'] !== this.form.controls.language.value; return this.#profileModel['language'] !== this.form.get('language').value;
} }
get themeChanged(): boolean { get themeChanged(): boolean {
return this.initialFormValue['darkTheme'] !== this.form.controls.darkTheme.value; return this.#profileModel['darkTheme'] !== this.form.get('darkTheme').value;
} }
get emailChanged(): boolean { get emailChanged(): boolean {
return this.initialFormValue['email'] !== this.form.controls.email.value; return this.#profileModel['email'] !== this.form.get('email').value;
} }
get profileChanged(): boolean { get profileChanged(): boolean {
return this.profileKeys.some(key => this.initialFormValue[key] !== this.form.get(key).value); const keys = Object.keys(this.form.getRawValue());
keys.splice(keys.indexOf('language'), 1);
keys.splice(keys.indexOf('darkTheme'), 1);
for (const key of keys) {
if (this.#profileModel[key] !== this.form.get(key).value) {
return true;
}
}
return false;
}
get languages(): string[] {
return this._translateService.langs;
}
ngOnInit() {
this._initializeForm();
} }
async save(): Promise<void> { async save(): Promise<void> {
@ -116,17 +93,15 @@ export class UserProfileScreenComponent extends BaseFormComponent {
} }
if (this.languageChanged) { if (this.languageChanged) {
await this._languageService.change(this.form.controls.language.value); await this._languageService.change(this.form.get('language').value);
await this._pdfViewer.instance?.UI.setLanguage(this._languageService.currentLanguage);
} }
if (this.themeChanged) { if (this.themeChanged) {
await this._userPreferenceService.saveTheme(this.form.controls.darkTheme.value ? 'dark' : 'light'); await this._userPreferenceService.saveTheme(this.form.get('darkTheme').value ? 'dark' : 'light');
} }
this.initialFormValue = this.form.getRawValue(); this._initializeForm();
this._changeRef.markForCheck();
this._loadingService.stop();
this._toaster.success(_('user-profile-screen.update.success')); this._toaster.success(_('user-profile-screen.update.success'));
} catch (e) { } catch (e) {
this._loadingService.stop(); this._loadingService.stop();
@ -137,13 +112,35 @@ export class UserProfileScreenComponent extends BaseFormComponent {
await this._userService.createResetPasswordAction(); await this._userService.createResetPasswordAction();
} }
#getForm() { private _getForm(): UntypedFormGroup {
return this._formBuilder.group({ return this._formBuilder.group({
email: [this._userService.currentUser.email ?? '', [Validators.required, Validators.email]], email: ['', [Validators.required, Validators.email]],
firstName: [this._userService.currentUser.firstName ?? ''], firstName: [''],
lastName: [this._userService.currentUser.lastName ?? ''], lastName: [''],
language: [this._userPreferenceService.getLanguage()], language: [''],
darkTheme: [this._userPreferenceService.getTheme() === 'dark'], darkTheme: [false],
}); });
} }
private _initializeForm(): void {
try {
this.form = this._getForm();
if (!this._permissionsService.has(Roles.updateMyProfile)) {
this.form.disable();
}
this.#profileModel = {
email: this._userService.currentUser.email ?? '',
firstName: this._userService.currentUser.firstName ?? '',
lastName: this._userService.currentUser.lastName ?? '',
language: this._languageService.currentLanguage ?? '',
darkTheme: this._userPreferenceService.getTheme() === 'dark',
};
this.form.patchValue(this.#profileModel, { emitEvent: false });
this.initialFormValue = this.form.getRawValue();
} catch (e) {
} finally {
this._loadingService.stop();
this._changeRef.markForCheck();
}
}
} }

View File

@ -0,0 +1,19 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { CommonModule } from '@angular/common';
import { SharedModule } from '@shared/shared.module';
import { UserProfileScreenComponent } from './user-profile-screen/user-profile-screen.component';
import { PendingChangesGuard } from '@guards/can-deactivate.guard';
import { TranslateModule } from '@ngx-translate/core';
import { ConfirmPasswordDialogComponent } from './confirm-password-dialog/confirm-password-dialog.component';
import { UserProfileDialogService } from './services/user-profile-dialog.service';
import { CircleButtonComponent, IconButtonComponent } from '@iqser/common-ui';
const routes = [{ path: '', component: UserProfileScreenComponent, canDeactivate: [PendingChangesGuard] }];
@NgModule({
declarations: [UserProfileScreenComponent, ConfirmPasswordDialogComponent],
imports: [RouterModule.forChild(routes), CommonModule, SharedModule, TranslateModule, IconButtonComponent, CircleButtonComponent],
providers: [UserProfileDialogService],
})
export class UserProfileModule {}

View File

@ -1,8 +0,0 @@
import { UserProfileScreenComponent } from './user-profile-screen/user-profile-screen.component';
import { PendingChangesGuard } from '@guards/can-deactivate.guard';
import { UserProfileDialogService } from './services/user-profile-dialog.service';
import { IqserRoutes } from '@iqser/common-ui';
export default [
{ path: '', component: UserProfileScreenComponent, canDeactivate: [PendingChangesGuard], providers: [UserProfileDialogService] },
] satisfies IqserRoutes;

View File

@ -1,19 +1,12 @@
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { addHintTranslations } from '@translations/add-hint-translations';
import { redactTextTranslations } from '@translations/redact-text-translations'; import { redactTextTranslations } from '@translations/redact-text-translations';
import { RedactOrHintOptions, RemoveRedactionOptions } from '../../file-preview/utils/dialog-options';
import { removeRedactionTranslations } from '@translations/remove-redaction-translations'; import { removeRedactionTranslations } from '@translations/remove-redaction-translations';
import { import { addHintTranslations } from '@translations/add-hint-translations';
ForceAnnotationOptions, import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
RectangleRedactOptions,
RedactOrHintOptions,
RemoveRedactionOptions,
} from '../../file-preview/utils/dialog-types';
export const SystemDefaults = { export const SystemDefaults = {
RECTANGLE_REDACT_DEFAULT: RectangleRedactOptions.ONLY_THIS_PAGE,
ADD_REDACTION_DEFAULT: RedactOrHintOptions.IN_DOSSIER, ADD_REDACTION_DEFAULT: RedactOrHintOptions.IN_DOSSIER,
ADD_HINT_DEFAULT: RedactOrHintOptions.IN_DOSSIER, ADD_HINT_DEFAULT: RedactOrHintOptions.IN_DOSSIER,
FORCE_REDACTION_DEFAULT: ForceAnnotationOptions.ONLY_HERE,
REMOVE_REDACTION_DEFAULT: RemoveRedactionOptions.ONLY_HERE, REMOVE_REDACTION_DEFAULT: RemoveRedactionOptions.ONLY_HERE,
REMOVE_HINT_DEFAULT: RemoveRedactionOptions.ONLY_HERE, REMOVE_HINT_DEFAULT: RemoveRedactionOptions.ONLY_HERE,
REMOVE_RECOMMENDATION_DEFAULT: RemoveRedactionOptions.DO_NOT_RECOMMEND, REMOVE_RECOMMENDATION_DEFAULT: RemoveRedactionOptions.DO_NOT_RECOMMEND,
@ -35,10 +28,6 @@ export const redactionAddOptions = [
label: redactTextTranslations.onlyHere.label, label: redactTextTranslations.onlyHere.label,
value: RedactOrHintOptions.ONLY_HERE, value: RedactOrHintOptions.ONLY_HERE,
}, },
{
label: redactTextTranslations.inDocument.label,
value: RedactOrHintOptions.IN_DOCUMENT,
},
{ {
label: redactTextTranslations.inDossier.label, label: redactTextTranslations.inDossier.label,
value: RedactOrHintOptions.IN_DOSSIER, value: RedactOrHintOptions.IN_DOSSIER,

View File

@ -0,0 +1,291 @@
import { NgModule } from '@angular/core';
import { CompositeRouteGuard, IqserPermissionsGuard, IqserRoutes } from '@iqser/common-ui';
import { RedRoleGuard } from '@users/red-role.guard';
import { EntitiesListingScreenComponent } from './screens/entities-listing/entities-listing-screen.component';
import { PendingChangesGuard } from '@guards/can-deactivate.guard';
import { DefaultColorsScreenComponent } from './screens/default-colors/default-colors-screen.component';
import { UserListingScreenComponent } from './screens/user-listing/user-listing-screen.component';
import { DigitalSignatureScreenComponent } from './screens/digital-signature/digital-signature-screen.component';
import { AuditScreenComponent } from './screens/audit/audit-screen.component';
import { RouterModule } from '@angular/router';
import { GeneralConfigScreenComponent } from './screens/general-config/general-config-screen.component';
import { BaseAdminScreenComponent } from './base-admin-screen/base-admin-screen.component';
import { BaseDossierTemplateScreenComponent } from './base-dossier-templates-screen/base-dossier-template-screen.component';
import { DossierTemplatesGuard } from '@guards/dossier-templates.guard';
import { DOSSIER_TEMPLATE_ID, ENTITY_TYPE } from '@red/domain';
import { templateExistsWhenEnteringAdmin } from '@guards/dossier-template-exists.guard';
import { entityExistsGuard } from '@guards/entity-exists-guard.service';
import { BaseEntityScreenComponent } from './base-entity-screen/base-entity-screen.component';
import { PermissionsGuard } from '@guards/permissions-guard';
import { Roles } from '@users/roles';
import { IqserAuthGuard } from '@iqser/common-ui/lib/users';
import { ComponentMappingsScreenComponent } from './screens/component-mappings/component-mappings-screen.component';
const dossierTemplateIdRoutes: IqserRoutes = [
{
path: 'info',
component: BaseDossierTemplateScreenComponent,
loadChildren: () => import('./screens/info/dossier-template-info.module').then(m => m.DossierTemplateInfoModule),
},
{
path: 'entities',
children: [
{
path: '',
component: EntitiesListingScreenComponent,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [IqserAuthGuard, RedRoleGuard],
},
},
{
path: `:${ENTITY_TYPE}`,
component: BaseEntityScreenComponent,
canActivate: [CompositeRouteGuard, entityExistsGuard()],
loadChildren: () => import('./screens/entities/entities.module').then(m => m.EntitiesModule),
data: {
routeGuards: [IqserAuthGuard, RedRoleGuard],
},
},
],
},
{
path: 'entity-rules',
component: BaseDossierTemplateScreenComponent,
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
data: {
routeGuards: [IqserAuthGuard, RedRoleGuard],
permissions: {
allow: [Roles.rules.read],
redirectTo: 'info',
},
type: 'ENTITY',
},
loadChildren: () => import('./screens/rules/rules.module').then(m => m.RulesModule),
},
{
path: 'component-rules',
component: BaseDossierTemplateScreenComponent,
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
data: {
routeGuards: [IqserAuthGuard, RedRoleGuard],
permissions: {
allow: [Roles.rules.read],
redirectTo: 'info',
},
type: 'COMPONENT',
},
loadChildren: () => import('./screens/rules/rules.module').then(m => m.RulesModule),
},
{
path: 'component-mappings',
component: ComponentMappingsScreenComponent,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [IqserAuthGuard, RedRoleGuard],
},
},
{
path: 'file-attributes',
component: BaseDossierTemplateScreenComponent,
loadChildren: () =>
import('./screens/file-attributes-listing/file-attributes-listing.module').then(m => m.FileAttributesListingModule),
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [IqserAuthGuard, RedRoleGuard],
},
},
{
path: 'watermarks',
component: BaseDossierTemplateScreenComponent,
loadChildren: () => import('./screens/watermark/watermark.module').then(m => m.WatermarkModule),
},
{
path: 'reports',
component: BaseDossierTemplateScreenComponent,
loadChildren: () => import('./screens/reports/reports.module').then(m => m.ReportsModule),
canActivate: [IqserAuthGuard, IqserPermissionsGuard],
data: {
permissions: {
allow: [Roles.reportTemplates.read],
redirectTo: '/auth-error',
},
},
},
{
path: 'dossier-attributes',
component: BaseDossierTemplateScreenComponent,
loadChildren: () =>
import('./screens/dossier-attributes-listing/dossier-attributes-listing.module').then(m => m.DossierAttributesListingModule),
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [IqserAuthGuard, RedRoleGuard],
},
},
{
path: 'dossier-states',
component: BaseDossierTemplateScreenComponent,
loadChildren: () =>
import('./screens/dossier-states-listing/dossier-states-listing.module').then(m => m.DossierStatesListingModule),
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [IqserAuthGuard, RedRoleGuard],
},
},
{
path: 'default-colors',
component: DefaultColorsScreenComponent,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [IqserAuthGuard, RedRoleGuard],
},
},
{
path: 'justifications',
component: BaseDossierTemplateScreenComponent,
loadChildren: () => import('./screens/justifications/justifications.module').then(m => m.JustificationsModule),
canActivate: [IqserPermissionsGuard],
data: {
permissions: {
allow: [Roles.legalBasis.read],
redirectTo: '/auth-error',
},
},
},
{ path: '', redirectTo: 'info', pathMatch: 'full' },
];
const dossierTemplatesRoutes: IqserRoutes = [
{
path: '',
component: BaseAdminScreenComponent,
canActivate: [IqserAuthGuard],
loadChildren: () =>
import('./screens/dossier-templates-listing/dossier-templates-listing.module').then(m => m.DossierTemplatesListingModule),
},
{
path: `:${DOSSIER_TEMPLATE_ID}`,
children: dossierTemplateIdRoutes,
canActivate: [templateExistsWhenEnteringAdmin()],
},
];
const routes: IqserRoutes = [
{ path: '', redirectTo: 'dossier-templates', pathMatch: 'full' },
{
path: 'dossier-templates',
children: dossierTemplatesRoutes,
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
data: {
routeGuards: [IqserAuthGuard, RedRoleGuard, DossierTemplatesGuard],
requiredRoles: ['RED_MANAGER', 'RED_ADMIN'],
permissions: {
allow: [Roles.templates.read],
redirectTo: '/',
},
},
},
{
path: 'users',
component: BaseAdminScreenComponent,
children: [
{
path: '',
component: UserListingScreenComponent,
},
],
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
data: {
routeGuards: [IqserAuthGuard, RedRoleGuard],
permissions: {
allow: [Roles.users.read, 'RED_USER_ADMIN'],
redirectTo: '/',
},
},
},
{
path: 'dossier-permissions',
component: BaseAdminScreenComponent,
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
data: {
routeGuards: [IqserAuthGuard, RedRoleGuard, PermissionsGuard],
permissionsObject: 'Dossier',
permissions: {
allow: [Roles.manageAclPermissions, 'RED_ADMIN'],
redirectTo: '/',
},
},
loadChildren: () => import('./screens/permissions/permissions.module').then(m => m.PermissionsModule),
},
{
path: 'license-info',
component: BaseAdminScreenComponent,
canActivate: [IqserAuthGuard, IqserPermissionsGuard, RedRoleGuard],
data: {
permissions: {
allow: [Roles.license.readReport, 'RED_ADMIN'],
redirectTo: '/',
},
},
loadChildren: () => import('./screens/license/license.module').then(m => m.LicenseModule),
},
{
path: 'digital-signature',
component: BaseAdminScreenComponent,
children: [
{
path: '',
component: DigitalSignatureScreenComponent,
},
],
canActivate: [IqserAuthGuard, IqserPermissionsGuard, RedRoleGuard],
data: {
permissions: {
allow: [Roles.digitalSignature.read, 'RED_ADMIN'],
redirectTo: '/',
},
},
},
{
path: 'audit',
component: BaseAdminScreenComponent,
children: [
{
path: '',
component: AuditScreenComponent,
},
],
canActivate: [IqserAuthGuard, IqserPermissionsGuard, RedRoleGuard],
data: {
permissions: {
allow: [Roles.searchAudit, 'RED_ADMIN'],
redirectTo: '/',
},
},
},
{
path: 'general-config',
component: BaseAdminScreenComponent,
children: [
{
path: '',
component: GeneralConfigScreenComponent,
},
],
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
canDeactivate: [PendingChangesGuard],
data: {
routeGuards: [IqserAuthGuard, RedRoleGuard],
permissions: {
allow: [Roles.generalConfiguration.read, Roles.smtp.read, 'RED_ADMIN'],
redirectTo: '/',
},
},
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class AdminRoutingModule {}

View File

@ -0,0 +1,132 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { AdminRoutingModule } from './admin-routing.module';
import { SharedModule } from '@shared/shared.module';
import { AuditScreenComponent } from './screens/audit/audit-screen.component';
import { DefaultColorsScreenComponent } from './screens/default-colors/default-colors-screen.component';
import { EntitiesListingScreenComponent } from './screens/entities-listing/entities-listing-screen.component';
import { DigitalSignatureScreenComponent } from './screens/digital-signature/digital-signature-screen.component';
import { UserListingScreenComponent } from './screens/user-listing/user-listing-screen.component';
import { DossierTemplateBreadcrumbsComponent } from './shared/components/dossier-template-breadcrumbs/dossier-template-breadcrumbs.component';
import { AddEntityDialogComponent } from './dialogs/add-entity-dialog/add-entity-dialog.component';
import { EditColorDialogComponent } from './dialogs/edit-color-dialog/edit-color-dialog.component';
import { AdminDialogService } from './services/admin-dialog.service';
import { GeneralConfigScreenComponent } from './screens/general-config/general-config-screen.component';
import { SmtpAuthDialogComponent } from './dialogs/smtp-auth-dialog/smtp-auth-dialog.component';
import { AddEditUserDialogComponent } from './dialogs/add-edit-user-dialog/add-edit-user-dialog.component';
import { UsersStatsComponent } from './components/users-stats/users-stats.component';
import { ResetPasswordComponent } from './dialogs/add-edit-user-dialog/reset-password/reset-password.component';
import { UserDetailsComponent } from './dialogs/add-edit-user-dialog/user-details/user-details.component';
import { AuditService } from './services/audit.service';
import { DigitalSignatureService } from './services/digital-signature.service';
import { BaseAdminScreenComponent } from './base-admin-screen/base-admin-screen.component';
import { RulesService } from './services/rules.service';
import { SmtpConfigService } from './services/smtp-config.service';
import { UploadDictionaryDialogComponent } from './dialogs/upload-dictionary-dialog/upload-dictionary-dialog.component';
import { GeneralConfigFormComponent } from './screens/general-config/general-config-form/general-config-form.component';
import { SmtpFormComponent } from './screens/general-config/smtp-form/smtp-form.component';
import { BaseDossierTemplateScreenComponent } from './base-dossier-templates-screen/base-dossier-template-screen.component';
import { A11yModule } from '@angular/cdk/a11y';
import { BaseEntityScreenComponent } from './base-entity-screen/base-entity-screen.component';
import { AdminSideNavComponent } from './shared/components/admin-side-nav/admin-side-nav.component';
import { SystemPreferencesFormComponent } from './screens/general-config/system-preferences-form/system-preferences-form.component';
import { ConfigureCertificateDialogComponent } from './dialogs/configure-digital-signature-dialog/configure-certificate-dialog.component';
import { PkcsSignatureConfigurationComponent } from './dialogs/configure-digital-signature-dialog/form/pkcs-signature-configuration/pkcs-signature-configuration.component';
import { KmsSignatureConfigurationComponent } from './dialogs/configure-digital-signature-dialog/form/kms-signature-configuration/kms-signature-configuration.component';
import {
ChevronButtonComponent,
CircleButtonComponent,
DetailsRadioComponent,
EditableInputComponent,
EmptyStateComponent,
HasScrollbarDirective,
HumanizePipe,
IconButtonComponent,
InputWithActionComponent,
IqserAllowDirective,
IqserDenyDirective,
IqserHelpModeModule,
IqserListingModule,
IqserUploadFileModule,
RoundCheckboxComponent,
} from '@iqser/common-ui';
import { TranslateModule } from '@ngx-translate/core';
import { AuditInfoDialogComponent } from './dialogs/audit-info-dialog/audit-info-dialog.component';
import { DossierTemplateActionsComponent } from './shared/components/dossier-template-actions/dossier-template-actions.component';
import { IqserUsersModule } from '@iqser/common-ui/lib/users';
import { SelectComponent } from '@shared/components/select/select.component';
import { PaginationComponent } from '@common-ui/pagination/pagination.component';
import { AddCloneDossierTemplateDialogComponent } from './dialogs/add-clone-dossier-template-dialog/add-clone-dossier-template-dialog.component';
import { ComponentMappingsScreenComponent } from './screens/component-mappings/component-mappings-screen.component';
const dialogs = [
AddCloneDossierTemplateDialogComponent,
AddEntityDialogComponent,
EditColorDialogComponent,
SmtpAuthDialogComponent,
AddEditUserDialogComponent,
UploadDictionaryDialogComponent,
ConfigureCertificateDialogComponent,
AuditInfoDialogComponent,
];
const screens = [
AuditScreenComponent,
DefaultColorsScreenComponent,
EntitiesListingScreenComponent,
DigitalSignatureScreenComponent,
UserListingScreenComponent,
GeneralConfigScreenComponent,
ComponentMappingsScreenComponent,
];
const components = [
UsersStatsComponent,
ResetPasswordComponent,
UserDetailsComponent,
BaseAdminScreenComponent,
BaseDossierTemplateScreenComponent,
BaseEntityScreenComponent,
GeneralConfigFormComponent,
SmtpFormComponent,
SystemPreferencesFormComponent,
PkcsSignatureConfigurationComponent,
KmsSignatureConfigurationComponent,
...dialogs,
...screens,
];
@NgModule({
declarations: [...components],
providers: [AdminDialogService, AuditService, DigitalSignatureService, RulesService, SmtpConfigService],
imports: [
CommonModule,
SharedModule,
AdminRoutingModule,
A11yModule,
IqserUsersModule,
TranslateModule,
HumanizePipe,
IqserListingModule,
IqserUploadFileModule,
IqserHelpModeModule,
AdminSideNavComponent,
DossierTemplateActionsComponent,
DossierTemplateBreadcrumbsComponent,
IconButtonComponent,
CircleButtonComponent,
ChevronButtonComponent,
EmptyStateComponent,
HasScrollbarDirective,
RoundCheckboxComponent,
InputWithActionComponent,
EditableInputComponent,
DetailsRadioComponent,
IqserAllowDirective,
IqserDenyDirective,
SelectComponent,
PaginationComponent,
],
})
export class AdminModule {}

View File

@ -1,302 +0,0 @@
import { inject, provideEnvironmentInitializer } from '@angular/core';
import { PendingChangesGuard } from '@guards/can-deactivate.guard';
import { templateExistsWhenEnteringAdmin } from '@guards/dossier-template-exists.guard';
import { DossierTemplatesGuard } from '@guards/dossier-templates.guard';
import { entityExistsGuard } from '@guards/entity-exists-guard.service';
import { PermissionsGuard } from '@guards/permissions-guard';
import { CompositeRouteGuard, IqserPermissionsGuard, IqserRoutes } from '@iqser/common-ui';
import { IqserAuthGuard } from '@iqser/common-ui/lib/users';
import { DOSSIER_TEMPLATE_ID, ENTITY_TYPE } from '@red/domain';
import { CopilotService } from '@services/copilot.service';
import { RedRoleGuard } from '@users/red-role.guard';
import { Roles } from '@users/roles';
import { BaseAdminScreenComponent } from './base-admin-screen/base-admin-screen.component';
import { BaseDossierTemplateScreenComponent } from './base-dossier-templates-screen/base-dossier-template-screen.component';
import { BaseEntityScreenComponent } from './base-entity-screen/base-entity-screen.component';
import { AuditScreenComponent } from './screens/audit/audit-screen.component';
import { DefaultColorsScreenComponent } from './screens/default-colors/default-colors-screen.component';
import { DigitalSignatureScreenComponent } from './screens/digital-signature/digital-signature-screen.component';
import { EntitiesListingScreenComponent } from './screens/entities-listing/entities-listing-screen.component';
import { GeneralConfigScreenComponent } from './screens/general-config/general-config-screen.component';
import { UserListingScreenComponent } from './screens/user-listing/user-listing-screen.component';
import { AdminDialogService } from './services/admin-dialog.service';
import { AuditService } from './services/audit.service';
import { DigitalSignatureService } from './services/digital-signature.service';
import { RulesService } from './services/rules.service';
import { SmtpConfigService } from './services/smtp-config.service';
const entityRoutes: IqserRoutes = [
{
path: '',
component: BaseDossierTemplateScreenComponent,
children: [{ path: '', component: EntitiesListingScreenComponent }],
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [IqserAuthGuard, RedRoleGuard],
},
},
{
path: `:${ENTITY_TYPE}`,
component: BaseEntityScreenComponent,
canActivate: [CompositeRouteGuard, entityExistsGuard()],
data: {
routeGuards: [IqserAuthGuard, RedRoleGuard],
},
loadChildren: () => import('./screens/entities/entities.routes'),
},
];
const dossierTemplateIdRoutes: IqserRoutes = [
{ path: '', redirectTo: 'info', pathMatch: 'full' },
{
path: 'info',
component: BaseDossierTemplateScreenComponent,
children: [
{
path: '',
canDeactivate: [PendingChangesGuard],
loadComponent: () => import('./screens/info/dossier-template-info-screen/dossier-template-info-screen.component'),
},
],
},
{
path: 'entities',
children: entityRoutes,
},
{
path: '',
component: BaseDossierTemplateScreenComponent,
children: [
{
path: 'entity-rules',
loadComponent: () => import('./screens/rules/rules-screen/rules-screen.component'),
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
canDeactivate: [PendingChangesGuard],
data: {
routeGuards: [IqserAuthGuard, RedRoleGuard],
permissions: {
allow: [Roles.rules.read],
redirectTo: 'info',
},
type: 'ENTITY',
},
providers: [
RulesService,
provideEnvironmentInitializer(() => {
return inject(CopilotService).connectAsync('/api/llm/llm-websocket');
}),
],
},
{
path: 'component-rules',
loadComponent: () => import('./screens/rules/rules-screen/rules-screen.component'),
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
canDeactivate: [PendingChangesGuard],
data: {
routeGuards: [IqserAuthGuard, RedRoleGuard],
permissions: {
allow: [Roles.rules.read],
redirectTo: 'info',
},
type: 'COMPONENT',
},
providers: [RulesService],
},
{
path: 'component-mappings',
loadComponent: () => import('./screens/component-mappings/component-mappings-screen.component'),
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [IqserAuthGuard, RedRoleGuard],
},
},
{
path: 'components',
loadComponent: () => import('./screens/component-definitions/component-definitions.component'),
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [IqserAuthGuard, RedRoleGuard],
},
},
{
path: 'file-attributes',
loadComponent: () => import('./screens/file-attributes-listing/file-attributes-listing-screen.component'),
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [IqserAuthGuard, RedRoleGuard],
},
},
{
path: 'watermarks',
loadChildren: () => import('./screens/watermark/watermark.routes'),
},
{
path: 'reports',
loadComponent: () => import('./screens/reports/reports-screen/reports-screen.component'),
canActivate: [IqserAuthGuard, IqserPermissionsGuard],
data: {
permissions: {
allow: [Roles.reportTemplates.read],
redirectTo: '/auth-error',
},
},
},
{
path: 'dossier-attributes',
loadComponent: () => import('./screens/dossier-attributes-listing/dossier-attributes-listing-screen.component'),
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [IqserAuthGuard, RedRoleGuard],
},
},
{
path: 'dossier-states',
loadComponent: () =>
import('./screens/dossier-states-listing/dossier-states-listing-screen/dossier-states-listing-screen.component'),
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [IqserAuthGuard, RedRoleGuard],
},
},
{
path: 'default-colors',
component: DefaultColorsScreenComponent,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [IqserAuthGuard, RedRoleGuard],
},
},
{
path: 'justifications',
loadComponent: () => import('./screens/justifications/justifications-screen/justifications-screen.component'),
canActivate: [IqserPermissionsGuard],
data: {
permissions: {
allow: [Roles.legalBasis.read],
redirectTo: '/auth-error',
},
},
},
],
},
];
const dossierTemplatesRoutes: IqserRoutes = [
{
path: '',
component: BaseAdminScreenComponent,
children: [
{
path: '',
loadComponent: () =>
import(
'./screens/dossier-templates-listing/dossier-templates-listing-screen/dossier-templates-listing-screen.component'
),
},
],
canActivate: [IqserAuthGuard],
},
{
path: `:${DOSSIER_TEMPLATE_ID}`,
children: dossierTemplateIdRoutes,
canActivate: [templateExistsWhenEnteringAdmin()],
},
];
export default [
{ path: '', redirectTo: 'dossier-templates', pathMatch: 'full' },
{
path: 'dossier-templates',
children: dossierTemplatesRoutes,
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
data: {
routeGuards: [IqserAuthGuard, RedRoleGuard, DossierTemplatesGuard],
requiredRoles: ['RED_MANAGER', 'RED_ADMIN'],
permissions: {
allow: [Roles.templates.read],
redirectTo: '/',
},
},
},
{
path: '',
component: BaseAdminScreenComponent,
providers: [AdminDialogService],
children: [
{
path: 'users',
component: UserListingScreenComponent,
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
data: {
routeGuards: [IqserAuthGuard, RedRoleGuard],
permissions: {
allow: [Roles.users.read, 'RED_USER_ADMIN'],
redirectTo: '/',
},
},
},
{
path: 'dossier-permissions',
loadComponent: () => import('./screens/permissions/permissions-screen/permissions-screen.component'),
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
data: {
routeGuards: [IqserAuthGuard, RedRoleGuard, PermissionsGuard],
permissionsObject: 'Dossier',
permissions: {
allow: [Roles.manageAclPermissions, 'RED_ADMIN'],
redirectTo: '/',
},
},
},
{
path: 'license-info',
loadChildren: () => import('./screens/license/license.routes'),
canActivate: [IqserAuthGuard, IqserPermissionsGuard, RedRoleGuard],
data: {
permissions: {
allow: [Roles.license.readReport, 'RED_ADMIN'],
redirectTo: '/',
},
},
},
{
path: 'digital-signature',
component: DigitalSignatureScreenComponent,
canActivate: [IqserAuthGuard, IqserPermissionsGuard, RedRoleGuard],
data: {
permissions: {
allow: [Roles.digitalSignature.read, 'RED_ADMIN'],
redirectTo: '/',
},
},
providers: [DigitalSignatureService],
},
{
path: 'audit',
component: AuditScreenComponent,
canActivate: [IqserAuthGuard, IqserPermissionsGuard, RedRoleGuard],
data: {
permissions: {
allow: [Roles.searchAudit, 'RED_ADMIN'],
redirectTo: '/',
},
},
providers: [AuditService],
},
{
path: 'general-config',
component: GeneralConfigScreenComponent,
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
canDeactivate: [PendingChangesGuard],
data: {
routeGuards: [IqserAuthGuard, RedRoleGuard],
permissions: {
allow: [Roles.generalConfiguration.read, Roles.smtp.read, 'RED_ADMIN'],
redirectTo: '/',
},
},
providers: [SmtpConfigService],
},
],
},
] satisfies IqserRoutes;

View File

@ -1,10 +1,7 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { AdminSideNavComponent } from '../shared/components/admin-side-nav/admin-side-nav.component';
import { RouterOutlet } from '@angular/router';
@Component({ @Component({
templateUrl: './base-admin-screen.component.html', templateUrl: './base-admin-screen.component.html',
styleUrls: ['./base-admin-screen.component.scss'], styleUrls: ['./base-admin-screen.component.scss'],
imports: [AdminSideNavComponent, RouterOutlet],
}) })
export class BaseAdminScreenComponent {} export class BaseAdminScreenComponent {}

View File

@ -1,3 +1,5 @@
<!--TODO: Use this for all dossier template screens -->
<section> <section>
<div class="page-header"> <div class="page-header">
<redaction-dossier-template-breadcrumbs class="flex-1"></redaction-dossier-template-breadcrumbs> <redaction-dossier-template-breadcrumbs class="flex-1"></redaction-dossier-template-breadcrumbs>

View File

@ -1,22 +1,7 @@
import { ChangeDetectionStrategy, Component } from '@angular/core'; import { ChangeDetectionStrategy, Component } from '@angular/core';
import { DossierTemplateBreadcrumbsComponent } from '../shared/components/dossier-template-breadcrumbs/dossier-template-breadcrumbs.component';
import { DossierTemplateActionsComponent } from '../shared/components/dossier-template-actions/dossier-template-actions.component';
import { CircleButtonComponent } from '@iqser/common-ui';
import { RouterLink, RouterOutlet } from '@angular/router';
import { TranslateModule } from '@ngx-translate/core';
import { AdminSideNavComponent } from '../shared/components/admin-side-nav/admin-side-nav.component';
@Component({ @Component({
templateUrl: './base-dossier-template-screen.component.html', templateUrl: './base-dossier-template-screen.component.html',
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
DossierTemplateBreadcrumbsComponent,
DossierTemplateActionsComponent,
CircleButtonComponent,
RouterLink,
TranslateModule,
AdminSideNavComponent,
RouterOutlet,
],
}) })
export class BaseDossierTemplateScreenComponent {} export class BaseDossierTemplateScreenComponent {}

View File

@ -1,33 +1,19 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { DOSSIER_TEMPLATE_ID, ENTITY_TYPE } from '@red/domain'; import { DOSSIER_TEMPLATE_ID, ENTITY_TYPE } from '@red/domain';
import { Router, RouterLink, RouterOutlet } from '@angular/router'; import { Router } from '@angular/router';
import { firstValueFrom, Observable } from 'rxjs'; import { firstValueFrom, Observable } from 'rxjs';
import { AdminDialogService } from '../services/admin-dialog.service'; import { AdminDialogService } from '../services/admin-dialog.service';
import { DictionaryService } from '@services/entity-services/dictionary.service'; import { DictionaryService } from '@services/entity-services/dictionary.service';
import { CircleButtonComponent, LoadingService } from '@iqser/common-ui'; import { LoadingService } from '@iqser/common-ui';
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service'; import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service'; import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { PermissionsService } from '@services/permissions.service'; import { PermissionsService } from '@services/permissions.service';
import { getParam } from '@iqser/common-ui/lib/utils'; import { getParam } from '@iqser/common-ui/lib/utils';
import { DossierTemplateBreadcrumbsComponent } from '../shared/components/dossier-template-breadcrumbs/dossier-template-breadcrumbs.component';
import { AsyncPipe, NgIf } from '@angular/common';
import { TranslateModule } from '@ngx-translate/core';
import { AdminSideNavComponent } from '../shared/components/admin-side-nav/admin-side-nav.component';
@Component({ @Component({
templateUrl: './base-entity-screen.component.html', templateUrl: './base-entity-screen.component.html',
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
DossierTemplateBreadcrumbsComponent,
CircleButtonComponent,
NgIf,
AsyncPipe,
TranslateModule,
RouterLink,
AdminSideNavComponent,
RouterOutlet,
],
}) })
export class BaseEntityScreenComponent implements OnInit { export class BaseEntityScreenComponent implements OnInit {
readonly disabledItems$: Observable<string[]>; readonly disabledItems$: Observable<string[]>;

View File

@ -23,7 +23,7 @@
<div class="mt-44"> <div class="mt-44">
<redaction-donut-chart <redaction-donut-chart
[config]="chartConfig()" [config]="chartConfig"
[radius]="63" [radius]="63"
[strokeWidth]="15" [strokeWidth]="15"
[subtitles]="['user-stats.chart.users' | translate]" [subtitles]="['user-stats.chart.users' | translate]"

View File

@ -1,16 +1,12 @@
import { Component, EventEmitter, input, Input, output, Output } from '@angular/core'; import { Component, EventEmitter, Input, Output } from '@angular/core';
import { DonutChartConfig } from '@red/domain'; import { DonutChartConfig } from '@red/domain';
import { CircleButtonComponent } from '@iqser/common-ui';
import { TranslateModule } from '@ngx-translate/core';
import { DonutChartComponent } from '@shared/components/donut-chart/donut-chart.component';
@Component({ @Component({
selector: 'redaction-users-stats', selector: 'redaction-users-stats',
templateUrl: './users-stats.component.html', templateUrl: './users-stats.component.html',
styleUrls: ['./users-stats.component.scss'], styleUrls: ['./users-stats.component.scss'],
imports: [CircleButtonComponent, TranslateModule, DonutChartComponent],
}) })
export class UsersStatsComponent { export class UsersStatsComponent {
readonly chartConfig = input.required<DonutChartConfig[]>(); @Output() toggleCollapse = new EventEmitter();
readonly toggleCollapse = output(); @Input() chartConfig: DonutChartConfig[];
} }

View File

@ -1,13 +1,12 @@
import { HttpStatusCode } from '@angular/common/http'; import { HttpStatusCode } from '@angular/common/http';
import { Component, Inject } from '@angular/core'; import { Component, Inject } from '@angular/core';
import { ReactiveFormsModule, Validators } from '@angular/forms'; import { Validators } from '@angular/forms';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { BaseDialogComponent, CircleButtonComponent, getConfig, IconButtonComponent, SaveOptions } from '@iqser/common-ui'; import { BaseDialogComponent, getConfig, SaveOptions } from '@iqser/common-ui';
import { DossierTemplate } from '@red/domain'; import { DossierTemplate } from '@red/domain';
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service'; import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { TranslateModule } from '@ngx-translate/core';
export interface CloneTemplateData { export interface CloneTemplateData {
dossierTemplateId?: string; dossierTemplateId?: string;
@ -16,7 +15,6 @@ export interface CloneTemplateData {
@Component({ @Component({
templateUrl: './add-clone-dossier-template-dialog.component.html', templateUrl: './add-clone-dossier-template-dialog.component.html',
styleUrls: ['./add-clone-dossier-template-dialog.component.scss'], styleUrls: ['./add-clone-dossier-template-dialog.component.scss'],
imports: [TranslateModule, ReactiveFormsModule, IconButtonComponent, CircleButtonComponent],
}) })
export class AddCloneDossierTemplateDialogComponent extends BaseDialogComponent { export class AddCloneDossierTemplateDialogComponent extends BaseDialogComponent {
readonly dossierTemplate?: DossierTemplate; readonly dossierTemplate?: DossierTemplate;

View File

@ -1,23 +1,18 @@
import { Component, Inject, ViewChild } from '@angular/core'; import { Component, Inject, ViewChild } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { BaseDialogComponent, CircleButtonComponent } from '@iqser/common-ui';
import { User } from '@red/domain'; import { User } from '@red/domain';
import { ResetPasswordComponent } from './reset-password/reset-password.component';
import { UserDetailsComponent } from './user-details/user-details.component'; import { UserDetailsComponent } from './user-details/user-details.component';
import { BaseDialogComponent } from '@iqser/common-ui';
@Component({ @Component({
selector: 'redaction-add-edit-user-dialog', selector: 'redaction-add-edit-user-dialog',
templateUrl: './add-edit-user-dialog.component.html', templateUrl: './add-edit-user-dialog.component.html',
imports: [UserDetailsComponent, ResetPasswordComponent, CircleButtonComponent],
}) })
export class AddEditUserDialogComponent extends BaseDialogComponent { export class AddEditUserDialogComponent extends BaseDialogComponent {
@ViewChild(UserDetailsComponent) private readonly _userDetailsComponent: UserDetailsComponent;
resettingPassword = false; resettingPassword = false;
@ViewChild(UserDetailsComponent) private readonly _userDetailsComponent: UserDetailsComponent;
constructor( constructor(protected readonly _dialogRef: MatDialogRef<AddEditUserDialogComponent>, @Inject(MAT_DIALOG_DATA) readonly user: User) {
protected readonly _dialogRef: MatDialogRef<AddEditUserDialogComponent>,
@Inject(MAT_DIALOG_DATA) readonly user: User,
) {
super(_dialogRef, !!user); super(_dialogRef, !!user);
} }

View File

@ -1,16 +1,13 @@
import { Component, EventEmitter, Input, Output } from '@angular/core'; import { Component, EventEmitter, Input, Output } from '@angular/core';
import { ReactiveFormsModule, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { UserService } from '@users/user.service'; import { UserService } from '@users/user.service';
import { IconButtonComponent, IconButtonTypes, LoadingService } from '@iqser/common-ui'; import { IconButtonTypes, LoadingService } from '@iqser/common-ui';
import { User } from '@red/domain'; import { User } from '@red/domain';
import { firstValueFrom } from 'rxjs'; import { firstValueFrom } from 'rxjs';
import { TranslateModule } from '@ngx-translate/core';
import { NamePipe } from '@common-ui/users/name.pipe';
@Component({ @Component({
selector: 'redaction-reset-password', selector: 'redaction-reset-password',
templateUrl: './reset-password.component.html', templateUrl: './reset-password.component.html',
imports: [TranslateModule, NamePipe, ReactiveFormsModule, IconButtonComponent],
}) })
export class ResetPasswordComponent { export class ResetPasswordComponent {
readonly iconButtonTypes = IconButtonTypes; readonly iconButtonTypes = IconButtonTypes;

View File

@ -1,6 +1,6 @@
<div <div
[translateParams]="{ [translateParams]="{
type: !!user() ? 'edit' : 'create', type: user ? 'edit' : 'create'
}" }"
[translate]="'add-edit-user.title'" [translate]="'add-edit-user.title'"
class="dialog-header heading-l" class="dialog-header heading-l"
@ -32,22 +32,14 @@
[formControlName]="role" [formControlName]="role"
color="primary" color="primary"
> >
{{ translations[role] | translate: { count: 1 } }} {{ translations[role] | translate }}
</mat-checkbox> </mat-checkbox>
</div> </div>
</div> </div>
@if (!user()) {
<div class="iqser-input-group">
<label [translate]="'add-edit-user.form.account-setup'"></label>
<mat-checkbox formControlName="sendSetPasswordMail">{{ 'add-edit-user.form.send-email' | translate }}</mat-checkbox>
<span [translate]="'add-edit-user.form.send-email-explanation'" class="hint"></span>
</div>
}
<div <div
(click)="toggleResetPassword.emit()" (click)="toggleResetPassword.emit()"
*ngIf="!!user()" *ngIf="!!user"
[translate]="'add-edit-user.form.reset-password'" [translate]="'add-edit-user.form.reset-password'"
class="mt-24 fit-content link-action" class="mt-24 fit-content link-action"
></div> ></div>
@ -56,14 +48,14 @@
<div class="dialog-actions"> <div class="dialog-actions">
<iqser-icon-button <iqser-icon-button
[disabled]="form.invalid || !changed" [disabled]="form.invalid || !changed"
[label]="(user() ? 'add-edit-user.actions.save-changes' : 'add-edit-user.actions.save') | translate" [label]="(user ? 'add-edit-user.actions.save-changes' : 'add-edit-user.actions.save') | translate"
[submit]="true" [submit]="true"
[type]="iconButtonTypes.primary" [type]="iconButtonTypes.primary"
></iqser-icon-button> ></iqser-icon-button>
<iqser-icon-button <iqser-icon-button
(action)="delete()" (action)="delete()"
*ngIf="user() && !disabledDelete(user())" *ngIf="user && !disabledDelete(user)"
[label]="'add-edit-user.actions.delete' | translate" [label]="'add-edit-user.actions.delete' | translate"
[type]="iconButtonTypes.dark" [type]="iconButtonTypes.dark"
icon="iqser:trash" icon="iqser:trash"

View File

@ -5,7 +5,3 @@
margin-top: 8px; margin-top: 8px;
width: 300px; width: 300px;
} }
.hint {
margin-left: 23px;
}

View File

@ -1,31 +1,29 @@
import { Component, input, OnInit, output } from '@angular/core'; import { Component, EventEmitter, Input, OnChanges, Output } from '@angular/core';
import { ReactiveFormsModule, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { AdminDialogService } from '../../../services/admin-dialog.service'; import { AdminDialogService } from '../../../services/admin-dialog.service';
import { BaseFormComponent, IconButtonComponent, LoadingService, Toaster } from '@iqser/common-ui'; import { BaseFormComponent, LoadingService, Toaster } from '@iqser/common-ui';
import { rolesTranslations } from '@translations/roles-translations'; import { rolesTranslations } from '@translations/roles-translations';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { User } from '@red/domain'; import { User } from '@red/domain';
import { UserService } from '@users/user.service'; import { UserService } from '@users/user.service';
import { HttpStatusCode } from '@angular/common/http';
import { firstValueFrom } from 'rxjs'; import { firstValueFrom } from 'rxjs';
import { IProfileUpdateRequest } from '@iqser/common-ui/lib/users'; import { IProfileUpdateRequest } from '@iqser/common-ui/lib/users';
import { TranslateModule } from '@ngx-translate/core';
import { MatCheckbox } from '@angular/material/checkbox';
import { NgForOf, NgIf } from '@angular/common';
@Component({ @Component({
selector: 'redaction-user-details', selector: 'redaction-user-details',
templateUrl: './user-details.component.html', templateUrl: './user-details.component.html',
styleUrls: ['./user-details.component.scss'], styleUrls: ['./user-details.component.scss'],
imports: [TranslateModule, ReactiveFormsModule, MatCheckbox, NgForOf, IconButtonComponent, NgIf],
}) })
export class UserDetailsComponent extends BaseFormComponent implements OnInit { export class UserDetailsComponent extends BaseFormComponent implements OnChanges {
user = input<User>(); /** e.g. a RED_ADMIN is automatically a RED_USER_ADMIN => can't disable RED_USER_ADMIN as long as RED_ADMIN is checked */
readonly toggleResetPassword = output(); private readonly _ROLE_REQUIREMENTS = { RED_MANAGER: 'RED_USER', RED_ADMIN: 'RED_USER_ADMIN' };
readonly closeDialog = output<boolean>(); @Input() user: User;
readonly cancel = output(); @Output() readonly toggleResetPassword = new EventEmitter();
@Output() readonly closeDialog = new EventEmitter();
@Output() readonly cancel = new EventEmitter();
readonly ROLES = ['RED_USER', 'RED_MANAGER', 'RED_USER_ADMIN', 'RED_ADMIN']; readonly ROLES = ['RED_USER', 'RED_MANAGER', 'RED_USER_ADMIN', 'RED_ADMIN'];
readonly translations = rolesTranslations; readonly translations = rolesTranslations;
/** e.g. a RED_ADMIN is automatically a RED_USER_ADMIN => can't disable RED_USER_ADMIN as long as RED_ADMIN is checked */
readonly #ROLE_REQUIREMENTS = { RED_MANAGER: 'RED_USER', RED_ADMIN: 'RED_USER_ADMIN' };
constructor( constructor(
private readonly _formBuilder: UntypedFormBuilder, private readonly _formBuilder: UntypedFormBuilder,
@ -46,17 +44,13 @@ export class UserDetailsComponent extends BaseFormComponent implements OnInit {
}, []); }, []);
} }
get sendSetPasswordMail() { private get _rolesControls(): any {
return !this.form.controls.sendSetPasswordMail.value;
}
get #rolesControls() {
return this.ROLES.reduce( return this.ROLES.reduce(
(prev, role) => ({ (prev, role) => ({
...prev, ...prev,
[role]: [ [role]: [
{ {
value: this.user() && this.user().has(role), value: this.user && this.user.has(role),
disabled: this.shouldBeDisabled(role), disabled: this.shouldBeDisabled(role),
}, },
], ],
@ -65,20 +59,20 @@ export class UserDetailsComponent extends BaseFormComponent implements OnInit {
); );
} }
ngOnInit() { ngOnChanges() {
this.form = this.#getForm(); this.form = this._getForm();
this.initialFormValue = this.form.getRawValue(); this.initialFormValue = this.form.getRawValue();
} }
shouldBeDisabled(role: string): boolean { shouldBeDisabled(role: string): boolean {
const isCurrentAdminUser = this.user() && this.user().isAdmin && this.user().id === this._userService.currentUser.id; const isCurrentAdminUser = this.user && this.user.isAdmin && this.user.id === this._userService.currentUser.id;
return ( return (
// RED_ADMIN can't remove own RED_ADMIN role // RED_ADMIN can't remove own RED_ADMIN role
(role === 'RED_ADMIN' && isCurrentAdminUser) || (role === 'RED_ADMIN' && isCurrentAdminUser) ||
// only RED_ADMINs can edit RED_ADMIN roles // only RED_ADMINs can edit RED_ADMIN roles
(role === 'RED_ADMIN' && !this._userService.currentUser.isAdmin) || (role === 'RED_ADMIN' && !this._userService.currentUser.isAdmin) ||
Object.keys(this.#ROLE_REQUIREMENTS).reduce( Object.keys(this._ROLE_REQUIREMENTS).reduce(
(value, key) => value || (role === this.#ROLE_REQUIREMENTS[key] && this.user()?.roles.includes(key)), (value, key) => value || (role === this._ROLE_REQUIREMENTS[key] && this.user?.roles.includes(key)),
false, false,
) )
); );
@ -86,38 +80,38 @@ export class UserDetailsComponent extends BaseFormComponent implements OnInit {
async save() { async save() {
this._loadingService.start(); this._loadingService.start();
const userData: IProfileUpdateRequest = { const userData: IProfileUpdateRequest = { ...this.form.getRawValue(), roles: this.activeRoles };
...this.form.getRawValue(),
roles: this.activeRoles,
sendSetPasswordMail: this.sendSetPasswordMail,
};
if (!this.user()) { if (!this.user) {
await firstValueFrom(this._userService.create(userData)) await firstValueFrom(this._userService.create(userData))
.then(() => { .then(() => {
this.closeDialog.emit(true); this.closeDialog.emit(true);
}) })
.catch(error => { .catch(error => {
this._toaster.error(null, { error }); if (error.status === HttpStatusCode.Conflict) {
this._toaster.error(_('add-edit-user.error.email-already-used'));
} else {
this._toaster.error(_('add-edit-user.error.generic'));
}
this._loadingService.stop(); this._loadingService.stop();
}); });
} else { } else {
await firstValueFrom(this._userService.updateProfile(userData, this.user().id)); await firstValueFrom(this._userService.updateProfile(userData, this.user.id));
this.closeDialog.emit(true); this.closeDialog.emit(true);
} }
} }
delete() { delete() {
this._dialogService.deleteUsers([this.user().id], () => this.closeDialog.emit(true)); this._dialogService.deleteUsers([this.user.id], () => this.closeDialog.emit(true));
} }
setRolesRequirements(checked: boolean, role: string): void { setRolesRequirements(checked: boolean, role: string): void {
if (Object.keys(this.#ROLE_REQUIREMENTS).includes(role)) { if (Object.keys(this._ROLE_REQUIREMENTS).includes(role)) {
if (checked) { if (checked) {
this.form.patchValue({ [this.#ROLE_REQUIREMENTS[role]]: true }); this.form.patchValue({ [this._ROLE_REQUIREMENTS[role]]: true });
this.form.controls[this.#ROLE_REQUIREMENTS[role]].disable(); this.form.controls[this._ROLE_REQUIREMENTS[role]].disable();
} else { } else {
this.form.controls[this.#ROLE_REQUIREMENTS[role]].enable(); this.form.controls[this._ROLE_REQUIREMENTS[role]].enable();
} }
} }
} }
@ -128,19 +122,18 @@ export class UserDetailsComponent extends BaseFormComponent implements OnInit {
return user.id === this._userService.currentUser.id || (userAdmin && !currentUserAdmin); return user.id === this._userService.currentUser.id || (userAdmin && !currentUserAdmin);
} }
#getForm(): UntypedFormGroup { private _getForm(): UntypedFormGroup {
return this._formBuilder.group({ return this._formBuilder.group({
firstName: [this.user()?.firstName, Validators.required], firstName: [this.user?.firstName, Validators.required],
lastName: [this.user()?.lastName, Validators.required], lastName: [this.user?.lastName, Validators.required],
email: [ email: [
{ {
value: this.user()?.email, value: this.user?.email,
disabled: !!this.user()?.email, disabled: !!this.user?.email,
}, },
[Validators.required, Validators.email], [Validators.required, Validators.email],
], ],
...this.#rolesControls, ...this._rolesControls,
sendSetPasswordMail: [false],
}); });
} }
} }

View File

@ -1,16 +1,7 @@
import { Component, inject, ViewChild } from '@angular/core'; import { Component, inject, ViewChild } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { import { BaseDialogComponent, getConfig, IconButtonTypes } from '@iqser/common-ui';
BaseDialogComponent,
CircleButtonComponent,
getConfig,
HelpButtonComponent,
IconButtonComponent,
IconButtonTypes,
} from '@iqser/common-ui';
import { AddEditEntityComponent } from '@shared/components/add-edit-entity/add-edit-entity.component'; import { AddEditEntityComponent } from '@shared/components/add-edit-entity/add-edit-entity.component';
import { TranslateModule } from '@ngx-translate/core';
import { NgIf } from '@angular/common';
interface DialogData { interface DialogData {
readonly dossierTemplateId: string; readonly dossierTemplateId: string;
@ -19,7 +10,6 @@ interface DialogData {
@Component({ @Component({
templateUrl: './add-entity-dialog.component.html', templateUrl: './add-entity-dialog.component.html',
styleUrls: ['./add-entity-dialog.component.scss'], styleUrls: ['./add-entity-dialog.component.scss'],
imports: [AddEditEntityComponent, TranslateModule, IconButtonComponent, NgIf, CircleButtonComponent, HelpButtonComponent],
}) })
export class AddEntityDialogComponent extends BaseDialogComponent { export class AddEntityDialogComponent extends BaseDialogComponent {
@ViewChild(AddEditEntityComponent, { static: true }) private readonly _addEditEntityComponent: AddEditEntityComponent; @ViewChild(AddEditEntityComponent, { static: true }) private readonly _addEditEntityComponent: AddEditEntityComponent;

View File

@ -6,11 +6,9 @@
<div class="table-header">Key</div> <div class="table-header">Key</div>
<div class="table-header">Value</div> <div class="table-header">Value</div>
<ng-container *ngFor="let entry of data.auditEntry.details | keyvalue: originalOrder"> <ng-container *ngFor="let entry of data.auditEntry.details | keyvalue : originalOrder">
<div class="bold">{{ entry.key | humanize }}</div> <div class="bold">{{ entry.key | humanize }}</div>
<div> <div>{{ entry.value }}</div>
<pre>{{ entry.value | json }}</pre>
</div>
</ng-container> </ng-container>
</div> </div>
</div> </div>

View File

@ -1,9 +1,8 @@
import { JsonPipe, KeyValue, KeyValuePipe, NgForOf } from '@angular/common'; import { KeyValue } from '@angular/common';
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; import { ChangeDetectionStrategy, Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { BaseDialogComponent, CircleButtonComponent, HumanizePipe } from '@iqser/common-ui'; import { BaseDialogComponent } from '@iqser/common-ui';
import { IAudit } from '@red/domain'; import { IAudit } from '@red/domain';
import { TranslateModule } from '@ngx-translate/core';
interface DialogData { interface DialogData {
readonly auditEntry: IAudit; readonly auditEntry: IAudit;
@ -16,7 +15,6 @@ type OrderFn = (a: KeyValue<string, string>, b: KeyValue<string, string>) => num
templateUrl: './audit-info-dialog.component.html', templateUrl: './audit-info-dialog.component.html',
styleUrls: ['./audit-info-dialog.component.scss'], styleUrls: ['./audit-info-dialog.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [TranslateModule, NgForOf, KeyValuePipe, HumanizePipe, CircleButtonComponent, JsonPipe],
}) })
export class AuditInfoDialogComponent extends BaseDialogComponent { export class AuditInfoDialogComponent extends BaseDialogComponent {
constructor( constructor(

View File

@ -1,18 +1,12 @@
import { NgIf } from '@angular/common';
import { HttpStatusCode } from '@angular/common/http';
import { ChangeDetectorRef, Component, ViewChild } from '@angular/core'; import { ChangeDetectorRef, Component, ViewChild } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { MatDialogClose, MatDialogRef } from '@angular/material/dialog';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { DetailsRadioOption } from '@common-ui/inputs/details-radio/details-radio-option';
import { DetailsRadioComponent } from '@common-ui/inputs/details-radio/details-radio.component';
import { BaseDialogComponent, CircleButtonComponent, IconButtonComponent } from '@iqser/common-ui';
import { TranslateModule } from '@ngx-translate/core';
import { DigitalSignatureOption, DigitalSignatureOptions } from '@red/domain';
import { DigitalSignatureService } from '../../services/digital-signature.service';
import { digitalSignatureDialogTranslations } from '../../translations/digital-signature-dialog-translations'; import { digitalSignatureDialogTranslations } from '../../translations/digital-signature-dialog-translations';
import { KmsSignatureConfigurationComponent } from './form/kms-signature-configuration/kms-signature-configuration.component'; import { BaseDialogComponent, DetailsRadioOption } from '@iqser/common-ui';
import { MatDialogRef } from '@angular/material/dialog';
import { PkcsSignatureConfigurationComponent } from './form/pkcs-signature-configuration/pkcs-signature-configuration.component'; import { PkcsSignatureConfigurationComponent } from './form/pkcs-signature-configuration/pkcs-signature-configuration.component';
import { KmsSignatureConfigurationComponent } from './form/kms-signature-configuration/kms-signature-configuration.component';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { HttpStatusCode } from '@angular/common/http';
import { DigitalSignatureOption, DigitalSignatureOptions } from '@red/domain';
const DEFAULT_DIALOG_WIDTH = '662px'; const DEFAULT_DIALOG_WIDTH = '662px';
const KMS_SIGNATURE_DIALOG_WIDTH = '810px'; const KMS_SIGNATURE_DIALOG_WIDTH = '810px';
@ -20,18 +14,6 @@ const KMS_SIGNATURE_DIALOG_WIDTH = '810px';
@Component({ @Component({
templateUrl: './configure-certificate-dialog.component.html', templateUrl: './configure-certificate-dialog.component.html',
styleUrls: ['./configure-certificate-dialog.component.scss'], styleUrls: ['./configure-certificate-dialog.component.scss'],
imports: [
DetailsRadioComponent,
NgIf,
ReactiveFormsModule,
TranslateModule,
PkcsSignatureConfigurationComponent,
KmsSignatureConfigurationComponent,
IconButtonComponent,
MatDialogClose,
CircleButtonComponent,
],
providers: [DigitalSignatureService],
}) })
export class ConfigureCertificateDialogComponent extends BaseDialogComponent { export class ConfigureCertificateDialogComponent extends BaseDialogComponent {
@ViewChild(PkcsSignatureConfigurationComponent) pkcsSignatureConfigurationComponent: PkcsSignatureConfigurationComponent; @ViewChild(PkcsSignatureConfigurationComponent) pkcsSignatureConfigurationComponent: PkcsSignatureConfigurationComponent;

View File

@ -1,27 +1,20 @@
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core'; import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
import { ReactiveFormsModule, UntypedFormBuilder, Validators } from '@angular/forms'; import { UntypedFormBuilder, Validators } from '@angular/forms';
import { BaseSignatureConfigurationComponent } from '../base-signature-configuration-component'; import { BaseSignatureConfigurationComponent } from '../base-signature-configuration-component';
import { DigitalSignatureOptions, IKmsDigitalSignature, IKmsDigitalSignatureRequest } from '@red/domain'; import { DigitalSignatureOptions, IKmsDigitalSignature, IKmsDigitalSignatureRequest } from '@red/domain';
import { firstValueFrom } from 'rxjs'; import { firstValueFrom } from 'rxjs';
import { DigitalSignatureService } from '../../../../services/digital-signature.service'; import { DigitalSignatureService } from '../../../../services/digital-signature.service';
import { UploadFileComponent } from '@iqser/common-ui';
import { NgIf } from '@angular/common';
import { TranslateModule } from '@ngx-translate/core';
@Component({ @Component({
selector: 'redaction-kms-signature-configuration', selector: 'redaction-kms-signature-configuration',
templateUrl: './kms-signature-configuration.component.html', templateUrl: './kms-signature-configuration.component.html',
styleUrls: ['./kms-signature-configuration.component.scss'], styleUrls: ['./kms-signature-configuration.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [UploadFileComponent, ReactiveFormsModule, NgIf, TranslateModule],
}) })
export class KmsSignatureConfigurationComponent extends BaseSignatureConfigurationComponent implements OnInit { export class KmsSignatureConfigurationComponent extends BaseSignatureConfigurationComponent implements OnInit {
@Input() digitalSignature!: IKmsDigitalSignatureRequest; @Input() digitalSignature!: IKmsDigitalSignatureRequest;
constructor( constructor(protected readonly _digitalSignatureService: DigitalSignatureService, private readonly _formBuilder: UntypedFormBuilder) {
protected readonly _digitalSignatureService: DigitalSignatureService,
private readonly _formBuilder: UntypedFormBuilder,
) {
super(_digitalSignatureService, DigitalSignatureOptions.KMS); super(_digitalSignatureService, DigitalSignatureOptions.KMS);
} }

View File

@ -1,20 +1,16 @@
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core'; import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
import { ReactiveFormsModule, UntypedFormBuilder, Validators } from '@angular/forms'; import { UntypedFormBuilder, Validators } from '@angular/forms';
import { DigitalSignatureOptions, IPkcsDigitalSignature, IPkcsDigitalSignatureRequest } from '@red/domain'; import { DigitalSignatureOptions, IPkcsDigitalSignature, IPkcsDigitalSignatureRequest } from '@red/domain';
import { lastIndexOfEnd } from '@utils/functions'; import { lastIndexOfEnd } from '@utils/functions';
import { firstValueFrom } from 'rxjs'; import { firstValueFrom } from 'rxjs';
import { DigitalSignatureService } from '../../../../services/digital-signature.service'; import { DigitalSignatureService } from '../../../../services/digital-signature.service';
import { BaseSignatureConfigurationComponent } from '../base-signature-configuration-component'; import { BaseSignatureConfigurationComponent } from '../base-signature-configuration-component';
import { UploadFileComponent } from '@iqser/common-ui';
import { NgIf } from '@angular/common';
import { TranslateModule } from '@ngx-translate/core';
@Component({ @Component({
selector: 'redaction-pkcs-signature-configuration', selector: 'redaction-pkcs-signature-configuration',
templateUrl: './pkcs-signature-configuration.component.html', templateUrl: './pkcs-signature-configuration.component.html',
styleUrls: ['./pkcs-signature-configuration.component.scss'], styleUrls: ['./pkcs-signature-configuration.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [UploadFileComponent, ReactiveFormsModule, NgIf, TranslateModule],
}) })
export class PkcsSignatureConfigurationComponent extends BaseSignatureConfigurationComponent implements OnInit { export class PkcsSignatureConfigurationComponent extends BaseSignatureConfigurationComponent implements OnInit {
@Input() digitalSignature!: IPkcsDigitalSignatureRequest; @Input() digitalSignature!: IPkcsDigitalSignatureRequest;

View File

@ -1,16 +1,12 @@
import { Component, Inject } from '@angular/core'; import { Component, Inject } from '@angular/core';
import { DefaultColorType } from '@red/domain'; import { DefaultColorType } from '@red/domain';
import { BaseDialogComponent, CircleButtonComponent, IconButtonComponent, IconButtonTypes } from '@iqser/common-ui'; import { BaseDialogComponent, IconButtonTypes } from '@iqser/common-ui';
import { ReactiveFormsModule, UntypedFormGroup, Validators } from '@angular/forms'; import { UntypedFormGroup, Validators } from '@angular/forms';
import { defaultColorsTranslations } from '@translations/default-colors-translations'; import { defaultColorsTranslations } from '@translations/default-colors-translations';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { firstValueFrom } from 'rxjs'; import { firstValueFrom } from 'rxjs';
import { DefaultColorsService } from '@services/entity-services/default-colors.service'; import { DefaultColorsService } from '@services/entity-services/default-colors.service';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { TranslateModule } from '@ngx-translate/core';
import { ColorPickerModule } from 'ngx-color-picker';
import { MatIcon } from '@angular/material/icon';
import { NgIf } from '@angular/common';
interface IEditColorData { interface IEditColorData {
colorKey: DefaultColorType; colorKey: DefaultColorType;
@ -20,7 +16,6 @@ interface IEditColorData {
@Component({ @Component({
templateUrl: './edit-color-dialog.component.html', templateUrl: './edit-color-dialog.component.html',
styleUrls: ['./edit-color-dialog.component.scss'], styleUrls: ['./edit-color-dialog.component.scss'],
imports: [ReactiveFormsModule, TranslateModule, ColorPickerModule, MatIcon, NgIf, IconButtonComponent, CircleButtonComponent],
}) })
export class EditColorDialogComponent extends BaseDialogComponent { export class EditColorDialogComponent extends BaseDialogComponent {
readonly iconButtonTypes = IconButtonTypes; readonly iconButtonTypes = IconButtonTypes;

View File

@ -1,15 +1,13 @@
import { Component, Inject } from '@angular/core'; import { Component, Inject } from '@angular/core';
import { ReactiveFormsModule, Validators } from '@angular/forms'; import { Validators } from '@angular/forms';
import { MAT_DIALOG_DATA, MatDialogClose, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { BaseDialogComponent, CircleButtonComponent, IconButtonComponent } from '@iqser/common-ui'; import { BaseDialogComponent } from '@iqser/common-ui';
import { ISmtpConfiguration } from '@red/domain'; import { ISmtpConfiguration } from '@red/domain';
import { getCurrentUser } from '@users/user.service'; import { getCurrentUser } from '@users/user.service';
import { TranslateModule } from '@ngx-translate/core';
@Component({ @Component({
selector: 'redaction-smtp-auth-dialog', selector: 'redaction-smtp-auth-dialog',
templateUrl: './smtp-auth-dialog.component.html', templateUrl: './smtp-auth-dialog.component.html',
imports: [ReactiveFormsModule, TranslateModule, IconButtonComponent, CircleButtonComponent, MatDialogClose],
}) })
export class SmtpAuthDialogComponent extends BaseDialogComponent { export class SmtpAuthDialogComponent extends BaseDialogComponent {
readonly #currentUser = getCurrentUser(); readonly #currentUser = getCurrentUser();

View File

@ -1,13 +1,11 @@
import { ChangeDetectionStrategy, Component } from '@angular/core'; import { ChangeDetectionStrategy, Component } from '@angular/core';
import { MatDialogRef } from '@angular/material/dialog'; import { MatDialogRef } from '@angular/material/dialog';
import { IconButtonComponent, IconButtonTypes } from '@iqser/common-ui'; import { IconButtonTypes } from '@iqser/common-ui';
import { TranslateModule } from '@ngx-translate/core';
@Component({ @Component({
templateUrl: './upload-dictionary-dialog.component.html', templateUrl: './upload-dictionary-dialog.component.html',
styleUrls: ['./upload-dictionary-dialog.component.scss'], styleUrls: ['./upload-dictionary-dialog.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [IconButtonComponent, TranslateModule],
}) })
export class UploadDictionaryDialogComponent { export class UploadDictionaryDialogComponent {
readonly iconButtonTypes = IconButtonTypes; readonly iconButtonTypes = IconButtonTypes;

View File

@ -1,34 +1,17 @@
import { Component, inject, OnDestroy, OnInit } from '@angular/core'; import { Component, inject, OnDestroy, OnInit } from '@angular/core';
import { ReactiveFormsModule, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { import { IqserPermissionsService, ListingComponent, listingProvidersFactory, LoadingService, TableColumnConfig } from '@iqser/common-ui';
CircleButtonComponent, import { getCurrentUser } from '@iqser/common-ui/lib/users';
HumanizePipe,
IqserListingModule,
IqserPermissionsService,
ListingComponent,
listingProvidersFactory,
LoadingService,
TableColumnConfig,
} from '@iqser/common-ui';
import { getCurrentUser, InitialsAvatarComponent } from '@iqser/common-ui/lib/users';
import { Audit, IAudit, IAuditResponse, IAuditSearchRequest, User } from '@red/domain'; import { Audit, IAudit, IAuditResponse, IAuditSearchRequest, User } from '@red/domain';
import { RouterHistoryService } from '@services/router-history.service'; import { RouterHistoryService } from '@services/router-history.service';
import { auditCategoriesTranslations } from '@translations/audit-categories-translations'; import { auditCategoriesTranslations } from '@translations/audit-categories-translations';
import { Roles } from '@users/roles'; import { Roles } from '@users/roles';
import { applyIntervalConstraints } from '@utils/date-inputs-utils'; import { applyIntervalConstraints } from '@utils/date-inputs-utils';
import dayjs, { Dayjs } from 'dayjs'; import { Dayjs } from 'dayjs';
import { firstValueFrom } from 'rxjs'; import { firstValueFrom } from 'rxjs';
import { AdminDialogService } from '../../services/admin-dialog.service'; import { AdminDialogService } from '../../services/admin-dialog.service';
import { AuditService } from '../../services/audit.service'; import { AuditService } from '../../services/audit.service';
import { TranslateModule } from '@ngx-translate/core';
import { PaginationComponent } from '@common-ui/pagination';
import { MatFormField, MatSuffix } from '@angular/material/form-field';
import { MatOption, MatSelect, MatSelectTrigger } from '@angular/material/select';
import { AsyncPipe, NgForOf, NgIf } from '@angular/common';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatIcon } from '@angular/material/icon';
import { DatePipe } from '@shared/pipes/date.pipe';
const PAGE_SIZE = 50; const PAGE_SIZE = 50;
@ -36,26 +19,6 @@ const PAGE_SIZE = 50;
templateUrl: './audit-screen.component.html', templateUrl: './audit-screen.component.html',
styleUrls: ['./audit-screen.component.scss'], styleUrls: ['./audit-screen.component.scss'],
providers: listingProvidersFactory(AuditScreenComponent), providers: listingProvidersFactory(AuditScreenComponent),
imports: [
IqserListingModule,
TranslateModule,
PaginationComponent,
ReactiveFormsModule,
MatFormField,
MatOption,
NgForOf,
InitialsAvatarComponent,
NgIf,
MatIcon,
MatSuffix,
AsyncPipe,
DatePipe,
HumanizePipe,
CircleButtonComponent,
MatSelectTrigger,
MatSelect,
MatDatepickerModule,
],
}) })
export class AuditScreenComponent extends ListingComponent<Audit> implements OnInit, OnDestroy { export class AuditScreenComponent extends ListingComponent<Audit> implements OnInit, OnDestroy {
private _previousFrom: Dayjs; private _previousFrom: Dayjs;
@ -63,7 +26,7 @@ export class AuditScreenComponent extends ListingComponent<Audit> implements OnI
readonly ALL_CATEGORIES = 'allCategories'; readonly ALL_CATEGORIES = 'allCategories';
readonly ALL_USERS = _('audit-screen.all-users'); readonly ALL_USERS = _('audit-screen.all-users');
readonly translations = auditCategoriesTranslations; readonly translations = auditCategoriesTranslations;
readonly form: UntypedFormGroup = this.#getForm(); readonly form: UntypedFormGroup = this._getForm();
readonly routerHistoryService = inject(RouterHistoryService); readonly routerHistoryService = inject(RouterHistoryService);
readonly permissionsService = inject(IqserPermissionsService); readonly permissionsService = inject(IqserPermissionsService);
readonly roles = Roles; readonly roles = Roles;
@ -96,17 +59,17 @@ export class AuditScreenComponent extends ListingComponent<Audit> implements OnI
} }
async pageChanged(page: number) { async pageChanged(page: number) {
await this.#fetchData(page); await this._fetchData(page);
} }
async ngOnInit() { async ngOnInit() {
this.sortingService.setSortingOption({ column: 'recordDate', order: 'desc' }); this.sortingService.setSortingOption({ column: 'recordDate', order: 'desc' });
await this.#fetchData(); await this._fetchData();
} }
async filterChange() { async filterChange() {
if (!this.#updateDateFilters(this.form.value)) { if (!this._updateDateFilters(this.form.value)) {
await this.#fetchData(); await this._fetchData();
} }
} }
@ -114,7 +77,7 @@ export class AuditScreenComponent extends ListingComponent<Audit> implements OnI
this._dialogService.openDialog('auditInfo', { auditEntry: log }); this._dialogService.openDialog('auditInfo', { auditEntry: log });
} }
#getForm(): UntypedFormGroup { private _getForm(): UntypedFormGroup {
return this._formBuilder.group({ return this._formBuilder.group({
category: [this.ALL_CATEGORIES], category: [this.ALL_CATEGORIES],
userId: [this.ALL_USERS], userId: [this.ALL_USERS],
@ -123,7 +86,7 @@ export class AuditScreenComponent extends ListingComponent<Audit> implements OnI
}); });
} }
#updateDateFilters(value): boolean { private _updateDateFilters(value): boolean {
if (applyIntervalConstraints(value, this._previousFrom, this._previousTo, this.form, 'from', 'to')) { if (applyIntervalConstraints(value, this._previousFrom, this._previousTo, this.form, 'from', 'to')) {
return true; return true;
} }
@ -133,14 +96,21 @@ export class AuditScreenComponent extends ListingComponent<Audit> implements OnI
return false; return false;
} }
async #fetchData(page?: number) { private async _fetchData(page?: number) {
this._loadingService.start(); this._loadingService.start();
const promises = []; const promises = [];
const category = this.form.get('category').value; const category = this.form.get('category').value;
const userId = this.form.get('userId').value; const userId = this.form.get('userId').value;
const from = this.form.get('from').value ? dayjs(this.form.get('from').value).startOf('day').toISOString() : null; const from = this.form.get('from').value;
const to = this.form.get('to').value ? dayjs(this.form.get('to').value).endOf('day').toISOString() : null; let to = this.form.get('to').value;
if (to) {
const hoursLeft = new Date(to).getHours();
const minutesLeft = new Date(to).getMinutes();
to = to
.clone()
.add(24 - hoursLeft - 1, 'h')
.add(60 - minutesLeft - 1);
}
const logsRequestBody: IAuditSearchRequest = { const logsRequestBody: IAuditSearchRequest = {
pageSize: PAGE_SIZE, pageSize: PAGE_SIZE,
page: page, page: page,

View File

@ -1,130 +0,0 @@
@if (componentDefinitions$ | async; as componentDefinitions) {
<div class="content-container">
<div class="content-header">
<div class="header-title">
<span
class="all-caps-label"
[innerHTML]="'component-definitions.title' | translate: { length: componentDefinitions.length }"
></span>
</div>
<div class="actions">
@if (permissionsService.canEditEntities()) {
<iqser-icon-button
(action)="createEmptyComponentDefinition()"
[label]="'component-definitions.add-new' | translate"
[type]="iconButtonTypes.primary"
icon="iqser:plus"
></iqser-icon-button>
}
</div>
</div>
<div class="content">
<div class="components-list">
<div class="header">
<div class="item-content">
<div class="all-caps-label">{{ 'component-definitions.columns.position' | translate }}</div>
<div class="all-caps-label">{{ 'component-definitions.columns.name' | translate }}</div>
</div>
</div>
<div (cdkDropListDropped)="drop($event)" cdkDropList class="list-content">
@for (component of componentDefinitions; track component) {
<div
class="list-item"
[class.selected]="selectedComponent?.id === component.id"
cdkDrag
(click)="selectComponent(component)"
>
<div class="item-content">
<div class="table-item-title heading">
<mat-icon cdkDragHandle class="draggable" svgIcon="red:draggable-dots"></mat-icon>
<span> {{ component.rank }} </span>
</div>
<div class="table-item-title heading">{{ component.displayName }}</div>
<div class="right-content">
@if (permissionsService.canEditEntities()) {
<iqser-circle-button
[class]="'delete-component-definition'"
(action)="deleteComponent(component.id)"
[tooltip]="'trash.action.delete' | translate"
icon="iqser:trash"
></iqser-circle-button>
}
<mat-icon
[class.not-visible]="selectedComponent?.id !== component.id"
class="arrow-right"
svgIcon="red:arrow-right"
></mat-icon>
</div>
</div>
</div>
}
</div>
</div>
@if (selectedComponent) {
<section class="dialog">
<div class="dialog-header">
@if (selectedComponent.id) {
<div
class="heading-l"
[innerHTML]="'component-definitions.edit-title' | translate: { displayName: selectedComponent.displayName }"
></div>
} @else {
<div class="heading-l" [innerHTML]="'component-definitions.add-title' | translate"></div>
}
</div>
@if (form) {
<form (submit)="save()" [formGroup]="form">
<div class="dialog-content">
<div class="iqser-input-group w-300 required">
<label [translate]="'component-definitions.form.display-name'"></label>
<input
formControlName="displayName"
name="displayName"
placeholder="{{ 'component-definitions.form.display-name-placeholder' | translate }}"
type="text"
/>
</div>
<div class="iqser-input-group w-450">
<label [translate]="'component-definitions.form.description'"></label>
<textarea
[placeholder]="'component-definitions.form.description-placeholder' | translate"
formControlName="description"
iqserHasScrollbar
name="description"
rows="5"
type="text"
></textarea>
</div>
<div class="iqser-input-group w-450">
<label [translate]="'component-definitions.form.technical-name-label'"></label>
<span class="technical-name"> {{ technicalName }} </span>
<label [translate]="'component-definitions.form.autogenerated-label'"></label>
</div>
</div>
<div class="dialog-actions">
<iqser-icon-button
[disabled]="disabled"
[label]="'component-definitions.actions.save' | translate"
[submit]="true"
[type]="iconButtonTypes.primary"
></iqser-icon-button>
@if (selectedComponent.id) {
<div
(click)="initForm()"
[class.disabled]="disabled"
[translate]="'component-definitions.actions.revert'"
class="all-caps-label cancel"
></div>
}
</div>
</form>
}
</section>
}
</div>
</div>
}

View File

@ -1,145 +0,0 @@
%item-content-style {
position: relative;
display: flex;
flex: 1;
margin: 5px;
div:first-child {
width: 100px;
}
div {
display: flex;
align-items: center;
justify-content: center;
.draggable {
cursor: grab;
transform: scale(0.7);
width: 40px;
margin-left: -30px;
::ng-deep svg {
fill: var(--iqser-grey-7);
}
}
}
.right-content {
visibility: hidden;
flex: 1;
justify-content: flex-end;
align-items: center;
gap: 5px;
iqser-circle-button {
visibility: hidden;
}
.arrow-right {
transform: scale(0.7);
&.not-visible {
visibility: hidden;
}
}
}
}
.content-container {
background-color: var(--iqser-grey-6);
height: 100vh;
display: flex;
flex-direction: column;
.content-header {
display: flex;
height: 50px;
background-color: var(--iqser-grey-6);
align-items: center;
padding: 0 24px;
.actions {
display: flex;
flex: 1;
justify-content: flex-end;
}
}
.content {
flex: 1;
display: flex;
gap: 20px;
overflow: hidden;
.components-list {
flex: 1;
background-color: var(--iqser-white);
display: flex;
flex-direction: column;
.header {
height: 30px;
}
.list-content {
overflow-y: auto;
height: calc(100vh - 30px);
outline: none;
}
.list-item {
height: 80px;
transition: background 0.3s ease;
}
.header,
.list-item {
display: flex;
border-bottom: 1px solid var(--iqser-separator);
.item-content {
@extend %item-content-style;
}
}
.list-item:hover,
.selected {
cursor: pointer;
.item-content {
background-color: var(--iqser-grey-8);
.right-content {
visibility: visible;
}
}
}
.list-item:hover > .item-content > .right-content > iqser-circle-button {
visibility: visible;
}
}
section {
background-color: var(--iqser-white);
border-radius: 8px;
width: 700px;
height: 470px;
margin-right: 150px;
.technical-name {
min-height: 16px;
}
}
}
}
.cdk-drag-preview {
.list-item {
transition: background 0.3s ease;
}
.item-content {
@extend %item-content-style;
}
}

View File

@ -1,138 +0,0 @@
import { Component, OnInit, signal } from '@angular/core';
import {
BaseFormComponent,
CircleButtonComponent,
HasScrollbarDirective,
IconButtonComponent,
listingProvidersFactory,
LoadingService,
} from '@iqser/common-ui';
import { ComponentDefinitionsService } from '@services/entity-services/component-definitions.service';
import { firstValueFrom } from 'rxjs';
import { getParam } from '@common-ui/utils';
import { DOSSIER_TEMPLATE_ID, IComponentDefinition } from '@red/domain';
import { toObservable } from '@angular/core/rxjs-interop';
import { CommonModule } from '@angular/common';
import { TranslateModule } from '@ngx-translate/core';
import { PermissionsService } from '@services/permissions.service';
import { MatIcon } from '@angular/material/icon';
import { CdkDrag, CdkDragDrop, CdkDragHandle, CdkDropList, moveItemInArray } from '@angular/cdk/drag-drop';
import { FormBuilder, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
import { AdminDialogService } from '../../services/admin-dialog.service';
@Component({
templateUrl: './component-definitions.component.html',
styleUrls: ['./component-definitions.component.scss'],
providers: listingProvidersFactory(ComponentDefinitionsComponent),
imports: [
IconButtonComponent,
TranslateModule,
CommonModule,
MatIcon,
CdkDragHandle,
CdkDropList,
CdkDrag,
FormsModule,
ReactiveFormsModule,
CircleButtonComponent,
HasScrollbarDirective,
],
})
export default class ComponentDefinitionsComponent extends BaseFormComponent implements OnInit {
readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);
readonly #componentDefinitions = signal<IComponentDefinition[]>([]);
protected readonly componentDefinitions$ = toObservable(this.#componentDefinitions);
protected selectedComponent: IComponentDefinition | null = null;
constructor(
private readonly _loadingService: LoadingService,
private readonly _componentDefinitionsService: ComponentDefinitionsService,
private readonly _formBuilder: FormBuilder,
private readonly _dialogService: AdminDialogService,
protected readonly permissionsService: PermissionsService,
) {
super();
}
async ngOnInit() {
this._loadingService.stop();
await this.#loadData();
}
async #loadData() {
const componentDefinitions = await firstValueFrom(
this._componentDefinitionsService.getComponentDefinitions(this.#dossierTemplateId),
);
this.#componentDefinitions.set(componentDefinitions);
}
async createEmptyComponentDefinition() {
this.selectedComponent = {
displayName: '',
description: '',
} as IComponentDefinition;
this.initForm();
}
async save() {
if (this.selectedComponent.id) {
const component = { ...this.form.getRawValue(), id: this.selectedComponent.id };
await firstValueFrom(this._componentDefinitionsService.updateComponentDefinition(this.#dossierTemplateId, component));
} else {
const component = {
...this.form.getRawValue(),
dossierTemplateId: this.#dossierTemplateId,
technicalName: this.technicalName,
};
await firstValueFrom(this._componentDefinitionsService.createComponentDefinition(this.#dossierTemplateId, component));
}
await this.#loadData();
this.selectComponent();
await this.initForm();
}
async drop(event: CdkDragDrop<string>) {
if (event.isPointerOverContainer) {
moveItemInArray(this.#componentDefinitions(), event.previousIndex, event.currentIndex);
const componentIds = this.#componentDefinitions().map(c => c.id);
const componentDefinitions = await firstValueFrom(
this._componentDefinitionsService.reorderComponentDefinitions(this.#dossierTemplateId, componentIds),
);
this.#componentDefinitions.set(componentDefinitions);
}
}
async deleteComponent(componentId: string) {
this._dialogService.openDialog('confirm', null, async () => {
await firstValueFrom(this._componentDefinitionsService.deleteComponentDefinitions(this.#dossierTemplateId, [componentId]));
await this.#loadData();
this.selectedComponent = null;
});
}
initForm() {
this.form = this.#getForm();
this.initialFormValue = this.form.getRawValue();
}
#getForm() {
return this._formBuilder.group({
displayName: [this.selectedComponent?.displayName, Validators.required],
description: [this.selectedComponent?.description],
});
}
selectComponent(component?: IComponentDefinition) {
if (component && this.selectedComponent?.id !== component.id) {
this.selectedComponent = component;
this.initForm();
return;
}
this.selectedComponent = null;
}
get technicalName() {
return this.selectedComponent.technicalName ?? this.form.get('displayName')?.value?.toLowerCase().trim().replace(/\s+/g, '_');
}
}

View File

@ -24,15 +24,10 @@
<div class="iqser-input-group required"> <div class="iqser-input-group required">
<label translate="add-edit-component-mapping.form.file"></label> <label translate="add-edit-component-mapping.form.file"></label>
<iqser-upload-file (fileChanged)="changeFile($event)" [file]="activeFile" [accept]="'.csv'" /> <iqser-upload-file (fileChanged)="fileChanged($event)" [file]="activeFile" [accept]="'.csv'" />
</div> </div>
<div <div class="row">
class="row"
[matTooltip]="'add-edit-component-mapping.disabled-file-options' | translate"
[matTooltipDisabled]="!form.get('encoding')?.disabled"
[matTooltipPosition]="'above'"
>
<div class="iqser-input-group required w-150"> <div class="iqser-input-group required w-150">
<label translate="add-edit-component-mapping.form.delimiter"></label> <label translate="add-edit-component-mapping.form.delimiter"></label>
<input <input
@ -42,15 +37,6 @@
type="text" type="text"
/> />
</div> </div>
<div class="iqser-input-group required w-150">
<label translate="add-edit-component-mapping.form.quote-char"></label>
<input
[placeholder]="'add-edit-component-mapping.form.quote-char-placeholder' | translate"
formControlName="quoteChar"
name="quoteChar"
type="text"
/>
</div>
<div class="iqser-input-group required w-150"> <div class="iqser-input-group required w-150">
<label translate="add-edit-component-mapping.form.encoding-type"></label> <label translate="add-edit-component-mapping.form.encoding-type"></label>

View File

@ -1,36 +1,33 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { CircleButtonComponent, IconButtonComponent, IqserDialogComponent, UploadFileComponent } from '@iqser/common-ui'; import { CircleButtonComponent, IconButtonComponent, IconButtonTypes, IqserDialogComponent, IqserUploadFileModule } from '@iqser/common-ui';
import { FileAttributeEncodingTypes, IComponentMapping } from '@red/domain'; import { FileAttributeEncodingTypes, IComponentMapping } from '@red/domain';
import { FormBuilder, ReactiveFormsModule, UntypedFormGroup, Validators } from '@angular/forms'; import { FormBuilder, ReactiveFormsModule, UntypedFormGroup, Validators } from '@angular/forms';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { NgForOf, NgIf } from '@angular/common'; import { NgForOf, NgIf } from '@angular/common';
import { MatFormFieldModule } from '@angular/material/form-field'; import { MatFormFieldModule } from '@angular/material/form-field';
import { MatDialogModule } from '@angular/material/dialog'; import { MatDialogClose } from '@angular/material/dialog';
import { MatOption } from '@angular/material/autocomplete'; import { MatOption } from '@angular/material/autocomplete';
import { MatSelect } from '@angular/material/select'; import { MatSelect } from '@angular/material/select';
import { fileAttributeEncodingTypesTranslations } from '@translations/file-attribute-encoding-types-translations'; import { fileAttributeEncodingTypesTranslations } from '@translations/file-attribute-encoding-types-translations';
import { firstValueFrom } from 'rxjs'; import { firstValueFrom } from 'rxjs';
import { ComponentMappingsService } from '@services/entity-services/component-mappings.service'; import { ComponentMappingsService } from '@services/entity-services/component-mappings.service';
import { MatTooltip } from '@angular/material/tooltip';
interface DialogData { interface DialogData {
dossierTemplateId: string; dossierTemplateId: string;
mapping: IComponentMapping; mapping: IComponentMapping;
} }
interface DialogResult { interface DialogResult {
id: string; id: string;
name: string; name: string;
file: Blob; file: Blob;
encoding: string; encoding: string;
delimiter: string; delimiter: string;
quoteChar: string;
fileName?: string;
} }
@Component({ @Component({
templateUrl: './add-edit-component-mapping-dialog.component.html', templateUrl: './add-edit-component-mapping-dialog.component.html',
styleUrls: ['./add-edit-component-mapping-dialog.component.scss'], styleUrls: ['./add-edit-component-mapping-dialog.component.scss'],
standalone: true,
imports: [ imports: [
TranslateModule, TranslateModule,
ReactiveFormsModule, ReactiveFormsModule,
@ -38,12 +35,11 @@ interface DialogResult {
MatFormFieldModule, MatFormFieldModule,
NgForOf, NgForOf,
CircleButtonComponent, CircleButtonComponent,
MatDialogModule, MatDialogClose,
IqserUploadFileModule,
MatOption, MatOption,
MatSelect, MatSelect,
IconButtonComponent, IconButtonComponent,
UploadFileComponent,
MatTooltip,
], ],
}) })
export class AddEditComponentMappingDialogComponent export class AddEditComponentMappingDialogComponent
@ -69,17 +65,14 @@ export class AddEditComponentMappingDialogComponent
const fileContent = await firstValueFrom( const fileContent = await firstValueFrom(
this._componentMappingService.getComponentMappingFile(this.data.dossierTemplateId, this.data.mapping.id), this._componentMappingService.getComponentMappingFile(this.data.dossierTemplateId, this.data.mapping.id),
); );
const file = new Blob([fileContent.body as Blob], { type: 'text/csv' }); const file = new Blob([fileContent], { type: 'text/csv' });
this.form.get('file').setValue(file); this.form.get('file').setValue(file);
this.initialFormValue = this.form.getRawValue(); this.initialFormValue = this.form.getRawValue();
this.#disableEncodingAndQuoteCharAndDelimiter();
} }
} }
changeFile(file: File) { fileChanged(file: Blob) {
this.form.get('file').setValue(file); this.form.get('file').setValue(file);
this.form.get('fileName').setValue(file?.name);
this.#enableEncodingAndQuoteCharAndDelimiter();
} }
save() { save() {
@ -90,22 +83,8 @@ export class AddEditComponentMappingDialogComponent
return this._formBuilder.group({ return this._formBuilder.group({
name: [this.data?.mapping?.name, Validators.required], name: [this.data?.mapping?.name, Validators.required],
file: [null, Validators.required], file: [null, Validators.required],
fileName: [this.data?.mapping?.fileName, Validators.required],
encoding: this.encodingTypeOptions.find(e => e === this.data?.mapping?.encoding) ?? this.encodingTypeOptions[0], encoding: this.encodingTypeOptions.find(e => e === this.data?.mapping?.encoding) ?? this.encodingTypeOptions[0],
delimiter: [this.data?.mapping?.delimiter ?? ',', Validators.required], delimiter: [this.data?.mapping?.delimiter ?? ',', Validators.required],
quoteChar: [this.data?.mapping?.quoteChar ?? '"', Validators.required],
}); });
} }
#disableEncodingAndQuoteCharAndDelimiter() {
this.form.get('encoding').disable();
this.form.get('delimiter').disable();
this.form.get('quoteChar').disable();
}
#enableEncodingAndQuoteCharAndDelimiter() {
this.form.get('encoding').enable();
this.form.get('delimiter').enable();
this.form.get('quoteChar').enable();
}
} }

View File

@ -1,16 +1,39 @@
<div class="content-container" *ngIf="context$ | async as context"> <section *ngIf="context$ | async as context">
<iqser-table <div class="page-header">
(noDataAction)="openAddEditComponentMappingDialog()" <redaction-dossier-template-breadcrumbs class="flex-1"></redaction-dossier-template-breadcrumbs>
[headerTemplate]="headerTemplate"
[itemSize]="80" <div class="flex-1 actions">
[tableColumnConfigs]="tableColumnConfigs" <redaction-dossier-template-actions></redaction-dossier-template-actions>
[noDataText]="'component-mappings-screen.no-data.title' | translate"
[showNoDataButton]="roles.componentMappings.write && currentUser.isAdmin" <iqser-circle-button
[noDataButtonLabel]="'component-mappings-screen.no-data.action' | translate" [routerLink]="['../..']"
emptyColumnWidth="2fr" [tooltip]="'common.close' | translate"
noDataIcon="iqser:csv" icon="iqser:close"
></iqser-table> tooltipPosition="below"
</div> ></iqser-circle-button>
</div>
</div>
<div class="content-inner">
<div class="overlay-shadow"></div>
<redaction-admin-side-nav type="dossierTemplates"></redaction-admin-side-nav>
<div class="content-container">
<iqser-table
(noDataAction)="openAddEditComponentMappingDialog()"
[headerTemplate]="headerTemplate"
[itemSize]="80"
[tableColumnConfigs]="tableColumnConfigs"
[noDataText]="'component-mappings-screen.no-data.title' | translate"
[showNoDataButton]="roles.componentMappings.write && currentUser.isAdmin"
[noDataButtonLabel]="'component-mappings-screen.no-data.action' | translate"
emptyColumnWidth="2fr"
noDataIcon="iqser:csv"
></iqser-table>
</div>
</div>
</section>
<ng-template #headerTemplate> <ng-template #headerTemplate>
<div class="table-header-actions"> <div class="table-header-actions">
@ -44,10 +67,6 @@
<span>{{ entity.numberOfLines }}</span> <span>{{ entity.numberOfLines }}</span>
</div> </div>
<div class="cell" [matTooltip]="entity.columnLabelsString" [matTooltipPosition]="'above'" [matTooltipClass]="'custom-tooltip'">
<span class="ellipsis">{{ entity.columnLabelsString }}</span>
</div>
<div class="cell"> <div class="cell">
<div *allow="roles.componentMappings.write; if: currentUser.isAdmin" class="action-buttons"> <div *allow="roles.componentMappings.write; if: currentUser.isAdmin" class="action-buttons">
<iqser-circle-button <iqser-circle-button
@ -56,12 +75,6 @@
icon="iqser:edit" icon="iqser:edit"
></iqser-circle-button> ></iqser-circle-button>
<iqser-circle-button
(action)="downloadMapping(entity)"
[tooltip]="'component-mappings-screen.action.download' | translate"
icon="iqser:download"
></iqser-circle-button>
<iqser-circle-button <iqser-circle-button
(action)="openDeleteComponentMappingDialog(entity)" (action)="openDeleteComponentMappingDialog(entity)"
[tooltip]="'component-mappings-screen.action.delete' | translate" [tooltip]="'component-mappings-screen.action.delete' | translate"

View File

@ -1,11 +0,0 @@
.ellipsis {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
::ng-deep .custom-tooltip {
max-width: 300px;
white-space: normal;
word-wrap: break-word;
}

View File

@ -1,63 +1,43 @@
import { AsyncPipe, NgIf } from '@angular/common'; import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { Component, OnInit } from '@angular/core';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { InputWithActionComponent } from '@common-ui/inputs/input-with-action/input-with-action.component';
import { getCurrentUser } from '@common-ui/users';
import { getParam } from '@common-ui/utils';
import { import {
CircleButtonComponent,
IconButtonComponent,
IconButtonTypes,
IqserAllowDirective,
IqserDialog,
IqserListingModule,
ListingComponent, ListingComponent,
TableColumnConfig,
listingProvidersFactory, listingProvidersFactory,
LoadingService, LoadingService,
TableColumnConfig, IconButtonTypes,
IqserDialog,
} from '@iqser/common-ui'; } from '@iqser/common-ui';
import { TranslateModule } from '@ngx-translate/core'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { ComponentMapping, DOSSIER_TEMPLATE_ID, User } from '@red/domain';
import { ComponentMappingsService } from '@services/entity-services/component-mappings.service';
import { defaultColorsTranslations } from '@translations/default-colors-translations'; import { defaultColorsTranslations } from '@translations/default-colors-translations';
import { Roles } from '@users/roles'; import { Roles } from '@users/roles';
import { getCurrentUser } from '@common-ui/users';
import { DOSSIER_TEMPLATE_ID, User } from '@red/domain';
import { ComponentMapping } from '@red/domain';
import { combineLatest, firstValueFrom } from 'rxjs'; import { combineLatest, firstValueFrom } from 'rxjs';
import { ComponentMappingsService } from '@services/entity-services/component-mappings.service';
import { map, tap } from 'rxjs/operators'; import { map, tap } from 'rxjs/operators';
import { AdminDialogService } from '../../services/admin-dialog.service';
import { AddEditComponentMappingDialogComponent } from './add-edit-component-mapping-dialog/add-edit-component-mapping-dialog.component'; import { AddEditComponentMappingDialogComponent } from './add-edit-component-mapping-dialog/add-edit-component-mapping-dialog.component';
import { download } from '@utils/file-download-utils'; import { AdminDialogService } from '../../services/admin-dialog.service';
import { MatTooltip } from '@angular/material/tooltip'; import { getParam } from '@common-ui/utils';
@Component({ @Component({
templateUrl: './component-mappings-screen.component.html', templateUrl: './component-mappings-screen.component.html',
styleUrls: ['./component-mappings-screen.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush,
providers: listingProvidersFactory(ComponentMappingsScreenComponent), providers: listingProvidersFactory(ComponentMappingsScreenComponent),
imports: [
AsyncPipe,
NgIf,
CircleButtonComponent,
TranslateModule,
IqserListingModule,
InputWithActionComponent,
IconButtonComponent,
IqserAllowDirective,
MatTooltip,
],
}) })
export default class ComponentMappingsScreenComponent extends ListingComponent<ComponentMapping> implements OnInit { export class ComponentMappingsScreenComponent extends ListingComponent<ComponentMapping> implements OnInit {
readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID); tableColumnConfigs: readonly TableColumnConfig<ComponentMapping>[] = [
{ label: _('component-mappings-screen.table-col-names.name'), sortByKey: 'searchKey' },
{ label: _('component-mappings-screen.table-col-names.version') },
{ label: _('component-mappings-screen.table-col-names.number-of-lines') },
];
readonly tableHeaderLabel = _('component-mappings-screen.table-header.title');
protected readonly context$; protected readonly context$;
protected readonly currentUser = getCurrentUser<User>(); protected readonly currentUser = getCurrentUser<User>();
protected readonly translations = defaultColorsTranslations; protected readonly translations = defaultColorsTranslations;
protected readonly roles = Roles; protected readonly roles = Roles;
protected readonly iconButtonTypes = IconButtonTypes; protected readonly iconButtonTypes = IconButtonTypes;
tableColumnConfigs: readonly TableColumnConfig<ComponentMapping>[] = [ readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);
{ label: _('component-mappings-screen.table-col-names.name'), sortByKey: 'searchKey' },
{ label: _('component-mappings-screen.table-col-names.version') },
{ label: _('component-mappings-screen.table-col-names.number-of-lines') },
{ label: _('component-mappings-screen.table-col-names.column-labels') },
];
readonly tableHeaderLabel = _('component-mappings-screen.table-header.title');
constructor( constructor(
private readonly _loadingService: LoadingService, private readonly _loadingService: LoadingService,
@ -90,8 +70,8 @@ export default class ComponentMappingsScreenComponent extends ListingComponent<C
const result = await dialog.result(); const result = await dialog.result();
if (result) { if (result) {
this._loadingService.start(); this._loadingService.start();
const { id, name, encoding, delimiter, fileName, quoteChar } = result; const { id, name, encoding, delimiter } = result;
const newMapping = { id, name, encoding, delimiter, fileName, quoteChar }; const newMapping = { id, name, encoding, delimiter };
await firstValueFrom( await firstValueFrom(
this._componentMappingService.createUpdateComponentMapping(this.#dossierTemplateId, newMapping, result.file), this._componentMappingService.createUpdateComponentMapping(this.#dossierTemplateId, newMapping, result.file),
); );
@ -100,13 +80,6 @@ export default class ComponentMappingsScreenComponent extends ListingComponent<C
} }
} }
async downloadMapping(entity: ComponentMapping) {
const componentMappingFile = await firstValueFrom(
this._componentMappingService.getComponentMappingFile(this.#dossierTemplateId, entity.id),
);
download(componentMappingFile, entity.fileName);
}
openDeleteComponentMappingDialog(entity: ComponentMapping) { openDeleteComponentMappingDialog(entity: ComponentMapping) {
this._dialogService.openDialog('confirm', null, async confirmation => { this._dialogService.openDialog('confirm', null, async confirmation => {
if (confirmation) { if (confirmation) {

View File

@ -1,6 +1,29 @@
<div class="content-container" *ngIf="context$ | async as context"> <section *ngIf="context$ | async as context">
<iqser-table [itemSize]="80" [tableColumnConfigs]="tableColumnConfigs" emptyColumnWidth="2fr"></iqser-table> <div class="page-header">
</div> <redaction-dossier-template-breadcrumbs class="flex-1"></redaction-dossier-template-breadcrumbs>
<div class="flex-1 actions">
<redaction-dossier-template-actions></redaction-dossier-template-actions>
<iqser-circle-button
[routerLink]="['../..']"
[tooltip]="'common.close' | translate"
icon="iqser:close"
tooltipPosition="below"
></iqser-circle-button>
</div>
</div>
<div class="content-inner">
<div class="overlay-shadow"></div>
<redaction-admin-side-nav type="dossierTemplates"></redaction-admin-side-nav>
<div class="content-container">
<iqser-table [itemSize]="80" [tableColumnConfigs]="tableColumnConfigs" emptyColumnWidth="2fr"></iqser-table>
</div>
</div>
</section>
<ng-template #tableItemTemplate let-entity="entity"> <ng-template #tableItemTemplate let-entity="entity">
<div> <div>

View File

@ -1,16 +1,6 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { import { getConfig, IListable, ListingComponent, listingProvidersFactory, LoadingService, TableColumnConfig } from '@iqser/common-ui';
CircleButtonComponent,
getConfig,
IListable,
IqserAllowDirective,
IqserListingModule,
ListingComponent,
listingProvidersFactory,
LoadingService,
TableColumnConfig,
} from '@iqser/common-ui';
import { getCurrentUser } from '@iqser/common-ui/lib/users'; import { getCurrentUser } from '@iqser/common-ui/lib/users';
import { getParam } from '@iqser/common-ui/lib/utils'; import { getParam } from '@iqser/common-ui/lib/utils';
import { DefaultColorTypes, DOSSIER_TEMPLATE_ID, User } from '@red/domain'; import { DefaultColorTypes, DOSSIER_TEMPLATE_ID, User } from '@red/domain';
@ -20,8 +10,6 @@ import { Roles } from '@users/roles';
import { combineLatest } from 'rxjs'; import { combineLatest } from 'rxjs';
import { map, tap } from 'rxjs/operators'; import { map, tap } from 'rxjs/operators';
import { AdminDialogService } from '../../services/admin-dialog.service'; import { AdminDialogService } from '../../services/admin-dialog.service';
import { AsyncPipe, NgIf, NgStyle } from '@angular/common';
import { TranslateModule } from '@ngx-translate/core';
interface ListItem extends IListable { interface ListItem extends IListable {
readonly key: string; readonly key: string;
@ -33,7 +21,6 @@ interface ListItem extends IListable {
styleUrls: ['./default-colors-screen.component.scss'], styleUrls: ['./default-colors-screen.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
providers: listingProvidersFactory(DefaultColorsScreenComponent), providers: listingProvidersFactory(DefaultColorsScreenComponent),
imports: [IqserListingModule, NgStyle, CircleButtonComponent, IqserAllowDirective, TranslateModule, AsyncPipe, NgIf],
}) })
export class DefaultColorsScreenComponent extends ListingComponent<ListItem> implements OnInit { export class DefaultColorsScreenComponent extends ListingComponent<ListItem> implements OnInit {
readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID); readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);

View File

@ -1,40 +1,20 @@
import { AsyncPipe, NgIf } from '@angular/common';
import { ChangeDetectorRef, Component, computed, OnInit, ViewChild } from '@angular/core'; import { ChangeDetectorRef, Component, computed, OnInit, ViewChild } from '@angular/core';
import { IconButtonTypes, IqserPermissionsService, LoadingService, Toaster } from '@iqser/common-ui';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { EmptyStateComponent } from '@common-ui/empty-state';
import {
IconButtonComponent,
IconButtonTypes,
IqserListingModule,
IqserPermissionsService,
LoadingService,
Toaster,
} from '@iqser/common-ui';
import { getCurrentUser } from '@iqser/common-ui/lib/users';
import { TranslateModule } from '@ngx-translate/core';
import { DigitalSignatureOptions, IKmsDigitalSignatureRequest, IPkcsDigitalSignatureRequest, User } from '@red/domain';
import { RouterHistoryService } from '@services/router-history.service'; import { RouterHistoryService } from '@services/router-history.service';
import { Roles } from '@users/roles';
import { firstValueFrom } from 'rxjs';
import { KmsSignatureConfigurationComponent } from '../../dialogs/configure-digital-signature-dialog/form/kms-signature-configuration/kms-signature-configuration.component';
import { PkcsSignatureConfigurationComponent } from '../../dialogs/configure-digital-signature-dialog/form/pkcs-signature-configuration/pkcs-signature-configuration.component';
import { AdminDialogService } from '../../services/admin-dialog.service';
import { DigitalSignatureService } from '../../services/digital-signature.service'; import { DigitalSignatureService } from '../../services/digital-signature.service';
import { firstValueFrom } from 'rxjs';
import { AdminDialogService } from '../../services/admin-dialog.service';
import { PkcsSignatureConfigurationComponent } from '../../dialogs/configure-digital-signature-dialog/form/pkcs-signature-configuration/pkcs-signature-configuration.component';
import { KmsSignatureConfigurationComponent } from '../../dialogs/configure-digital-signature-dialog/form/kms-signature-configuration/kms-signature-configuration.component';
import { DigitalSignatureOptions, IKmsDigitalSignatureRequest, IPkcsDigitalSignatureRequest, User } from '@red/domain';
import { Roles } from '@users/roles';
import { getCurrentUser } from '@iqser/common-ui/lib/users';
@Component({ @Component({
selector: 'redaction-digital-signature-screen', selector: 'redaction-digital-signature-screen',
templateUrl: './digital-signature-screen.component.html', templateUrl: './digital-signature-screen.component.html',
styleUrls: ['./digital-signature-screen.component.scss'], styleUrls: ['./digital-signature-screen.component.scss'],
imports: [
IqserListingModule,
EmptyStateComponent,
NgIf,
TranslateModule,
AsyncPipe,
PkcsSignatureConfigurationComponent,
KmsSignatureConfigurationComponent,
IconButtonComponent,
],
}) })
export class DigitalSignatureScreenComponent implements OnInit { export class DigitalSignatureScreenComponent implements OnInit {
@ViewChild(PkcsSignatureConfigurationComponent) pkcsSignatureConfigurationComponent: PkcsSignatureConfigurationComponent; @ViewChild(PkcsSignatureConfigurationComponent) pkcsSignatureConfigurationComponent: PkcsSignatureConfigurationComponent;

View File

@ -20,6 +20,7 @@ export interface AddEditDossierAttributeDialogData {
@Component({ @Component({
templateUrl: './add-edit-dossier-attribute-dialog.component.html', templateUrl: './add-edit-dossier-attribute-dialog.component.html',
styleUrls: ['./add-edit-dossier-attribute-dialog.component.scss'], styleUrls: ['./add-edit-dossier-attribute-dialog.component.scss'],
standalone: true,
imports: [ imports: [
TranslateModule, TranslateModule,
ReactiveFormsModule, ReactiveFormsModule,
@ -90,7 +91,7 @@ export class AddEditDossierAttributeDialogComponent extends BaseDialogComponent
const createOrUpdate = this._dossierAttributesService.createOrUpdate(attribute, this.data.dossierTemplateId); const createOrUpdate = this._dossierAttributesService.createOrUpdate(attribute, this.data.dossierTemplateId);
const result = await createOrUpdate.catch((error: HttpErrorResponse) => { const result = await createOrUpdate.catch((error: HttpErrorResponse) => {
this._loadingService.stop(); this._loadingService.stop();
this._toaster.rawError(error.error.message); this._toaster.error(_('add-edit-dossier-attribute.error.generic'), { error });
return undefined; return undefined;
}); });

View File

@ -1,14 +1,9 @@
import { AsyncPipe, NgForOf, NgIf } from '@angular/common';
import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core'; import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { InputWithActionComponent } from '@common-ui/inputs/input-with-action/input-with-action.component';
import { import {
CircleButtonComponent,
defaultDialogConfig, defaultDialogConfig,
IconButtonComponent,
IconButtonTypes, IconButtonTypes,
IqserListingModule,
ListingComponent, ListingComponent,
listingProvidersFactory, listingProvidersFactory,
LoadingService, LoadingService,
@ -16,7 +11,6 @@ import {
} from '@iqser/common-ui'; } from '@iqser/common-ui';
import { getCurrentUser } from '@iqser/common-ui/lib/users'; import { getCurrentUser } from '@iqser/common-ui/lib/users';
import { getParam } from '@iqser/common-ui/lib/utils'; import { getParam } from '@iqser/common-ui/lib/utils';
import { TranslateModule } from '@ngx-translate/core';
import { DOSSIER_TEMPLATE_ID, DossierAttributeConfig, IDossierAttributeConfig, User } from '@red/domain'; import { DOSSIER_TEMPLATE_ID, DossierAttributeConfig, IDossierAttributeConfig, User } from '@red/domain';
import { DossierAttributesService } from '@services/entity-services/dossier-attributes.service'; import { DossierAttributesService } from '@services/entity-services/dossier-attributes.service';
import { PermissionsService } from '@services/permissions.service'; import { PermissionsService } from '@services/permissions.service';
@ -26,7 +20,6 @@ import {
AddEditDossierAttributeDialogComponent, AddEditDossierAttributeDialogComponent,
AddEditDossierAttributeDialogData, AddEditDossierAttributeDialogData,
} from './add-edit-dossier-attribute-dialog/add-edit-dossier-attribute-dialog.component'; } from './add-edit-dossier-attribute-dialog/add-edit-dossier-attribute-dialog.component';
import { TableItemComponent } from './table-item/table-item.component';
@Component({ @Component({
templateUrl: './dossier-attributes-listing-screen.component.html', templateUrl: './dossier-attributes-listing-screen.component.html',
@ -35,19 +28,8 @@ import { TableItemComponent } from './table-item/table-item.component';
entitiesService: DossierAttributesService, entitiesService: DossierAttributesService,
component: DossierAttributesListingScreenComponent, component: DossierAttributesListingScreenComponent,
}), }),
imports: [
IqserListingModule,
TranslateModule,
CircleButtonComponent,
AsyncPipe,
NgIf,
InputWithActionComponent,
IconButtonComponent,
NgForOf,
TableItemComponent,
],
}) })
export default class DossierAttributesListingScreenComponent extends ListingComponent<DossierAttributeConfig> implements OnInit { export class DossierAttributesListingScreenComponent extends ListingComponent<DossierAttributeConfig> implements OnInit {
readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID); readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);
readonly iconButtonTypes = IconButtonTypes; readonly iconButtonTypes = IconButtonTypes;
readonly currentUser = getCurrentUser<User>(); readonly currentUser = getCurrentUser<User>();

View File

@ -0,0 +1,38 @@
import { NgModule } from '@angular/core';
import { DossierAttributesListingScreenComponent } from './dossier-attributes-listing-screen.component';
import { CommonModule } from '@angular/common';
import {
CircleButtonComponent,
IconButtonComponent,
InputWithActionComponent,
IqserHelpModeModule,
IqserListingModule,
IqserRoutes,
} from '@iqser/common-ui';
import { RouterModule } from '@angular/router';
import { TranslateModule } from '@ngx-translate/core';
import { MatTooltipModule } from '@angular/material/tooltip';
import { TableItemComponent } from './table-item/table-item.component';
const routes: IqserRoutes = [
{
path: '',
component: DossierAttributesListingScreenComponent,
},
];
@NgModule({
declarations: [DossierAttributesListingScreenComponent, TableItemComponent],
imports: [
CommonModule,
RouterModule.forChild(routes),
TranslateModule,
CircleButtonComponent,
InputWithActionComponent,
IconButtonComponent,
IqserHelpModeModule,
MatTooltipModule,
IqserListingModule,
],
})
export class DossierAttributesListingModule {}

View File

@ -1,16 +1,11 @@
import { Component, EventEmitter, Input, Output } from '@angular/core'; import { Component, EventEmitter, Input, Output } from '@angular/core';
import { DossierAttributeConfig, IDossierAttributeConfig } from '@red/domain'; import { DossierAttributeConfig, IDossierAttributeConfig } from '@red/domain';
import { dossierAttributeTypesTranslations } from '@translations/dossier-attribute-types-translations'; import { dossierAttributeTypesTranslations } from '@translations/dossier-attribute-types-translations';
import { MatTooltip } from '@angular/material/tooltip';
import { TranslateModule } from '@ngx-translate/core';
import { CircleButtonComponent } from '@iqser/common-ui';
import { NgIf } from '@angular/common';
@Component({ @Component({
selector: 'redaction-table-item [attribute] [canEditDossierAttributes]', selector: 'redaction-table-item [attribute] [canEditDossierAttributes]',
templateUrl: './table-item.component.html', templateUrl: './table-item.component.html',
styleUrls: ['./table-item.component.scss'], styleUrls: ['./table-item.component.scss'],
imports: [MatTooltip, TranslateModule, CircleButtonComponent, NgIf],
}) })
export class TableItemComponent { export class TableItemComponent {
readonly translations = dossierAttributeTypesTranslations; readonly translations = dossierAttributeTypesTranslations;

View File

@ -47,7 +47,6 @@
<div class="dialog-actions"> <div class="dialog-actions">
<iqser-icon-button <iqser-icon-button
(action)="save()" (action)="save()"
[buttonId]="'save-dossier-state'"
[disabled]="disabled" [disabled]="disabled"
[label]="'add-edit-dossier-state.save' | translate" [label]="'add-edit-dossier-state.save' | translate"
[submit]="true" [submit]="true"

View File

@ -1,14 +1,10 @@
import { Component, Inject } from '@angular/core'; import { Component, Inject } from '@angular/core';
import { BaseDialogComponent, CircleButtonComponent, IconButtonComponent } from '@iqser/common-ui'; import { BaseDialogComponent } from '@iqser/common-ui';
import { ReactiveFormsModule, Validators } from '@angular/forms'; import { Validators } from '@angular/forms';
import { IDossierState } from '@red/domain'; import { IDossierState } from '@red/domain';
import { DossierStatesService } from '@services/entity-services/dossier-states.service'; import { DossierStatesService } from '@services/entity-services/dossier-states.service';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { TranslateModule } from '@ngx-translate/core';
import { ColorPickerModule } from 'ngx-color-picker';
import { MatIcon } from '@angular/material/icon';
import { NgIf } from '@angular/common';
export interface AddEditDossierStateDialogData { export interface AddEditDossierStateDialogData {
readonly dossierState?: IDossierState; readonly dossierState?: IDossierState;
@ -18,7 +14,6 @@ export interface AddEditDossierStateDialogData {
@Component({ @Component({
templateUrl: './add-edit-dossier-state-dialog.component.html', templateUrl: './add-edit-dossier-state-dialog.component.html',
styleUrls: ['./add-edit-dossier-state-dialog.component.scss'], styleUrls: ['./add-edit-dossier-state-dialog.component.scss'],
imports: [TranslateModule, ReactiveFormsModule, ColorPickerModule, MatIcon, NgIf, IconButtonComponent, CircleButtonComponent],
}) })
export class AddEditDossierStateDialogComponent extends BaseDialogComponent { export class AddEditDossierStateDialogComponent extends BaseDialogComponent {
readonly type = this.data.dossierState ? 'edit' : 'create'; readonly type = this.data.dossierState ? 'edit' : 'create';

View File

@ -4,12 +4,12 @@
</div> </div>
<div class="dialog-content"> <div class="dialog-content">
<div [innerHTML]="'confirm-delete-dossier-state.warning' | translate: translateArgs" class="heading"></div> <div [innerHTML]="'confirm-delete-dossier-state.warning' | translate : translateArgs" class="heading"></div>
<form *ngIf="data.dossierCount !== 0 && data.otherStates.length > 0" [formGroup]="form" class="mt-16"> <form *ngIf="data.dossierCount !== 0 && data.otherStates.length > 0" [formGroup]="form" class="mt-16">
<div class="iqser-input-group"> <div class="iqser-input-group">
<mat-checkbox color="primary" formControlName="replace"> <mat-checkbox color="primary" formControlName="replace">
{{ 'confirm-delete-dossier-state.question' | translate: { count: data.dossierCount } }} {{ 'confirm-delete-dossier-state.question' | translate : { count: data.dossierCount } }}
</mat-checkbox> </mat-checkbox>
</div> </div>
@ -30,12 +30,7 @@
</div> </div>
<div class="dialog-actions"> <div class="dialog-actions">
<iqser-icon-button <iqser-icon-button (action)="save()" [label]="label | translate" [type]="iconButtonTypes.primary"></iqser-icon-button>
(action)="save()"
[buttonId]="'confirm-delete-dossier-state'"
[label]="label | translate"
[type]="iconButtonTypes.primary"
></iqser-icon-button>
<div [translate]="'confirm-delete-dossier-state.cancel'" class="all-caps-label cancel" mat-dialog-close></div> <div [translate]="'confirm-delete-dossier-state.cancel'" class="all-caps-label cancel" mat-dialog-close></div>
</div> </div>

View File

@ -1,18 +1,13 @@
import { Component, HostListener, Inject } from '@angular/core'; import { Component, Inject } from '@angular/core';
import { DossierState } from '@red/domain'; import { DossierState } from '@red/domain';
import { MAT_DIALOG_DATA, MatDialogClose, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { FormBuilder, ReactiveFormsModule } from '@angular/forms'; import { FormBuilder } from '@angular/forms';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { combineLatest, firstValueFrom } from 'rxjs'; import { combineLatest, firstValueFrom } from 'rxjs';
import { DossierStatesService } from '@services/entity-services/dossier-states.service'; import { DossierStatesService } from '@services/entity-services/dossier-states.service';
import { CircleButtonComponent, IconButtonComponent, IconButtonTypes, LoadingService, Toaster } from '@iqser/common-ui'; import { IconButtonTypes, LoadingService, Toaster } from '@iqser/common-ui';
import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service'; import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service';
import { ArchivedDossiersService } from '@services/dossiers/archived-dossiers.service'; import { ArchivedDossiersService } from '@services/dossiers/archived-dossiers.service';
import { TranslateModule } from '@ngx-translate/core';
import { MatCheckbox } from '@angular/material/checkbox';
import { MatFormField } from '@angular/material/form-field';
import { MatOption, MatSelect } from '@angular/material/select';
import { NgForOf, NgIf } from '@angular/common';
export interface ConfirmDeleteDossierStateDialogData { export interface ConfirmDeleteDossierStateDialogData {
readonly toBeDeletedState: DossierState; readonly toBeDeletedState: DossierState;
@ -23,19 +18,6 @@ export interface ConfirmDeleteDossierStateDialogData {
@Component({ @Component({
templateUrl: './confirm-delete-dossier-state-dialog.component.html', templateUrl: './confirm-delete-dossier-state-dialog.component.html',
styleUrls: ['./confirm-delete-dossier-state-dialog.component.scss'], styleUrls: ['./confirm-delete-dossier-state-dialog.component.scss'],
imports: [
TranslateModule,
ReactiveFormsModule,
MatCheckbox,
MatFormField,
MatSelect,
MatOption,
NgForOf,
IconButtonComponent,
CircleButtonComponent,
NgIf,
MatDialogClose,
],
}) })
export class ConfirmDeleteDossierStateDialogComponent { export class ConfirmDeleteDossierStateDialogComponent {
readonly iconButtonTypes = IconButtonTypes; readonly iconButtonTypes = IconButtonTypes;
@ -71,16 +53,6 @@ export class ConfirmDeleteDossierStateDialogComponent {
}); });
} }
@HostListener('window:keyup.Escape')
onEscape() {
this._dialogRef.close();
}
@HostListener('window:keyup.Enter')
async onEnter() {
await this.save();
}
async save() { async save() {
this._loadingService.start(); this._loadingService.start();

View File

@ -1,50 +1,33 @@
import { AsyncPipe, NgIf } from '@angular/common';
import { Component, OnDestroy, OnInit } from '@angular/core'; import { Component, OnDestroy, OnInit } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { InputWithActionComponent } from '@common-ui/inputs/input-with-action/input-with-action.component';
import { import {
defaultDialogConfig, defaultDialogConfig,
IconButtonComponent,
IconButtonTypes, IconButtonTypes,
IqserListingModule,
ListingComponent, ListingComponent,
listingProvidersFactory, listingProvidersFactory,
LoadingService, LoadingService,
TableColumnConfig, TableColumnConfig,
} from '@iqser/common-ui'; } from '@iqser/common-ui';
import { SortingOrders } from '@iqser/common-ui/lib/sorting';
import { getParam } from '@iqser/common-ui/lib/utils';
import { TranslateModule } from '@ngx-translate/core';
import { type DonutChartConfig, DOSSIER_TEMPLATE_ID, type DossierState } from '@red/domain'; import { type DonutChartConfig, DOSSIER_TEMPLATE_ID, type DossierState } from '@red/domain';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { firstValueFrom, Observable } from 'rxjs';
import { DossierStatesMapService } from '@services/entity-services/dossier-states-map.service'; import { DossierStatesMapService } from '@services/entity-services/dossier-states-map.service';
import { map, tap } from 'rxjs/operators';
import { DossierStatesService } from '@services/entity-services/dossier-states.service'; import { DossierStatesService } from '@services/entity-services/dossier-states.service';
import { PermissionsService } from '@services/permissions.service'; import { PermissionsService } from '@services/permissions.service';
import { DonutChartComponent } from '@shared/components/donut-chart/donut-chart.component'; import { MatDialog } from '@angular/material/dialog';
import { firstValueFrom, Observable } from 'rxjs';
import { map, tap } from 'rxjs/operators';
import { import {
AddEditDossierStateDialogComponent, AddEditDossierStateDialogComponent,
AddEditDossierStateDialogData, AddEditDossierStateDialogData,
} from '../add-edit-dossier-state-dialog/add-edit-dossier-state-dialog.component'; } from '../add-edit-dossier-state-dialog/add-edit-dossier-state-dialog.component';
import { DossierStatesTableItemComponent } from '../dossier-states-table-item/dossier-states-table-item.component'; import { SortingOrders } from '@iqser/common-ui/lib/sorting';
import { getParam } from '@iqser/common-ui/lib/utils';
@Component({ @Component({
templateUrl: './dossier-states-listing-screen.component.html', templateUrl: './dossier-states-listing-screen.component.html',
styleUrls: ['./dossier-states-listing-screen.component.scss'], styleUrls: ['./dossier-states-listing-screen.component.scss'],
providers: listingProvidersFactory(DossierStatesListingScreenComponent), providers: listingProvidersFactory(DossierStatesListingScreenComponent),
imports: [
IqserListingModule,
DonutChartComponent,
AsyncPipe,
NgIf,
TranslateModule,
InputWithActionComponent,
IconButtonComponent,
DossierStatesTableItemComponent,
],
}) })
export default class DossierStatesListingScreenComponent extends ListingComponent<DossierState> implements OnInit, OnDestroy { export class DossierStatesListingScreenComponent extends ListingComponent<DossierState> implements OnInit, OnDestroy {
readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID); readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID);
readonly iconButtonTypes = IconButtonTypes; readonly iconButtonTypes = IconButtonTypes;
readonly tableHeaderLabel = _('dossier-states-listing.table-header.title'); readonly tableHeaderLabel = _('dossier-states-listing.table-header.title');

View File

@ -0,0 +1,62 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { DossierStatesListingScreenComponent } from './dossier-states-listing-screen/dossier-states-listing-screen.component';
import { DossierStatesTableItemComponent } from './dossier-states-table-item/dossier-states-table-item.component';
import { RouterModule } from '@angular/router';
import {
CircleButtonComponent,
IconButtonComponent,
InputWithActionComponent,
IqserHelpModeModule,
IqserListingModule,
} from '@iqser/common-ui';
import { MatTooltipModule } from '@angular/material/tooltip';
import { TranslateModule } from '@ngx-translate/core';
import { DonutChartComponent } from '@shared/components/donut-chart/donut-chart.component';
import { AdminSideNavComponent } from '../../shared/components/admin-side-nav/admin-side-nav.component';
import { DossierTemplateActionsComponent } from '../../shared/components/dossier-template-actions/dossier-template-actions.component';
import { DossierTemplateBreadcrumbsComponent } from '../../shared/components/dossier-template-breadcrumbs/dossier-template-breadcrumbs.component';
import { AddEditDossierStateDialogComponent } from './add-edit-dossier-state-dialog/add-edit-dossier-state-dialog.component';
import { ReactiveFormsModule } from '@angular/forms';
import { ColorPickerModule } from 'ngx-color-picker';
import { MatIconModule } from '@angular/material/icon';
import { ConfirmDeleteDossierStateDialogComponent } from './confirm-delete-dossier-state-dialog/confirm-delete-dossier-state-dialog.component';
import { MatSelectModule } from '@angular/material/select';
import { MatDialogModule } from '@angular/material/dialog';
import { MatCheckboxModule } from '@angular/material/checkbox';
@NgModule({
declarations: [
DossierStatesListingScreenComponent,
DossierStatesTableItemComponent,
AddEditDossierStateDialogComponent,
ConfirmDeleteDossierStateDialogComponent,
],
imports: [
CommonModule,
RouterModule.forChild([
{
path: '',
component: DossierStatesListingScreenComponent,
},
]),
MatDialogModule,
CircleButtonComponent,
IqserHelpModeModule,
MatTooltipModule,
IconButtonComponent,
TranslateModule,
IqserListingModule,
DonutChartComponent,
AdminSideNavComponent,
DossierTemplateActionsComponent,
InputWithActionComponent,
DossierTemplateBreadcrumbsComponent,
ReactiveFormsModule,
ColorPickerModule,
MatIconModule,
MatCheckboxModule,
MatSelectModule,
],
})
export class DossierStatesListingModule {}

View File

@ -13,20 +13,18 @@
<span class="small-label">{{ state.dossierCount }}</span> <span class="small-label">{{ state.dossierCount }}</span>
</div> </div>
<div [id]="'dossier_' + (state.name | snakeCase)" class="cell"> <div class="cell">
<div *ngIf="permissionsService.canPerformDossierStatesActions()" class="action-buttons"> <div *ngIf="permissionsService.canPerformDossierStatesActions()" class="action-buttons">
<div [attr.help-mode-key]="'edit_delete_dossier_state'"> <div [attr.help-mode-key]="'edit_delete_dossier_state'">
<iqser-circle-button <iqser-circle-button
(action)="openEditStateDialog(state)" (action)="openEditStateDialog(state)"
[tooltip]="'dossier-states-listing.action.edit' | translate" [tooltip]="'dossier-states-listing.action.edit' | translate"
[buttonId]="'dossier-state-edit-button'"
icon="iqser:edit" icon="iqser:edit"
></iqser-circle-button> ></iqser-circle-button>
<iqser-circle-button <iqser-circle-button
(action)="openConfirmDeleteStateDialog(state)" (action)="openConfirmDeleteStateDialog(state)"
[tooltip]="'dossier-states-listing.action.delete' | translate" [tooltip]="'dossier-states-listing.action.delete' | translate"
[buttonId]="'dossier-state-delete-button'"
icon="iqser:trash" icon="iqser:trash"
></iqser-circle-button> ></iqser-circle-button>
</div> </div>

View File

@ -1,6 +1,6 @@
import { Component, inject, Input } from '@angular/core'; import { Component, inject, Input } from '@angular/core';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { CircleButtonComponent, defaultDialogConfig, EntitiesService } from '@iqser/common-ui'; import { defaultDialogConfig, EntitiesService } from '@iqser/common-ui';
import { DossierState, IDossierState } from '@red/domain'; import { DossierState, IDossierState } from '@red/domain';
import { PermissionsService } from '@services/permissions.service'; import { PermissionsService } from '@services/permissions.service';
import { import {
@ -11,16 +11,11 @@ import {
ConfirmDeleteDossierStateDialogComponent, ConfirmDeleteDossierStateDialogComponent,
ConfirmDeleteDossierStateDialogData, ConfirmDeleteDossierStateDialogData,
} from '../confirm-delete-dossier-state-dialog/confirm-delete-dossier-state-dialog.component'; } from '../confirm-delete-dossier-state-dialog/confirm-delete-dossier-state-dialog.component';
import { MatTooltip } from '@angular/material/tooltip';
import { TranslateModule } from '@ngx-translate/core';
import { NgIf } from '@angular/common';
import { SnakeCasePipe } from '@common-ui/pipes/snake-case.pipe';
@Component({ @Component({
selector: 'redaction-dossier-states-table-item', selector: 'redaction-dossier-states-table-item',
templateUrl: './dossier-states-table-item.component.html', templateUrl: './dossier-states-table-item.component.html',
styleUrls: ['./dossier-states-table-item.component.scss'], styleUrls: ['./dossier-states-table-item.component.scss'],
imports: [MatTooltip, CircleButtonComponent, TranslateModule, NgIf, SnakeCasePipe],
}) })
export class DossierStatesTableItemComponent { export class DossierStatesTableItemComponent {
readonly #dialog = inject(MatDialog); readonly #dialog = inject(MatDialog);

View File

@ -1,13 +1,7 @@
import { AsyncPipe } from '@angular/common';
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { InputWithActionComponent } from '@common-ui/inputs/input-with-action/input-with-action.component';
import { import {
CircleButtonComponent,
IconButtonComponent,
IconButtonTypes, IconButtonTypes,
IqserAllowDirective,
IqserListingModule,
IqserPermissionsService, IqserPermissionsService,
ListingComponent, ListingComponent,
listingProvidersFactory, listingProvidersFactory,
@ -15,7 +9,6 @@ import {
TableColumnConfig, TableColumnConfig,
} from '@iqser/common-ui'; } from '@iqser/common-ui';
import { getCurrentUser } from '@iqser/common-ui/lib/users'; import { getCurrentUser } from '@iqser/common-ui/lib/users';
import { TranslateModule } from '@ngx-translate/core';
import { DossierTemplate, User } from '@red/domain'; import { DossierTemplate, User } from '@red/domain';
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service'; import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
import { RouterHistoryService } from '@services/router-history.service'; import { RouterHistoryService } from '@services/router-history.service';
@ -23,7 +16,6 @@ import { Roles } from '@users/roles';
import { UserPreferenceService } from '@users/user-preference.service'; import { UserPreferenceService } from '@users/user-preference.service';
import { firstValueFrom } from 'rxjs'; import { firstValueFrom } from 'rxjs';
import { AdminDialogService } from '../../../services/admin-dialog.service'; import { AdminDialogService } from '../../../services/admin-dialog.service';
import { TableItemComponent } from '../table-item/table-item.component';
@Component({ @Component({
templateUrl: './dossier-templates-listing-screen.component.html', templateUrl: './dossier-templates-listing-screen.component.html',
@ -33,18 +25,8 @@ import { TableItemComponent } from '../table-item/table-item.component';
entitiesService: DossierTemplatesService, entitiesService: DossierTemplatesService,
component: DossierTemplatesListingScreenComponent, component: DossierTemplatesListingScreenComponent,
}), }),
imports: [
IqserListingModule,
TranslateModule,
AsyncPipe,
CircleButtonComponent,
IqserAllowDirective,
InputWithActionComponent,
IconButtonComponent,
TableItemComponent,
],
}) })
export default class DossierTemplatesListingScreenComponent extends ListingComponent<DossierTemplate> implements OnInit { export class DossierTemplatesListingScreenComponent extends ListingComponent<DossierTemplate> implements OnInit {
readonly iconButtonTypes = IconButtonTypes; readonly iconButtonTypes = IconButtonTypes;
readonly roles = Roles; readonly roles = Roles;
readonly currentUser = getCurrentUser<User>(); readonly currentUser = getCurrentUser<User>();
@ -76,7 +58,7 @@ export default class DossierTemplatesListingScreenComponent extends ListingCompo
openBulkDeleteTemplatesDialog() { openBulkDeleteTemplatesDialog() {
return this._dialogService.openDialog('confirm', null, () => { return this._dialogService.openDialog('confirm', null, () => {
this._loadingService.loadWhile(this.#deleteTemplates()); this._loadingService.loadWhile(this._deleteTemplates());
}); });
} }
@ -84,7 +66,7 @@ export default class DossierTemplatesListingScreenComponent extends ListingCompo
this._dialogService.openDialog('addCloneDossierTemplate', {}); this._dialogService.openDialog('addCloneDossierTemplate', {});
} }
async #deleteTemplates(templateIds = this.listingService.selected.map(d => d.dossierTemplateId)) { private async _deleteTemplates(templateIds = this.listingService.selected.map(d => d.dossierTemplateId)) {
await firstValueFrom(this._dossierTemplatesService.delete(templateIds)); await firstValueFrom(this._dossierTemplatesService.delete(templateIds));
} }
} }

View File

@ -0,0 +1,39 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { SharedModule } from '@shared/shared.module';
import { TableItemComponent } from './table-item/table-item.component';
import { DossierTemplatesListingScreenComponent } from './dossier-templates-listing-screen/dossier-templates-listing-screen.component';
import {
CircleButtonComponent,
IconButtonComponent,
InputWithActionComponent,
IqserAllowDirective,
IqserHelpModeModule,
IqserListingModule,
IqserRoutes,
} from '@iqser/common-ui';
import { TranslateModule } from '@ngx-translate/core';
import { DossierTemplateActionsComponent } from '../../shared/components/dossier-template-actions/dossier-template-actions.component';
import { IqserUsersModule } from '@iqser/common-ui/lib/users';
const routes: IqserRoutes = [{ path: '', component: DossierTemplatesListingScreenComponent }];
@NgModule({
declarations: [TableItemComponent, DossierTemplatesListingScreenComponent],
imports: [
RouterModule.forChild(routes),
CommonModule,
SharedModule,
IqserUsersModule,
TranslateModule,
IqserListingModule,
IqserHelpModeModule,
DossierTemplateActionsComponent,
CircleButtonComponent,
IconButtonComponent,
InputWithActionComponent,
IqserAllowDirective,
],
})
export class DossierTemplatesListingModule {}

View File

@ -4,20 +4,12 @@ import { BehaviorSubject, Observable } from 'rxjs';
import { DossierTemplateStatsService } from '@services/entity-services/dossier-template-stats.service'; import { DossierTemplateStatsService } from '@services/entity-services/dossier-template-stats.service';
import { switchMap } from 'rxjs/operators'; import { switchMap } from 'rxjs/operators';
import { dossierTemplateStatusTranslations } from '@translations/dossier-template-status-translations'; import { dossierTemplateStatusTranslations } from '@translations/dossier-template-status-translations';
import { MatTooltip } from '@angular/material/tooltip';
import { AsyncPipe, NgIf } from '@angular/common';
import { MatIcon } from '@angular/material/icon';
import { TranslateModule } from '@ngx-translate/core';
import { DossierTemplateActionsComponent } from '../../../shared/components/dossier-template-actions/dossier-template-actions.component';
import { InitialsAvatarComponent } from '@common-ui/users';
import { DatePipe } from '@shared/pipes/date.pipe';
@Component({ @Component({
selector: 'redaction-table-item [dossierTemplate]', selector: 'redaction-table-item [dossierTemplate]',
templateUrl: './table-item.component.html', templateUrl: './table-item.component.html',
styleUrls: ['./table-item.component.scss'], styleUrls: ['./table-item.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [MatTooltip, NgIf, AsyncPipe, MatIcon, TranslateModule, DatePipe, DossierTemplateActionsComponent, InitialsAvatarComponent],
}) })
export class TableItemComponent implements OnChanges { export class TableItemComponent implements OnChanges {
@Input() dossierTemplate!: DossierTemplate; @Input() dossierTemplate!: DossierTemplate;

Some files were not shown because too many files have changed in this diff Show More