TSK-1474: Update classification details layout (#1358)

Co-authored-by: Chi Nguyen <c.nguyen.prog@gmail.com>
This commit is contained in:
Sofie Hofmann 2020-12-08 17:37:20 +01:00 committed by GitHub
parent ecfe7c3046
commit fc6f420b96
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 283 additions and 251 deletions

View File

@ -1,11 +1,11 @@
<div class="classification-details"> <div class="classification-details">
<div class="classification-details__wrapper" id="classification-details" *ngIf="classification && !requestInProgress"> <div class="classification-details-wrapper" id="classification-details" *ngIf="classification && !requestInProgress">
<!-- TITLE + ACTION BUTTONS --> <!-- TITLE + ACTION BUTTONS -->
<mat-toolbar class="classification-details__action-toolbar"> <mat-toolbar class="action-toolbar">
<h4 class="classification-details__headline">{{classification.name}}&nbsp; [{{classification.type}}] <h4 class="action-toolbar__headline">{{classification.name}}&nbsp; [{{classification.type}}]
<span *ngIf="isCreatingNewClassification" class="badge warning"> {{badgeMessage$ | async}}</span> <span *ngIf="isCreatingNewClassification" class="action-toolbar__badge-message"> {{badgeMessage$ | async}}</span>
</h4> </h4>
<div> <div>
@ -16,7 +16,7 @@
<button mat-stroked-button class="action-toolbar__button" matTooltip="Revert changes to previous saved state" (click)="onRestore()"> <button mat-stroked-button class="action-toolbar__button" matTooltip="Revert changes to previous saved state" (click)="onRestore()">
Undo Changes Undo Changes
<mat-icon class="button__green-blue md-20">restore</mat-icon> <mat-icon class="action-toolbar__aquamarine-button md-20">restore</mat-icon>
</button> </button>
<button mat-stroked-button [matMenuTriggerFor]="buttonMenu" matTooltip="More actions" class="action-toolbar__button" id="action-toolbar__more-buttons"> <button mat-stroked-button [matMenuTriggerFor]="buttonMenu" matTooltip="More actions" class="action-toolbar__button" id="action-toolbar__more-buttons">
@ -25,11 +25,11 @@
<mat-menu #buttonMenu="matMenu"> <mat-menu #buttonMenu="matMenu">
<button mat-menu-item class="action-toolbar__dropdown" matTooltip="Copy current values to create new classification" (click)="onCopy()"> <button mat-menu-item class="action-toolbar__dropdown" matTooltip="Copy current values to create new classification" (click)="onCopy()">
<mat-icon class="button__green-blue">content_copy</mat-icon> <mat-icon class="action-toolbar__aquamarine-button">content_copy</mat-icon>
<span>Copy</span> <span>Copy</span>
</button> </button>
<button mat-menu-item class="action-toolbar__dropdown" matTooltip="Delete this classification" (click)="onRemoveClassification()"> <button mat-menu-item class="action-toolbar__dropdown" matTooltip="Delete this classification" (click)="onRemoveClassification()">
<mat-icon class="button__red">delete</mat-icon> <mat-icon class="action-toolbar__red-button">delete</mat-icon>
<span>Delete</span> <span>Delete</span>
</button> </button>
<button mat-menu-item class="action-toolbar__dropdown" style="border-bottom-style: none;" matTooltip="Close this classification and discard all changes" (click)="onCloseClassification()"> <button mat-menu-item class="action-toolbar__dropdown" style="border-bottom-style: none;" matTooltip="Close this classification and discard all changes" (click)="onCloseClassification()">
@ -38,16 +38,22 @@
</button> </button>
</mat-menu> </mat-menu>
</div> </div>
</mat-toolbar> </mat-toolbar>
<!-- DETAILED FIELDS -->
<div class="panel-body" style="padding: 0">
<ng-form #ClassificationForm="ngForm">
<div class="classification__detailed-fields">
<h6 class="classification-details__subheading" style="margin-top: 65px;"> General </h6> <!-- DETAILED FIELDS -->
<mat-divider class="classification-details__horizontal-line"> </mat-divider> <div style="padding: 0">
<ng-form #ClassificationForm="ngForm">
<div class="detailed-fields">
<h6 class="detailed-fields__subheading" style="margin-top: 65px;"> General </h6>
<mat-divider class="detailed-fields__horizontal-line"> </mat-divider>
<!-- GENERAL -->
<div class="detailed-fields__general">
<!-- GENERAL LEFT COLUMN -->
<div class="detailed-fields__general-left-column">
<!-- KEY --> <!-- KEY -->
<mat-form-field appearance="outline"> <mat-form-field appearance="outline">
@ -77,11 +83,54 @@
errorMessage="* Name is required"> errorMessage="* Name is required">
</taskana-shared-field-error-display> --> </taskana-shared-field-error-display> -->
<!-- DESCRIPTION -->
<mat-form-field appearance="outline">
<mat-label>Description</mat-label>
<label for="classification-description"></label>
<textarea matInput
cdkTextareaAutosize
cdkAutosizeMinRows="6"
cdkAutosizeMaxRows="6"
maxlength="255"
id="classification-description" placeholder="Description"
[(ngModel)]="classification.description"
name="classification.description" #description="ngModel"
(input)="validateInputOverflow(description, 255)"></textarea>
</mat-form-field>
<div *ngIf="inputOverflowMap.get(description.name)" class="error">{{lengthError}}</div>
</div>
<div class="detailed-fields__spacer"> </div>
<!-- GENERAL RIGHT ROW -->
<div class="detailed-fields__general-right-column">
<!-- DOMAIN -->
<mat-form-field appearance="outline">
<mat-label>Domain</mat-label>
<label for="classification-domain"></label>
<input matInput type="text" disabled id="classification-domain"
placeholder="Domain" [(ngModel)]="classification.domain"
name="classification.domain">
</mat-form-field>
<div *ngIf="!masterDomainSelected()" class="detailed-fields__domain-checkbox">
Valid in Domain
<a (click)="validChanged()" title="Valid in Domain">
<mat-icon class="detailed-fields__domain-checkbox-icon">{{classification.isValidInDomain ? 'check_box' : 'check_box_outline_blank'}}</mat-icon>
</a>
</div>
<!-- SERVICE LEVEL AND PRIORITY--> <!-- SERVICE LEVEL AND PRIORITY-->
<div class="classification-details__service-and-priority"> <div class="detailed-fields__service-and-priority">
<div class="classification-details__service">
<mat-form-field appearance="outline" class="classification-details__mat-form-field"> <!-- SERVICE LEVEL -->
<div style="width: 48%">
<mat-form-field appearance="outline" style="width: 100%">
<mat-label> Service Level </mat-label> <mat-label> Service Level </mat-label>
<label for="classification-service-level"></label> <label for="classification-service-level"></label>
<input matInput type="text" required maxlength="255" <input matInput type="text" required maxlength="255"
@ -92,16 +141,9 @@
<div *ngIf="inputOverflowMap.get(serviceLevel.name)" class="error">{{lengthError}}</div> <div *ngIf="inputOverflowMap.get(serviceLevel.name)" class="error">{{lengthError}}</div>
</div> </div>
<div class="classification-details__priority"> <!-- PRIORITY-->
<!-- I replaced this component by the mat-form-field. Is this the same? <div style="width: 48%">
I don't understand all methods in the number-picker component. <mat-form-field appearance="outline" style="width: 100%">
<taskana-shared-number-picker [(ngModel)]="classification.priority"
name="classification.priority"
id="classification-priority"
[required]="true"
[name]="'Priority'">
</taskana-shared-number-picker> -->
<mat-form-field appearance="outline">
<mat-label>Priority</mat-label> <mat-label>Priority</mat-label>
<label for="classification-priority"></label> <label for="classification-priority"></label>
<input matInput type="number" [(ngModel)]="classification.priority" <input matInput type="number" [(ngModel)]="classification.priority"
@ -113,47 +155,9 @@
[validationTrigger]="this.toggleValidationMap.get('classification.priority.name')" [validationTrigger]="this.toggleValidationMap.get('classification.priority.name')"
errorMessage="* Priority is required"> errorMessage="* Priority is required">
</taskana-shared-field-error-display> </taskana-shared-field-error-display>
</div> </div>
</div> </div>
<!-- DOMAIN AND CATEGORY -->
<div class="classification-details__domain-and-category">
<mat-form-field class="classification-details__mat-form-field" appearance="outline">
<mat-label>Domain</mat-label>
<label for="classification-domain"></label>
<input matInput type="text" disabled id="classification-domain"
placeholder="Domain" [(ngModel)]="classification.domain"
name="classification.domain">
</mat-form-field>
<div class="domain-and-category__domain-checkbox">
Valid in Domain
<a *ngIf="!masterDomainSelected()" (click)="validChanged()" title="Valid in Domain">
<mat-icon class="domain-and-category__domain-checkbox-icon">{{classification.isValidInDomain ? 'check_box' : 'check_box_outline_blank'}}</mat-icon>
</a>
</div>
<mat-form-field appearance="outline">
<mat-label>Category</mat-label>
<mat-select required [(value)]="this.classification.category">
<mat-select-trigger>
<svg-icon
class="domain-and-category__category-icon" [src]="(getCategoryIcon(this.classification.category) | async)?.name">
</svg-icon>
{{this.classification.category}}
</mat-select-trigger>
<mat-option *ngFor="let category of categories$ | async" value="{{category}}">
<svg-icon class="domain-and-category__category-icon" [src]="(getCategoryIcon(category) | async)?.name"></svg-icon>
{{category}}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<!-- APPLICATION ENTRY POINT --> <!-- APPLICATION ENTRY POINT -->
<mat-form-field appearance="outline"> <mat-form-field appearance="outline">
<mat-label>Application entry point</mat-label> <mat-label>Application entry point</mat-label>
@ -167,30 +171,39 @@
</mat-form-field> </mat-form-field>
<div *ngIf="inputOverflowMap.get(appEntryPoint.name)" class="error">{{lengthError}}</div> <div *ngIf="inputOverflowMap.get(appEntryPoint.name)" class="error">{{lengthError}}</div>
<!-- DESCRIPTION --> <!-- CATEGORY -->
<mat-form-field appearance="outline"> <mat-form-field appearance="outline">
<mat-label>Description</mat-label> <mat-label>Category</mat-label>
<label for="classification-description"></label> <mat-select required [(value)]="this.classification.category">
<textarea matInput <mat-select-trigger>
cdkTextareaAutosize <svg-icon
cdkAutosizeMinRows="1" class="detailed-fields__category-icon" [src]="(getCategoryIcon(this.classification.category) | async)?.name">
cdkAutosizeMaxRows="5" </svg-icon>
maxlength="255" {{this.classification.category}}
id="classification-description" placeholder="Description" </mat-select-trigger>
[(ngModel)]="classification.description" <mat-option *ngFor="let category of categories$ | async" value="{{category}}">
name="classification.description" #description="ngModel" <svg-icon class="detailed-fields__category-icon" [src]="(getCategoryIcon(category) | async)?.name"></svg-icon>
(input)="validateInputOverflow(description, 255)"></textarea> {{category}}
</mat-option>
</mat-select>
</mat-form-field> </mat-form-field>
<div *ngIf="inputOverflowMap.get(description.name)" class="error">{{lengthError}}</div>
</div>
</div>
<!-- CUSTOM FIELDS --> <!-- CUSTOM FIELDS -->
<h6 class="classification-details__subheading" style="padding-top: 50px"> Custom Fields </h6> <h6 class="detailed-fields__subheading" style="padding-top: 50px"> Custom Fields </h6>
<mat-divider class="classification-details__horizontal-line"> </mat-divider> <mat-divider class="detailed-fields__horizontal-line"> </mat-divider>
<div class="detailed-fields__custom-fields">
<div *ngFor="let customField of (customFields$ | async), let i = index" > <div *ngFor="let customField of (customFields$ | async), let i = index" class="detailed-fields__input-custom-field">
<div *ngIf="customField.visible">
<mat-form-field appearance="outline" style="width: 100%"> <mat-form-field appearance="outline" style="width: 100%">
<mat-label>{{customField.field}}</mat-label> <mat-label>{{customField.field}}</mat-label>
<label for="classification-custom-{{i + 1}}"></label> <label for="classification-custom-{{i + 1}}"></label>
@ -203,7 +216,6 @@
<div *ngIf="inputOverflowMap.get(custom.name)" class="error">{{lengthError}}</div> <div *ngIf="inputOverflowMap.get(custom.name)" class="error">{{lengthError}}</div>
</div> </div>
</div> </div>
</div> </div>
</ng-form> </ng-form>
</div> </div>

View File

@ -5,16 +5,11 @@
overflow-y: auto; overflow-y: auto;
} }
.classification-details__wrapper { .classification-details-wrapper {
position: relative; position: relative;
} }
/* ACTION TOOLBAR */ .action-toolbar {
.classification-details__headline {
font-size: 1.5rem;
padding: 0.5rem;
}
.classification-details__action-toolbar {
padding: 8px 30px 12px 24px; padding: 8px 30px 12px 24px;
height: 68px; height: 68px;
background-color: #fff; background-color: #fff;
@ -23,93 +18,121 @@
justify-content: space-between; justify-content: space-between;
flex-wrap: wrap; flex-wrap: wrap;
z-index: 10; z-index: 10;
}
.action-toolbar__button { &__headline {
font-size: 1.5rem;
padding: 0.5rem;
}
&__badge-message {
font-size: 13px;
background-color: $transparent-grey;
}
&__button {
margin-top: 0.25rem; margin-top: 0.25rem;
margin-right: 6px; margin-right: 6px;
background-color: #fff; background-color: #fff;
} }
.action-toolbar__dropdown { &__dropdown {
border-color: $transparent-grey; border-color: $transparent-grey;
border-bottom-style: solid; border-bottom-style: solid;
border-width: 1px; border-width: 1px;
} }
.action-toolbar__save-button { &__save-button {
background-color: $aquamarine; background-color: $aquamarine;
color: white; color: white;
} }
.button__green-blue { &__aquamarine-button {
color: $aquamarine; color: $aquamarine;
} }
.button__red { &__red-button {
color: $invalid; color: $invalid;
}
} }
/* DETAILED FIELDS */ .detailed-fields {
.classification__detailed-fields {
padding: 15px; padding: 15px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
}
.classification-details__subheading { &__subheading {
font-weight: bold; font-weight: bold;
padding-left: 15px; padding-left: 15px;
margin-bottom: 0; margin-bottom: 0;
} }
.classification-details__horizontal-line { &__horizontal-line {
margin: 5px 5px 25px 5px; margin: 5px 5px 25px 5px;
border-top-color: #555; border-top-color: #555;
border-top-width: 1.35px; border-top-width: 1.35px;
} }
.classification-details__domain-and-category { &__general {
position: relative;
display: flex; display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-between; justify-content: space-between;
padding-bottom: 16px; }
}
.domain-and-category__domain-checkbox { &__general-right-column {
display: flex;
flex-direction: column;
width: 30%;
}
&__spacer {
display: block;
width: 20px;
}
&__general-left-column {
display: flex;
flex-direction: column;
flex-wrap: wrap;
width: 70%;
}
&__domain-checkbox {
position: absolute; position: absolute;
top: 64px; top: 172px;
left: 12px; right: 12px;
font-size: 14px; font-size: 12px;
color: #555; color: #555;
} }
.domain-and-category__domain-checkbox-icon {
&__domain-checkbox-icon {
cursor: pointer; cursor: pointer;
margin-top: 2px; margin-top: 2px;
font-size: 20px; font-size: 18px;
} }
.domain-and-category__category-icon {
&__service-and-priority {
display: flex;
justify-content: space-between;
width: 100%;
}
&__category-icon {
fill: #555; fill: #555;
margin-right: 5px; margin-right: 5px;
top: -2px; top: -2px;
} }
.classification-details__service-and-priority { &__custom-fields {
display: flex; display: flex;
width: 100%; flex-direction: row;
} flex-wrap: wrap;
.classification-details__service { justify-content: space-between;
width: 100%; }
margin-right: 10px;
}
.classification-details__priority {
flex-grow: 1;
}
.classification-details__mat-form-field { &__input-custom-field {
width: 100%; flex: 0 49%;
margin-right: 10px; }
} }
input:invalid.dirty { input:invalid.dirty {

View File

@ -248,21 +248,21 @@ describe('ClassificationDetailsComponent', () => {
component.requestInProgress = true; component.requestInProgress = true;
component.classification = {}; component.classification = {};
fixture.detectChanges(); fixture.detectChanges();
expect(debugElement.nativeElement.querySelector('.classification-details__action-toolbar')).toBeFalsy(); expect(debugElement.nativeElement.querySelector('.action-toolbar')).toBeFalsy();
expect(debugElement.nativeElement.querySelector('.classification__detailed-fields')).toBeFalsy(); expect(debugElement.nativeElement.querySelector('.detailed-fields')).toBeFalsy();
}); });
it('should not show details when classification does not exist', () => { it('should not show details when classification does not exist', () => {
component.requestInProgress = false; component.requestInProgress = false;
component.classification = null; component.classification = null;
fixture.detectChanges(); fixture.detectChanges();
expect(debugElement.nativeElement.querySelector('.classification-details__action-toolbar')).toBeFalsy(); expect(debugElement.nativeElement.querySelector('.action-toolbar')).toBeFalsy();
expect(debugElement.nativeElement.querySelector('.classification__detailed-fields')).toBeFalsy(); expect(debugElement.nativeElement.querySelector('.detailed-fields')).toBeFalsy();
}); });
it('should show details when classification exists and spinner is not running', () => { it('should show details when classification exists and spinner is not running', () => {
expect(debugElement.nativeElement.querySelector('.classification-details__action-toolbar')).toBeTruthy(); expect(debugElement.nativeElement.querySelector('.action-toolbar')).toBeTruthy();
expect(debugElement.nativeElement.querySelector('.classification__detailed-fields')).toBeTruthy(); expect(debugElement.nativeElement.querySelector('.detailed-fields')).toBeTruthy();
}); });
/* HTML: TITLE + ACTION BUTTONS */ /* HTML: TITLE + ACTION BUTTONS */
@ -270,7 +270,7 @@ describe('ClassificationDetailsComponent', () => {
component.classification = { name: 'Recommendation', type: 'DOCUMENT' }; component.classification = { name: 'Recommendation', type: 'DOCUMENT' };
component.isCreatingNewClassification = true; component.isCreatingNewClassification = true;
fixture.detectChanges(); fixture.detectChanges();
const headline = debugElement.nativeElement.querySelector('.classification-details__headline'); const headline = debugElement.nativeElement.querySelector('.action-toolbar__headline');
expect(headline).toBeTruthy(); expect(headline).toBeTruthy();
expect(headline.textContent).toContain('Recommendation'); expect(headline.textContent).toContain('Recommendation');
expect(headline.textContent).toContain('DOCUMENT'); expect(headline.textContent).toContain('DOCUMENT');
@ -290,8 +290,7 @@ describe('ClassificationDetailsComponent', () => {
}); });
it('should restore selected classification when button is clicked', async () => { it('should restore selected classification when button is clicked', async () => {
const button = debugElement.nativeElement.querySelector('.classification-details__action-toolbar').children[1] const button = debugElement.nativeElement.querySelector('.action-toolbar').children[1].children[1];
.children[1];
expect(button).toBeTruthy(); expect(button).toBeTruthy();
expect(button.textContent).toContain('Undo Changes'); expect(button.textContent).toContain('Undo Changes');
expect(button.textContent).toContain('restore'); expect(button.textContent).toContain('restore');
@ -391,7 +390,7 @@ describe('ClassificationDetailsComponent', () => {
}); });
it('should change isValidInDomain when button is clicked', () => { it('should change isValidInDomain when button is clicked', () => {
const button = debugElement.nativeElement.querySelector('.domain-and-category__domain-checkbox-icon').parentNode; const button = debugElement.nativeElement.querySelector('.detailed-fields__domain-checkbox-icon').parentNode;
expect(button).toBeTruthy(); expect(button).toBeTruthy();
component.classification.isValidInDomain = false; component.classification.isValidInDomain = false;
button.click(); button.click();

View File

@ -69,7 +69,8 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
ngOnInit() { ngOnInit() {
this.customFields$ = this.store.select(EngineConfigurationSelectors.classificationsCustomisation).pipe( this.customFields$ = this.store.select(EngineConfigurationSelectors.classificationsCustomisation).pipe(
map((customisation) => customisation.information), map((customisation) => customisation.information),
getCustomFields(customFieldCount) getCustomFields(customFieldCount),
map((customisationFields) => customisationFields.filter((customisation) => customisation.visible))
); );
this.selectedClassification$.pipe(takeUntil(this.destroy$)).subscribe((classification) => { this.selectedClassification$.pipe(takeUntil(this.destroy$)).subscribe((classification) => {

View File

@ -23,8 +23,8 @@
<!-- FILTER --> <!-- FILTER -->
<div class="classification-list__filter" *ngIf="showFilter"> <div class="classification-list__filter" *ngIf="showFilter">
<div class="classification-list__category-filter"> <div class="classification-list__category-filter">
<button mat-icon-button class="mr-2 category-filter__filter-button" [matMenuTriggerFor]="menu" <button mat-icon-button [matMenuTriggerFor]="menu"
matTooltip="Filter Category"> matTooltip="Filter Category" class="category-filter__filter-button">
<mat-icon *ngIf="selectedCategory == ''">filter_list</mat-icon> <mat-icon *ngIf="selectedCategory == ''">filter_list</mat-icon>
<svg-icon class="category-filter__icons" [src]="(getCategoryIcon(selectedCategory) | async)?.name" <svg-icon class="category-filter__icons" [src]="(getCategoryIcon(selectedCategory) | async)?.name"
[title]="(getCategoryIcon(selectedCategory) | async)?.text" [title]="(getCategoryIcon(selectedCategory) | async)?.text"
@ -34,12 +34,12 @@
<mat-menu #menu="matMenu"> <mat-menu #menu="matMenu">
<button mat-menu-item (click)="selectCategory('')" class="category-filter__all-button"> <button mat-menu-item (click)="selectCategory('')" class="category-filter__all-button">
<svg-icon class="category-filter__categories pr-2" src="./assets/icons/asterisk.svg" <svg-icon class="category-filter__categories" src="./assets/icons/asterisk.svg"
[title]="(getCategoryIcon('all') | async)?.text"></svg-icon> [title]="(getCategoryIcon('all') | async)?.text"></svg-icon>
<span>All</span> <span>All</span>
</button> </button>
<button mat-menu-item *ngFor="let category of categories$ | async" (click)="selectCategory(category)"> <button mat-menu-item *ngFor="let category of categories$ | async" (click)="selectCategory(category)">
<svg-icon class="category-filter__categories pr-2" [src]="(getCategoryIcon(category) | async)?.name" <svg-icon class="category-filter__categories" [src]="(getCategoryIcon(category) | async)?.name"
[title]="(getCategoryIcon(category) | async)?.text"></svg-icon> [title]="(getCategoryIcon(category) | async)?.text"></svg-icon>
<span> {{category}} </span> <span> {{category}} </span>
</button> </button>

View File

@ -32,6 +32,7 @@
.category-filter__icons { .category-filter__icons {
height: 33px; height: 33px;
width: 16px; width: 16px;
fill: #555;
} }
.category-filter__categories { .category-filter__categories {
fill: #555; fill: #555;
@ -39,10 +40,6 @@
top: -2px; top: -2px;
} }
.category-filter__filter-button {
color: #555;
}
.filter__input { .filter__input {
width: 100%; width: 100%;
margin-right: 12px; margin-right: 12px;

View File

@ -6,7 +6,7 @@
align-items: stretch; align-items: stretch;
} }
taskana-administration-classification-list { taskana-administration-classification-list {
width: 500px; min-width: 500px;
} }
taskana-administration-classification-details { taskana-administration-classification-details {
flex-grow: 1; flex-grow: 1;