Remove claim details module (#105)

This commit is contained in:
Ildar Galeev 2022-12-21 15:45:11 +03:00 committed by GitHub
parent fdb6d848d9
commit 53893ef0ef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
100 changed files with 9 additions and 2470 deletions

View File

@ -29,8 +29,7 @@
"name": "main"
},
"sentryDsn": "https://public@sentry.example.com/1",
"keycloakEndpoint": "https://auth.example.com",
"fileStorageEndpoint": "https://fs.example.com"
"keycloakEndpoint": "https://auth.example.com"
}
```

21
package-lock.json generated
View File

@ -34,7 +34,6 @@
"@vality/swag-anapi-v2": "2.0.1-0405be2.0",
"@vality/swag-claim-management": "0.1.1-bfc2e6c.0",
"@vality/swag-dark-api": "0.1.1-a3f1678.0",
"@vality/swag-messages": "1.0.1-03e1014.0",
"@vality/swag-organizations": "1.0.1-cd6cc10.0",
"@vality/swag-payments": "0.1.1-d71aebc.0",
"@vality/swag-questionary-aggr-proxy": "0.1.1-1dc5add.0",
@ -5015,18 +5014,6 @@
"@angular/core": "^13.0.0"
}
},
"node_modules/@vality/swag-messages": {
"version": "1.0.1-03e1014.0",
"resolved": "https://registry.npmjs.org/@vality/swag-messages/-/swag-messages-1.0.1-03e1014.0.tgz",
"integrity": "sha512-iaKra/x2etzbD1kwjvmoKqXqX1yTd4RblMwhhKyUDFzSxWwija/jcsPpFqOznwWD5z95EncPsLXGvjsMwxr7Fg==",
"dependencies": {
"tslib": "^2.3.0"
},
"peerDependencies": {
"@angular/common": "^13.0.0",
"@angular/core": "^13.0.0"
}
},
"node_modules/@vality/swag-organizations": {
"version": "1.0.1-cd6cc10.0",
"resolved": "https://registry.npmjs.org/@vality/swag-organizations/-/swag-organizations-1.0.1-cd6cc10.0.tgz",
@ -21318,14 +21305,6 @@
"tslib": "^2.3.0"
}
},
"@vality/swag-messages": {
"version": "1.0.1-03e1014.0",
"resolved": "https://registry.npmjs.org/@vality/swag-messages/-/swag-messages-1.0.1-03e1014.0.tgz",
"integrity": "sha512-iaKra/x2etzbD1kwjvmoKqXqX1yTd4RblMwhhKyUDFzSxWwija/jcsPpFqOznwWD5z95EncPsLXGvjsMwxr7Fg==",
"requires": {
"tslib": "^2.3.0"
}
},
"@vality/swag-organizations": {
"version": "1.0.1-cd6cc10.0",
"resolved": "https://registry.npmjs.org/@vality/swag-organizations/-/swag-organizations-1.0.1-cd6cc10.0.tgz",

View File

@ -13,7 +13,7 @@
"i18n:extract": "transloco-keys-manager extract",
"i18n:check": "transloco-keys-manager find --emit-error-on-extra-keys",
"coverage": "npx http-server -c-1 -o -p 9875 ./coverage",
"lint-cmd": "eslint \"src/**/*.{ts,js,html}\" --max-warnings 1137",
"lint-cmd": "eslint \"src/**/*.{ts,js,html}\" --max-warnings 1067",
"lint": "npm run lint-cmd",
"lint-fix": "npm run lint-cmd -- --fix",
"lint-errors": "npm run lint-cmd -- --quiet",
@ -52,7 +52,6 @@
"@vality/swag-anapi-v2": "2.0.1-0405be2.0",
"@vality/swag-claim-management": "0.1.1-bfc2e6c.0",
"@vality/swag-dark-api": "0.1.1-a3f1678.0",
"@vality/swag-messages": "1.0.1-03e1014.0",
"@vality/swag-organizations": "1.0.1-cd6cc10.0",
"@vality/swag-payments": "0.1.1-d71aebc.0",
"@vality/swag-questionary-aggr-proxy": "0.1.1-1dc5add.0",

View File

@ -1,9 +0,0 @@
import { Injectable } from '@angular/core';
import { ConversationsService as ApiConversationsService } from '@vality/swag-messages';
import { createApi } from '../utils';
@Injectable({
providedIn: 'root',
})
export class ConversationsService extends createApi(ApiConversationsService) {}

View File

@ -1,3 +0,0 @@
export * from './messages.module';
export * from './conversations.service';
export * from './utils';

View File

@ -1,16 +0,0 @@
import { NgModule } from '@angular/core';
import { Configuration } from '@vality/swag-messages';
import { ConfigService } from '../../config';
@NgModule({
providers: [
{
provide: Configuration,
deps: [ConfigService],
useFactory: (configService: ConfigService) =>
new Configuration({ basePath: `${configService.apiEndpoint}/dark-api/v1` }),
},
],
})
export class MessagesModule {}

View File

@ -1,6 +0,0 @@
import { ConversationParam } from '@vality/swag-messages';
import { v4 as uuid } from 'uuid';
export const createSingleMessageConversationParams = (conversationId: string, text: string): ConversationParam[] => [
{ conversationId, messages: [{ messageId: uuid(), text }] },
];

View File

@ -1 +0,0 @@
export * from './create-single-message-conversation-params';

View File

@ -19,7 +19,6 @@ import * as Sentry from '@sentry/angular';
import { AnapiModule } from '@dsh/api/anapi';
import { ClaimManagementModule } from '@dsh/api/claim-management';
import { DarkApiModule } from '@dsh/api/dark-api';
import { MessagesModule } from '@dsh/api/messages';
import { PaymentsModule } from '@dsh/api/payments';
import { QuestionaryAggrProxyModule } from '@dsh/api/questionary-aggr-proxy';
import { UrlShortenerModule } from '@dsh/api/url-shortener';
@ -62,7 +61,6 @@ import { TranslocoHttpLoaderService } from './transloco-http-loader.service';
ClaimManagementModule,
AnapiModule,
PaymentsModule,
MessagesModule,
OrganizationsModule,
UrlShortenerModule,
QuestionaryAggrProxyModule,

View File

@ -12,6 +12,5 @@ export interface Config {
};
sentryDsn?: string;
keycloakEndpoint: string;
fileStorageEndpoint: string;
}
export const BASE_CONFIG = getBaseClass<Config>();

View File

@ -28,7 +28,7 @@ export const initializer =
},
loadUserProfileAtStartUp: true,
enableBearerInterceptor: true,
bearerExcludedUrls: ['/assets', configService.fileStorageEndpoint],
bearerExcludedUrls: ['/assets'],
bearerPrefix: 'Bearer',
}),
])

View File

@ -1,9 +0,0 @@
<dsh-panel>
<dsh-panel-header>
{{ comment$ | async }}
<div *transloco="let c; scope: 'components'; read: 'components.shared'">
<div *ngIf="isLoading$ | async">{{ c('loading') }}</div>
<div *ngIf="error$ | async">{{ c('httpError') }}</div>
</div>
</dsh-panel-header>
</dsh-panel>

View File

@ -1,25 +0,0 @@
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import { CommentModificationUnit } from '@vality/swag-claim-management';
import { CommentContainerService } from './comment-container.service';
@Component({
selector: 'dsh-comment-container',
templateUrl: 'comment-container.component.html',
providers: [CommentContainerService],
})
export class CommentContainerComponent implements OnChanges {
@Input() unit: CommentModificationUnit;
comment$ = this.commentContainerService.comment$;
isLoading$ = this.commentContainerService.isLoading$;
error$ = this.commentContainerService.error$;
constructor(private commentContainerService: CommentContainerService) {}
ngOnChanges({ unit }: SimpleChanges) {
if (unit.firstChange || unit.currentValue.commentId !== unit.previousValue.commentId) {
this.commentContainerService.receiveConversation(unit.currentValue.commentId);
}
}
}

View File

@ -1,16 +0,0 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FlexLayoutModule } from '@angular/flex-layout';
import { TranslocoModule } from '@ngneat/transloco';
import { MessagesModule } from '@dsh/api/messages';
import { LayoutModule } from '@dsh/components/layout';
import { CommentContainerComponent } from './comment-container.component';
@NgModule({
imports: [CommonModule, LayoutModule, FlexLayoutModule, TranslocoModule, MessagesModule],
declarations: [CommentContainerComponent],
exports: [CommentContainerComponent],
})
export class CommentContainerModule {}

View File

@ -1,35 +0,0 @@
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';
import { map, pluck, shareReplay, switchMap } from 'rxjs/operators';
import { ConversationsService } from '@dsh/api/messages';
import { takeError } from '@dsh/operators';
@Injectable()
export class CommentContainerService {
private receiveConversation$: Subject<string> = new Subject();
// eslint-disable-next-line @typescript-eslint/member-ordering
comment$ = this.receiveConversation$.pipe(
switchMap((conversationId) => this.conversationsService.getConversations({ conversationId: [conversationId] })),
map(({ conversations }) =>
conversations.reduce((acc, { messages }) => (messages.length > 0 ? messages[0] : acc), { text: '' })
),
pluck('text'),
shareReplay(1)
);
// eslint-disable-next-line @typescript-eslint/member-ordering
isLoading$ = this.comment$.pipe(shareReplay(1));
// eslint-disable-next-line @typescript-eslint/member-ordering
error$ = this.comment$.pipe(takeError, shareReplay(1));
constructor(private conversationsService: ConversationsService) {
this.comment$.subscribe();
}
receiveConversation(commentId: string) {
this.receiveConversation$.next(commentId);
}
}

View File

@ -1 +0,0 @@
export * from './comment-container.module';

View File

@ -1,13 +0,0 @@
<dsh-panel *transloco="let c; scope: 'components'; read: 'components.shared'" color="accent">
<dsh-panel-header>
<div *ngIf="isLoading$ | async">{{ c('loading') }}</div>
<div *ngIf="error$ | async">{{ c('httpError') }}</div>
<div *ngIf="fileInfo$ | async as fileInfo" fxLayout="row" fxLayoutAlign="space-between" fxLayoutGap="20px">
<div>{{ fileInfo.fileName }}</div>
<div fxLayout="row" fxLayoutGap="8px" fxLayoutAlign=" center">
<dsh-bi *ngIf="deletion" class="icon" icon="x" size="lg" (click)="deleteByCondition()"></dsh-bi>
<dsh-panel-header-icon class="icon" icon="save" (click)="download()"></dsh-panel-header-icon>
</div>
</div>
</dsh-panel-header>
</dsh-panel>

View File

@ -1,47 +0,0 @@
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { FileModificationUnit } from '@vality/swag-claim-management';
import { filter } from 'rxjs/operators';
import { ConfirmActionDialogComponent } from '@dsh/components/popups';
import { coerceBoolean } from '../../../../../utils';
import { FileContainerService } from './file-container.service';
@Component({
selector: 'dsh-file-container',
templateUrl: 'file-container.component.html',
styleUrls: ['file-container.component.scss'],
providers: [FileContainerService],
})
export class FileContainerComponent implements OnChanges {
@Input() unit: FileModificationUnit;
@Input() @coerceBoolean deletion = false;
@Output() delete = new EventEmitter<FileModificationUnit>();
fileInfo$ = this.fileContainerService.fileInfo$;
isLoading$ = this.fileContainerService.isLoading$;
error$ = this.fileContainerService.error$;
constructor(private fileContainerService: FileContainerService, private dialog: MatDialog) {}
ngOnChanges({ unit }: SimpleChanges) {
if (unit.firstChange || unit.currentValue.fileId !== unit.previousValue.fileId) {
this.fileContainerService.getFileInfo(unit.currentValue.fileId);
}
}
download() {
this.fileContainerService.downloadFile(this.unit.fileId);
}
deleteByCondition() {
this.dialog
.open(ConfirmActionDialogComponent)
.afterClosed()
.pipe(filter((r) => r === 'confirm'))
.subscribe(() => {
this.delete.emit(this.unit);
});
}
}

View File

@ -1,26 +0,0 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FlexLayoutModule } from '@angular/flex-layout';
import { MatDialogModule } from '@angular/material/dialog';
import { TranslocoModule } from '@ngneat/transloco';
import { BootstrapIconModule } from '@dsh/components/indicators';
import { LayoutModule } from '@dsh/components/layout';
import { ConfirmActionDialogModule } from '@dsh/components/popups';
import { FileContainerComponent } from './file-container.component';
@NgModule({
imports: [
CommonModule,
FlexLayoutModule,
LayoutModule,
TranslocoModule,
ConfirmActionDialogModule,
MatDialogModule,
BootstrapIconModule,
],
declarations: [FileContainerComponent],
exports: [FileContainerComponent],
})
export class FileContainerModule {}

View File

@ -1,46 +0,0 @@
import { Injectable } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { TranslocoService } from '@ngneat/transloco';
import { FileData } from '@vality/swag-dark-api';
import { Observable, Subject } from 'rxjs';
import { shareReplay, switchMap } from 'rxjs/operators';
import { FilesService } from '@dsh/api/dark-api';
import { takeError } from '@dsh/operators';
import { download } from '@dsh/utils';
@Injectable()
export class FileContainerService {
private getFileInfo$ = new Subject<string>();
// eslint-disable-next-line @typescript-eslint/member-ordering
fileInfo$: Observable<FileData> = this.getFileInfo$.pipe(
switchMap((fileID) => this.filesService.getDecodedFileInfo({ fileID })),
shareReplay(1)
);
// eslint-disable-next-line @typescript-eslint/member-ordering
isLoading$ = this.fileInfo$.pipe(shareReplay(1));
// eslint-disable-next-line @typescript-eslint/member-ordering
error$ = this.fileInfo$.pipe(takeError, shareReplay(1));
constructor(
private filesService: FilesService,
private snackBar: MatSnackBar,
private transloco: TranslocoService
) {
this.fileInfo$.subscribe();
}
getFileInfo(fileID: string) {
this.getFileInfo$.next(fileID);
}
downloadFile(fileID: string) {
this.filesService.downloadFile({ fileID }).subscribe(
({ url }) => download(url),
() => this.snackBar.open(this.transloco.translate('shared.commonError', null, 'components'), 'OK')
);
}
}

View File

@ -1 +0,0 @@
export * from './file-container.module';

View File

@ -1,2 +0,0 @@
export * from './comment-container';
export * from './file-container';

View File

@ -15,10 +15,6 @@ const CLAIM_SECTION_ROUTES: Routes = [
path: 'claims',
loadChildren: () => import('./claims/claims.module').then((m) => m.ClaimsModule),
},
{
path: 'claims/:claimId',
loadChildren: () => import('./claim/claim.module').then((m) => m.ClaimModule),
},
{ path: '', redirectTo: 'claims', pathMatch: 'full' },
],
},

View File

@ -1,17 +0,0 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { ClaimComponent } from './claim.component';
const CLAIM_ROUTES: Routes = [
{
path: '',
component: ClaimComponent,
},
];
@NgModule({
imports: [RouterModule.forChild(CLAIM_ROUTES)],
exports: [RouterModule],
})
export class ClaimRoutingModule {}

View File

@ -1,58 +0,0 @@
<div
*transloco="let t; scope: 'claim-section'; read: 'claimSection.claim'"
class="container"
fxLayout="column"
fxLayoutGap="32px"
>
<div fxLayout="column" fxLayoutGap="8px">
<h1 class="dsh-display-1">
{{ t('headline') }} <span dshTextColor="secondary">#{{ claimID$ | async }}</span>
</h1>
<ol dsh-breadcrumb>
<li dsh-breadcrumb-item>
<a dshTextColor="secondary" [routerLink]="['/claim-section/claims']">{{ t('breadcrumbs.claims') }}</a>
</li>
<li dsh-breadcrumb-item>{{ t('breadcrumbs.claimDetails') }}</li>
</ol>
</div>
<ng-container *ngIf="!(error$ | async)">
<ng-container *ngIf="isLoading$ | async | debounce; else content">
<div fxLayout fxLayoutAlign="center center">
<dsh-spinner></dsh-spinner>
</div>
</ng-container>
<ng-template #content>
<dsh-card>
<dsh-status class="claim-status" [color]="claimStatus$ | async | claimStatusColor" mark="false">
{{ (claimStatusDict$ | async)?.[claimStatus$ | async] }}
</dsh-status>
<div fxLayout="column" fxLayoutGap="24px">
<dsh-conversation></dsh-conversation>
<div fxLayout="row" fxLayoutAlign="space-between">
<div>
<button
*ngIf="revokeAvailable$ | async"
dsh-text-button
color="warn"
(click)="revokeClaim()"
>
{{ t('revokeClaim') }}
</button>
</div>
<div>
<button
*ngIf="reviewAvailable$ | async"
[disabled]="reviewInProgress$ | async"
dsh-button
color="accent"
(click)="reviewClaim()"
>
{{ t('reviewClaim') }}
</button>
</div>
</div>
</div>
</dsh-card>
</ng-template>
</ng-container>
</div>

View File

@ -1,13 +0,0 @@
$dsh-claim-details-padding: 80px 0 0 0;
$dsh-claim-details-max-width: 936px;
.container {
padding: $dsh-claim-details-padding;
max-width: $dsh-claim-details-max-width;
margin: 0 auto;
}
.claim-status {
position: absolute;
right: 24px;
}

View File

@ -1,52 +0,0 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { pluck } from 'rxjs/operators';
import { ClaimManagementDictionaryService } from '@dsh/api/claim-management';
import { ReceiveClaimService } from './receive-claim.service';
import { ReviewClaimService } from './review-claim.service';
import { RevokeClaimService } from './revoke-claim.service';
import { RouteParamClaimService } from './route-param-claim.service';
import { UpdateClaimService } from './update-claim';
@Component({
templateUrl: 'claim.component.html',
styleUrls: ['claim.component.scss'],
providers: [
RouteParamClaimService,
ReceiveClaimService,
RevokeClaimService,
UpdateClaimService,
ReviewClaimService,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ClaimComponent implements OnInit {
claimID$ = this.receiveClaimService.claim$.pipe(pluck('id'));
claimStatus$ = this.receiveClaimService.claim$.pipe(pluck('status'));
isLoading$ = this.receiveClaimService.isLoading$;
error$ = this.receiveClaimService.error$;
revokeAvailable$ = this.revokeClaimService.revokeAvailable$;
reviewAvailable$ = this.reviewClaimService.reviewAvailable$;
reviewInProgress$ = this.reviewClaimService.inProgress$;
claimStatusDict$ = this.claimManagementDictionaryService.claimStatus$;
constructor(
private receiveClaimService: ReceiveClaimService,
private revokeClaimService: RevokeClaimService,
private reviewClaimService: ReviewClaimService,
private claimManagementDictionaryService: ClaimManagementDictionaryService
) {}
ngOnInit() {
this.receiveClaimService.receiveClaim();
}
revokeClaim() {
this.revokeClaimService.revokeClaim();
}
reviewClaim() {
this.reviewClaimService.reviewClaim();
}
}

View File

@ -1,44 +0,0 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FlexLayoutModule } from '@angular/flex-layout';
import { ReactiveFormsModule } from '@angular/forms';
import { MatInputModule } from '@angular/material/input';
import { MatSelectModule } from '@angular/material/select';
import { TranslocoModule } from '@ngneat/transloco';
import { BaseDialogModule } from '@dsh/app/shared/components/dialog/base-dialog';
import { ApiModelTypesModule } from '@dsh/app/shared/pipes/api-model-types';
import { ButtonModule } from '@dsh/components/buttons';
import { IndicatorsModule } from '@dsh/components/indicators';
import { LayoutModule } from '@dsh/components/layout';
import { BreadcrumbModule } from '@dsh/components/navigation';
import { ConfirmActionDialogModule } from '@dsh/components/popups';
import { DebounceModule } from '@dsh/pipes/debounce/debounce.module';
import { ClaimRoutingModule } from './claim-routing.module';
import { ClaimComponent } from './claim.component';
import { ConversationModule } from './conversation';
import { RevokeClaimDialogComponent } from './revoke-claim-dialog';
@NgModule({
imports: [
CommonModule,
LayoutModule,
ButtonModule,
FlexLayoutModule,
ClaimRoutingModule,
ConversationModule,
TranslocoModule,
IndicatorsModule,
ReactiveFormsModule,
MatInputModule,
ConfirmActionDialogModule,
BaseDialogModule,
MatSelectModule,
BreadcrumbModule,
ApiModelTypesModule,
DebounceModule,
],
declarations: [ClaimComponent, RevokeClaimDialogComponent],
})
export class ClaimModule {}

View File

@ -1,20 +0,0 @@
import { Pipe, PipeTransform } from '@angular/core';
import { StatusColor } from '../../../../theme-manager';
import { TimelineAction } from './to-timeline-info';
@Pipe({
name: 'actionColor',
})
export class ActionColorPipe implements PipeTransform {
transform(action: TimelineAction): StatusColor | null {
switch (action) {
case TimelineAction.StatusAccepted:
return StatusColor.Success;
case TimelineAction.StatusDenied:
case TimelineAction.StatusRevoked:
return StatusColor.Warn;
}
return null;
}
}

View File

@ -1,23 +0,0 @@
import { Pipe, PipeTransform } from '@angular/core';
import { TimelineAction } from './to-timeline-info';
@Pipe({
name: 'actionIcon',
})
export class ActionIconPipe implements PipeTransform {
transform(action: TimelineAction): string {
return (
{
[TimelineAction.StatusPending]: 'plus',
[TimelineAction.StatusReview]: 'check',
[TimelineAction.StatusRevoked]: 'x',
[TimelineAction.StatusDenied]: 'x',
[TimelineAction.StatusAccepted]: 'check-all',
[TimelineAction.FilesAdded]: 'plus',
[TimelineAction.CommentAdded]: 'plus',
[TimelineAction.ChangesAdded]: 'plus',
} as const
)[action];
}
}

View File

@ -1,51 +0,0 @@
<div *transloco="let t; scope: 'claim-section'; read: 'claimSection.conversation'" fxLayout="column" fxLayoutGap="20px">
<dsh-timeline>
<dsh-timeline-item>
<dsh-timeline-item-title>
<span>{{ t('timeline.claimCreated') }}&nbsp;</span>
<span>{{ claimCreatedAt$ | async | humanizedDuration: { largest: 1 } }} {{ t('ago') }}</span>
</dsh-timeline-item-title>
<dsh-timeline-item-badge>
<dsh-bi icon="pencil" size="sm"></dsh-bi>
</dsh-timeline-item-badge>
</dsh-timeline-item>
<dsh-timeline-item *ngFor="let item of timelineInfo$ | async; trackBy: simpleTrackBy">
<dsh-timeline-item-title>
<div fxFlex fxLayout="row" fxLayoutAlign="space-between end">
<div>
<span class="dsh-body-2">{{ userTypeLabels[item.userInfo.userType] | async }} </span>
<span class="mat-body-2">{{ timelineActionLabels[item.action] | async }} </span>
<span>{{ item.createdAt | humanizedDuration: { largest: 1 } }} {{ t('ago') }}</span>
</div>
<dsh-bi
*ngIf="item.action === 'changesAdded'"
class="title-action-icon"
svgIcon="three-dots"
[matMenuTriggerFor]="menu"
></dsh-bi>
</div>
<mat-menu #menu="matMenu" xPosition="before">
<button mat-menu-item (click)="expandAll = !expandAll">{{ t('action.toggleDetails') }}</button>
</mat-menu>
</dsh-timeline-item-title>
<dsh-timeline-item-badge [color]="item.action | actionColor">
<dsh-bi [icon]="item.action | actionIcon"></dsh-bi>
</dsh-timeline-item-badge>
<dsh-timeline-item-content *ngIf="item.modifications.length !== 0">
<div *ngFor="let modification of item.modifications; trackBy: simpleTrackBy">
<dsh-comment-container
*ngIf="isCommentModificationUnit(modification)"
[unit]="modification.claimModificationType"
></dsh-comment-container>
<dsh-file-container
*ngIf="isFileModificationUnit(modification)"
[unit]="modification.claimModificationType"
deletion
(delete)="deleteFile(modification)"
></dsh-file-container>
</div>
</dsh-timeline-item-content>
</dsh-timeline-item>
</dsh-timeline>
<dsh-send-comment (conversationSaved)="commentSaved($event)"></dsh-send-comment>
</div>

View File

@ -1,3 +0,0 @@
.title-action-icon {
cursor: pointer;
}

View File

@ -1,114 +0,0 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { TranslocoService } from '@ngneat/transloco';
import { FileModificationUnit, Modification, UserInfo } from '@vality/swag-claim-management';
import { Conversation } from '@vality/swag-messages';
import { Observable } from 'rxjs';
import {
isClaimModification,
isCommentModificationUnit,
isFileModificationUnit,
SpecificClaimModificationUnit,
} from '@dsh/api/claim-management';
import { ConversationService } from './conversation.service';
import UserTypeEnum = UserInfo.UserTypeEnum;
@Component({
selector: 'dsh-conversation',
templateUrl: 'conversation.component.html',
styleUrls: ['conversation.component.scss'],
providers: [ConversationService],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ConversationComponent {
timelineInfo$ = this.conversationService.timelineInfo$;
claimCreatedAt$ = this.conversationService.claimCreatedAt$;
timelineActionLabels = this.getTimelineActionLabels();
userTypeLabels = this.getUserTypeLabels();
expandAll = false;
constructor(private conversationService: ConversationService, private transloco: TranslocoService) {}
isCommentModificationUnit(m: Modification): boolean {
return isClaimModification(m) && isCommentModificationUnit(m.claimModificationType);
}
isFileModificationUnit(m: Modification): boolean {
return isClaimModification(m) && isFileModificationUnit(m.claimModificationType);
}
commentSaved(id: Conversation['conversationId']) {
this.conversationService.commentSaved(id);
}
simpleTrackBy(index: number): number {
return index;
}
deleteFile(m: SpecificClaimModificationUnit<FileModificationUnit>) {
this.conversationService.deleteFile(m.claimModificationType.fileId);
}
private getTimelineActionLabels() {
return {
changesAdded: this.transloco.selectTranslate(
'conversation.timelineActions.changesAdded',
null,
'claim-section'
),
commentAdded: this.transloco.selectTranslate(
'conversation.timelineActions.commentAdded',
null,
'claim-section'
),
filesAdded: this.transloco.selectTranslate(
'conversation.timelineActions.filesAdded',
null,
'claim-section'
),
statusAccepted: this.transloco.selectTranslate(
'conversation.timelineActions.statusAccepted',
null,
'claim-section'
),
statusDenied: this.transloco.selectTranslate(
'conversation.timelineActions.statusDenied',
null,
'claim-section'
),
statusPending: this.transloco.selectTranslate(
'conversation.timelineActions.statusPending',
null,
'claim-section'
),
statusReview: this.transloco.selectTranslate(
'conversation.timelineActions.statusReview',
null,
'claim-section'
),
statusRevoked: this.transloco.selectTranslate(
'conversation.timelineActions.statusRevoked',
null,
'claim-section'
),
};
}
private getUserTypeLabels(): Record<UserTypeEnum, Observable<string>> {
return {
internal_user: this.transloco.selectTranslate(
'conversation.userTypes.internal_user',
null,
'claim-section'
),
external_user: this.transloco.selectTranslate(
'conversation.userTypes.external_user',
null,
'claim-section'
),
};
}
}

View File

@ -1,46 +0,0 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FlexLayoutModule } from '@angular/flex-layout';
import { ReactiveFormsModule } from '@angular/forms';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatMenuModule } from '@angular/material/menu';
import { TranslocoModule } from '@ngneat/transloco';
import { ngfModule } from 'angular-file';
import { MessagesModule } from '@dsh/api/messages';
import { ButtonModule } from '@dsh/components/buttons';
import { BootstrapIconModule } from '@dsh/components/indicators';
import { LayoutModule } from '@dsh/components/layout';
import { ConfirmActionDialogModule } from '@dsh/components/popups';
import { HumanizeDurationModule } from '../../../../humanize-duration';
import { CommentContainerModule, FileContainerModule } from '../../claim-modification-containers';
import { ActionColorPipe } from './action-color.pipe';
import { ActionIconPipe } from './action-icon.pipe';
import { ConversationComponent } from './conversation.component';
import { SendCommentComponent } from './send-comment';
@NgModule({
imports: [
LayoutModule,
ButtonModule,
FlexLayoutModule,
MatFormFieldModule,
MatInputModule,
BootstrapIconModule,
CommonModule,
HumanizeDurationModule,
TranslocoModule,
CommentContainerModule,
FileContainerModule,
ReactiveFormsModule,
MessagesModule,
MatMenuModule,
ConfirmActionDialogModule,
ngfModule,
],
declarations: [ConversationComponent, ActionColorPipe, ActionIconPipe, SendCommentComponent],
exports: [ConversationComponent],
})
export class ConversationModule {}

View File

@ -1,24 +0,0 @@
import { Injectable } from '@angular/core';
import { FileModification } from '@vality/swag-claim-management';
import { Conversation } from '@vality/swag-messages';
import { map, pluck, shareReplay } from 'rxjs/operators';
import { ReceiveClaimService } from '../receive-claim.service';
import { UpdateClaimService } from '../update-claim';
import { toTimelineInfo } from './to-timeline-info';
@Injectable()
export class ConversationService {
timelineInfo$ = this.receiveClaimService.claim$.pipe(pluck('changeset'), map(toTimelineInfo), shareReplay(1));
claimCreatedAt$ = this.receiveClaimService.claim$.pipe(pluck('createdAt'), shareReplay(1));
constructor(private receiveClaimService: ReceiveClaimService, private updateClaimService: UpdateClaimService) {}
commentSaved(id: Conversation['conversationId']) {
this.updateClaimService.updateByConversation(id);
}
deleteFile(id: string) {
this.updateClaimService.updateByFiles([id], FileModification.FileModificationTypeEnum.FileDeleted);
}
}

View File

@ -1,2 +0,0 @@
export * from './conversation.component';
export * from './conversation.module';

View File

@ -1 +0,0 @@
export * from './send-comment.component';

View File

@ -1,38 +0,0 @@
<div *transloco="let t; scope: 'claim-section'; read: 'claimSection.conversation'">
<form [formGroup]="form" fxLayout="row" fxLayoutGap="10px">
<mat-form-field fxFlex>
<mat-label>{{ t('sendComment') }}</mat-label>
<input formControlName="comment" matInput type="text" autocomplete="off" />
</mat-form-field>
<div class="action">
<button
dsh-icon-button
color="accent"
(click)="sendComment(form.value.comment)"
[disabled]="(inProgress$ | async) || !form.valid"
>
<dsh-bi icon="send"></dsh-bi>
</button>
</div>
<div class="action">
<button
dsh-icon-button
[disabled]="inProgress$ | async"
ngfSelect
(filesChange)="startUploading($event)"
[files]="[]"
>
<dsh-bi icon="paperclip"></dsh-bi>
</button>
</div>
</form>
<div *ngIf="errorCode$ | async">
<div
*transloco="let errors; scope: 'claim-section'; read: 'claimSection.sendComment.errors'"
class="mat-caption"
>
<!-- t(saveConversationsFailed) -->
{{ errors(errorCode$ | async) }}
</div>
</div>
</div>

View File

@ -1,5 +0,0 @@
$dsh-action-padding: 10px 0 0 0;
.action {
padding: $dsh-action-padding;
}

View File

@ -1,38 +0,0 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Output } from '@angular/core';
import { UntypedFormGroup } from '@angular/forms';
import { Conversation } from '@vality/swag-messages';
import { combineLatest } from 'rxjs';
import { map, shareReplay } from 'rxjs/operators';
import { SendCommentService } from './send-comment.service';
import { UploadFilesService } from './upload-files.service';
@Component({
selector: 'dsh-send-comment',
templateUrl: 'send-comment.component.html',
styleUrls: ['send-comment.component.scss'],
providers: [SendCommentService, UploadFilesService],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SendCommentComponent {
@Output() conversationSaved: EventEmitter<Conversation['conversationId']> = new EventEmitter();
form: UntypedFormGroup = this.sendCommentService.form;
errorCode$ = this.sendCommentService.errorCode$;
inProgress$ = combineLatest([this.sendCommentService.inProgress$, this.fileUploaderService.isUploading$]).pipe(
map((v) => v.includes(true)),
shareReplay(1)
);
constructor(private sendCommentService: SendCommentService, private fileUploaderService: UploadFilesService) {
this.sendCommentService.conversationSaved$.subscribe((id) => this.conversationSaved.next(id));
}
sendComment(comment: string) {
this.sendCommentService.sendComment(comment);
}
startUploading(files: File[]) {
this.fileUploaderService.uploadFiles(files);
}
}

View File

@ -1,63 +0,0 @@
import { Injectable } from '@angular/core';
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { Conversation } from '@vality/swag-messages';
import { BehaviorSubject, forkJoin, merge, Observable, of, Subject, EMPTY } from 'rxjs';
import { catchError, filter, pluck, switchMap, tap } from 'rxjs/operators';
import { v4 as uuid } from 'uuid';
import { createSingleMessageConversationParams, ConversationsService } from '@dsh/api/messages';
import { progress } from '@dsh/operators';
import { UiError } from '../../../../ui-error';
@Injectable()
export class SendCommentService {
private conversationId$: BehaviorSubject<Conversation['conversationId'] | null> = new BehaviorSubject(null);
private error$: BehaviorSubject<UiError> = new BehaviorSubject({ hasError: false });
private sendComment$: Subject<string> = new Subject();
// eslint-disable-next-line @typescript-eslint/member-ordering
form: UntypedFormGroup;
// eslint-disable-next-line @typescript-eslint/member-ordering
conversationSaved$: Observable<Conversation['conversationId']> = this.conversationId$.pipe(filter((id) => !!id));
// eslint-disable-next-line @typescript-eslint/member-ordering
errorCode$: Observable<string> = this.error$.pipe(pluck('code'));
// eslint-disable-next-line @typescript-eslint/member-ordering
inProgress$: Observable<boolean> = progress(this.sendComment$, merge(this.conversationId$, this.error$));
constructor(private fb: UntypedFormBuilder, private conversationsService: ConversationsService) {
this.form = this.fb.group({
comment: ['', [Validators.maxLength(1000)]],
});
this.sendComment$
.pipe(
tap(() => this.error$.next({ hasError: false })),
switchMap((text) => {
const conversationId = uuid();
const conversationParam = createSingleMessageConversationParams(conversationId, text);
return forkJoin([
of(conversationId),
this.conversationsService.saveConversations({ conversationParam }).pipe(
catchError((ex) => {
console.error(ex);
const error = { hasError: true, code: 'saveConversationsFailed' };
this.error$.next(error);
return EMPTY;
})
),
]);
})
)
.subscribe(([conversationId]) => {
this.conversationId$.next(conversationId);
this.form.reset();
});
}
sendComment(comment: string) {
if (comment.length === 0) {
return;
}
this.sendComment$.next(comment);
}
}

View File

@ -1,55 +0,0 @@
import { Injectable } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { TranslocoService } from '@ngneat/transloco';
import { combineLatest, merge, Observable, Subject } from 'rxjs';
import { map, share, shareReplay, switchMap } from 'rxjs/operators';
import { FilesService } from '@dsh/api/dark-api';
import { progress, filterError, filterPayload, replaceError } from '@dsh/operators';
import { UpdateClaimService } from '../../update-claim';
@Injectable()
export class UploadFilesService {
private uploadFiles$ = new Subject<File[]>();
// eslint-disable-next-line @typescript-eslint/member-ordering
uploadedFiles$: Observable<string[]>;
// eslint-disable-next-line @typescript-eslint/member-ordering
errors$: Observable<any>;
// eslint-disable-next-line @typescript-eslint/member-ordering
isUploading$: Observable<boolean>;
constructor(
private filesService: FilesService,
private snackBar: MatSnackBar,
private transloco: TranslocoService,
private updateClaimService: UpdateClaimService
) {
const uploadFilesWithError$ = this.uploadFiles$.pipe(
switchMap((files) => this.filesService.uploadFiles(files).pipe(replaceError)),
share()
);
this.uploadedFiles$ = uploadFilesWithError$.pipe(filterPayload, shareReplay(1));
this.errors$ = uploadFilesWithError$.pipe(filterError, shareReplay(1));
const isUploading$ = progress(this.uploadFiles$, merge(this.uploadedFiles$, this.errors$));
this.isUploading$ = combineLatest([isUploading$, this.updateClaimService.inProgress$]).pipe(
map((v) => v.includes(true)),
shareReplay(1)
);
this.errors$.subscribe(() =>
this.snackBar.open(this.transloco.translate('shared.commonError', null, 'components'), 'OK')
);
this.uploadedFiles$.subscribe(() =>
this.snackBar.open(this.transloco.translate('conversation.filesUploaded', null, 'claim-section'), 'OK', {
duration: 5000,
})
);
this.uploadedFiles$.subscribe((fileIds) => this.updateClaimService.updateByFiles(fileIds));
}
uploadFiles(files: File[]) {
this.uploadFiles$.next(files);
}
}

View File

@ -1,56 +0,0 @@
import {
ClaimModificationType,
FileModification,
FileModificationUnit,
StatusModificationUnit,
} from '@vality/swag-claim-management';
import {
isCommentModificationUnit,
isDocumentModificationUnit,
isFileModificationUnit,
isStatusModificationUnit,
} from '@dsh/api/claim-management';
import { TimelineAction } from './model';
function getStatusModificationTimelineAction(unit: StatusModificationUnit): TimelineAction | null {
const status = StatusModificationUnit.StatusEnum;
switch (unit.status) {
case status.Accepted:
return TimelineAction.StatusAccepted;
case status.Denied:
return TimelineAction.StatusDenied;
case status.Pending:
return TimelineAction.StatusPending;
case status.Review:
return TimelineAction.StatusReview;
case status.Revoked:
return TimelineAction.StatusRevoked;
case status.PendingAcceptance:
return null;
}
}
function getFileModificationTimelineAction(unit: FileModificationUnit): TimelineAction {
const type = FileModification.FileModificationTypeEnum;
switch (unit.fileModification.fileModificationType) {
case type.FileCreated:
return TimelineAction.FilesAdded;
case type.FileDeleted:
return TimelineAction.FilesDeleted;
}
}
export function getClaimModificationTimelineAction(m: ClaimModificationType): TimelineAction | null {
if (isFileModificationUnit(m)) {
return getFileModificationTimelineAction(m);
} else if (isStatusModificationUnit(m)) {
return getStatusModificationTimelineAction(m);
} else if (isDocumentModificationUnit(m)) {
return TimelineAction.ChangesAdded;
} else if (isCommentModificationUnit(m)) {
return TimelineAction.CommentAdded;
}
throw new Error(`Unknown claimModificationType: ${m.claimModificationType}`);
}

View File

@ -1,2 +0,0 @@
export * from './to-timeline-info';
export * from './model';

View File

@ -1,2 +0,0 @@
export * from './timeline-item-info';
export * from './timeline-action';

View File

@ -1,11 +0,0 @@
export enum TimelineAction {
ChangesAdded = 'changesAdded',
FilesAdded = 'filesAdded',
FilesDeleted = 'filesDeleted',
CommentAdded = 'commentAdded',
StatusReview = 'statusReview',
StatusPending = 'statusPending',
StatusDenied = 'statusDenied',
StatusRevoked = 'statusRevoked',
StatusAccepted = 'statusAccepted',
}

View File

@ -1,10 +0,0 @@
import { Modification, UserInfo } from '@vality/swag-claim-management';
import { TimelineAction } from './timeline-action';
export interface TimelineItemInfo {
action: TimelineAction;
userInfo: UserInfo;
createdAt: string;
modifications: Modification[];
}

View File

@ -1,229 +0,0 @@
import {
ClaimModification,
Modification,
ModificationUnit,
StatusModificationUnit,
UserInfo,
} from '@vality/swag-claim-management';
import { TimelineAction, TimelineItemInfo } from './model';
import { toTimelineInfo } from './to-timeline-info';
const genUserInfo = (userType: UserInfo.UserTypeEnum): UserInfo => ({
userId: '',
email: '',
username: '',
userType,
});
const genPartialModification = (
modificationID: number,
createdAt: string,
modificationType: Modification.ModificationTypeEnum,
userType: UserInfo.UserTypeEnum = 'external_user'
) => ({
modificationID,
createdAt: createdAt as any,
userInfo: genUserInfo(userType),
modification: {
modificationType,
},
});
const genStatusModificationUnit = (
modificationID: number,
createdAt: string,
status: StatusModificationUnit.StatusEnum
): ModificationUnit => {
const m = genPartialModification(modificationID, createdAt, 'ClaimModification');
return {
...m,
modification: {
...m.modification,
claimModificationType: {
claimModificationType: 'StatusModificationUnit',
status,
statusModification: { statusModificationType: 'StatusChanged' },
},
} as ClaimModification,
};
};
const genCommentModificationUnit = (modificationID: number, createdAt: string, commentId: string): ModificationUnit => {
const m = genPartialModification(modificationID, createdAt, 'ClaimModification');
return {
...m,
modification: {
...m.modification,
claimModificationType: {
claimModificationType: 'CommentModificationUnit',
commentId,
commentModification: { commentModificationType: 'CommentCreated' },
},
} as ClaimModification,
};
};
const genFileModificationUnit = (modificationID: number, createdAt: string, fileId: string): ModificationUnit => {
const m = genPartialModification(modificationID, createdAt, 'ClaimModification');
return {
...m,
modification: {
...m.modification,
claimModificationType: {
claimModificationType: 'FileModificationUnit',
fileId,
fileModification: { fileModificationType: 'FileCreated' },
},
} as ClaimModification,
};
};
const genDocumentModificationUnit = (
modificationID: number,
createdAt: string,
documentId: string
): ModificationUnit => {
const m = genPartialModification(modificationID, createdAt, 'ClaimModification');
return {
...m,
modification: {
...m.modification,
claimModificationType: {
claimModificationType: 'DocumentModificationUnit',
documentId,
documentModification: { documentModificationType: 'DocumentCreated' },
},
} as ClaimModification,
};
};
describe('toTimelineInfo', () => {
it('DocumentModificationUnit should return changesAdded action', () => {
const createdAt = '2019-11-21T18:30:00.000000Z';
const units = [genDocumentModificationUnit(66, createdAt, '7dfbc2fe-7ac4-416a-9f96-documentId1')];
const result = toTimelineInfo(units);
const expected = [
{
action: TimelineAction.ChangesAdded,
userInfo: units[0].userInfo,
createdAt,
modifications: [units[0].modification],
} as TimelineItemInfo,
];
expect(result).toEqual(expected);
});
it('FileModificationUnit should return filesAdded action', () => {
const createdAt = '2019-11-21T18:40:00.000000Z';
const units = [genFileModificationUnit(67, createdAt, '7dfbc2fe-7ac4-416a-9f96-fileId1')];
const result = toTimelineInfo(units);
const expected = [
{
action: TimelineAction.FilesAdded,
userInfo: units[0].userInfo,
createdAt,
modifications: [units[0].modification],
} as TimelineItemInfo,
];
expect(result).toEqual(expected);
});
it('StatusModificationUnit with status review should return statusReview action', () => {
const createdAt = '2019-11-21T18:43:00.000000Z';
const units = [genStatusModificationUnit(69, createdAt, 'review')];
const result = toTimelineInfo(units);
const expected = [
{
action: TimelineAction.StatusReview,
userInfo: units[0].userInfo,
createdAt,
modifications: [],
} as TimelineItemInfo,
];
expect(result).toEqual(expected);
});
it('StatusModificationUnit with status accepted should return statusAccepted action', () => {
const createdAt = '2019-11-21T18:43:00.000000Z';
const units = [genStatusModificationUnit(69, createdAt, 'accepted')];
const result = toTimelineInfo(units);
const expected = [
{
action: TimelineAction.StatusAccepted,
userInfo: units[0].userInfo,
createdAt,
modifications: [],
} as TimelineItemInfo,
];
expect(result).toEqual(expected);
});
it('StatusModificationUnit with status denied should return statusDenied action', () => {
const createdAt = '2019-11-21T18:43:00.000000Z';
const units = [genStatusModificationUnit(69, createdAt, 'denied')];
const result = toTimelineInfo(units);
const expected = [
{
action: TimelineAction.StatusDenied,
userInfo: units[0].userInfo,
createdAt,
modifications: [],
} as TimelineItemInfo,
];
expect(result).toEqual(expected);
});
it('StatusModificationUnit with status revoked should return statusRevoked action', () => {
const createdAt = '2019-11-21T18:43:00.000000Z';
const units = [genStatusModificationUnit(69, createdAt, 'revoked')];
const result = toTimelineInfo(units);
const expected = [
{
action: TimelineAction.StatusRevoked,
userInfo: units[0].userInfo,
createdAt,
modifications: [],
} as TimelineItemInfo,
];
expect(result).toEqual(expected);
});
it('StatusModificationUnit with status pending should return statusPending action', () => {
const createdAt = '2019-11-21T18:43:00.000000Z';
const units = [genStatusModificationUnit(69, createdAt, 'pending')];
const result = toTimelineInfo(units);
const expected = [
{
action: TimelineAction.StatusPending,
userInfo: units[0].userInfo,
createdAt,
modifications: [],
} as TimelineItemInfo,
];
expect(result).toEqual(expected);
});
it('StatusModificationUnit with status pendingAcceptance should ignore', () => {
const createdAt = '2019-11-21T18:43:00.000000Z';
const units = [genStatusModificationUnit(69, createdAt, 'pendingAcceptance')];
const result = toTimelineInfo(units);
const expected = [];
expect(result).toEqual(expected);
});
it('CommentModificationUnit should return commentAdded action', () => {
const createdAt = '2019-11-21T18:43:00.000000Z';
const units = [genCommentModificationUnit(70, createdAt, '7dfbc2fe-7ac4-416a-9f96-commentId1')];
const result = toTimelineInfo(units);
const expected = [
{
action: TimelineAction.CommentAdded,
userInfo: units[0].userInfo,
createdAt,
modifications: [units[0].modification],
} as TimelineItemInfo,
];
expect(result).toEqual(expected);
});
});

View File

@ -1,70 +0,0 @@
import { ClaimModification, FileModificationUnit, Modification, ModificationUnit } from '@vality/swag-claim-management';
import { isClaimModification, sortUnitsByCreatedAtAsc } from '@dsh/api/claim-management';
import { getClaimModificationTimelineAction } from './get-claim-modification-timeline-action';
import { TimelineAction, TimelineItemInfo } from './model';
const getUnitTimelineAction = (modification: Modification): TimelineAction | null => {
if (isClaimModification(modification)) {
return getClaimModificationTimelineAction(modification.claimModificationType);
}
return null;
};
const deleteAddedFile = (acc: TimelineItemInfo[], deletedFileId: string): TimelineItemInfo[] => {
const result = acc.slice();
for (let i = 0, item = result[0]; i < result.length; i += 1, item = result[i]) {
if (item.action !== TimelineAction.FilesAdded) {
continue;
}
const fileModificationIdx = (item.modifications as ClaimModification[]).findIndex(
({ claimModificationType }) => (claimModificationType as FileModificationUnit).fileId === deletedFileId
);
if (fileModificationIdx === -1) {
continue;
}
if (item.modifications.length === 1) {
result.splice(i, 1);
} else {
item.modifications.splice(fileModificationIdx, 1);
}
return result;
}
console.error(`Deleted file "${deletedFileId}" not found`);
return result;
};
const getFileId = (modification: Modification): string =>
((modification as ClaimModification).claimModificationType as FileModificationUnit).fileId;
const reduceToAcceptedTimelineItem = (
acc: TimelineItemInfo[],
{ createdAt, modification, userInfo }: ModificationUnit
): TimelineItemInfo[] => {
const action = getUnitTimelineAction(modification);
if (!action) {
return acc;
}
const modifications = [];
switch (action) {
case TimelineAction.FilesDeleted:
return deleteAddedFile(acc, getFileId(modification));
case TimelineAction.ChangesAdded:
case TimelineAction.FilesAdded:
case TimelineAction.CommentAdded:
modifications.push(modification);
}
const timelineInfo = {
action,
userInfo,
createdAt: createdAt as any,
modifications,
};
return [...acc, timelineInfo];
};
export const toTimelineInfo = (units: ModificationUnit[]): TimelineItemInfo[] =>
Array.isArray(units) && units.length
? units.slice().sort(sortUnitsByCreatedAtAsc).reduce(reduceToAcceptedTimelineItem, [])
: [];

View File

@ -1 +0,0 @@
export * from './claim.module';

View File

@ -1,48 +0,0 @@
import { Injectable } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { TranslocoService } from '@ngneat/transloco';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { Claim } from '@vality/swag-claim-management';
import { BehaviorSubject, Subject, timer, defer } from 'rxjs';
import { filter, shareReplay, switchMap } from 'rxjs/operators';
import { RouteParamClaimService } from './route-param-claim.service';
const POLLING_PERIOD = 5000;
@UntilDestroy()
@Injectable()
export class ReceiveClaimService {
claim$ = defer(() => this.claimState$).pipe(filter(Boolean), shareReplay(1));
error$ = defer(() => this.receiveClaimError$);
isLoading$ = this.routeParamClaimService.isLoading$;
private claimState$ = new BehaviorSubject<Claim>(null);
private receiveClaimError$ = new BehaviorSubject(false);
private receiveClaim$ = new Subject<void>();
constructor(
private routeParamClaimService: RouteParamClaimService,
private snackBar: MatSnackBar,
private transloco: TranslocoService
) {
this.receiveClaim$
.pipe(
switchMap(() => timer(0, POLLING_PERIOD)),
switchMap(() => this.routeParamClaimService.claim$),
untilDestroyed(this)
)
.subscribe(
(claim) => this.claimState$.next(claim),
(err) => {
console.error(err);
this.snackBar.open(this.transloco.translate('shared.commonError', null, 'components'), 'OK');
this.receiveClaimError$.next(true);
}
);
}
receiveClaim() {
this.receiveClaim$.next();
}
}

View File

@ -1,90 +0,0 @@
import { Injectable } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { TranslocoService } from '@ngneat/transloco';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { BehaviorSubject, Observable, of, Subject } from 'rxjs';
import { catchError, filter, map, shareReplay, switchMap, tap } from 'rxjs/operators';
import { ClaimsService } from '@dsh/api/claim-management';
import { NotificationService } from '@dsh/app/shared';
import { ConfirmActionDialogComponent } from '@dsh/components/popups';
import { UiError } from '../../ui-error';
import { ReceiveClaimService } from './receive-claim.service';
import { RouteParamClaimService } from './route-param-claim.service';
@UntilDestroy()
@Injectable()
export class ReviewClaimService {
private reviewClaim$: Subject<void> = new Subject();
private error$: BehaviorSubject<UiError> = new BehaviorSubject({ hasError: false });
private progress$: BehaviorSubject<boolean> = new BehaviorSubject(false);
// eslint-disable-next-line @typescript-eslint/member-ordering
reviewAvailable$: Observable<boolean> = this.receiveClaimService.claim$.pipe(
map(({ status }) => status === 'pending'),
shareReplay(1)
);
// eslint-disable-next-line @typescript-eslint/member-ordering
inProgress$: Observable<boolean> = this.progress$.asObservable();
constructor(
private claimsApiService: ClaimsService,
private routeParamClaimService: RouteParamClaimService,
private receiveClaimService: ReceiveClaimService,
private dialog: MatDialog,
private transloco: TranslocoService,
private notificationService: NotificationService
) {
this.reviewClaim$
.pipe(
tap(() => {
this.error$.next({ hasError: false });
this.progress$.next(true);
}),
switchMap(() =>
this.dialog
.open(ConfirmActionDialogComponent)
.afterClosed()
.pipe(
tap((r) => {
if (r === 'cancel') {
this.progress$.next(false);
}
}),
filter((r) => r === 'confirm')
)
),
switchMap(() => this.routeParamClaimService.claim$),
switchMap(({ id, revision }) =>
this.claimsApiService
.requestReviewClaimByIDWithRevisionCheck({ claimID: id, claimRevision: revision })
.pipe(
catchError((ex) => {
this.progress$.next(false);
console.error(ex);
const error = { hasError: true, code: 'requestReviewClaimByIDFailed' };
this.notificationService.error(
this.transloco.translate(
'claim.requestReviewClaimByIDFailed',
null,
'claim-section'
)
);
this.error$.next(error);
return of(error);
})
)
),
untilDestroyed(this)
)
.subscribe(() => {
this.receiveClaimService.receiveClaim();
this.notificationService.success(this.transloco.translate('claim.reviewed', null, 'claim-section'));
});
}
reviewClaim() {
this.reviewClaim$.next();
}
}

View File

@ -1 +0,0 @@
export * from './revoke-claim-dialog.component';

View File

@ -1,33 +0,0 @@
<ng-container *transloco="let t; scope: 'claim-section'; read: 'claimSection.revokeClaimDialog'">
<dsh-base-dialog [title]="t('subheading')" (cancel)="cancel()">
<form [formGroup]="form">
<mat-form-field fxFlex>
<mat-label>{{ t('reason') }}</mat-label>
<mat-select formControlName="reason">
<mat-option *ngFor="let reason of reasons" [value]="reason">
{{ reason }}
</mat-option>
</mat-select>
</mat-form-field>
</form>
<div *ngIf="errorCode$ | async">
<div
*transloco="let errors; scope: 'claim-section'; read: 'claimSection.revokeClaimDialog.errors'"
class="mat-caption"
>
<!-- t(revokeClaimByIDFailed) -->
{{ errors(errorCode$ | async) }}
</div>
</div>
<div dshBaseDialogActions fxLayout="row" fxLayoutAlign="end">
<button
dsh-button
color="warn"
(click)="revoke(form.value.reason)"
[disabled]="!form.valid || (inProgress$ | async)"
>
{{ t('revoke') }}
</button>
</div>
</dsh-base-dialog>
</ng-container>

View File

@ -1,33 +0,0 @@
import { Component } from '@angular/core';
import { RevokeClaimDialogService } from './revoke-claim-dialog.service';
@Component({
templateUrl: 'revoke-claim-dialog.component.html',
providers: [RevokeClaimDialogService],
})
export class RevokeClaimDialogComponent {
form = this.revokeClaimDialogService.form;
// It's necessary to hardcode reasons (backend issue)
reasons = [
'Длительное ожидание подключения',
'Не устраивает комиссия',
'Большой пакет документов',
'Не подходит продукт',
'Нет сплитов',
];
errorCode$ = this.revokeClaimDialogService.errorCode$;
inProgress$ = this.revokeClaimDialogService.inProgress$;
constructor(private revokeClaimDialogService: RevokeClaimDialogService) {}
cancel(): void {
this.revokeClaimDialogService.back();
}
revoke(reason: string): void {
this.revokeClaimDialogService.revoke(reason);
}
}

View File

@ -1,61 +0,0 @@
import { Inject, Injectable } from '@angular/core';
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import get from 'lodash-es/get';
import { BehaviorSubject, Observable, of, Subject } from 'rxjs';
import { catchError, filter, pluck, switchMap, tap } from 'rxjs/operators';
import { ClaimsService } from '@dsh/api/claim-management';
import { progress } from '../../../../custom-operators';
import { UiError } from '../../../ui-error';
@Injectable()
export class RevokeClaimDialogService {
private revoke$: Subject<string> = new Subject();
private error$: BehaviorSubject<UiError> = new BehaviorSubject({ hasError: false });
// eslint-disable-next-line @typescript-eslint/member-ordering
errorCode$: Observable<string> = this.error$.pipe(pluck('code'));
// eslint-disable-next-line @typescript-eslint/member-ordering
inProgress$: Observable<boolean> = progress(this.revoke$, this.error$);
// eslint-disable-next-line @typescript-eslint/member-ordering
form: UntypedFormGroup;
constructor(
private dialogRef: MatDialogRef<unknown, 'cancel' | 'revoked'>,
private claimsApiService: ClaimsService,
private fb: UntypedFormBuilder,
@Inject(MAT_DIALOG_DATA) private data: { claimId: number; revision: number }
) {
this.form = this.fb.group({
reason: ['', [Validators.required, Validators.maxLength(1000)]],
});
this.revoke$
.pipe(
tap(() => this.error$.next({ hasError: false })),
switchMap((reason) =>
this.claimsApiService
.revokeClaimByID({ claimID: this.data.claimId, claimRevision: this.data.revision, reason })
.pipe(
catchError((ex) => {
console.error(ex);
const error = { hasError: true, code: 'revokeClaimByIDFailed' };
this.error$.next(error);
return of(error);
})
)
),
filter((res) => get(res, ['hasError']) !== true)
)
.subscribe(() => this.dialogRef.close('revoked'));
}
back() {
this.dialogRef.close('cancel');
}
revoke(reason: string) {
this.revoke$.next(reason);
}
}

View File

@ -1,57 +0,0 @@
import { Injectable } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { MatSnackBar } from '@angular/material/snack-bar';
import { TranslocoService } from '@ngneat/transloco';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { Subject } from 'rxjs';
import { filter, first, map, switchMap } from 'rxjs/operators';
import { ReceiveClaimService } from './receive-claim.service';
import { RevokeClaimDialogComponent } from './revoke-claim-dialog';
import { RouteParamClaimService } from './route-param-claim.service';
@UntilDestroy()
@Injectable()
export class RevokeClaimService {
private revokeClaim$ = new Subject<void>();
// eslint-disable-next-line @typescript-eslint/member-ordering
revokeAvailable$ = this.receiveClaimService.claim$.pipe(
map(({ status }) => status !== 'revoked' && status !== 'denied' && status !== 'accepted')
);
constructor(
private routeParamClaimService: RouteParamClaimService,
private receiveClaimService: ReceiveClaimService,
private snackBar: MatSnackBar,
private transloco: TranslocoService,
private dialog: MatDialog
) {
this.revokeClaim$
.pipe(
switchMap(() => this.routeParamClaimService.claim$.pipe(first())),
switchMap(({ id, revision }) => this.openRevokeClaimDialog(id, revision)),
untilDestroyed(this)
)
.subscribe(() => {
this.receiveClaimService.receiveClaim();
this.snackBar.open(this.transloco.translate('claim.revoked', null, 'claim-section'), 'OK', {
duration: 2000,
});
});
}
revokeClaim() {
this.revokeClaim$.next();
}
private openRevokeClaimDialog(claimId: number, revision: number) {
return this.dialog
.open(RevokeClaimDialogComponent, {
width: '500px',
data: { claimId, revision },
})
.afterClosed()
.pipe(filter((r) => r === 'revoked'));
}
}

View File

@ -1,20 +0,0 @@
import { Injectable } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { BehaviorSubject } from 'rxjs';
import { switchMap, pluck } from 'rxjs/operators';
import { ClaimsService } from '@dsh/api/claim-management';
import { progressTo, inProgressFrom } from '@dsh/utils';
@Injectable()
export class RouteParamClaimService {
claim$ = this.route.params.pipe(
pluck('claimId'),
switchMap((claimID) => this.claimsService.getClaimByID({ claimID }).pipe(progressTo(this.progress$)))
);
isLoading$ = inProgressFrom(() => this.progress$, this.claim$);
private progress$ = new BehaviorSubject(0);
constructor(private route: ActivatedRoute, private claimsService: ClaimsService) {}
}

View File

@ -1 +0,0 @@
export * from './update-claim.service';

View File

@ -1,15 +0,0 @@
import { FileModification } from '@vality/swag-claim-management';
import { Conversation } from '@vality/swag-messages';
export interface UpdateParams {
type: 'updateConversation' | 'updateFiles';
}
export interface UpdateConversationParams extends UpdateParams {
conversationId: Conversation['conversationId'];
}
export interface UpdateFilesParams extends UpdateParams {
fileIds: string[];
fileModificationType: FileModification.FileModificationTypeEnum;
}

View File

@ -1,21 +0,0 @@
import { Modification } from '@vality/swag-claim-management';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { createCommentModificationUnit, createFileModificationUnit } from '@dsh/api/claim-management';
import { UpdateParams } from './model';
import { isUpdateConversation, isUpdateFiles } from './type-guards';
export const toChangeset = (s: Observable<UpdateParams>): Observable<Modification[]> =>
s.pipe(
map((params) => {
if (isUpdateConversation(params)) {
return [createCommentModificationUnit(params.conversationId)];
}
if (isUpdateFiles(params)) {
return params.fileIds.map((id) => createFileModificationUnit(id, params.fileModificationType));
}
throw new Error('Unknown update claim params');
})
);

View File

@ -1,5 +0,0 @@
import { UpdateConversationParams, UpdateFilesParams, UpdateParams } from './model';
export const isUpdateConversation = (p: UpdateParams): p is UpdateConversationParams => p.type === 'updateConversation';
export const isUpdateFiles = (p: UpdateParams): p is UpdateFilesParams => p.type === 'updateFiles';

View File

@ -1,74 +0,0 @@
import { Injectable } from '@angular/core';
import { TranslocoService } from '@ngneat/transloco';
import { FileModification } from '@vality/swag-claim-management';
import { Conversation } from '@vality/swag-messages';
import { BehaviorSubject, combineLatest, Observable, of, Subject } from 'rxjs';
import { catchError, share, switchMap, tap } from 'rxjs/operators';
import { ClaimsService } from '@dsh/api/claim-management';
import { NotificationService } from '@dsh/app/shared';
import { progress } from '../../../../custom-operators';
import { UiError } from '../../../ui-error';
import { ReceiveClaimService } from '../receive-claim.service';
import { RouteParamClaimService } from '../route-param-claim.service';
import { UpdateConversationParams, UpdateFilesParams, UpdateParams } from './model';
import { toChangeset } from './to-changeset';
@Injectable()
export class UpdateClaimService {
inProgress$: Observable<boolean>;
private updateBy$ = new Subject<UpdateParams>();
private error$ = new BehaviorSubject<UiError>({ hasError: false });
constructor(
private receiveClaimService: ReceiveClaimService,
private routeParamClaimService: RouteParamClaimService,
private claimApiService: ClaimsService,
private notificationService: NotificationService,
private transloco: TranslocoService
) {
const updated$ = this.updateBy$.pipe(
tap(() => this.error$.next({ hasError: false })),
toChangeset,
switchMap((changeset) => combineLatest([of(changeset), this.routeParamClaimService.claim$])),
switchMap(([changeset, { id, revision }]) =>
this.claimApiService.updateClaimByID({ claimID: id, claimRevision: revision, changeset }).pipe(
catchError((ex) => {
console.error(ex);
const error = { hasError: true, code: 'updateClaimByIDFailed' };
this.notificationService.error(
this.transloco.translate(`updateClaim.updateClaimByIDFailed`, null, 'claim-section')
);
this.error$.next(error);
return of(error);
})
)
),
share()
);
this.inProgress$ = progress(this.updateBy$, updated$);
updated$.subscribe(() => this.receiveClaimService.receiveClaim());
}
updateByConversation(conversationId: Conversation['conversationId']) {
this.updateBy$.next({
type: 'updateConversation',
conversationId,
} as UpdateConversationParams);
}
updateByFiles(
fileIds: string[],
fileModificationType: FileModification.FileModificationTypeEnum = FileModification.FileModificationTypeEnum
.FileCreated
) {
this.updateBy$.next({
type: 'updateFiles',
fileIds,
fileModificationType,
} as UpdateFilesParams);
}
}

View File

@ -6,7 +6,7 @@
<dsh-last-updated (update)="refreshList()" [lastUpdated]="lastUpdated"></dsh-last-updated>
<dsh-claim-row-header></dsh-claim-row-header>
<ng-container *ngFor="let claim of claimList">
<dsh-claim-row [claim]="claim" (goToClaimDetails)="goToClaimDetails.emit($event)"></dsh-claim-row>
<dsh-claim-row [claim]="claim"></dsh-claim-row>
</ng-container>
<dsh-show-more-panel *ngIf="hasMore" (showMore)="showMoreElements()" [isLoading]="isLoading"></dsh-show-more-panel>
<div *ngIf="isEmptyList" class="mat-headline">

View File

@ -1,111 +0,0 @@
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FlexLayoutModule } from '@angular/flex-layout';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { TranslocoTestingModule } from '@ngneat/transloco';
import { Claim } from '@vality/swag-claim-management';
import { EmptySearchResultModule } from '@dsh/components/empty-search-result';
import { SpinnerModule } from '@dsh/components/indicators';
import { AccordionModule, CardModule } from '@dsh/components/layout';
import { ShowMorePanelModule } from '@dsh/components/show-more-panel';
import { generateMockClaim } from '../tests/generate-mock-claim';
import { ClaimsListComponent } from './claims-list.component';
@Component({
selector: 'dsh-claim-row-header',
template: '',
})
class MockRowHeaderComponent {}
@Component({
selector: 'dsh-claim-row',
template: '',
})
class MockRowComponent {
@Input() claim: Claim;
@Output() goToClaimDetails: EventEmitter<number> = new EventEmitter();
}
describe('ClaimsListComponent', () => {
let component: ClaimsListComponent;
let fixture: ComponentFixture<ClaimsListComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [
FlexLayoutModule,
SpinnerModule,
EmptySearchResultModule,
AccordionModule,
CardModule,
ShowMorePanelModule,
NoopAnimationsModule,
HttpClientTestingModule,
TranslocoTestingModule.withLangs(
{
ru: {
emptySearchResult: 'Данные за указанный период отсутствуют',
},
},
{
availableLangs: ['ru'],
defaultLang: 'ru',
}
),
],
declarations: [ClaimsListComponent, MockRowHeaderComponent, MockRowComponent],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ClaimsListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
describe('isEmptyList', () => {
it('should be false if list was not provided', () => {
expect(component.isEmptyList).toBe(false);
});
it('should be true if list is empty', () => {
component.claimList = [];
fixture.detectChanges();
expect(component.isEmptyList).toBe(true);
});
it('should be false if list contains at least one element', () => {
component.claimList = new Array(1).fill(generateMockClaim(1));
fixture.detectChanges();
expect(component.isEmptyList).toBe(false);
component.claimList = new Array(15).fill(generateMockClaim(1));
fixture.detectChanges();
expect(component.isEmptyList).toBe(false);
});
});
describe('showMoreElements', () => {
it('should emit output event showMore', () => {
const spyOnShowMore = spyOn(component.showMore, 'emit');
component.showMoreElements();
expect(spyOnShowMore).toHaveBeenCalledTimes(1);
});
});
});

View File

@ -16,7 +16,6 @@ export class ClaimsListComponent {
@Output() refresh = new EventEmitter<void>();
@Output() showMore = new EventEmitter<void>();
@Output() goToClaimDetails: EventEmitter<number> = new EventEmitter();
get isListExist(): boolean {
return !isNil(this.claimList);

View File

@ -6,6 +6,7 @@
>
<dsh-row-header-label fxHide.lt-md fxFlex="15">#</dsh-row-header-label>
<dsh-row-header-label fxHide.lt-md fxFlex="25"> {{ claims('panel.status') }} </dsh-row-header-label>
<dsh-row-header-label fxFlex.gt-sm="30" fxFlex> {{ claims('panel.updatedAt') }} </dsh-row-header-label>
<dsh-row-header-label fxFlex.gt-sm="30" fxFlex></dsh-row-header-label>
<dsh-row-header-label fxFlex.gt-sm="30" fxFlex fxLayoutAlign="end center">
{{ claims('panel.updatedAt') }}
</dsh-row-header-label>
</dsh-row>

View File

@ -1,46 +0,0 @@
import { ChangeDetectionStrategy } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { TranslocoTestingModule } from '@ngneat/transloco';
import { RowModule } from '@dsh/components/layout';
import * as ru from '../../../../../../../assets/i18n/ru.json';
import { ClaimRowHeaderComponent } from './claim-row-header.component';
const TRANSLATION_CONFIG = {
ru,
};
describe('ClaimRowHeaderComponent', () => {
let fixture: ComponentFixture<ClaimRowHeaderComponent>;
let component: ClaimRowHeaderComponent;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
RowModule,
TranslocoTestingModule.withLangs(TRANSLATION_CONFIG, {
availableLangs: ['ru'],
defaultLang: 'ru',
}),
],
declarations: [ClaimRowHeaderComponent],
})
.overrideComponent(ClaimRowHeaderComponent, {
set: {
changeDetection: ChangeDetectionStrategy.Default,
},
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ClaimRowHeaderComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -14,15 +14,8 @@
>{{ (claimStatusDict$ | async)?.[item.status] }}</dsh-status
>
</dsh-row-label>
<dsh-row-label fxFlex.gt-sm="30" fxFlex>
<dsh-row-label fxFlex.gt-sm="30" fxFlex fxLayoutAlign="end center">
<span fxHide.lt-md>{{ item.updatedAt | date: 'dd MMMM yyyy, HH:mm' }}</span>
<span fxHide.gt-sm>{{ item.updatedAt | date: 'dd.MM.yyyy, HH:mm' }}</span>
</dsh-row-label>
<dsh-row-label fxFlex.gt-sm="30" fxFlex fxLayout="row" fxLayoutAlign="end center"
><dsh-navigation-link
*transloco="let claims; scope: 'claim-section'; read: 'claimSection.claimRow'"
[text]="claims('details')"
(click)="goToClaimDetails.emit(claim.id)"
></dsh-navigation-link
></dsh-row-label>
</ng-template>

View File

@ -1,77 +0,0 @@
import { ChangeDetectionStrategy } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { TranslocoTestingModule } from '@ngneat/transloco';
import * as moment from 'moment';
import { ClaimStatusColorPipe } from '@dsh/app/shared/pipes/api-model-types/claim-status-color.pipe';
import { StatusModule } from '@dsh/components/indicators';
import { RowModule } from '@dsh/components/layout';
import * as ru from '../../../../../../../assets/i18n/ru.json';
import { generateMockClaim } from '../../../tests/generate-mock-claim';
import { ClaimRowComponent } from './claim-row.component';
const TRANSLATION_CONFIG = {
ru,
};
describe('ClaimRowComponent', () => {
let fixture: ComponentFixture<ClaimRowComponent>;
let component: ClaimRowComponent;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
RowModule,
TranslocoTestingModule.withLangs(TRANSLATION_CONFIG, {
availableLangs: ['ru'],
defaultLang: 'ru',
}),
StatusModule,
],
declarations: [ClaimRowComponent, ClaimStatusColorPipe],
})
.overrideComponent(ClaimRowComponent, {
set: {
changeDetection: ChangeDetectionStrategy.Default,
},
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ClaimRowComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
describe('template', () => {
it('should show loading value if claim was not provided', () => {
const labels = fixture.debugElement.queryAll(By.css('dsh-row dsh-row-label'));
expect(labels.length).toBe(1);
expect(labels[0].nativeElement.textContent.trim()).toBe('Loading ...');
});
it('should show row component if claim was provided', () => {
const claim = generateMockClaim();
const { createdAt } = claim;
component.claim = claim;
fixture.detectChanges();
const labels = fixture.debugElement.queryAll(By.css('dsh-row dsh-row-label'));
expect(labels[0].nativeElement.textContent.trim()).toBe('1');
expect(labels[1].nativeElement.textContent.trim()).toBe('В ожидании');
expect(labels[2].nativeElement.children[0].textContent.trim()).toBe(
moment(createdAt).format('DD MMMM YYYY, HH:mm')
);
});
});
});

View File

@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { Claim } from '@vality/swag-claim-management';
import { ClaimManagementDictionaryService } from '@dsh/api/claim-management';
@ -11,8 +11,6 @@ import { ClaimManagementDictionaryService } from '@dsh/api/claim-management';
export class ClaimRowComponent {
@Input() claim: Claim;
@Output() goToClaimDetails: EventEmitter<number> = new EventEmitter();
claimStatusDict$ = this.claimManagementDictionaryService.claimStatus$;
constructor(private claimManagementDictionaryService: ClaimManagementDictionaryService) {}

View File

@ -25,6 +25,5 @@
[claimList]="claimsList$ | async"
(refresh)="refresh()"
(showMore)="fetchMore()"
(goToClaimDetails)="goToClaimDetails($event)"
></dsh-claims-list>
</div>

View File

@ -1,5 +1,4 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { Router } from '@angular/router';
import { QueryParamsService } from '@dsh/app/shared';
import { ShopCreationService } from '@dsh/app/shared/components/shop-creation';
@ -25,7 +24,6 @@ export class ClaimsComponent {
constructor(
private fetchClaimsService: FetchClaimsService,
private router: Router,
private qp: QueryParamsService<Filters>,
private shopCreationService: ShopCreationService
) {}
@ -43,10 +41,6 @@ export class ClaimsComponent {
this.fetchClaimsService.refresh();
}
goToClaimDetails(id: number): void {
void this.router.navigate(['claim-section', 'claims', id]);
}
createShop(): void {
this.shopCreationService.createShop();
}

View File

@ -1,19 +1,4 @@
{
"claim": {
"breadcrumbs": {
"claimDetails": "Claim details",
"claims": "Claims"
},
"headline": "Claim details",
"requestReviewClaimByIDFailed": "Failed to send the claim for review",
"reviewClaim": "Send for review",
"reviewed": "The claim has been successfully submitted for review",
"revokeClaim": "Revoke",
"revoked": "The claim has been successfully revoked"
},
"claimRow": {
"details": "Claim details"
},
"claimRowHeader": {
"panel": {
"status": "Status",
@ -26,46 +11,5 @@
"claims": {
"createClaim": "Create claim",
"title": "Claims"
},
"conversation": {
"action": {
"toggleDetails": "Disclose / hide the details"
},
"ago": "ago",
"filesUploaded": "The file has been successfully uploaded",
"sendComment": "Add a comment",
"timeline": {
"claimCreated": "The claim has been created"
},
"timelineActions": {
"changesAdded": "have added the following information",
"commentAdded": "have added comments",
"filesAdded": "have added documents",
"statusAccepted": "approved the claim",
"statusDenied": "denied the claim",
"statusPending": "reviewed the claim",
"statusReview": "have submitted a claim for review",
"statusRevoked": "revoked the claim"
},
"userTypes": {
"external_user": "You",
"internal_user": "Manager"
}
},
"revokeClaimDialog": {
"errors": {
"revokeClaimByIDFailed": "Failed to revoke the claim"
},
"reason": "Specify the reason",
"revoke": "Revoke",
"subheading": "Withdrawal of claim"
},
"sendComment": {
"errors": {
"saveConversationsFailed": "Failed to save comment"
}
},
"updateClaim": {
"updateClaimByIDFailed": "Failed to update the claim"
}
}

View File

@ -1,19 +1,4 @@
{
"claim": {
"breadcrumbs": {
"claimDetails": "Детали заявки",
"claims": "Заявки"
},
"headline": "Детали заявки",
"requestReviewClaimByIDFailed": "Не удалось отправить заявку на рассмотрение",
"reviewClaim": "Отправить на рассмотрение",
"reviewed": "Заявка успешно отправлена на рассмотрение",
"revokeClaim": "Отозвать",
"revoked": "Заявка успешно отозвана"
},
"claimRow": {
"details": "Детали заявки"
},
"claimRowHeader": {
"panel": {
"status": "Статус",
@ -26,46 +11,5 @@
"claims": {
"createClaim": "Создать заявку",
"title": "Заявки"
},
"conversation": {
"action": {
"toggleDetails": "Раскрыть / скрыть детали"
},
"ago": "назад",
"filesUploaded": "Файл успешно загружен",
"sendComment": "Оставьте комментарий",
"timeline": {
"claimCreated": "Заявка создана"
},
"timelineActions": {
"changesAdded": "добавлены следующие сведения",
"commentAdded": "добавлены комментарии",
"filesAdded": "добавлены документы",
"statusAccepted": "одобрена заявка",
"statusDenied": "отклонена заявка",
"statusPending": "рассмотрена заявка",
"statusReview": "заявка отправлена на рассмотрение",
"statusRevoked": "отозвана заявка"
},
"userTypes": {
"external_user": "Вами",
"internal_user": "Менеджером"
}
},
"revokeClaimDialog": {
"errors": {
"revokeClaimByIDFailed": "Не удалось отозвать заявку"
},
"reason": "Укажите причину",
"revoke": "Отозвать",
"subheading": "Отзыв заявки"
},
"sendComment": {
"errors": {
"saveConversationsFailed": "Не удалось сохранить комментарий"
}
},
"updateClaim": {
"updateClaimByIDFailed": "Не удалось обновить заявку"
}
}

View File

@ -1,7 +1,6 @@
export * from './layout.module';
export * from './card';
export * from './panel';
export * from './timeline';
export * from './dropdown';
export * from './details-item';
export * from './row';

View File

@ -11,13 +11,11 @@ import { LinkLabelModule } from './link-label';
import { PanelModule } from './panel';
import { RowModule } from './row';
import { SectionHeaderModule } from './section-header';
import { TimelineModule } from './timeline';
const EXPORTED_MODULES = [
CardModule,
DropdownModule,
PanelModule,
TimelineModule,
DetailsItemModule,
RowModule,
AccordionModule,

View File

@ -1,30 +0,0 @@
@use '@angular/material' as mat;
@import './timeline-item/timeline-item-badge/timeline-item-badge-theme';
@mixin dsh-timeline-theme($theme) {
$foreground: map-get($theme, foreground);
$success-base: map-get($theme, success-base);
$pending-base: map-get($theme, pending-base);
$warn-base: map-get($theme, warn-base);
$line-color: map-get($foreground, border);
.dsh-timeline {
border-bottom-color: $line-color;
&::after {
background-color: $line-color;
}
}
.dsh-timeline-item-badge {
background-color: $line-color;
}
@include dsh-timeline-item-badge-theme($theme);
}
@mixin dsh-timeline-typography($config) {
.dsh-timeline {
@include mat.typography-level($config, body-1);
}
}

View File

@ -1 +0,0 @@
export * from './timeline.module';

View File

@ -1 +0,0 @@
export * from './timeline-item.component';

View File

@ -1,24 +0,0 @@
@import '../../../../../styles/utils/fill';
@mixin dsh-timeline-item-badge-theme($theme) {
$success-base: map-get($theme, success-base);
$pending-base: map-get($theme, pending-base);
$warn-base: map-get($theme, warn-base);
.dsh-timeline-item-badge {
&-success {
background-color: $success-base;
@include fill(map-get($background, card));
}
&-pending {
background-color: $pending-base;
@include fill(map-get($background, card));
}
&-warn {
background-color: $warn-base;
@include fill(map-get($background, card));
}
}
}

View File

@ -1 +0,0 @@
export * from './timeline-item-badge.component';

View File

@ -1,20 +0,0 @@
@import '../../timeline.scss';
.dsh-timeline-item-badge {
position: absolute;
left: 0;
top: 0;
display: flex;
height: $badge-size;
width: $badge-size;
justify-content: center;
align-items: center;
border-radius: 50%;
z-index: 10;
mat-icon {
height: $badge-icon-size;
width: $badge-icon-size;
font-size: $badge-icon-size;
}
}

View File

@ -1,28 +0,0 @@
import { ChangeDetectionStrategy, Component, HostBinding, Input, ViewEncapsulation } from '@angular/core';
import { StatusColor } from '../../../../../app/theme-manager';
@Component({
selector: 'dsh-timeline-item-badge',
template: '<ng-content></ng-content>',
styleUrls: ['timeline-item-badge.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
})
export class TimelineItemBadgeComponent {
@Input() color: StatusColor;
@HostBinding('class.dsh-timeline-item-badge') rootClass = true;
@HostBinding('class.dsh-timeline-item-badge-success') get success() {
return this.color === StatusColor.Success;
}
@HostBinding('class.dsh-timeline-item-badge-warn') get warn() {
return this.color === StatusColor.Warn;
}
@HostBinding('class.dsh-timeline-item-badge-pending') get pending() {
return this.color === StatusColor.Pending;
}
}

View File

@ -1 +0,0 @@
export * from './timeline-item-content.component';

View File

@ -1,6 +0,0 @@
@import '../../timeline.scss';
.dsh-timeline-item-content {
display: block;
padding-top: $content-padding;
}

View File

@ -1,12 +0,0 @@
import { ChangeDetectionStrategy, Component, HostBinding, ViewEncapsulation } from '@angular/core';
@Component({
selector: 'dsh-timeline-item-content, [dsh-timeline-item-content]',
template: '<ng-content></ng-content>',
styleUrls: ['timeline-item-content.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
})
export class TimelineItemContentComponent {
@HostBinding('class.dsh-timeline-item-content') rootClass = true;
}

View File

@ -1 +0,0 @@
export * from './timeline-item-title.component';

View File

@ -1,7 +0,0 @@
@import '../../timeline.scss';
.dsh-timeline-item-title {
min-height: $badge-size;
display: flex;
align-items: center;
}

View File

@ -1,12 +0,0 @@
import { ChangeDetectionStrategy, Component, HostBinding, ViewEncapsulation } from '@angular/core';
@Component({
selector: 'dsh-timeline-item-title',
template: '<ng-content></ng-content>',
styleUrls: ['timeline-item-title.component.scss'],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TimelineItemTitleComponent {
@HostBinding('class.dsh-timeline-item-title') rootClass = true;
}

View File

@ -1,8 +0,0 @@
@import '../timeline.scss';
.dsh-timeline-item {
display: block;
padding-bottom: $content-padding;
padding-left: $badge-size + $content-padding;
position: relative;
}

View File

@ -1,12 +0,0 @@
import { ChangeDetectionStrategy, Component, HostBinding, ViewEncapsulation } from '@angular/core';
@Component({
selector: 'dsh-timeline-item',
template: '<ng-content></ng-content>',
styleUrls: ['timeline-item.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
})
export class TimelineItemComponent {
@HostBinding('class.dsh-timeline-item') rootClass = true;
}

View File

@ -1,15 +0,0 @@
@import './timeline.scss';
.dsh-timeline {
position: relative;
border-bottom: $line-width solid;
&::after {
content: '';
position: absolute;
top: 0;
bottom: 0;
width: $line-width;
left: $badge-size * 0.5 - $line-width * 0.5;
}
}

View File

@ -1,12 +0,0 @@
import { ChangeDetectionStrategy, Component, HostBinding, ViewEncapsulation } from '@angular/core';
@Component({
selector: 'dsh-timeline',
template: '<ng-content></ng-content>',
styleUrls: ['timeline.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
})
export class TimelineComponent {
@HostBinding('class.dsh-timeline') rootClass = true;
}

View File

@ -1,24 +0,0 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FlexLayoutModule } from '@angular/flex-layout';
import { TimelineItemComponent } from './timeline-item';
import { TimelineItemBadgeComponent } from './timeline-item/timeline-item-badge';
import { TimelineItemContentComponent } from './timeline-item/timeline-item-content';
import { TimelineItemTitleComponent } from './timeline-item/timeline-item-title';
import { TimelineComponent } from './timeline.component';
const EXPORTED_DECLARATIONS = [
TimelineComponent,
TimelineItemComponent,
TimelineItemTitleComponent,
TimelineItemBadgeComponent,
TimelineItemContentComponent,
];
@NgModule({
imports: [FlexLayoutModule, CommonModule],
declarations: EXPORTED_DECLARATIONS,
exports: EXPORTED_DECLARATIONS,
})
export class TimelineModule {}

View File

@ -1,4 +0,0 @@
$badge-size: 32px;
$badge-icon-size: 16px;
$line-width: 2px;
$content-padding: 16px;

View File

@ -1,77 +0,0 @@
import { Component, Provider, Type } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { StatusColor } from '../../../app/theme-manager';
import { TimelineItemBadgeComponent } from './timeline-item/timeline-item-badge';
import { TimelineItemContentComponent } from './timeline-item/timeline-item-content';
import { TimelineItemTitleComponent } from './timeline-item/timeline-item-title';
import { TimelineModule } from './timeline.module';
@Component({
template: `
<dsh-timeline>
<dsh-timeline-item>
<dsh-timeline-item-badge [color]="color">Badge</dsh-timeline-item-badge>
<dsh-timeline-item-title>Title</dsh-timeline-item-title>
<dsh-timeline-item-content>Content</dsh-timeline-item-content>
</dsh-timeline-item>
</dsh-timeline>
`,
})
class SampleTimelineComponent {
color: StatusColor;
}
describe('Timeline', () => {
function createComponent<T>(
component: Type<T>,
providers: Provider[] = [],
declarations: any[] = []
): ComponentFixture<T> {
TestBed.configureTestingModule({
imports: [TimelineModule, NoopAnimationsModule],
declarations: [component, ...declarations],
providers,
}).compileComponents();
const fixture = TestBed.createComponent<T>(component);
fixture.detectChanges();
return fixture;
}
describe('TimelineItem', () => {
it('should have badge', () => {
const fixture = createComponent(SampleTimelineComponent);
expect(
fixture.debugElement.query(By.directive(TimelineItemBadgeComponent)).nativeElement.textContent.trim()
).toBe('Badge');
});
it('should have title', () => {
const fixture = createComponent(SampleTimelineComponent);
expect(
fixture.debugElement.query(By.directive(TimelineItemTitleComponent)).nativeElement.textContent.trim()
).toBe('Title');
});
it('should have content', () => {
const fixture = createComponent(SampleTimelineComponent);
expect(
fixture.debugElement.query(By.directive(TimelineItemContentComponent)).nativeElement.textContent.trim()
).toBe('Content');
});
it('should change class when setting color', () => {
const getClassName = <T>(f: ComponentFixture<T>, type = TimelineItemBadgeComponent): string =>
f.debugElement.query(By.directive(type)).nativeElement.className;
const fixture = createComponent(SampleTimelineComponent);
const sourceBadgeClass = getClassName(fixture);
expect(sourceBadgeClass).toEqual('dsh-timeline-item-badge');
fixture.componentInstance.color = StatusColor.Warn;
fixture.detectChanges();
const badgeClassAfterSetColor = getClassName(fixture);
expect(badgeClassAfterSetColor).toEqual('dsh-timeline-item-badge dsh-timeline-item-badge-warn');
});
});
});

View File

@ -3,7 +3,6 @@
@import '../../components/buttons/button-toggle/button-toggle-theme';
@import '../../components/layout/card/card-theme';
@import '../../components/layout/panel/panel-theme';
@import '../../components/layout/timeline/timeline-theme';
@import '../../components/layout/dropdown/dropdown-theme';
@import '../../components/layout/details-item/details-item-theme';
@import '../../components/layout/row/row-theme';
@ -43,7 +42,6 @@
@include dsh-button-theme($theme);
@include dsh-button-toggle-theme($theme);
@include dsh-status-theme($theme);
@include dsh-timeline-theme($theme);
@include dsh-dadata-autocomplete-theme($theme);
@include dsh-details-item-theme($theme);
@include dsh-file-uploader-theme($theme);

View File

@ -3,7 +3,6 @@
@import '../../components/buttons/button/button-theme';
@import '../../components/buttons/button-toggle/button-toggle-theme';
@import '../../components/layout/card/card-theme';
@import '../../components/layout/timeline/timeline-theme';
@import '../../components/layout/panel/panel-theme';
@import '../../components/layout/dropdown/dropdown-theme';
@import '../../components/layout/row/row-theme';
@ -33,7 +32,6 @@
@include dsh-card-typography($config);
@include dsh-button-toggle-typography($config);
@include dsh-dropdown-typography($config);
@include dsh-timeline-typography($config);
@include dsh-dadata-autocomplete-typography($config);
@include dsh-panel-typography($config);
@include dsh-charts-typography($config);