working entire platform search
This commit is contained in:
parent
567980baa4
commit
5f8f11108f
@ -1,4 +1,2 @@
|
||||
<router-outlet></router-outlet>
|
||||
<redaction-full-page-loading-indicator
|
||||
[displayed]="loadingService.isLoading | async"
|
||||
></redaction-full-page-loading-indicator>
|
||||
<redaction-full-page-loading-indicator [displayed]="loadingService.isLoading$ | async"></redaction-full-page-loading-indicator>
|
||||
|
||||
@ -32,6 +32,7 @@ import { GlobalErrorHandler } from '@utils/global-error-handler.service';
|
||||
import { REDMissingTranslationHandler } from '@utils/missing-translations-handler';
|
||||
import { configurationInitializer } from '@app-config/configuration.initializer';
|
||||
import { AppConfigService } from '@app-config/app-config.service';
|
||||
import { SpotlightSearchComponent } from '@components/spotlight-search/spotlight-search.component';
|
||||
|
||||
export function httpLoaderFactory(httpClient: HttpClient) {
|
||||
return new TranslateHttpLoader(httpClient, '/assets/i18n/', '.json');
|
||||
@ -49,7 +50,15 @@ function cleanupBaseUrl(baseUrl: string) {
|
||||
|
||||
const screens = [BaseScreenComponent, DownloadsListScreenComponent, UserProfileScreenComponent];
|
||||
|
||||
const components = [AppComponent, LogoComponent, AuthErrorComponent, ToastComponent, NotificationsComponent, ...screens];
|
||||
const components = [
|
||||
AppComponent,
|
||||
LogoComponent,
|
||||
AuthErrorComponent,
|
||||
ToastComponent,
|
||||
NotificationsComponent,
|
||||
SpotlightSearchComponent,
|
||||
...screens
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
declarations: [...components],
|
||||
|
||||
@ -6,11 +6,7 @@
|
||||
<mat-icon svgIcon="red:menu"></mat-icon>
|
||||
</button>
|
||||
<mat-menu #menuNav="matMenu">
|
||||
<button
|
||||
mat-menu-item
|
||||
routerLink="/main/dossiers"
|
||||
translate="top-bar.navigation-items.dossiers"
|
||||
></button>
|
||||
<button mat-menu-item routerLink="/main/dossiers" translate="top-bar.navigation-items.dossiers"></button>
|
||||
<button
|
||||
*ngIf="appStateService.activeDossier"
|
||||
[routerLink]="'/main/dossiers/' + appStateService.activeDossierId"
|
||||
@ -20,22 +16,14 @@
|
||||
</button>
|
||||
<button
|
||||
*ngIf="appStateService.activeFile"
|
||||
[routerLink]="
|
||||
'/main/dossiers/' +
|
||||
appStateService.activeDossierId +
|
||||
'/file/' +
|
||||
appStateService.activeFile.fileId
|
||||
"
|
||||
[routerLink]="'/main/dossiers/' + appStateService.activeDossierId + '/file/' + appStateService.activeFile.fileId"
|
||||
mat-menu-item
|
||||
>
|
||||
{{ appStateService.activeFile.filename }}
|
||||
</button>
|
||||
</mat-menu>
|
||||
</div>
|
||||
<div
|
||||
*ngIf="permissionsService.isUser()"
|
||||
class="menu flex-2 visible-lg breadcrumbs-container"
|
||||
>
|
||||
<div *ngIf="permissionsService.isUser()" class="menu flex-2 visible-lg breadcrumbs-container">
|
||||
<a
|
||||
*ngIf="dossiersView"
|
||||
[routerLinkActiveOptions]="{ exact: true }"
|
||||
@ -49,15 +37,8 @@
|
||||
{{ 'top-bar.navigation-items.back' | translate }}
|
||||
</a>
|
||||
<ng-container *ngIf="dossiersView">
|
||||
<mat-icon
|
||||
*ngIf="!appStateService.activeDossier"
|
||||
class="primary"
|
||||
svgIcon="red:arrow-down"
|
||||
></mat-icon>
|
||||
<mat-icon
|
||||
*ngIf="appStateService.activeDossier"
|
||||
svgIcon="red:arrow-right"
|
||||
></mat-icon>
|
||||
<mat-icon *ngIf="!appStateService.activeDossier" class="primary" svgIcon="red:arrow-down"></mat-icon>
|
||||
<mat-icon *ngIf="appStateService.activeDossier" svgIcon="red:arrow-right"></mat-icon>
|
||||
<a
|
||||
*ngIf="appStateService.activeDossier"
|
||||
[routerLinkActiveOptions]="{ exact: true }"
|
||||
@ -70,12 +51,7 @@
|
||||
<mat-icon *ngIf="appStateService.activeFile" svgIcon="red:arrow-right"></mat-icon>
|
||||
<a
|
||||
*ngIf="appStateService.activeFile"
|
||||
[routerLink]="
|
||||
'/main/dossiers/' +
|
||||
appStateService.activeDossierId +
|
||||
'/file/' +
|
||||
appStateService.activeFile.fileId
|
||||
"
|
||||
[routerLink]="'/main/dossiers/' + appStateService.activeDossierId + '/file/' + appStateService.activeFile.fileId"
|
||||
class="breadcrumb"
|
||||
routerLinkActive="active"
|
||||
>
|
||||
@ -90,26 +66,21 @@
|
||||
<div class="app-name">{{ titleService.getTitle() }}</div>
|
||||
</div>
|
||||
<div class="menu right flex-2">
|
||||
<redaction-notifications
|
||||
*ngIf="userPreferenceService.areDevFeaturesEnabled"
|
||||
class="mr-8"
|
||||
></redaction-notifications>
|
||||
<redaction-circle-button
|
||||
*ngIf="!isSearchScreen"
|
||||
[icon]="'red:search'"
|
||||
(action)="openSpotlightSearch()"
|
||||
[tooltip]="'search.header-label' | translate"
|
||||
tooltipPosition="below"
|
||||
></redaction-circle-button>
|
||||
|
||||
<redaction-user-button
|
||||
[matMenuTriggerFor]="userMenu"
|
||||
[showDot]="showPendingDownloadsDot"
|
||||
[user]="user"
|
||||
></redaction-user-button>
|
||||
<redaction-notifications *ngIf="userPreferenceService.areDevFeaturesEnabled" class="mr-8"></redaction-notifications>
|
||||
|
||||
<redaction-user-button [matMenuTriggerFor]="userMenu" [showDot]="showPendingDownloadsDot" [user]="user"></redaction-user-button>
|
||||
|
||||
<mat-menu #userMenu="matMenu" xPosition="before">
|
||||
<ng-container *ngFor="let item of userMenuItems; trackBy: trackByName">
|
||||
<button
|
||||
(click)="(item.action)"
|
||||
*ngIf="item.show"
|
||||
[routerLink]="item.routerLink"
|
||||
mat-menu-item
|
||||
translate
|
||||
>
|
||||
<button (click)="(item.action)" *ngIf="item.show" [routerLink]="item.routerLink" mat-menu-item translate>
|
||||
{{ item.name }}
|
||||
</button>
|
||||
</ng-container>
|
||||
@ -124,10 +95,5 @@
|
||||
<div class="divider"></div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
*ngIf="userPreferenceService.areDevFeaturesEnabled"
|
||||
class="dev-mode"
|
||||
translate="dev-mode"
|
||||
></div>
|
||||
|
||||
<div *ngIf="userPreferenceService.areDevFeaturesEnabled" class="dev-mode" translate="dev-mode"></div>
|
||||
<router-outlet></router-outlet>
|
||||
|
||||
@ -7,6 +7,10 @@ import { Router } from '@angular/router';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { FileDownloadService } from '@upload-download/services/file-download.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { SpotlightSearchComponent } from '@components/spotlight-search/spotlight-search.component';
|
||||
import { SpotlightSearchAction } from '@components/spotlight-search/spotlight-search-action';
|
||||
import { SpotlightSearchDialogData } from '@components/spotlight-search/spotlight-search-dialog-data';
|
||||
|
||||
interface MenuItem {
|
||||
name: string;
|
||||
@ -44,6 +48,8 @@ export class BaseScreenComponent {
|
||||
}
|
||||
];
|
||||
|
||||
showSearch = false;
|
||||
|
||||
constructor(
|
||||
readonly appStateService: AppStateService,
|
||||
readonly permissionsService: PermissionsService,
|
||||
@ -52,10 +58,11 @@ export class BaseScreenComponent {
|
||||
readonly fileDownloadService: FileDownloadService,
|
||||
private readonly _router: Router,
|
||||
private readonly _userService: UserService,
|
||||
private readonly _translateService: TranslateService
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _dialog: MatDialog
|
||||
) {
|
||||
_router.events.subscribe(() => {
|
||||
this._dossiersView = _router.url.indexOf('/main/dossiers') === 0;
|
||||
this._dossiersView = _router.url.includes('/main/dossiers') && !this.isSearchScreen;
|
||||
});
|
||||
}
|
||||
|
||||
@ -65,6 +72,10 @@ export class BaseScreenComponent {
|
||||
return this._dossiersView;
|
||||
}
|
||||
|
||||
get isSearchScreen() {
|
||||
return this._router.url.includes('/search');
|
||||
}
|
||||
|
||||
get user() {
|
||||
return this._userService.user;
|
||||
}
|
||||
@ -77,6 +88,44 @@ export class BaseScreenComponent {
|
||||
return this._translateService.langs;
|
||||
}
|
||||
|
||||
openSpotlightSearch() {
|
||||
const spotlightSearchActions: SpotlightSearchAction[] = [
|
||||
{
|
||||
text: this._translateService.instant('search.this-dossier'),
|
||||
icon: 'red:enter',
|
||||
hide: !this.appStateService.activeDossier,
|
||||
action: query => this._searchThisDossier(query)
|
||||
},
|
||||
{
|
||||
text: this._translateService.instant('search.entire-platform'),
|
||||
icon: 'red:enter',
|
||||
action: query => this._searchEntirePlatform(query)
|
||||
}
|
||||
];
|
||||
|
||||
this._dialog.open(SpotlightSearchComponent, {
|
||||
data: {
|
||||
actionsConfig: spotlightSearchActions,
|
||||
placeholder: this._translateService.instant('search.placeholder')
|
||||
} as SpotlightSearchDialogData
|
||||
});
|
||||
}
|
||||
|
||||
private _searchThisDossier(query: string) {
|
||||
this._router
|
||||
.navigate(['main/dossiers/search'], {
|
||||
queryParams: {
|
||||
query: query,
|
||||
dossierId: this.appStateService.activeDossier.dossierId
|
||||
}
|
||||
})
|
||||
.then();
|
||||
}
|
||||
|
||||
private _searchEntirePlatform(query: string) {
|
||||
this._router.navigate(['main/dossiers/search'], { queryParams: { query: query } }).then();
|
||||
}
|
||||
|
||||
logout() {
|
||||
this._userService.logout();
|
||||
}
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
export interface SpotlightSearchAction {
|
||||
text: string;
|
||||
action: (query: string) => void;
|
||||
icon?: string;
|
||||
hide?: boolean;
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
import { SpotlightSearchAction } from './spotlight-search-action';
|
||||
|
||||
export interface SpotlightSearchDialogData {
|
||||
actionsConfig: SpotlightSearchAction[];
|
||||
placeholder: string;
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
<div class="spotlight-wrapper">
|
||||
<form [formGroup]="formGroup">
|
||||
<div class="search d-flex">
|
||||
<input
|
||||
tabindex="-1"
|
||||
id="query"
|
||||
type="text"
|
||||
formControlName="query"
|
||||
autocomplete="off"
|
||||
class="spotlight-row"
|
||||
[placeholder]="data.placeholder"
|
||||
/>
|
||||
|
||||
<mat-icon class="mr-34" *ngIf="(showActions$ | async) === false" [svgIcon]="'red:search'"></mat-icon>
|
||||
|
||||
<redaction-circle-button
|
||||
*ngIf="showActions$ | async"
|
||||
class="mr-24"
|
||||
(action)="close()"
|
||||
icon="red:close"
|
||||
></redaction-circle-button>
|
||||
</div>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<ng-container *ngIf="showActions$ | async">
|
||||
<ng-container *ngFor="let item of data.actionsConfig">
|
||||
<button
|
||||
*ngIf="!item.hide"
|
||||
#actions
|
||||
(keydown.space)="$event.preventDefault()"
|
||||
(keyup.space)="$event.preventDefault()"
|
||||
tabindex="0"
|
||||
class="spotlight-row focus pointer"
|
||||
(click)="item.action(formGroup.get('query').value); close()"
|
||||
>
|
||||
<mat-icon class="mr-16" [svgIcon]="item.icon"></mat-icon>
|
||||
<span>{{ item.text }}</span>
|
||||
</button>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</form>
|
||||
</div>
|
||||
@ -0,0 +1,51 @@
|
||||
@import 'apps/red-ui/src/assets/styles/red-variables';
|
||||
|
||||
.spotlight-wrapper {
|
||||
overflow: hidden;
|
||||
width: 750px;
|
||||
margin: auto;
|
||||
position: absolute;
|
||||
top: 15%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 10px 30px 0 rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.spotlight-row {
|
||||
display: block;
|
||||
width: 750px;
|
||||
height: 60px;
|
||||
margin: auto;
|
||||
text-align: left;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
outline: none;
|
||||
color: $grey-1;
|
||||
padding: 0 24px;
|
||||
background-color: $white;
|
||||
}
|
||||
|
||||
.focus:focus {
|
||||
background-color: $grey-2;
|
||||
}
|
||||
|
||||
.search {
|
||||
background-color: $white;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 1px;
|
||||
background-color: rgba(226, 228, 233, 0.9);
|
||||
}
|
||||
|
||||
input {
|
||||
width: 668px !important;
|
||||
}
|
||||
|
||||
mat-icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
@ -0,0 +1,68 @@
|
||||
import { ChangeDetectionStrategy, Component, ElementRef, HostListener, Inject, QueryList, ViewChildren } from '@angular/core';
|
||||
import { FormBuilder } from '@angular/forms';
|
||||
import { debounceTime, map, startWith, tap } from 'rxjs/operators';
|
||||
import { debounce } from '@utils/debounce';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { SpotlightSearchDialogData } from '@components/spotlight-search/spotlight-search-dialog-data';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-spotlight-search',
|
||||
templateUrl: './spotlight-search.component.html',
|
||||
styleUrls: ['./spotlight-search.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class SpotlightSearchComponent {
|
||||
@ViewChildren('actions')
|
||||
private readonly _actions: QueryList<ElementRef>;
|
||||
private _currentActionIdx = 0;
|
||||
|
||||
formGroup = this._formBuilder.group({ query: [''] });
|
||||
query$ = this.formGroup.get('query').valueChanges.pipe(startWith(''));
|
||||
showActions$ = this.query$.pipe(
|
||||
debounceTime(300),
|
||||
tap(value => this._restoreFocusOnAction(value === '')),
|
||||
map(value => value !== '')
|
||||
);
|
||||
|
||||
constructor(
|
||||
private readonly _formBuilder: FormBuilder,
|
||||
private readonly _dialogRef: MatDialogRef<SpotlightSearchComponent>,
|
||||
@Inject(MAT_DIALOG_DATA)
|
||||
readonly data: SpotlightSearchDialogData
|
||||
) {}
|
||||
|
||||
close() {
|
||||
this._dialogRef.close();
|
||||
}
|
||||
|
||||
@HostListener('document:keyup', ['$event'])
|
||||
handleKeyDown(event: KeyboardEvent) {
|
||||
console.log(event);
|
||||
if (event.code === 'ArrowDown' && this._actions) {
|
||||
this._currentActionIdx++;
|
||||
return this._restoreFocusOnAction(this._currentActionIdx === this._actions.length);
|
||||
}
|
||||
|
||||
if (event.code === 'ArrowUp' && this._actions) {
|
||||
if (this._currentActionIdx === 0) this._currentActionIdx = this._actions.length - 1;
|
||||
else this._currentActionIdx--;
|
||||
return this._restoreFocusOnAction();
|
||||
}
|
||||
|
||||
if (['Tab'].includes(event.code)) {
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById('query').focus();
|
||||
let query = this.formGroup.get('query').value as string;
|
||||
if (event.code === 'Backspace') query = query.substring(0, query.length - 1);
|
||||
else if (event.key.length === 1) query = query + event.key;
|
||||
this.formGroup.patchValue({ query: query });
|
||||
}
|
||||
|
||||
@debounce(50)
|
||||
private _restoreFocusOnAction(resetToFirst = false) {
|
||||
if (resetToFirst) this._currentActionIdx = 0;
|
||||
this._actions.find((_, index) => index === this._currentActionIdx)?.nativeElement.focus();
|
||||
}
|
||||
}
|
||||
@ -21,42 +21,39 @@ export class RedRoleGuard implements CanActivate {
|
||||
this._loadingService.stop();
|
||||
obs.next(false);
|
||||
obs.complete();
|
||||
} else {
|
||||
// we have at least 1 RED Role -> if it's not user he must be admin
|
||||
return;
|
||||
}
|
||||
// we have at least 1 RED Role -> if it's not user he must be admin
|
||||
|
||||
if (
|
||||
this._userService.user.isUserAdmin &&
|
||||
!this._userService.user.isAdmin &&
|
||||
!(
|
||||
state.url.startsWith('/main/admin/users') ||
|
||||
state.url.startsWith('/main/my-profile')
|
||||
)
|
||||
) {
|
||||
this._router.navigate(['/main/admin/users']);
|
||||
obs.next(false);
|
||||
obs.complete();
|
||||
return;
|
||||
}
|
||||
if (
|
||||
this._userService.user.isUserAdmin &&
|
||||
!this._userService.user.isAdmin &&
|
||||
!(state.url.startsWith('/main/admin/users') || state.url.startsWith('/main/my-profile'))
|
||||
) {
|
||||
this._router.navigate(['/main/admin/users']);
|
||||
obs.next(false);
|
||||
obs.complete();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._userService.isUser() && state.url.startsWith('/main/dossiers')) {
|
||||
this._router.navigate(['/main/admin']);
|
||||
obs.next(false);
|
||||
obs.complete();
|
||||
return;
|
||||
}
|
||||
if (route.data.requiredRoles) {
|
||||
if (this._userService.hasAnyRole(route.data.requiredRoles)) {
|
||||
obs.next(true);
|
||||
obs.complete();
|
||||
} else {
|
||||
this._router.navigate(['/main/dossiers']);
|
||||
obs.next(false);
|
||||
obs.complete();
|
||||
}
|
||||
} else {
|
||||
if (!this._userService.isUser() && state.url.startsWith('/main/dossiers')) {
|
||||
this._router.navigate(['/main/admin']);
|
||||
obs.next(false);
|
||||
obs.complete();
|
||||
return;
|
||||
}
|
||||
if (route.data.requiredRoles) {
|
||||
if (this._userService.hasAnyRole(route.data.requiredRoles)) {
|
||||
obs.next(true);
|
||||
obs.complete();
|
||||
} else {
|
||||
this._router.navigate(['/main/dossiers']);
|
||||
obs.next(false);
|
||||
obs.complete();
|
||||
}
|
||||
} else {
|
||||
obs.next(true);
|
||||
obs.complete();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -27,10 +27,6 @@ mat-slide-toggle {
|
||||
line-height: 33px;
|
||||
}
|
||||
|
||||
.mr-24 {
|
||||
margin-right: 24px;
|
||||
}
|
||||
|
||||
mat-slide-toggle {
|
||||
margin-left: 8px;
|
||||
margin-right: 5px;
|
||||
|
||||
@ -1,15 +1,7 @@
|
||||
<button
|
||||
(click)="scroll(buttonType.TOP)"
|
||||
[hidden]="!showScroll(buttonType.TOP)"
|
||||
class="scroll-button top pointer"
|
||||
>
|
||||
<button (click)="scroll(buttonType.top)" [hidden]="!showScroll(buttonType.top)" class="scroll-button top pointer">
|
||||
<mat-icon svgIcon="red:arrow-down-o"></mat-icon>
|
||||
</button>
|
||||
|
||||
<button
|
||||
(click)="scroll(buttonType.BOTTOM)"
|
||||
[hidden]="!showScroll(buttonType.BOTTOM)"
|
||||
class="scroll-button bottom pointer"
|
||||
>
|
||||
<button (click)="scroll(buttonType.bottom)" [hidden]="!showScroll(buttonType.bottom)" class="scroll-button bottom pointer">
|
||||
<mat-icon svgIcon="red:arrow-down-o"></mat-icon>
|
||||
</button>
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
import { Component, HostListener, Input } from '@angular/core';
|
||||
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
|
||||
|
||||
enum ButtonType {
|
||||
TOP = 'top',
|
||||
BOTTOM = 'bottom'
|
||||
}
|
||||
const ButtonTypes = {
|
||||
top: 'top',
|
||||
bottom: 'bottom'
|
||||
} as const;
|
||||
|
||||
type ButtonType = keyof typeof ButtonTypes;
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-scroll-button',
|
||||
@ -12,7 +14,7 @@ enum ButtonType {
|
||||
styleUrls: ['./scroll-button.component.scss']
|
||||
})
|
||||
export class ScrollButtonComponent {
|
||||
buttonType = ButtonType;
|
||||
buttonType = ButtonTypes;
|
||||
|
||||
@Input()
|
||||
scrollViewport: CdkVirtualScrollViewport;
|
||||
@ -20,9 +22,7 @@ export class ScrollButtonComponent {
|
||||
itemSize: number;
|
||||
|
||||
scroll(type: ButtonType): void {
|
||||
const viewportSize =
|
||||
(this.scrollViewport?.getViewportSize() - this.itemSize) *
|
||||
(type === ButtonType.TOP ? -1 : 1);
|
||||
const viewportSize = (this.scrollViewport?.getViewportSize() - this.itemSize) * (type === ButtonTypes.top ? -1 : 1);
|
||||
const scrollOffset = this.scrollViewport?.measureScrollOffset('top');
|
||||
this.scrollViewport?.scrollToOffset(scrollOffset + viewportSize, 'smooth');
|
||||
}
|
||||
@ -37,10 +37,10 @@ export class ScrollButtonComponent {
|
||||
|
||||
@HostListener('document:keyup', ['$event'])
|
||||
spaceAndPageDownScroll(event: KeyboardEvent): void {
|
||||
if (['Space', 'PageDown'].includes(event.code)) {
|
||||
this.scroll(ButtonType.BOTTOM);
|
||||
} else if (['PageUp'].includes(event.code)) {
|
||||
this.scroll(ButtonType.TOP);
|
||||
if (['Space', 'PageDown'].includes(event.code) && (event.target as any).tagName === 'BODY') {
|
||||
this.scroll(ButtonTypes.bottom);
|
||||
} else if (['PageUp'].includes(event.code) && (event.target as any).tagName === 'BODY') {
|
||||
this.scroll(ButtonTypes.top);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,21 +1,21 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { DossierListingScreenComponent } from './screens/dossier-listing-screen/dossier-listing-screen.component';
|
||||
import { CompositeRouteGuard } from '@guards/composite-route.guard';
|
||||
import { AuthGuard } from '../auth/auth.guard';
|
||||
import { RedRoleGuard } from '../auth/red-role.guard';
|
||||
import { AppStateGuard } from '@state/app-state.guard';
|
||||
import { DossierOverviewScreenComponent } from './screens/dossier-overview-screen/dossier-overview-screen.component';
|
||||
import { SearchScreenComponent } from './screens/search-screen/search-screen.component';
|
||||
import { FilePreviewScreenComponent } from './screens/file-preview-screen/file-preview-screen.component';
|
||||
import { DossierOverviewScreenComponent } from './screens/dossier-overview-screen/dossier-overview-screen.component';
|
||||
|
||||
const routes = [
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: DossierListingScreenComponent,
|
||||
path: 'search',
|
||||
component: SearchScreenComponent,
|
||||
canActivate: [CompositeRouteGuard],
|
||||
data: {
|
||||
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard],
|
||||
reuse: true
|
||||
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard]
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -35,6 +35,16 @@ const routes = [
|
||||
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard],
|
||||
reuse: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
pathMatch: 'full',
|
||||
component: DossierListingScreenComponent,
|
||||
canActivate: [CompositeRouteGuard],
|
||||
data: {
|
||||
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard],
|
||||
reuse: true
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@ -49,8 +49,9 @@ import { RecategorizeImageDialogComponent } from './dialogs/recategorize-image-d
|
||||
import { EditDossierAttributesComponent } from './dialogs/edit-dossier-dialog/attributes/edit-dossier-attributes.component';
|
||||
import { DossiersService } from './services/dossiers.service';
|
||||
import { DossierDetailsStatsComponent } from './components/dossier-details-stats/dossier-details-stats.component';
|
||||
import { SearchScreenComponent } from './screens/search-screen/search-screen.component';
|
||||
|
||||
const screens = [DossierListingScreenComponent, DossierOverviewScreenComponent, FilePreviewScreenComponent];
|
||||
const screens = [DossierListingScreenComponent, DossierOverviewScreenComponent, FilePreviewScreenComponent, SearchScreenComponent];
|
||||
|
||||
const dialogs = [
|
||||
AddDossierDialogComponent,
|
||||
|
||||
@ -0,0 +1,98 @@
|
||||
<section *ngIf="searchResults$ | async as searchResult">
|
||||
<redaction-page-header
|
||||
[showCloseButton]="true"
|
||||
[searchPlaceholder]="'search.placeholder' | translate"
|
||||
[searchWidth]="600"
|
||||
></redaction-page-header>
|
||||
|
||||
<div class="overlay-shadow"></div>
|
||||
|
||||
<div class="red-content-inner">
|
||||
<div class="content-container">
|
||||
<redaction-table-header
|
||||
[tableHeaderLabel]="'search-screen.table-header'"
|
||||
[tableColConfigs]="tableColConfigs"
|
||||
></redaction-table-header>
|
||||
|
||||
<redaction-empty-state
|
||||
*ngIf="searchResult.length === 0"
|
||||
[icon]="'red:search'"
|
||||
[text]="'search-screen.no-data' | translate"
|
||||
></redaction-empty-state>
|
||||
|
||||
<cdk-virtual-scroll-viewport [itemSize]="itemSize" redactionHasScrollbar>
|
||||
<div
|
||||
*cdkVirtualFor="let item of sortedDisplayedEntities$ | async; trackBy: trackByPrimaryKey"
|
||||
[class.pointer]="true"
|
||||
[routerLink]="item.routerLink"
|
||||
class="table-item"
|
||||
>
|
||||
<div class="filename">
|
||||
<div [matTooltip]="item.fileName" class="table-item-title heading" matTooltipPosition="above">
|
||||
<span
|
||||
*ngIf="item.highlights.filename; else defaultFilename"
|
||||
class="highlights"
|
||||
[innerHTML]="item.highlights.filename[0]"
|
||||
></span>
|
||||
<ng-template #defaultFilename>{{ item.fileName }}</ng-template>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="item.highlights['sections.text'] as highlights">
|
||||
<div class="small-label" *ngIf="highlights.length > 0">
|
||||
<span class="highlights" [innerHTML]="highlights[0]"></span>
|
||||
</div>
|
||||
<div class="small-label" *ngIf="highlights.length > 1">
|
||||
<span class="highlights" [innerHTML]="highlights[1]"></span>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<div class="small-label" *ngIf="item.unmatched as unmatched">
|
||||
<span>
|
||||
{{ 'search-screen.missing' | translate }}:<span *ngFor="let term of unmatched"
|
||||
> <s>{{ term }}</s></span
|
||||
>. {{ 'search-screen.must-contain' | translate }}:
|
||||
<span
|
||||
*ngFor="let term of unmatched"
|
||||
(click)="$event.stopPropagation(); updateNavigation({ query: search$.getValue(), mustContain: term })"
|
||||
> <u>{{ term }}</u></span
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<redaction-status-bar
|
||||
[small]="true"
|
||||
[config]="[
|
||||
{
|
||||
color: item.status,
|
||||
label: item.status | translate,
|
||||
length: 1
|
||||
}
|
||||
]"
|
||||
></redaction-status-bar>
|
||||
</div>
|
||||
|
||||
<div class="small-label">
|
||||
{{ item.dossierName }}
|
||||
</div>
|
||||
|
||||
<div class="small-label stats-subtitle">
|
||||
<div>
|
||||
<mat-icon svgIcon="red:pages"></mat-icon>
|
||||
{{ item.pages }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="scrollbar-placeholder"></div>
|
||||
</div>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
|
||||
<redaction-scroll-button
|
||||
*ngIf="(screenStateService.noData$ | async) === false"
|
||||
[itemSize]="itemSize"
|
||||
[scrollViewport]="scrollViewport"
|
||||
></redaction-scroll-button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@ -0,0 +1,38 @@
|
||||
@import 'apps/red-ui/src/assets/styles/red-mixins';
|
||||
@import 'apps/red-ui/src/assets/styles/red-variables';
|
||||
|
||||
.content-container {
|
||||
position: relative;
|
||||
|
||||
cdk-virtual-scroll-viewport {
|
||||
::ng-deep.cdk-virtual-scroll-content-wrapper {
|
||||
grid-template-columns: 2fr 1fr 1fr auto 11px;
|
||||
|
||||
.table-item {
|
||||
> div {
|
||||
height: 85px;
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
.status-container {
|
||||
width: 160px;
|
||||
padding-right: 13px;
|
||||
}
|
||||
|
||||
.highlights em {
|
||||
background-color: #fffcc4;
|
||||
}
|
||||
|
||||
.highlights {
|
||||
@include line-clamp(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.has-scrollbar:hover {
|
||||
::ng-deep.cdk-virtual-scroll-content-wrapper {
|
||||
grid-template-columns: 2fr 1fr 1fr auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,155 @@
|
||||
import { Component, Injector, OnDestroy } from '@angular/core';
|
||||
import { BaseListingComponent } from '../../../shared/base/base-listing.component';
|
||||
import { SearchControllerService, SearchResult } from '@redaction/red-ui-http';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { debounceTime, map, switchMap, tap } from 'rxjs/operators';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { MatchedDocument } from '@redaction/red-ui-http';
|
||||
import { TableColConfig } from '../../../shared/components/table-col-name/table-col-name.component';
|
||||
import { FilterService } from '../../../shared/services/filter.service';
|
||||
import { SearchService } from '../../../shared/services/search.service';
|
||||
import { ScreenStateService } from '../../../shared/services/screen-state.service';
|
||||
import { SortingService } from '../../../../services/sorting.service';
|
||||
import { AppStateService } from '../../../../state/app-state.service';
|
||||
import { FileStatusWrapper } from '../../../../models/file/file-status.wrapper';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { LoadingService } from '../../../../services/loading.service';
|
||||
|
||||
interface ListItem {
|
||||
fileName: string;
|
||||
matchedDocument: MatchedDocument;
|
||||
unmatched: string[] | null;
|
||||
highlights: { [key: string]: string[] };
|
||||
routerLink: string;
|
||||
status: string;
|
||||
dossierName: string;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
@Component({
|
||||
templateUrl: './search-screen.component.html',
|
||||
styleUrls: ['./search-screen.component.scss'],
|
||||
providers: [FilterService, SearchService, ScreenStateService, SortingService]
|
||||
})
|
||||
export class SearchScreenComponent extends BaseListingComponent<ListItem> implements OnDestroy {
|
||||
protected readonly _primaryKey = 'fileName';
|
||||
readonly itemSize = 85;
|
||||
|
||||
readonly search$ = new BehaviorSubject<string>(null);
|
||||
readonly searchResults$: Observable<ListItem[]> = this.search$.asObservable().pipe(
|
||||
switchMap(query => this._search(query)),
|
||||
map(searchResult => this._toMatchedDocuments(searchResult)),
|
||||
map(documents => this._toListItems(documents)),
|
||||
tap(result => this.screenStateService.setEntities(result)),
|
||||
tap(() => this._loadingService.stop())
|
||||
);
|
||||
|
||||
private _dossierId: string;
|
||||
|
||||
readonly tableColConfigs: TableColConfig[] = [
|
||||
{
|
||||
label: this._translateService.instant('search-screen.cols.document')
|
||||
},
|
||||
{
|
||||
label: this._translateService.instant('search-screen.cols.status')
|
||||
},
|
||||
{
|
||||
label: this._translateService.instant('search-screen.cols.dossier')
|
||||
},
|
||||
{
|
||||
label: this._translateService.instant('search-screen.cols.pages')
|
||||
}
|
||||
];
|
||||
|
||||
constructor(
|
||||
protected readonly _injector: Injector,
|
||||
private readonly _searchControllerService: SearchControllerService,
|
||||
private readonly _activatedRoute: ActivatedRoute,
|
||||
private readonly _appStateService: AppStateService,
|
||||
private readonly _translateService: TranslateService,
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _router: Router
|
||||
) {
|
||||
super(_injector);
|
||||
|
||||
this.addSubscription = _activatedRoute.queryParamMap
|
||||
.pipe(
|
||||
tap(() => this._loadingService.start()),
|
||||
map(value => ({ query: value.get('query'), dossierId: value.get('dossierId') })),
|
||||
tap(mappedValue => this._updateValues(mappedValue))
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
this.addSubscription = this.searchService.searchForm
|
||||
.get('query')
|
||||
.valueChanges.pipe(debounceTime(300))
|
||||
.subscribe(value => this.updateNavigation({ query: value }));
|
||||
}
|
||||
|
||||
setInitialConfig() {
|
||||
return;
|
||||
}
|
||||
|
||||
updateNavigation({ query, mustContain }: { readonly query: string; readonly mustContain?: string }) {
|
||||
const newQuery = query?.replace(mustContain, `"${mustContain}"`);
|
||||
const queryParams = newQuery && newQuery !== '' ? { query: newQuery } : {};
|
||||
const queryParamsHandling = this._dossierId ? 'merge' : '';
|
||||
this._router
|
||||
.navigate([], {
|
||||
queryParams,
|
||||
queryParamsHandling
|
||||
})
|
||||
.then();
|
||||
}
|
||||
|
||||
private _search(query: string): Observable<SearchResult> {
|
||||
return this._searchControllerService.search({
|
||||
dossierId: this._dossierId,
|
||||
queryString: query ?? '',
|
||||
from: 0,
|
||||
returnSections: true,
|
||||
size: 100
|
||||
});
|
||||
}
|
||||
|
||||
private _updateValues({ query, dossierId }: { readonly query: string; readonly dossierId: string }) {
|
||||
this._dossierId = dossierId;
|
||||
this.searchService.searchValue = query;
|
||||
this.search$.next(query);
|
||||
}
|
||||
|
||||
private _getFileWrapper(dossierId: string, fileId: string): FileStatusWrapper {
|
||||
return this._appStateService.getFileById(dossierId, fileId);
|
||||
}
|
||||
|
||||
private _getDossierWrapper(dossierId: string) {
|
||||
return this._appStateService.getDossierById(dossierId);
|
||||
}
|
||||
|
||||
private _toMatchedDocuments({ matchedDocuments }: SearchResult) {
|
||||
return matchedDocuments.filter(doc => doc.score > 0 && doc.matchedTerms.length > 0);
|
||||
}
|
||||
|
||||
private _toListItems(matchedDocuments: MatchedDocument[]) {
|
||||
return matchedDocuments
|
||||
.map<ListItem>(document => {
|
||||
const fileStatus = this._getFileWrapper(document.dossierId, document.fileId);
|
||||
if (!fileStatus) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const { dossierId, dossierName } = this._getDossierWrapper(document.dossierId);
|
||||
return {
|
||||
matchedDocument: document,
|
||||
unmatched: document.unmatchedTerms.length ? document.unmatchedTerms : null,
|
||||
highlights: document.highlights,
|
||||
status: fileStatus.status,
|
||||
pages: fileStatus.numberOfPages,
|
||||
dossierName: dossierName,
|
||||
fileName: fileStatus.filename,
|
||||
routerLink: `/main/dossiers/${dossierId}/file/${fileStatus.fileId}`
|
||||
} as ListItem;
|
||||
})
|
||||
.filter(value => value);
|
||||
}
|
||||
}
|
||||
@ -9,10 +9,7 @@ import { DomSanitizer } from '@angular/platform-browser';
|
||||
exports: [MatIconModule]
|
||||
})
|
||||
export class IconsModule {
|
||||
constructor(
|
||||
private readonly _iconRegistry: MatIconRegistry,
|
||||
private readonly _sanitizer: DomSanitizer
|
||||
) {
|
||||
constructor(private readonly _iconRegistry: MatIconRegistry, private readonly _sanitizer: DomSanitizer) {
|
||||
const icons = [
|
||||
'add',
|
||||
'analyse',
|
||||
@ -38,6 +35,7 @@ export class IconsModule {
|
||||
'download',
|
||||
'edit',
|
||||
'entries',
|
||||
'enter',
|
||||
'error',
|
||||
'exclude-pages',
|
||||
'exit-fullscreen',
|
||||
|
||||
@ -17,7 +17,7 @@ export abstract class BaseListingComponent<T> extends AutoUnsubscribeComponent i
|
||||
readonly permissionsService: PermissionsService;
|
||||
readonly filterService: FilterService;
|
||||
readonly sortingService: SortingService;
|
||||
readonly searchService: SearchService<T>;
|
||||
readonly searchService: SearchService;
|
||||
readonly screenStateService: ScreenStateService<T>;
|
||||
|
||||
readonly sortedDisplayedEntities$: Observable<T[]>;
|
||||
|
||||
@ -11,11 +11,7 @@
|
||||
<span *ngIf="hint" [translate]="hint" class="hint"></span>
|
||||
|
||||
<!-- Search-->
|
||||
<mat-icon
|
||||
*ngIf="type === 'search' && !hasContent"
|
||||
class="icon-right"
|
||||
svgIcon="red:search"
|
||||
></mat-icon>
|
||||
<mat-icon *ngIf="type === 'search' && !hasContent" class="icon-right" svgIcon="red:search"></mat-icon>
|
||||
|
||||
<redaction-circle-button
|
||||
(action)="clearContent()"
|
||||
@ -23,8 +19,7 @@
|
||||
[disabled]="form.invalid"
|
||||
[size]="25"
|
||||
icon="red:close"
|
||||
>
|
||||
</redaction-circle-button>
|
||||
></redaction-circle-button>
|
||||
|
||||
<!-- Submit-->
|
||||
<redaction-circle-button
|
||||
@ -34,7 +29,6 @@
|
||||
[icon]="icon"
|
||||
[isSubmit]="true"
|
||||
[size]="25"
|
||||
>
|
||||
</redaction-circle-button>
|
||||
></redaction-circle-button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@ -29,7 +29,7 @@ export class InputWithActionComponent {
|
||||
}
|
||||
|
||||
clearContent() {
|
||||
this.form.patchValue({ query: '' });
|
||||
this.form.patchValue({ query: '' }, { emitEvent: true });
|
||||
}
|
||||
|
||||
executeAction($event?: MouseEvent) {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<div class="page-header">
|
||||
<div *ngIf="pageLabel" class="breadcrumb">{{ pageLabel }}</div>
|
||||
|
||||
<div class="filters" *ngIf="filters$ | async as filters">
|
||||
<div class="filters" [style.max-width]="computedWidth" [style.width]="computedWidth" *ngIf="filters$ | async as filters">
|
||||
<div translate="filters.filter-by" *ngIf="filters.length"></div>
|
||||
|
||||
<ng-container *ngFor="let config of filters; trackBy: trackByLabel">
|
||||
@ -16,6 +16,7 @@
|
||||
</ng-container>
|
||||
|
||||
<redaction-input-with-action
|
||||
[width]="searchWidth"
|
||||
*ngIf="searchPlaceholder"
|
||||
[form]="searchService.searchForm"
|
||||
[placeholder]="searchPlaceholder"
|
||||
|
||||
@ -11,17 +11,18 @@ import { combineLatest, Observable, of } from 'rxjs';
|
||||
templateUrl: './page-header.component.html',
|
||||
styleUrls: ['./page-header.component.scss']
|
||||
})
|
||||
export class PageHeaderComponent<T> {
|
||||
export class PageHeaderComponent {
|
||||
@Input() pageLabel: string;
|
||||
@Input() showCloseButton: boolean;
|
||||
@Input() actionConfigs: ActionConfig[];
|
||||
@Input() buttonConfigs: ButtonConfig[];
|
||||
@Input() searchPlaceholder: string;
|
||||
@Input() searchWidth: number | 'full';
|
||||
|
||||
readonly filters$ = this.filterService?.filterGroups$.pipe(map(all => all.filter(f => f.icon)));
|
||||
readonly showResetFilters$ = this._showResetFilters$;
|
||||
|
||||
constructor(@Optional() readonly filterService: FilterService, @Optional() readonly searchService: SearchService<T>) {}
|
||||
constructor(@Optional() readonly filterService: FilterService, @Optional() readonly searchService: SearchService) {}
|
||||
|
||||
get _showResetFilters$(): Observable<boolean> {
|
||||
if (!this.filterService) return of(false);
|
||||
@ -36,6 +37,10 @@ export class PageHeaderComponent<T> {
|
||||
);
|
||||
}
|
||||
|
||||
get computedWidth() {
|
||||
return this.searchWidth === 'full' ? '100%' : `${this.searchWidth}px`;
|
||||
}
|
||||
|
||||
resetFilters(): void {
|
||||
this.filterService.reset();
|
||||
this.searchService.reset();
|
||||
|
||||
@ -25,8 +25,8 @@ export class SyncWidthDirective implements AfterViewInit, OnDestroy {
|
||||
@debounce(10)
|
||||
matchWidth() {
|
||||
const headerItems = this._elementRef.nativeElement.children;
|
||||
// const tableRows = document.getElementsByClassName(this.redactionSyncWidth);
|
||||
const tableRows = this._elementRef.nativeElement.parentElement.getElementsByClassName(this.redactionSyncWidth);
|
||||
const tableRows = document.getElementsByClassName(this.redactionSyncWidth);
|
||||
// const tableRows = this._elementRef.nativeElement.parentElement.getElementsByClassName(this.redactionSyncWidth);
|
||||
|
||||
if (!tableRows || !tableRows.length) {
|
||||
return;
|
||||
|
||||
@ -26,7 +26,7 @@ export class ScreenStateService<T> {
|
||||
readonly areSomeEntitiesSelected$ = this._areSomeEntitiesSelected$;
|
||||
readonly notAllEntitiesSelected$ = this._notAllEntitiesSelected$;
|
||||
|
||||
constructor(private readonly _filterService: FilterService, private readonly _searchService: SearchService<T>) {
|
||||
constructor(private readonly _filterService: FilterService, private readonly _searchService: SearchService) {
|
||||
// setInterval(() => {
|
||||
// console.log('All entities subs: ', this._allEntities$.observers);
|
||||
// console.log('Displayed entities subs: ', this._displayedEntities$.observers);
|
||||
|
||||
@ -3,7 +3,7 @@ import { FormBuilder } from '@angular/forms';
|
||||
import { startWith } from 'rxjs/operators';
|
||||
|
||||
@Injectable()
|
||||
export class SearchService<T> {
|
||||
export class SearchService {
|
||||
private _searchKey: string;
|
||||
|
||||
readonly searchForm = this._formBuilder.group({
|
||||
@ -14,7 +14,7 @@ export class SearchService<T> {
|
||||
|
||||
constructor(private readonly _formBuilder: FormBuilder) {}
|
||||
|
||||
searchIn(entities: T[]) {
|
||||
searchIn<T>(entities: T[]) {
|
||||
if (!this._searchKey) return entities;
|
||||
|
||||
const searchValue = this.searchValue.toLowerCase();
|
||||
@ -29,11 +29,15 @@ export class SearchService<T> {
|
||||
return this.searchForm.get('query').value;
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
this.searchForm.reset({ query: '' });
|
||||
set searchValue(value: string) {
|
||||
this.searchForm.patchValue({ query: value });
|
||||
}
|
||||
|
||||
private _searchField(entity: T): string {
|
||||
return entity[this._searchKey].toLowerCase();
|
||||
reset(): void {
|
||||
this.searchForm.reset({ query: '' }, { emitEvent: true });
|
||||
}
|
||||
|
||||
private _searchField<T>(entity: T): string {
|
||||
return entity[this._searchKey].toString().toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ export class LoadingService {
|
||||
private readonly _loadingEvent = new BehaviorSubject(false);
|
||||
private _loadingStarted: number;
|
||||
|
||||
get isLoading(): Observable<boolean> {
|
||||
get isLoading$(): Observable<boolean> {
|
||||
return this._loadingEvent.asObservable();
|
||||
}
|
||||
|
||||
|
||||
@ -10,7 +10,7 @@ export class RouterHistoryService {
|
||||
|
||||
constructor(private readonly _router: Router) {
|
||||
this._router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe((event: NavigationEnd) => {
|
||||
if (event.url.startsWith('/main/dossiers')) {
|
||||
if (event.url.startsWith('/main/dossiers') && !event.url.includes('/search')) {
|
||||
this._lastDossiersScreen = event.url;
|
||||
}
|
||||
});
|
||||
|
||||
@ -9,7 +9,7 @@ import {
|
||||
ReanalysisControllerService,
|
||||
StatusControllerService
|
||||
} from '@redaction/red-ui-http';
|
||||
import { Toaster } from '../services/toaster.service';
|
||||
import { Toaster } from '@services/toaster.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Event, NavigationEnd, ResolveStart, Router } from '@angular/router';
|
||||
import { UserService } from '@services/user.service';
|
||||
@ -156,7 +156,12 @@ export class AppStateService {
|
||||
}
|
||||
|
||||
private static _isDossierOverviewRoute(event: Event) {
|
||||
return event instanceof ResolveStart && event.url.includes('/main/dossiers/') && !event.url.includes('/file/');
|
||||
return (
|
||||
event instanceof ResolveStart &&
|
||||
event.url.includes('/main/dossiers/') &&
|
||||
!event.url.includes('/file/') &&
|
||||
!event.url.includes('/search')
|
||||
);
|
||||
}
|
||||
|
||||
private static _isRandomRoute(event: Event) {
|
||||
@ -205,7 +210,7 @@ export class AppStateService {
|
||||
}
|
||||
|
||||
getFileById(dossierId: string, fileId: string) {
|
||||
return this.getDossierById(dossierId).files.find(file => file.fileId === fileId);
|
||||
return this.getDossierById(dossierId)?.files.find(file => file.fileId === fileId);
|
||||
}
|
||||
|
||||
async loadAllDossiers(emitEvents: boolean = true) {
|
||||
|
||||
@ -1435,5 +1435,24 @@
|
||||
"hours": "hours",
|
||||
"day": "day",
|
||||
"days": "days"
|
||||
},
|
||||
"search": {
|
||||
"header-label": "Search entire platform",
|
||||
"placeholder": "Search for documents or document content",
|
||||
"this-dossier": "in this dossier",
|
||||
"entire-platform": "entire platform",
|
||||
"close": "Close"
|
||||
},
|
||||
"search-screen": {
|
||||
"table-header": "{{length}} search results",
|
||||
"cols": {
|
||||
"document": "Document",
|
||||
"status": "Status",
|
||||
"dossier": "Dossier",
|
||||
"pages": "Pages"
|
||||
},
|
||||
"no-data": "Please enter a keyword in the search input to look for documents or document content.",
|
||||
"missing": "Missing",
|
||||
"must-contain": "Must contain"
|
||||
}
|
||||
}
|
||||
|
||||
15
apps/red-ui/src/assets/icons/general/enter.svg
Normal file
15
apps/red-ui/src/assets/icons/general/enter.svg
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="14px" height="14px" viewBox="0 0 14 14" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<title>B99F3D5E-879A-47E3-A8C7-84877EE418F3</title>
|
||||
<g id="04.Search-all-in-Document" transform="translate(-319.000000, -144.000000)">
|
||||
<g id="Group-30" transform="translate(295.000000, 121.000000)">
|
||||
<g id="collapse" transform="translate(24.000000, 23.000000)" fill="currentColor"
|
||||
fill-rule="nonzero">
|
||||
<path
|
||||
d="M2.38,0 L2.38,7.98 L10.5,7.98 L8.75,6.16 L9.73,5.18 L13.16,8.68 L9.73,12.18 L8.75,11.2 L10.5,9.38 L1.96,9.38 L1.959,9.379 L0.98,9.38 L0.98,0 L2.38,0 Z"
|
||||
id="Combined-Shape"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 781 B |
@ -256,6 +256,10 @@ section.settings {
|
||||
margin-left: 14px;
|
||||
}
|
||||
|
||||
.mr-24 {
|
||||
margin-right: 24px;
|
||||
}
|
||||
|
||||
.pb-24 {
|
||||
padding-bottom: 24px;
|
||||
}
|
||||
@ -358,6 +362,10 @@ section.settings {
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.mr-34 {
|
||||
margin-right: 34px;
|
||||
}
|
||||
|
||||
.fit-content {
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
@ -78,3 +78,4 @@ export * from './legalBasisChangeRequest';
|
||||
export * from './manualLegalBasisChange';
|
||||
export * from './searchRequest';
|
||||
export * from './searchResult';
|
||||
export * from './matchedDocument';
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user