Merged PR 1381: #3326 Tätigkeitskalender Suchfunktion und neues Design

Tätigkeitskalender Suchfunktion und neues Design

Zusätzliche Änderungen im PR:
- Anpassungen für GitHub package Zugriff
- UiModalRef um ein afterChanged$ erweitert, um nach dem schließen zu erkennen ob ein reload notwendig ist
- ui-loader funktionierte nicht bei verwendung von ui-scroll-container mit useLoadAnimation false
- ui-skeleton-loader um Template für TK Listenitem erweitert

Related work items: #3419, #3420, #3421, #3422, #3423
This commit is contained in:
Andreas Schickinger
2022-10-04 09:42:49 +00:00
committed by Lorenz Hilpert
parent fdaceb9bf8
commit 9dd0954967
60 changed files with 1913 additions and 864 deletions

4
.npmrc
View File

@@ -1,3 +1 @@
@isa:registry=https://pkgs.dev.azure.com/hugendubel/_packaging/hugendubel%40Local/npm/registry/ @paragondata:registry=https://npm.pkg.github.com
@cmf:registry=https://pkgs.dev.azure.com/hugendubel/_packaging/hugendubel%40Local/npm/registry/
always-auth=true

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { EISPublicService, FileDTO, ProcessingStatus, QueryTokenDTO } from '@swagger/eis'; import { DisplayInfoDTO, EISPublicService, FileDTO, ProcessingStatus, QueryTokenDTO } from '@swagger/eis';
import { DateAdapter } from '@ui/common'; import { DateAdapter } from '@ui/common';
import { memorize } from '@utils/common'; import { memorize } from '@utils/common';
import { map, shareReplay, switchMap } from 'rxjs/operators'; import { map, shareReplay, switchMap } from 'rxjs/operators';
@@ -279,4 +279,80 @@ export class DomainTaskCalendarService {
) )
); );
} }
moveRemovedToEnd(a: DisplayInfoDTO, b: DisplayInfoDTO) {
const statusA = this.getProcessingStatusList(a)?.includes('Removed');
const statusB = this.getProcessingStatusList(b)?.includes('Removed');
if (statusA && statusB) {
return 0;
} else if (statusA && !statusB) {
return 1;
} else if (!statusA && statusB) {
return -1;
}
return 0;
}
/**
* Returns an Array of DisplayInfoDTO, sorted by ProcessingStatus
* Ignores Overdue if Task is already Completed
* Compared DisploayInfoDTO is of Type Task and Info Or PreInfo then sort by Type
* @param items DisplayInfoDTO Array to sort
* @param order Processing Status Order
* @returns DisplayInfoDTO Array ordered by Processing Status anf Type
*/
sort(items: DisplayInfoDTO[], order: ProcessingStatusList) {
let result = [...items];
const reversedOrder = [...order].reverse();
for (const status of reversedOrder) {
result = result?.sort((a, b) => {
const statusA = this.getProcessingStatusList(a);
const statusB = this.getProcessingStatusList(b);
// Ignore Overdue when it is already Completed
if (status === 'Overdue' && statusA.includes('Completed')) {
return 0;
}
const aHasStatus = statusA.includes(status);
const bHasStatus = statusB.includes(status);
if (aHasStatus && bHasStatus) {
// If it has the same ProcessingStatus then Sort by Type
const aType = this.getInfoType(a);
const bType = this.getInfoType(b);
if (aType !== bType) {
if (aType === 'Info' || aType === 'PreInfo') {
return -1;
} else {
return 1;
}
}
if (statusB.includes('Completed')) {
return -1;
}
return 0;
} else if (aHasStatus && !bHasStatus) {
return -1;
} else if (!aHasStatus && bHasStatus) {
return 1;
}
return 0;
});
}
return result;
}
getDateGroupKey(d: string) {
// Get Date as string key to ignore time for grouping
const date = new Date(d);
return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`;
}
} }

View File

@@ -212,4 +212,7 @@
<g id="shiiping_document" transform="matrix(1.11057,0,0,1.11057,2.2921,-1.02614)"> <g id="shiiping_document" transform="matrix(1.11057,0,0,1.11057,2.2921,-1.02614)">
<path d="M20.713,0.924L2.151,0.924C1.824,0.924 1.517,1.048 1.288,1.283C1.06,1.512 0.936,1.819 0.936,2.139L0.936,25.634C0.936,26.307 1.478,26.849 2.151,26.849C2.151,26.849 2.758,26.849 2.758,26.849C2.758,26.849 2.758,28.523 2.758,28.523C2.758,29.196 3.3,29.738 3.973,29.738L22.535,29.738C23.208,29.738 23.75,29.196 23.75,28.523L23.75,5.028C23.75,4.361 23.202,3.819 22.535,3.819C22.535,3.819 21.928,3.819 21.928,3.819C21.928,3.819 21.928,2.139 21.928,2.139C21.928,1.466 21.386,0.924 20.713,0.924ZM22.471,5.098L22.471,28.459L4.037,28.459L4.037,26.849C4.037,26.849 20.713,26.849 20.713,26.849C21.341,26.849 21.855,26.371 21.921,25.766L21.928,25.64C21.928,25.64 21.828,25.734 21.828,25.734L21.928,25.634L21.928,5.098L22.471,5.098ZM2.215,2.197L2.215,25.57L20.649,25.57L20.649,2.197L2.215,2.197ZM9.403,22.1L3.397,22.1C3.042,22.1 2.758,22.384 2.758,22.739C2.758,23.094 3.042,23.379 3.397,23.379C3.397,23.379 9.403,23.379 9.403,23.379C9.758,23.379 10.043,23.094 10.043,22.739C10.043,22.384 9.758,22.1 9.403,22.1ZM14.522,19.469L3.277,19.469C2.996,19.469 2.758,19.747 2.758,20.108C2.758,20.469 2.996,20.747 3.277,20.747L14.522,20.747C14.803,20.747 15.041,20.469 15.041,20.108C15.041,19.747 14.803,19.469 14.522,19.469ZM12.352,16.843L3.397,16.843C3.042,16.843 2.758,17.127 2.758,17.483C2.758,17.838 3.042,18.122 3.397,18.122C3.397,18.122 12.352,18.122 12.352,18.122C12.707,18.122 12.992,17.838 12.992,17.483C12.992,17.127 12.707,16.843 12.352,16.843ZM14.478,14.212L3.322,14.212C3.013,14.212 2.758,14.492 2.758,14.851C2.758,15.211 3.013,15.491 3.322,15.491C3.322,15.491 14.478,15.491 14.478,15.491C14.786,15.491 15.041,15.211 15.041,14.851C15.041,14.492 14.786,14.212 14.478,14.212ZM9.593,11.587L3.298,11.587C3.004,11.587 2.758,11.866 2.758,12.226C2.758,12.587 3.004,12.866 3.298,12.866C3.298,12.866 9.593,12.866 9.593,12.866C9.887,12.866 10.133,12.587 10.133,12.226C10.133,11.866 9.887,11.587 9.593,11.587ZM15.163,3.657C12.86,3.657 10.987,5.53 10.987,7.833C10.987,10.136 12.86,12.009 15.163,12.009C17.466,12.009 19.345,10.136 19.345,7.833C19.345,5.53 17.472,3.657 15.163,3.657ZM15.163,4.936C16.762,4.936 18.066,6.234 18.066,7.833C18.066,9.432 16.768,10.73 15.163,10.73C13.564,10.73 12.266,9.432 12.266,7.833C12.266,6.234 13.564,4.936 15.163,4.936ZM9.525,8.956L3.366,8.956C3.03,8.956 2.758,9.238 2.758,9.595C2.758,9.952 3.03,10.234 3.366,10.234L9.525,10.234C9.861,10.234 10.133,9.952 10.133,9.595C10.133,9.238 9.861,8.956 9.525,8.956ZM14.821,7.954L14.61,7.634C14.459,7.403 14.127,7.319 13.867,7.453C13.596,7.593 13.507,7.902 13.664,8.142C13.664,8.142 14.228,9.011 14.228,9.011C14.313,9.141 14.457,9.233 14.626,9.251C14.651,9.256 14.676,9.256 14.701,9.256C14.846,9.256 14.984,9.207 15.087,9.116C15.087,9.116 17.248,7.196 17.248,7.196C17.469,6.999 17.469,6.68 17.248,6.483C17.037,6.291 16.693,6.291 16.481,6.479L14.821,7.954ZM2.164,2.197C2.16,2.197 2.157,2.197 2.154,2.197C2.152,2.197 2.151,2.197 2.151,2.197L2.164,2.197ZM2.151,2.097L2.151,2.097L2.151,2.197L2.151,2.097Z" style="fill:rgb(0,4,0);"/> <path d="M20.713,0.924L2.151,0.924C1.824,0.924 1.517,1.048 1.288,1.283C1.06,1.512 0.936,1.819 0.936,2.139L0.936,25.634C0.936,26.307 1.478,26.849 2.151,26.849C2.151,26.849 2.758,26.849 2.758,26.849C2.758,26.849 2.758,28.523 2.758,28.523C2.758,29.196 3.3,29.738 3.973,29.738L22.535,29.738C23.208,29.738 23.75,29.196 23.75,28.523L23.75,5.028C23.75,4.361 23.202,3.819 22.535,3.819C22.535,3.819 21.928,3.819 21.928,3.819C21.928,3.819 21.928,2.139 21.928,2.139C21.928,1.466 21.386,0.924 20.713,0.924ZM22.471,5.098L22.471,28.459L4.037,28.459L4.037,26.849C4.037,26.849 20.713,26.849 20.713,26.849C21.341,26.849 21.855,26.371 21.921,25.766L21.928,25.64C21.928,25.64 21.828,25.734 21.828,25.734L21.928,25.634L21.928,5.098L22.471,5.098ZM2.215,2.197L2.215,25.57L20.649,25.57L20.649,2.197L2.215,2.197ZM9.403,22.1L3.397,22.1C3.042,22.1 2.758,22.384 2.758,22.739C2.758,23.094 3.042,23.379 3.397,23.379C3.397,23.379 9.403,23.379 9.403,23.379C9.758,23.379 10.043,23.094 10.043,22.739C10.043,22.384 9.758,22.1 9.403,22.1ZM14.522,19.469L3.277,19.469C2.996,19.469 2.758,19.747 2.758,20.108C2.758,20.469 2.996,20.747 3.277,20.747L14.522,20.747C14.803,20.747 15.041,20.469 15.041,20.108C15.041,19.747 14.803,19.469 14.522,19.469ZM12.352,16.843L3.397,16.843C3.042,16.843 2.758,17.127 2.758,17.483C2.758,17.838 3.042,18.122 3.397,18.122C3.397,18.122 12.352,18.122 12.352,18.122C12.707,18.122 12.992,17.838 12.992,17.483C12.992,17.127 12.707,16.843 12.352,16.843ZM14.478,14.212L3.322,14.212C3.013,14.212 2.758,14.492 2.758,14.851C2.758,15.211 3.013,15.491 3.322,15.491C3.322,15.491 14.478,15.491 14.478,15.491C14.786,15.491 15.041,15.211 15.041,14.851C15.041,14.492 14.786,14.212 14.478,14.212ZM9.593,11.587L3.298,11.587C3.004,11.587 2.758,11.866 2.758,12.226C2.758,12.587 3.004,12.866 3.298,12.866C3.298,12.866 9.593,12.866 9.593,12.866C9.887,12.866 10.133,12.587 10.133,12.226C10.133,11.866 9.887,11.587 9.593,11.587ZM15.163,3.657C12.86,3.657 10.987,5.53 10.987,7.833C10.987,10.136 12.86,12.009 15.163,12.009C17.466,12.009 19.345,10.136 19.345,7.833C19.345,5.53 17.472,3.657 15.163,3.657ZM15.163,4.936C16.762,4.936 18.066,6.234 18.066,7.833C18.066,9.432 16.768,10.73 15.163,10.73C13.564,10.73 12.266,9.432 12.266,7.833C12.266,6.234 13.564,4.936 15.163,4.936ZM9.525,8.956L3.366,8.956C3.03,8.956 2.758,9.238 2.758,9.595C2.758,9.952 3.03,10.234 3.366,10.234L9.525,10.234C9.861,10.234 10.133,9.952 10.133,9.595C10.133,9.238 9.861,8.956 9.525,8.956ZM14.821,7.954L14.61,7.634C14.459,7.403 14.127,7.319 13.867,7.453C13.596,7.593 13.507,7.902 13.664,8.142C13.664,8.142 14.228,9.011 14.228,9.011C14.313,9.141 14.457,9.233 14.626,9.251C14.651,9.256 14.676,9.256 14.701,9.256C14.846,9.256 14.984,9.207 15.087,9.116C15.087,9.116 17.248,7.196 17.248,7.196C17.469,6.999 17.469,6.68 17.248,6.483C17.037,6.291 16.693,6.291 16.481,6.479L14.821,7.954ZM2.164,2.197C2.16,2.197 2.157,2.197 2.154,2.197C2.152,2.197 2.151,2.197 2.151,2.197L2.164,2.197ZM2.151,2.097L2.151,2.097L2.151,2.197L2.151,2.097Z" style="fill:rgb(0,4,0);"/>
</g> </g>
<g id="filter_new" transform="matrix(1.77778,0,0,2,0.000431998,3.99944)">
<path d="M7,12L11,12L11,10L7,10L7,12ZM0,0L0,2L18,2L18,0L0,0ZM3,7L15,7L15,5L3,5L3,7Z" style="fill-rule:nonzero;"/>
</g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 105 KiB

View File

@@ -46,8 +46,7 @@ export class GoodsOutSearchFilterComponent implements OnInit, OnDestroy {
private _goodsOutSearchStore: GoodsOutSearchStore, private _goodsOutSearchStore: GoodsOutSearchStore,
private _breadcrumb: BreadcrumbService, private _breadcrumb: BreadcrumbService,
private _cdr: ChangeDetectorRef, private _cdr: ChangeDetectorRef,
private _router: Router, private _router: Router
private readonly _config: Config
) {} ) {}
ngOnInit() { ngOnInit() {

View File

@@ -1,6 +1,16 @@
import { Clipboard } from '@angular/cdk/clipboard'; import { Clipboard } from '@angular/cdk/clipboard';
import { DatePipe } from '@angular/common'; import { DatePipe } from '@angular/common';
import { Component, ChangeDetectionStrategy, Input, OnChanges, SimpleChanges, Optional, HostBinding } from '@angular/core'; import {
Component,
ChangeDetectionStrategy,
Input,
OnChanges,
SimpleChanges,
Optional,
HostBinding,
EventEmitter,
Output,
} from '@angular/core';
import { DomainTaskCalendarService } from '@domain/task-calendar'; import { DomainTaskCalendarService } from '@domain/task-calendar';
import { FileDTO } from '@swagger/checkout'; import { FileDTO } from '@swagger/checkout';
import { DisplayInfoDTO } from '@swagger/eis'; import { DisplayInfoDTO } from '@swagger/eis';
@@ -27,6 +37,9 @@ export class TaskInfoComponent implements OnChanges {
@Input() @Input()
info: DisplayInfoDTO; info: DisplayInfoDTO;
@Output()
changed = new EventEmitter<boolean>();
@Input() @Input()
showTaskDate: boolean; showTaskDate: boolean;
@@ -210,5 +223,6 @@ export class TaskInfoComponent implements OnChanges {
await this.domainTaskCalendarService.addComment({ infoId: this.info.id, text: note }).toPromise(); await this.domainTaskCalendarService.addComment({ infoId: this.info.id, text: note }).toPromise();
sessionStorage.removeItem(`INFO_NOTE_${this.info?.id}`); sessionStorage.removeItem(`INFO_NOTE_${this.info?.id}`);
this.noteAdded$.next(); this.noteAdded$.next();
this.changed.emit(true);
} }
} }

View File

@@ -1,33 +1,45 @@
<div class="date"> <div class="task-list-item-wrapper">
<ng-container *ngIf="showDate"> <div
{{ item?.taskDate || item?.publicationDate | date }} class="indicator invisible"
</ng-container> [class.show]="(isTask$ | async) && !(hasUpdate$ | async)"
<ng-container *ngIf="!showDate"> [style.backgroundColor]="indicatorColor$ | async"
<ng-container [ngSwitch]="showTime$ | async"> ></div>
<ng-container *ngSwitchCase="true"> {{ item?.timeFrom | date: 'HH:mm' }} - {{ item?.timeTo | date: 'HH:mm' }} </ng-container> <div class="date">
<ng-container *ngSwitchCase="false"> <ng-container *ngIf="showDate">
Ganztägig {{ item?.taskDate || item?.publicationDate | date }}
</ng-container>
<ng-container *ngIf="!showDate">
<ng-container [ngSwitch]="showTime$ | async">
<ng-container *ngSwitchCase="true"> {{ item?.timeFrom | date: 'HH:mm' }} - {{ item?.timeTo | date: 'HH:mm' }} </ng-container>
<ng-container *ngSwitchCase="false">
Ganztägig
</ng-container>
</ng-container> </ng-container>
</ng-container> </ng-container>
</div>
<ng-container *ngIf="itemType$ | async; let itemType">
<div class="icon invisible" [class.show]="hasIcon$ | async">
<ui-icon icon="info" *ngIf="isInfoOrPreInfo$ | async" size="16px"></ui-icon>
<ui-icon icon="calendar" size="16px" *ngIf="isPreInfo$ | async"></ui-icon>
<ui-icon icon="chat" size="16px" *ngIf="hasComments$ | async"></ui-icon>
</div>
</ng-container> </ng-container>
</div>
<ng-container *ngIf="itemType$ | async; let itemType">
<div class="indicator" *ngIf="(isTask$ | async) && !(hasUpdate$ | async)" [style.backgroundColor]="indicatorColor$ | async"></div>
<div class="icon" *ngIf="hasIcon$ | async">
<ui-icon icon="info" *ngIf="isInfoOrPreInfo$ | async" size="16px"></ui-icon>
<ui-icon icon="calendar" size="16px" *ngIf="isPreInfo$ | async"></ui-icon>
<ui-icon icon="chat" size="16px" *ngIf="hasComments$ | async"></ui-icon>
</div>
</ng-container>
<ui-tshirt class="shirt-size" [effort]="item?.effort"></ui-tshirt> <ui-tshirt class="shirt-size" [effort]="item?.effort"></ui-tshirt>
<div class="task-content"> <div class="task-content">
<div class="task-content-title"> <div class="task-content-title">
<b>{{ item?.title }}</b> <b>{{ item?.title }}</b>
<ui-icon class="icon" icon="camera" [style.transform]="'rotateX(0deg)'" size="16px" *ngIf="item.requiresImageOnConfirmation"></ui-icon> <ui-icon
<ui-icon class="icon" icon="attachment" size="16px" *ngIf="hasAttachments$ | async"></ui-icon> class="icon"
<ui-icon class="icon icon-update-comment" icon="refresh" size="16px" *ngIf="showUpdateIcon$ | async"></ui-icon> icon="camera"
[style.transform]="'rotateX(0deg)'"
size="16px"
*ngIf="item.requiresImageOnConfirmation"
></ui-icon>
<ui-icon class="icon" icon="attachment" size="16px" *ngIf="hasAttachments$ | async"></ui-icon>
<ui-icon class="icon icon-update-comment" icon="refresh" size="16px" *ngIf="showUpdateIcon$ | async"></ui-icon>
</div>
<div>{{ item?.category | replace: '##':' / ' }}</div>
<div class="task-content-text">{{ item?.text | stripHtmlTags | substr: 70 }}</div>
</div> </div>
<div>{{ item?.category | replace: '##':' / ' }}</div>
<div class="task-content-text">{{ item?.text | stripHtmlTags | substr: 70 }}</div>
</div> </div>

View File

@@ -1,13 +1,20 @@
:host { .task-list-item-wrapper {
@apply flex flex-row px-4 py-3; @apply flex flex-row pr-4 py-3 bg-white border-solid cursor-pointer;
border-radius: 5px 0px 5px 5px;
box-shadow: 0px 0px 10px rgba(220, 226, 233, 0.5);
.date { .date {
@apply font-bold mr-2 self-start flex-shrink-0 flex-grow-0; @apply font-bold mr-2 self-start flex-shrink-0 flex-grow-0 ml-4;
width: 112px !important; width: 112px !important;
} }
.indicator { .indicator {
@apply w-px-2 bg-gray-100 self-stretch mx-4; @apply bg-gray-100 justify-self-stretch;
width: 6px;
margin-top: -13px;
margin-bottom: -13px;
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
} }
.icon { .icon {
@@ -41,3 +48,7 @@
} }
} }
} }
.show {
@apply visible;
}

View File

@@ -2,7 +2,7 @@ import { Component, ChangeDetectionStrategy, Input, OnChanges, SimpleChanges, Ho
import { DomainTaskCalendarService } from '@domain/task-calendar'; import { DomainTaskCalendarService } from '@domain/task-calendar';
import { DisplayInfoDTO } from '@swagger/eis'; import { DisplayInfoDTO } from '@swagger/eis';
import { combineLatest, ReplaySubject } from 'rxjs'; import { combineLatest, ReplaySubject } from 'rxjs';
import { catchError, map, shareReplay, switchMap } from 'rxjs/operators'; import { map, shareReplay } from 'rxjs/operators';
@Component({ @Component({
selector: 'page-task-list-item', selector: 'page-task-list-item',

View File

@@ -1,52 +1,55 @@
<!-- <div class="task-list overdue-list" *ngIf="items | filterType: ['Task'] | filterStatus: ['Overdue']; let overdieItems"> --> <!-- <div class="task-list overdue-list" *ngIf="items | filterType: ['Task'] | filterStatus: ['Overdue']; let overdieItems"> -->
<ng-container *ngIf="isToday$ | async"> <ui-scroll-container [loading]="fetching$ | async" [useLoadAnimation]="false">
<div class="task-list overdue-list" *ngIf="overdueItems$ | async; let overdieItems"> <ng-container *ngIf="isToday$ | async">
<div class="head-row"> <div class="task-list overdue-list" *ngIf="overdueItems$ | async; let overdieItems">
<h4>Überfällig</h4> <div class="head-row">
<span class="muted"> {{ overdieItems?.length }} Aufgaben </span> <div class="head-row-title">
</div> <h4>Überfällig</h4>
<hr /> </div>
<ng-container *ngFor="let item of overdieItems"> <span class="muted"> {{ overdieItems?.length }} Aufgaben </span>
<page-task-list-item [item]="item" (click)="select.emit(item)"></page-task-list-item> </div>
<hr /> <hr />
</ng-container> <ng-container *ngFor="let item of overdieItems">
</div> <page-task-list-item [item]="item" (click)="select.emit(item)"></page-task-list-item>
</ng-container> <hr />
</ng-container>
</div>
</ng-container>
<ng-container *ngIf="selectedItems$ | async; let itemsForSelectedDate"> <ng-container *ngIf="selectedItems$ | async; let itemsForSelectedDate">
<div class="task-list today-list" *ngIf="itemsForSelectedDate?.length"> <div class="task-list today-list" *ngIf="itemsForSelectedDate?.length">
<div class="head-row"> <div class="head-row">
<div class="head-row-title"> <div class="head-row-title">
<h4>{{ selected | date: 'EEEE' }}</h4> <h4>{{ selected | date: 'EEEE' }}, {{ selected | date }}</h4>
<div class="muted">{{ selected | date }}</div> </div>
<div class="head-row-info">
<button (click)="print()" type="button" class="cta-print">Drucken</button>
<span class="muted"> {{ itemsForSelectedDate?.length }} Aufgaben und Infos </span>
</div>
</div> </div>
<div class="head-row-info">
<button (click)="print()" type="button" class="cta-print">Drucken</button>
<span class="muted"> {{ itemsForSelectedDate?.length }} Aufgaben und Infos </span>
</div>
</div>
<hr />
<ng-container *ngFor="let item of itemsForSelectedDate">
<page-task-list-item [item]="item" (click)="select.emit(item)" [showDate]="false"></page-task-list-item>
<hr /> <hr />
</ng-container> <ng-container *ngFor="let item of itemsForSelectedDate">
</div> <page-task-list-item [item]="item" (click)="select.emit(item)" [showDate]="false"></page-task-list-item>
</ng-container> <hr />
</ng-container>
</div>
</ng-container>
<ng-container *ngIf="isToday$ | async"> <ng-container *ngIf="isToday$ | async">
<div class="task-list ongoing-list" *ngIf="ongoingItems$ | async; let ongoingItems"> <div class="task-list ongoing-list" *ngIf="ongoingItems$ | async; let ongoingItems">
<div class="head-row"> <div class="head-row">
<div class="head-row-title"> <div class="head-row-title">
<h4>Laufende Aufgaben</h4> <h4>Laufende Aufgaben</h4>
</div>
<div class="head-row-info">
<span class="muted"> {{ ongoingItems?.length }} Aufgaben </span>
</div>
</div> </div>
<div class="head-row-info">
<span class="muted"> {{ ongoingItems?.length }} Aufgaben </span>
</div>
</div>
<hr />
<ng-container *ngFor="let item of ongoingItems">
<page-task-list-item [item]="item" (click)="select.emit(item)"></page-task-list-item>
<hr /> <hr />
</ng-container> <ng-container *ngFor="let item of ongoingItems">
</div> <page-task-list-item [item]="item" (click)="select.emit(item)"></page-task-list-item>
</ng-container> <hr />
</ng-container>
</div>
</ng-container>
</ui-scroll-container>

View File

@@ -3,13 +3,17 @@
} }
.task-list { .task-list {
@apply pt-6; @apply pt-4;
.head-row { .head-row {
@apply flex flex-row justify-between items-baseline px-4 py-3; @apply flex flex-row justify-between items-center px-5 py-3;
height: 53px;
background-color: #f5f7fa;
border-radius: 5px 5px 0px 0px;
box-shadow: 0px 0px 10px rgba(220, 226, 233, 0.5);
.head-row-title { .head-row-title {
@apply flex flex-row self-end; @apply flex flex-row text-lg;
.muted { .muted {
@apply ml-4 self-end; @apply ml-4 self-end;
@@ -17,10 +21,10 @@
} }
.head-row-info { .head-row-info {
@apply flex flex-col text-right items-end; @apply flex flex-col text-right justify-center items-end;
.cta-print { .cta-print {
@apply border-none outline-none bg-transparent text-brand text-cta-l font-bold pr-0 mb-3; @apply border-none outline-none bg-transparent text-brand text-cta-l font-bold pr-0;
} }
} }
@@ -50,3 +54,7 @@
@apply opacity-30 line-through; @apply opacity-30 line-through;
} }
} }
:host ::ng-deep ui-scroll-container .scroll-container {
@apply flex flex-col;
}

View File

@@ -1,7 +1,7 @@
import { DatePipe } from '@angular/common'; import { DatePipe } from '@angular/common';
import { Component, ChangeDetectionStrategy, Input, EventEmitter, Output } from '@angular/core'; import { Component, ChangeDetectionStrategy, Input, EventEmitter, Output } from '@angular/core';
import { DomainPrinterService } from '@domain/printer'; import { DomainPrinterService } from '@domain/printer';
import { DomainTaskCalendarService, ProcessingStatusList } from '@domain/task-calendar'; import { DomainTaskCalendarService } from '@domain/task-calendar';
import { PrintModalComponent, PrintModalData } from '@modal/printer'; import { PrintModalComponent, PrintModalData } from '@modal/printer';
import { DisplayInfoDTO } from '@swagger/eis'; import { DisplayInfoDTO } from '@swagger/eis';
import { DateAdapter } from '@ui/common'; import { DateAdapter } from '@ui/common';
@@ -43,6 +43,19 @@ export class TaskListComponent {
} }
} }
/* @internal */
fetching$ = new BehaviorSubject<boolean>(true);
@Input()
get fetching() {
return this.fetching$.value;
}
set fetching(value) {
if (this.fetching !== value) {
this.fetching$.next(value);
}
}
isToday$ = this.selected$.pipe(map((selected) => this.dateAdapter.equals({ first: selected, second: this.today, precision: 'day' }))); isToday$ = this.selected$.pipe(map((selected) => this.dateAdapter.equals({ first: selected, second: this.today, precision: 'day' })));
overdueItems$ = combineLatest([this.items$, this.selected$]).pipe( overdueItems$ = combineLatest([this.items$, this.selected$]).pipe(
@@ -65,7 +78,7 @@ export class TaskListComponent {
: item : item
) )
), ),
map((list) => list.sort(this.moveRemovedToEnd.bind(this))) map((list) => list.sort((a, b) => this.domainTaskCalendarService.moveRemovedToEnd(a, b)))
); );
ongoingItems$ = combineLatest([this.items$, this.selected$]).pipe( ongoingItems$ = combineLatest([this.items$, this.selected$]).pipe(
@@ -95,8 +108,8 @@ export class TaskListComponent {
: item : item
) )
), ),
map((list) => this.sort(list, ['Overdue', 'InProcess', 'Approved', 'Completed', 'Removed'])), map((list) => this.domainTaskCalendarService.sort(list, ['Overdue', 'InProcess', 'Approved', 'Completed', 'Removed'])),
map((list) => list.sort(this.moveRemovedToEnd.bind(this))) map((list) => list.sort(this.domainTaskCalendarService.moveRemovedToEnd))
); );
selectedItems$ = combineLatest([this.items$, this.selected$]).pipe( selectedItems$ = combineLatest([this.items$, this.selected$]).pipe(
@@ -116,8 +129,8 @@ export class TaskListComponent {
) )
), ),
// Sortierung der aufgaben nach Rot => Gelb => Grau => Grün // Sortierung der aufgaben nach Rot => Gelb => Grau => Grün
map((list) => this.sort(list, ['Overdue', 'InProcess', 'Approved', 'Completed', 'Removed'])), map((list) => this.domainTaskCalendarService.sort(list, ['Overdue', 'InProcess', 'Approved', 'Completed', 'Removed'])),
map((list) => list.sort(this.moveRemovedToEnd.bind(this))) map((list) => list.sort((a, b) => this.domainTaskCalendarService.moveRemovedToEnd(a, b)))
); );
@Output() @Output()
@@ -131,76 +144,6 @@ export class TaskListComponent {
private domainPrinterService: DomainPrinterService private domainPrinterService: DomainPrinterService
) {} ) {}
moveRemovedToEnd(a: DisplayInfoDTO, b: DisplayInfoDTO) {
const statusA = this.domainTaskCalendarService.getProcessingStatusList(a)?.includes('Removed');
const statusB = this.domainTaskCalendarService.getProcessingStatusList(b)?.includes('Removed');
if (statusA && statusB) {
return 0;
} else if (statusA && !statusB) {
return 1;
} else if (!statusA && statusB) {
return -1;
}
return 0;
}
/**
* Returns an Array of DisplayInfoDTO, sorted by ProcessingStatus
* Ignores Overdue if Task is already Completed
* Compared DisploayInfoDTO is of Type Task and Info Or PreInfo then sort by Type
* @param items DisplayInfoDTO Array to sort
* @param order Processing Status Order
* @returns DisplayInfoDTO Array ordered by Processing Status anf Type
*/
sort(items: DisplayInfoDTO[], order: ProcessingStatusList) {
let result = [...items];
const reversedOrder = [...order].reverse();
for (const status of reversedOrder) {
result = result?.sort((a, b) => {
const statusA = this.domainTaskCalendarService.getProcessingStatusList(a);
const statusB = this.domainTaskCalendarService.getProcessingStatusList(b);
// Ignore Overdue when it is already Completed
if (status === 'Overdue' && statusA.includes('Completed')) {
return 0;
}
const aHasStatus = statusA.includes(status);
const bHasStatus = statusB.includes(status);
if (aHasStatus && bHasStatus) {
// If it has the same ProcessingStatus then Sort by Type
const aType = this.domainTaskCalendarService.getInfoType(a);
const bType = this.domainTaskCalendarService.getInfoType(b);
if (aType !== bType) {
if (aType === 'Info' || aType === 'PreInfo') {
return -1;
} else {
return 1;
}
}
if (statusB.includes('Completed')) {
return -1;
}
return 0;
} else if (aHasStatus && !bHasStatus) {
return -1;
} else if (!aHasStatus && bHasStatus) {
return 1;
}
return 0;
});
}
return result;
}
async print() { async print() {
let displayInfos = await this.selectedItems$.pipe(first()).toPromise(); let displayInfos = await this.selectedItems$.pipe(first()).toPromise();
displayInfos = displayInfos.filter((di) => !this.domainTaskCalendarService.getProcessingStatusList(di).includes('Completed')); displayInfos = displayInfos.filter((di) => !this.domainTaskCalendarService.getProcessingStatusList(di).includes('Completed'));

View File

@@ -7,10 +7,13 @@ import { FilterStatusPipe, FilterTypePipe } from './pipes';
import { UiCommonModule } from '@ui/common'; import { UiCommonModule } from '@ui/common';
import { UiTshirtModule } from '@ui/tshirt'; import { UiTshirtModule } from '@ui/tshirt';
import { UiIconModule } from '@ui/icon'; import { UiIconModule } from '@ui/icon';
import { RouterModule } from '@angular/router';
import { UiSpinnerModule } from '@ui/spinner';
import { UiScrollContainerModule } from '@ui/scroll-container';
@NgModule({ @NgModule({
imports: [CommonModule, UiCommonModule, UiTshirtModule, UiIconModule], imports: [CommonModule, UiCommonModule, UiSpinnerModule, UiTshirtModule, UiIconModule, RouterModule, UiScrollContainerModule],
exports: [TaskListComponent], exports: [TaskListComponent, TaskListItemComponent],
declarations: [TaskListComponent, TaskListItemComponent, FilterStatusPipe, FilterTypePipe], declarations: [TaskListComponent, TaskListItemComponent, FilterStatusPipe, FilterTypePipe],
}) })
export class TaskListModule {} export class TaskListModule {}

View File

@@ -0,0 +1,16 @@
<ui-form-field>
<input #searchInput uiInput [(ngModel)]="search" (keydown.enter)="searchTasks()" class="w-full" placeholder="Suchen" type="text" />
</ui-form-field>
<button
*ngIf="!(searchActive$ | async); else clearButtonTemplate"
class="search"
(click)="searchTasks()"
[disabled]="searchDisabled$ | async"
>
<ui-icon icon="search" size="24px"></ui-icon>
</button>
<ng-template #clearButtonTemplate>
<button class="clear" (click)="clearSearch()">
<ui-icon icon="close" size="15px"></ui-icon>
</button>
</ng-template>

View File

@@ -0,0 +1,29 @@
:host {
@apply grid relative;
}
input {
@apply py-4 px-4 outline-none h-14 font-bold text-lg;
box-shadow: 0px 6px 24px rgba(206, 212, 219, 0.8);
border-radius: 5px;
}
button.search {
@apply grid items-center justify-center bg-brand text-white px-2 py-2 rounded-lg -ml-2 w-14 h-14 absolute;
right: 0;
}
button.clear {
@apply grid items-center justify-center px-2 py-2 rounded-lg -ml-2 w-14 h-14 absolute;
color: #1f466c;
right: 0;
}
button:disabled {
@apply bg-disabled-branch cursor-not-allowed;
}
::placeholder {
@apply font-bold text-lg;
color: #596470;
}

View File

@@ -0,0 +1,97 @@
import { Component, OnInit, OnDestroy, ViewChild, ElementRef, AfterViewInit } from '@angular/core';
import { ActivatedRoute, Params, Router } from '@angular/router';
import { isEqual } from 'lodash';
import { BehaviorSubject, combineLatest, Subject } from 'rxjs';
import { map, shareReplay, takeUntil, first } from 'rxjs/operators';
import { TaskCalendarStore } from '../../task-calendar.store';
@Component({
selector: 'page-task-searchbar',
templateUrl: 'task-searchbar.component.html',
styleUrls: ['task-searchbar.component.scss'],
})
export class TaskSearchbarComponent implements OnInit, OnDestroy, AfterViewInit {
private _onDestroy$ = new Subject();
@ViewChild('searchInput', { static: true })
searchInput: ElementRef;
filterActive$ = new BehaviorSubject<boolean>(false);
searchActive$ = new BehaviorSubject<boolean>(false);
searchDisabled$ = new BehaviorSubject<boolean>(true);
search$ = new BehaviorSubject<string>('');
filter$ = this.taskCalendarStore.selectFilter;
get search() {
return this.search$.value;
}
set search(search: string) {
this.search$.next(search);
this.searchDisabled$.next(search?.length < 3);
}
constructor(private taskCalendarStore: TaskCalendarStore, private _router: Router, private _activatedRoute: ActivatedRoute) {}
ngOnInit(): void {
this._activatedRoute.queryParams.pipe(takeUntil(this._onDestroy$)).subscribe(async (queryParams) => {
const filter = await this.taskCalendarStore.selectFilter.pipe(first()).toPromise();
const search = filter?.input?.find((_) => true)?.input.find((_) => true)?.value || queryParams?.main_qs || '';
this.search$.next(search);
this.searchActive$.next(!!search);
this.focusSearch();
});
}
ngAfterViewInit(): void {
this.focusSearch();
}
ngOnDestroy() {
this._onDestroy$.next();
this._onDestroy$.complete();
}
focusSearch() {
setTimeout(() => this.searchInput?.nativeElement?.focus(), 100);
}
async clearSearch() {
const filter = await this.filter$.pipe(first()).toPromise();
filter?.input
?.find((_) => true)
?.input.find((_) => true)
?.setValue('');
this.taskCalendarStore.setFilter({ filters: filter });
this.search = '';
this.searchActive$.next(false);
this.focusSearch();
}
async searchTasks() {
const filter = await this.filter$.pipe(first()).toPromise();
filter?.input
?.find((_) => true)
?.input.find((_) => true)
?.setValue(this.search);
this.taskCalendarStore.setFilter({ filters: filter });
if (this.search?.length >= 3) {
this.navigate('/filiale/task-calendar/search', { ...filter?.getQueryParams() });
// ActivatedRouteChange wird nicht aufgerufen, wenn sich die URL nicht verändert. In dem Fall erneute Suche ausführen
if (isEqual(filter.getQueryParams(), this._activatedRoute.snapshot.queryParams)) {
this.taskCalendarStore.search({ clear: true });
}
}
}
navigate(uri: string, queryParams?: Params) {
this._router.navigate([uri], { queryParams });
}
}

View File

@@ -0,0 +1,15 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { UiFormFieldModule } from '@paragondata/ngx-ui/form-field';
import { UiIconModule } from '@ui/icon';
import { TaskSearchbarComponent } from './task-searchbar.component';
@NgModule({
imports: [CommonModule, UiFormFieldModule, FormsModule, UiIconModule],
exports: [TaskSearchbarComponent],
declarations: [TaskSearchbarComponent],
providers: [],
})
export class TaskSearchbarModule {}

View File

@@ -1,5 +1,9 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Output } from '@angular/core'; import { ChangeDetectionStrategy, Component, EventEmitter, OnDestroy, OnInit, Output } from '@angular/core';
import { ActivatedRoute, Params, Router } from '@angular/router';
import { UiFilter } from '@ui/filter'; import { UiFilter } from '@ui/filter';
import { isEqual } from 'lodash';
import { BehaviorSubject, Subject } from 'rxjs';
import { first, takeUntil } from 'rxjs/operators';
import { TaskCalendarStore } from '../../task-calendar.store'; import { TaskCalendarStore } from '../../task-calendar.store';
@Component({ @Component({
@@ -8,24 +12,66 @@ import { TaskCalendarStore } from '../../task-calendar.store';
styleUrls: ['task-calendar-filter.component.scss'], styleUrls: ['task-calendar-filter.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class TaskCalendarFilterComponent { export class TaskCalendarFilterComponent implements OnInit, OnDestroy {
private _onDestroy$ = new Subject();
@Output() exitFilter = new EventEmitter<void>(); @Output() exitFilter = new EventEmitter<void>();
filter$ = this.taskCalendarStore.selectFilter; filter$ = new BehaviorSubject<UiFilter>(undefined);
fetching$ = this.taskCalendarStore.selectFetching; fetching$ = this.taskCalendarStore.selectFetching;
message$ = this.taskCalendarStore.selectMessage; message$ = this.taskCalendarStore.selectMessage;
constructor(private taskCalendarStore: TaskCalendarStore) {} constructor(private taskCalendarStore: TaskCalendarStore, private _router: Router, private _activatedRoute: ActivatedRoute) {}
private _initFilter(filter: UiFilter) {
this.filter$.next(UiFilter.create(filter));
}
async ngOnInit() {
this.taskCalendarStore.selectFilter.pipe(takeUntil(this._onDestroy$)).subscribe((filter) => {
this._initFilter(filter);
});
const filter = await this.taskCalendarStore.selectFilter.pipe(first()).toPromise();
this._initFilter(filter);
}
ngOnDestroy(): void {
this._onDestroy$.next();
this._onDestroy$.complete();
}
async applyFilter(filters: UiFilter) {
const queryParams = { ...filters?.getQueryParams() };
applyFilter(filters: UiFilter) {
this.taskCalendarStore.setFilter({ filters }); this.taskCalendarStore.setFilter({ filters });
this.taskCalendarStore.loadItems(); this.taskCalendarStore.setMessage({ message: '' });
this.exitFilter.emit();
const search = this.getSearch(filters);
if (search?.length >= 3 || search === '') {
this.navigate('/filiale/task-calendar/search', queryParams);
// ActivatedRouteChange wird nicht aufgerufen, wenn sich die URL nicht verändert. In dem Fall erneute Suche ausführen
if (isEqual(filters.getQueryParams(), this._activatedRoute.snapshot.queryParams)) {
this.taskCalendarStore.search({ clear: true });
}
this.exitFilter.emit();
} else {
this.taskCalendarStore.setMessage({ message: 'Mindestens 3 Zeichen' });
}
}
getSearch(filter: UiFilter) {
return filter?.input?.find((_) => true)?.input?.find((_) => true)?.value || '';
} }
resetFilter() { resetFilter() {
this.taskCalendarStore.loadFilter(); this.taskCalendarStore.loadFilter();
} }
navigate(uri: string, queryParams?: Params) {
this._router.navigate([uri], { queryParams });
}
} }

View File

@@ -3,7 +3,7 @@
<h1>{{ info?.title }}</h1> <h1>{{ info?.title }}</h1>
<ui-icon *ngIf="showUpdateIcon$ | async" icon="refresh" size="22px"></ui-icon> <ui-icon *ngIf="showUpdateIcon$ | async" icon="refresh" size="22px"></ui-icon>
</div> </div>
<page-task-info [info]="info"></page-task-info> <page-task-info [info]="info" (changed)="changed()"></page-task-info>
<div class="actions"> <div class="actions">
<button *ngIf="showOpenSuccessorCta$ | async" class="btn-cta" type="button" (click)="close(info.successor?.id)"> <button *ngIf="showOpenSuccessorCta$ | async" class="btn-cta" type="button" (click)="close(info.successor?.id)">

View File

@@ -13,7 +13,6 @@ import { map, shareReplay } from 'rxjs/operators';
}) })
export class InfoModalComponent { export class InfoModalComponent {
info: DisplayInfoDTO; info: DisplayInfoDTO;
info$ = new ReplaySubject<DisplayInfoDTO>(); info$ = new ReplaySubject<DisplayInfoDTO>();
processingStatus$ = this.info$.pipe( processingStatus$ = this.info$.pipe(
@@ -42,6 +41,10 @@ export class InfoModalComponent {
this.info$.next(this.info); this.info$.next(this.info);
} }
changed() {
this.modalRef.markChanged();
}
close(successorId: number = undefined) { close(successorId: number = undefined) {
this.modalRef.close({ successorId }); this.modalRef.close({ successorId });
} }

View File

@@ -3,7 +3,7 @@
<h1>{{ info?.title }}</h1> <h1>{{ info?.title }}</h1>
<ui-icon *ngIf="showUpdateIcon$ | async" icon="refresh" size="22px"></ui-icon> <ui-icon *ngIf="showUpdateIcon$ | async" icon="refresh" size="22px"></ui-icon>
</div> </div>
<page-task-info [info]="info" showTaskDate="true"></page-task-info> <page-task-info [info]="info" showTaskDate="true" (changed)="changed()"></page-task-info>
<div class="actions"> <div class="actions">
<button *ngIf="showOpenSuccessorCta$ | async" class="btn-cta" type="button" (click)="close(info.successor?.id)"> <button *ngIf="showOpenSuccessorCta$ | async" class="btn-cta" type="button" (click)="close(info.successor?.id)">

View File

@@ -42,6 +42,10 @@ export class PreInfoModalComponent {
this.info$.next(this.info); this.info$.next(this.info);
} }
changed() {
this.modalRef.markChanged();
}
close(successorId: number = undefined) { close(successorId: number = undefined) {
this.modalRef.close({ successorId }); this.modalRef.close({ successorId });
} }

View File

@@ -15,7 +15,7 @@
</div> </div>
<div class="body"> <div class="body">
<page-task-info [info]="info" showTaskDate="true"></page-task-info> <page-task-info [info]="info" showTaskDate="true" (changed)="changed()"></page-task-info>
</div> </div>
<div class="camera-preview" *ngIf="cameraPreview$ | async; let cameraPreview"> <div class="camera-preview" *ngIf="cameraPreview$ | async; let cameraPreview">

View File

@@ -122,19 +122,26 @@ export class TaskModalComponent {
this.modalRef.close({ successorId }); this.modalRef.close({ successorId });
} }
changed() {
this.modalRef.markChanged();
}
async startEdit() { async startEdit() {
await this.domainTaskCalendarService.setToEdit({ infoId: this.info.id }).toPromise(); await this.domainTaskCalendarService.setToEdit({ infoId: this.info.id }).toPromise();
this.reloadInfo(); this.reloadInfo();
this.changed();
} }
async completeEdit() { async completeEdit() {
await this.domainTaskCalendarService.complete({ infoId: this.info.id }).toPromise(); await this.domainTaskCalendarService.complete({ infoId: this.info.id }).toPromise();
this.changed();
this.close(); this.close();
} }
async edit() { async edit() {
await this.domainTaskCalendarService.setToEdit({ infoId: this.info.id }).toPromise(); await this.domainTaskCalendarService.setToEdit({ infoId: this.info.id }).toPromise();
this.reloadInfo(); this.reloadInfo();
this.changed();
} }
reloadInfo() { reloadInfo() {
@@ -159,6 +166,7 @@ export class TaskModalComponent {
if (result.data === '') { if (result.data === '') {
this.captureInput?.nativeElement?.click(); this.captureInput?.nativeElement?.click();
} else if (result.data?.length > 0) { } else if (result.data?.length > 0) {
this.changed();
this.close(); this.close();
} }
}); });

View File

@@ -3,6 +3,7 @@ import { RouterModule, Routes } from '@angular/router';
import { PageTaskCalendarComponent } from './page-task-calendar.component'; import { PageTaskCalendarComponent } from './page-task-calendar.component';
import { CalendarComponent } from './pages/calendar/calendar.component'; import { CalendarComponent } from './pages/calendar/calendar.component';
import { CalendarModule } from './pages/calendar/calendar.module'; import { CalendarModule } from './pages/calendar/calendar.module';
import { TaskSearchComponent } from './pages/task-search';
import { TasksComponent } from './pages/tasks/tasks.component'; import { TasksComponent } from './pages/tasks/tasks.component';
import { TasksModule } from './pages/tasks/tasks.module'; import { TasksModule } from './pages/tasks/tasks.module';
@@ -13,7 +14,8 @@ const routes: Routes = [
children: [ children: [
{ path: 'calendar', component: CalendarComponent }, { path: 'calendar', component: CalendarComponent },
{ path: 'tasks', component: TasksComponent }, { path: 'tasks', component: TasksComponent },
{ path: '', pathMatch: 'full', redirectTo: 'calendar' }, { path: 'search', component: TaskSearchComponent },
{ path: '', pathMatch: 'full', redirectTo: 'tasks' },
], ],
}, },
]; ];

View File

@@ -1,21 +1,16 @@
<shell-breadcrumb [key]="taskCalendarKey" [includesTags]="['task-calendar']"></shell-breadcrumb> <div class="pt-2">
<shell-breadcrumb [key]="taskCalendarKey" [includesTags]="['task-calendar']"></shell-breadcrumb>
</div>
<button class="filter" [class.active]="hasFilter$ | async" (click)="filterActive$.next(true); filterOverlay.open()"> <div class="content-header">
<ui-icon size="20px" icon="filter_alit"></ui-icon> <page-task-searchbar></page-task-searchbar>
<span class="label">Filter</span> <button class="filter" [class.active]="hasFilter$ | async" (click)="filterActive$.next(true); filterOverlay.open()">
</button> <ui-icon size="16px" icon="filter_new"></ui-icon>
<span class="label">Filter</span>
</button>
</div>
<div class="content-container"> <div class="content-container">
<div class="switch-group">
<a class="calendar-switch" [routerLink]="['./calendar']" routerLinkActive="active">
<ui-icon icon="calendar" size="16px"></ui-icon>
<span>Kalenderansicht</span>
</a>
<a class="calendar-switch" [routerLink]="['./tasks']" routerLinkActive="active">
<ui-icon icon="tasks" size="16px"></ui-icon>
<span>Listenansicht</span>
</a>
</div>
<router-outlet></router-outlet> <router-outlet></router-outlet>
</div> </div>

View File

@@ -1,20 +1,27 @@
:host { :host {
@apply flex flex-col w-full box-content relative; @apply flex flex-col w-full box-content relative;
height: calc(100vh - 14rem);
} }
shell-breadcrumb { shell-breadcrumb {
@apply sticky z-sticky top-0 py-4; @apply sticky z-sticky top-0 h-12 bg-white p-0;
box-shadow: 0px 0px 10px rgba(220, 226, 233, 0.5);
border-radius: 5px;
} }
.filter { .filter {
@apply absolute font-sans flex items-center font-bold bg-gray-400 border-0 text-regular -top-12 right-0 py-px-8 px-px-15 rounded-filter justify-center z-sticky; @apply font-sans flex items-center font-bold text-lg py-2 px-4 justify-center ml-3 h-14;
width: 108px;
right: 0; background-color: #aeb7c1;
top: 10px; box-shadow: 0px 0px 10px rgba(220, 226, 233, 0.5);
min-width: 106px; border-radius: 5px;
.label { .label {
@apply ml-px-5; @apply ml-2;
}
ui-icon {
width: 18px;
} }
&.active { &.active {
@@ -22,35 +29,27 @@ shell-breadcrumb {
} }
} }
.content-container { .content-header {
max-height: calc(100vh - 267px); @apply grid items-center mt-4 shadow-input gap-1;
overflow: scroll; height: 56px;
@apply bg-white rounded-card; grid-template-columns: 1fr 120px;
} }
.switch-group { .content-container {
@apply flex flex-row justify-center my-9; @apply overflow-auto;
max-height: calc(100vh - 267px);
a.calendar-switch { border-radius: 0px 0px 5px 5px;
@apply flex flex-row items-center px-7 py-2 no-underline text-black bg-munsell;
&:first-child {
@apply rounded-l-card;
}
&:last-child {
@apply rounded-r-card;
}
ui-icon {
@apply mr-3;
}
}
a.calendar-switch.active,
a.calendar-switch:active {
@apply text-white bg-active-branch;
}
} }
:host ::ng-deep ui-calendar .navigation .title { :host ::ng-deep ui-calendar .navigation .title {
margin-left: -140px; margin-left: -140px;
} }
:host ::ng-deep shell-breadcrumb .link-breadcrumb a {
color: #596470 !important;
}
:host ::ng-deep shell-breadcrumb .link-back {
@apply top-3 pl-4;
color: #596470 !important;
}

View File

@@ -1,4 +1,4 @@
import { Component } from '@angular/core'; import { Component, ViewChild, ElementRef } from '@angular/core';
import { Config } from '@core/config'; import { Config } from '@core/config';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash';
import { BehaviorSubject, combineLatest } from 'rxjs'; import { BehaviorSubject, combineLatest } from 'rxjs';
@@ -12,6 +12,9 @@ import { TaskCalendarStore } from './task-calendar.store';
providers: [TaskCalendarStore], providers: [TaskCalendarStore],
}) })
export class PageTaskCalendarComponent { export class PageTaskCalendarComponent {
@ViewChild('searchInput', { static: true })
searchInput: ElementRef;
filterActive$ = new BehaviorSubject<boolean>(false); filterActive$ = new BehaviorSubject<boolean>(false);
taskCalendarKey = this._config.get('process.ids.taskCalendar'); taskCalendarKey = this._config.get('process.ids.taskCalendar');

View File

@@ -4,6 +4,7 @@ import { ShellBreadcrumbModule } from '@shell/breadcrumb';
import { ShellFilterOverlayModule } from '@shell/filter-overlay'; import { ShellFilterOverlayModule } from '@shell/filter-overlay';
import { UiFilterNextModule } from '@ui/filter'; import { UiFilterNextModule } from '@ui/filter';
import { UiIconModule } from '@ui/icon'; import { UiIconModule } from '@ui/icon';
import { TaskSearchbarModule } from './components/task-searchbar/task-searchbar.module';
import { TaskCalendarFilterComponent } from './containers/task-calendar-filter/task-calendar-filter.component'; import { TaskCalendarFilterComponent } from './containers/task-calendar-filter/task-calendar-filter.component';
import { ModalsModule } from './modals/modals.module'; import { ModalsModule } from './modals/modals.module';
import { PageTaskCalendarRoutingModule } from './page-task-calendar-routing.module'; import { PageTaskCalendarRoutingModule } from './page-task-calendar-routing.module';
@@ -19,6 +20,7 @@ import { PageTaskCalendarComponent } from './page-task-calendar.component';
ModalsModule, ModalsModule,
UiFilterNextModule, UiFilterNextModule,
ShellFilterOverlayModule, ShellFilterOverlayModule,
TaskSearchbarModule,
], ],
exports: [PageTaskCalendarComponent], exports: [PageTaskCalendarComponent],
}) })

View File

@@ -1,10 +1,12 @@
<ui-calendar <div class="bg-white pb-8 rounded-lg">
mode="month" <ui-calendar
[selected]="selectedDate$ | async" mode="month"
[displayed]="displayedDate$ | async" [selected]="selectedDate$ | async"
[minDate]="minDate" [displayed]="displayedDate$ | async"
[maxDate]="maxDate" [minDate]="minDate"
[indicators]="indicators$ | async" [maxDate]="maxDate"
(selectedChange)="setSelectedDate($event)" [indicators]="indicators$ | async"
(displayedChange)="setDisplayedDate($event)" (selectedChange)="setSelectedDate($event)"
></ui-calendar> (displayedChange)="setDisplayedDate($event)"
></ui-calendar>
</div>

View File

@@ -0,0 +1,4 @@
// start:ng42.barrel
export * from './task-search.component';
export * from './task-search.module';
// end:ng42.barrel

View File

@@ -0,0 +1,39 @@
<ng-container *ngIf="!(showEmptyMessage$ | async); else emptyMessage">
<ui-scroll-container
class="mt-2"
skeletonTemplate="task-calendar"
[loading]="fetching$ | async"
(reachEnd)="search()"
[itemLength]="searchResultsLength$ | async"
[deltaEnd]="250"
>
<div class="task-list search-list" *ngFor="let group of displayItems$ | async" [id]="group.group">
<div class="head-row">
<div class="head-row-title">
<h4>{{ group.group | date: 'EEEE' }}, {{ group.group | date }}</h4>
</div>
<div class="head-row-info">
<button (click)="print(group.group)" type="button" class="cta-print">Drucken</button>
<span class="muted"> {{ group.items?.length }} Aufgaben und Infos </span>
</div>
</div>
<hr />
<ng-container *ngFor="let item of group?.items">
<page-task-list-item [item]="item" (click)="open(item)"></page-task-list-item>
<hr />
</ng-container>
</div>
</ui-scroll-container>
</ng-container>
<ng-template #emptyMessage>
<div class="empty-message">
Keine Suchergebnisse
</div>
</ng-template>
<div class="actions">
<a class="cta-back cta-action-primary" [routerLink]="['/filiale/task-calendar/tasks']">
Zurück zur Übersicht
</a>
</div>

View File

@@ -0,0 +1,73 @@
:host {
@apply flex flex-col box-border;
}
.task-list {
@apply pt-4;
.head-row {
@apply flex flex-row justify-between items-center px-5 py-3;
height: 53px;
background-color: #f5f7fa;
border-radius: 5px 5px 0px 0px;
box-shadow: 0px 0px 10px rgba(220, 226, 233, 0.5);
.head-row-title {
@apply flex flex-row text-lg;
.muted {
@apply ml-4 self-end;
}
}
.head-row-info {
@apply flex flex-col text-right justify-center items-end;
.cta-print {
@apply border-none outline-none bg-transparent text-brand text-cta-l font-bold pr-0;
}
}
h4 {
@apply m-0 font-bold;
}
.muted {
@apply text-active-branch font-semibold text-sm;
}
}
}
.empty-message {
@apply bg-white text-center font-semibold text-inactive-customer py-10 rounded-card;
}
:host ::ng-deep ui-scroll-container .scroll-container {
@apply flex flex-col;
max-height: calc(100vh - 23rem) !important;
}
.task-list ::ng-deep page-task-list-item.Removed {
.date,
.shirt-size,
.task-content {
@apply opacity-30 line-through;
}
}
.actions {
@apply fixed bottom-28 inline-grid grid-flow-col gap-7;
left: 50%;
transform: translateX(-50%);
.cta-back {
@apply border-2 border-solid border-brand rounded-full py-3 px-6 font-bold text-lg outline-none self-end whitespace-nowrap;
&:disabled {
@apply bg-inactive-branch border-inactive-branch;
}
}
.cta-action-primary {
@apply bg-brand text-white;
}
}

View File

@@ -0,0 +1,135 @@
import { DatePipe } from '@angular/common';
import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core';
import { ActivatedRoute, Params } from '@angular/router';
import { BreadcrumbService } from '@core/breadcrumb';
import { Config } from '@core/config';
import { DomainPrinterService } from '@domain/printer';
import { DomainTaskCalendarService } from '@domain/task-calendar';
import { PrintModalComponent, PrintModalData } from '@modal/printer';
import { DisplayInfoDTO } from '@swagger/eis';
import { DateAdapter, groupBy } from '@ui/common';
import { UiFilter } from '@ui/filter';
import { UiModalService } from '@ui/modal';
import { combineLatest, Subject } from 'rxjs';
import { first, map, takeUntil, debounceTime } from 'rxjs/operators';
import { TaskCalendarStore } from '../../task-calendar.store';
@Component({
selector: 'page-task-search',
templateUrl: 'task-search.component.html',
styleUrls: ['task-search.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [DatePipe],
})
export class TaskSearchComponent implements OnInit {
private _onDestroy$ = new Subject();
today = this.dateAdapter.today();
byDate = (item: DisplayInfoDTO) => this.domainTaskCalendarService.getDateGroupKey(item.taskDate || item.publicationDate);
searchResults$ = this.taskCalendarStore.searchResults$;
searchResultsLength$ = this.searchResults$.pipe(map((r) => r.length || 0));
fetching$ = this.taskCalendarStore.isSearching$;
displayItems$ = this.searchResults$.pipe(
map((r) => {
const grouped = groupBy(r, this.byDate);
grouped.sort((a, b) => new Date(b.group).getTime() - new Date(a.group).getTime());
return grouped;
}),
// Sortierung der aufgaben nach Rot => Gelb => Grau => Grün
map((grouped) =>
grouped.map((g) => ({
...g,
items: this.domainTaskCalendarService.sort(g.items, ['Overdue', 'InProcess', 'Approved', 'Completed', 'Removed']),
}))
),
// Entfernte ans ende der Gruppe setzen
map((grouped) => grouped.map((g) => ({ ...g, items: g.items.sort((a, b) => this.domainTaskCalendarService.moveRemovedToEnd(a, b)) })))
);
showEmptyMessage$ = combineLatest([this.fetching$, this.searchResultsLength$, this.taskCalendarStore.hits$]).pipe(
map(([fetching, length, hits]) => !fetching && length <= 0 && hits === 0)
);
constructor(
private dateAdapter: DateAdapter,
private taskCalendarStore: TaskCalendarStore,
private domainTaskCalendarService: DomainTaskCalendarService,
private uiModal: UiModalService,
private domainPrinterService: DomainPrinterService,
private datePipe: DatePipe,
private _activatedRoute: ActivatedRoute,
private _config: Config,
private _breadcrumb: BreadcrumbService
) {}
ngOnInit() {
this._activatedRoute.queryParams.pipe(takeUntil(this._onDestroy$), debounceTime(500)).subscribe(async (queryParams) => {
if (queryParams) {
const filters = UiFilter.create(await this.taskCalendarStore.selectFilter.pipe(first()).toPromise());
if (queryParams) {
filters.fromQueryParams(queryParams);
}
this.taskCalendarStore.setFilter({ filters });
}
this.taskCalendarStore.search({ clear: true });
this.updateBreadcrumb(queryParams);
});
this.taskCalendarStore.searchTarget$.pipe(takeUntil(this._onDestroy$)).subscribe((target) => {
setTimeout(() => {
document.getElementById(target)?.scrollIntoView();
}, 150);
});
}
open(item: DisplayInfoDTO) {
this.taskCalendarStore.open(item);
}
async print(date: string) {
let displayInfos = await this.searchResults$.pipe(first()).toPromise();
displayInfos = displayInfos.filter((di) => !this.domainTaskCalendarService.getProcessingStatusList(di).includes('Completed'));
this.uiModal.open({
content: PrintModalComponent,
data: {
printerType: 'Office',
print: (printer) =>
this.domainPrinterService
.printDisplayInfoDTOList({
displayInfos,
printer,
title: `Tätigkeitskalender \n Liste für ${this.datePipe.transform(date, 'EEEE, dd. MMMM yyyy')}`,
})
.toPromise(),
} as PrintModalData,
config: {
panelClass: [],
showScrollbarY: false,
},
});
}
async search() {
const hits = await this.taskCalendarStore.hits$.pipe(first()).toPromise();
const results = await this.taskCalendarStore.searchResults$.pipe(first()).toPromise();
const fetching = await this.taskCalendarStore.selectFetching.pipe(first()).toPromise();
if (hits > results.length && !fetching) {
this.taskCalendarStore.search({});
}
}
updateBreadcrumb(queryParams?: Params) {
this._breadcrumb.addOrUpdateBreadcrumbIfNotExists({
key: this._config.get('process.ids.taskCalendar'),
name: queryParams?.main_qs || 'Suchergebnisse',
path: '/filiale/task-calendar/search',
tags: ['task-calendar', 'search'],
section: 'branch',
params: queryParams,
});
}
}

View File

@@ -0,0 +1,16 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { UiCommonModule } from '@ui/common';
import { UiIconModule } from '@ui/icon';
import { RouterModule } from '@angular/router';
import { UiScrollContainerModule } from '@ui/scroll-container';
import { TaskSearchComponent } from './task-search.component';
import { TaskListModule } from '../../components/task-list';
@NgModule({
imports: [CommonModule, UiCommonModule, UiIconModule, RouterModule, UiScrollContainerModule, TaskListModule],
exports: [TaskSearchComponent],
declarations: [TaskSearchComponent],
})
export class TaskSearchModule {}

View File

@@ -1,12 +1,19 @@
<ui-calendar <div class="bg-white pb-8 rounded-lg">
mode="week" <ui-calendar
[selected]="selectedDate$ | async" mode="week"
[displayed]="displayedDate$ | async" [selected]="selectedDate$ | async"
[minDate]="minDate" [displayed]="displayedDate$ | async"
[maxDate]="maxDate" [minDate]="minDate"
[indicators]="indicators$ | async" [maxDate]="maxDate"
(selectedChange)="setSelectedAndDisplayedDate({ selectedDate: $event })" [indicators]="indicators$ | async"
(displayedChange)="setSelectedAndDisplayedDate({ displayDate: $event })" (selectedChange)="setSelectedAndDisplayedDate({ selectedDate: $event })"
></ui-calendar> (displayedChange)="setSelectedAndDisplayedDate({ displayDate: $event })"
></ui-calendar>
</div>
<page-task-list [items]="items$ | async" [selected]="selectedDate$ | async" (select)="open($event)"></page-task-list> <page-task-list
[items]="items$ | async"
[selected]="selectedDate$ | async"
(select)="open($event)"
[fetching]="fetching$ | async"
></page-task-list>

View File

@@ -1,3 +1,3 @@
:host { :host {
@apply block pb-24; @apply block;
} }

View File

@@ -21,6 +21,10 @@ export class TasksComponent implements OnInit {
readonly items$ = this.taskCalendarStore.selectDisplayInfos; readonly items$ = this.taskCalendarStore.selectDisplayInfos;
readonly searchResults$ = this.taskCalendarStore.searchResults$;
readonly fetching$ = this.taskCalendarStore.select((s) => s.fetching);
readonly minDate = this.dateAdapter.addCalendarMonths(this.dateAdapter.today(), -6); readonly minDate = this.dateAdapter.addCalendarMonths(this.dateAdapter.today(), -6);
readonly maxDate = this.dateAdapter.addCalendarMonths(this.dateAdapter.today(), 6); readonly maxDate = this.dateAdapter.addCalendarMonths(this.dateAdapter.today(), 6);
@@ -46,8 +50,11 @@ export class TasksComponent implements OnInit {
this.updateBreadcrumb({}); this.updateBreadcrumb({});
} }
this.taskCalendarStore.setMode({ mode: 'week' });
this.taskCalendarStore.loadItems(); this.taskCalendarStore.loadItems();
this.removeSearchBreadcrumbs();
this.taskCalendarStore.resetSearch();
this.taskCalendarStore.setMode({ mode: 'week' });
} }
setSelectedAndDisplayedDate({ displayDate, selectedDate }: { displayDate?: Date; selectedDate?: Date }) { setSelectedAndDisplayedDate({ displayDate, selectedDate }: { displayDate?: Date; selectedDate?: Date }) {
@@ -56,7 +63,6 @@ export class TasksComponent implements OnInit {
if (displayDate && selectedDate) { if (displayDate && selectedDate) {
this.taskCalendarStore.setSelectedDate({ date: selectedDate }); this.taskCalendarStore.setSelectedDate({ date: selectedDate });
this.taskCalendarStore.setDisplayedDate({ date: displayDate }); this.taskCalendarStore.setDisplayedDate({ date: displayDate });
this.taskCalendarStore.loadItems();
this.router.navigate([], { this.router.navigate([], {
queryParams: { displayDate: displayDate?.toJSON(), selectedDate: selectedDate?.toJSON() }, queryParams: { displayDate: displayDate?.toJSON(), selectedDate: selectedDate?.toJSON() },
}); });
@@ -74,7 +80,6 @@ export class TasksComponent implements OnInit {
if (displayDate) { if (displayDate) {
this.taskCalendarStore.setDisplayedDate({ date: displayDate }); this.taskCalendarStore.setDisplayedDate({ date: displayDate });
this.taskCalendarStore.loadItems();
this.router.navigate([], { this.router.navigate([], {
queryParams: { ...queryParams, displayDate: displayDate?.toJSON() }, queryParams: { ...queryParams, displayDate: displayDate?.toJSON() },
}); });
@@ -86,6 +91,10 @@ export class TasksComponent implements OnInit {
this.taskCalendarStore.open(item); this.taskCalendarStore.open(item);
} }
removeSearchBreadcrumbs() {
this.breadcrumb.removeBreadcrumbsByKeyAndTags(this._config.get('process.ids.taskCalendar'), ['task-calendar', 'search']);
}
updateBreadcrumb({ displayDate, selectedDate }: { displayDate?: Date; selectedDate?: Date }) { updateBreadcrumb({ displayDate, selectedDate }: { displayDate?: Date; selectedDate?: Date }) {
const queryParams = this.activatedRoute.snapshot.queryParams; const queryParams = this.activatedRoute.snapshot.queryParams;
this.breadcrumb.addOrUpdateBreadcrumbIfNotExists({ this.breadcrumb.addOrUpdateBreadcrumbIfNotExists({

View File

@@ -4,7 +4,6 @@ import { CommonModule } from '@angular/common';
import { TasksComponent } from './tasks.component'; import { TasksComponent } from './tasks.component';
import { UiCalendarModule } from '@ui/calendar'; import { UiCalendarModule } from '@ui/calendar';
import { TaskListModule } from '../../components/task-list'; import { TaskListModule } from '../../components/task-list';
@NgModule({ @NgModule({
imports: [CommonModule, UiCalendarModule, TaskListModule], imports: [CommonModule, UiCalendarModule, TaskListModule],
exports: [TasksComponent], exports: [TasksComponent],

View File

@@ -1,14 +1,14 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { DomainTaskCalendarService } from '@domain/task-calendar'; import { DomainTaskCalendarService } from '@domain/task-calendar';
import { ComponentStore, tapResponse } from '@ngrx/component-store'; import { ComponentStore, tapResponse } from '@ngrx/component-store';
import { DisplayInfoDTO, InputDTO, QuerySettingsDTO, ResponseArgsOfIEnumerableOfInputDTO } from '@swagger/eis'; import { DisplayInfoDTO, InputDTO, QuerySettingsDTO } from '@swagger/eis';
import { CalendarIndicator } from '@ui/calendar'; import { CalendarIndicator } from '@ui/calendar';
import { DateAdapter } from '@ui/common'; import { DateAdapter } from '@ui/common';
import { Filter, FilterOption, SelectFilter, UiFilter, UiFilterMappingService } from '@ui/filter'; import { Filter, FilterOption, SelectFilter, UiFilter, UiFilterMappingService } from '@ui/filter';
import { UiMessageModalComponent, UiModalRef, UiModalResult, UiModalService } from '@ui/modal'; import { UiMessageModalComponent, UiModalRef, UiModalResult, UiModalService } from '@ui/modal';
import { clone } from 'lodash'; import { clone } from 'lodash';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { debounceTime, map, switchMap, tap, withLatestFrom } from 'rxjs/operators'; import { debounceTime, map, switchMap, tap, withLatestFrom, first, filter } from 'rxjs/operators';
import { InfoModalComponent } from './modals/info/info-modal.component'; import { InfoModalComponent } from './modals/info/info-modal.component';
import { PreInfoModalComponent } from './modals/preinfo/preinfo-modal.component'; import { PreInfoModalComponent } from './modals/preinfo/preinfo-modal.component';
import { TaskModalComponent } from './modals/task/task-modal.component'; import { TaskModalComponent } from './modals/task/task-modal.component';
@@ -21,7 +21,11 @@ export interface TaskCalendarState {
initialFilter: UiFilter; initialFilter: UiFilter;
filter: UiFilter; filter: UiFilter;
message: string; message: string;
searchResults: DisplayInfoDTO[];
searchTarget: string;
fetching: boolean; fetching: boolean;
isSearching: boolean;
hits: number;
} }
@Injectable() @Injectable()
@@ -38,6 +42,14 @@ export class TaskCalendarStore extends ComponentStore<TaskCalendarState> {
readonly selectDisplayInfos = this.select((s) => s.displayInfos); readonly selectDisplayInfos = this.select((s) => s.displayInfos);
readonly searchResults$ = this.select((s) => s.searchResults);
readonly hits$ = this.select((s) => s.hits);
readonly searchTarget$ = this.select((s) => s.searchTarget);
readonly isSearching$ = this.select((s) => s.isSearching);
readonly selectCalendarIndicators = this.select(this.selectDisplayInfos, (displayItems) => readonly selectCalendarIndicators = this.select(this.selectDisplayInfos, (displayItems) =>
displayItems.reduce<CalendarIndicator[]>((agg, item) => { displayItems.reduce<CalendarIndicator[]>((agg, item) => {
const calendarIndicator = this.mapDisplayInfoToCalendarIndicator(item); const calendarIndicator = this.mapDisplayInfoToCalendarIndicator(item);
@@ -60,6 +72,7 @@ export class TaskCalendarStore extends ComponentStore<TaskCalendarState> {
readonly selectFilter = this.select((s) => s.filter); readonly selectFilter = this.select((s) => s.filter);
readonly selectFetching = this.select((s) => s.fetching); readonly selectFetching = this.select((s) => s.fetching);
readonly fetching = this.get((s) => s.fetching);
readonly selectMessage = this.select((s) => s.message); readonly selectMessage = this.select((s) => s.message);
@@ -88,9 +101,11 @@ export class TaskCalendarStore extends ComponentStore<TaskCalendarState> {
} }
}); });
hits = this.get((s) => s.hits);
constructor( constructor(
public domainTaskCalendarService: DomainTaskCalendarService,
private dateAdapter: DateAdapter, private dateAdapter: DateAdapter,
private domainTaskCalendarService: DomainTaskCalendarService,
private uiModal: UiModalService, private uiModal: UiModalService,
private uiFilterMappingService: UiFilterMappingService private uiFilterMappingService: UiFilterMappingService
) { ) {
@@ -103,6 +118,10 @@ export class TaskCalendarStore extends ComponentStore<TaskCalendarState> {
filter: undefined, filter: undefined,
message: undefined, message: undefined,
fetching: false, fetching: false,
searchResults: [],
isSearching: false,
hits: undefined,
searchTarget: '',
}); });
} }
@@ -126,11 +145,89 @@ export class TaskCalendarStore extends ComponentStore<TaskCalendarState> {
mode, mode,
})); }));
readonly setMessage = this.updater((s, { message }: { message: string }) => ({
...s,
message,
}));
/**
* Der Key der Datums-Gruppe, zu der initial navigiert werden soll
*/
readonly setSearchTarget = this.updater((s, { searchTarget }: { searchTarget: string }) => ({
...s,
searchTarget,
}));
readonly search = this.effect((options$: Observable<{ clear?: boolean }>) =>
options$.pipe(
tap(() => this.patchState({ isSearching: true })),
debounceTime(500),
withLatestFrom(this.domainTaskCalendarService.currentBranchId$, this.selectFilter),
switchMap(([options, branchId, filter]) => {
const querytoken = {
...filter?.getQueryToken(),
// Paging ist vorbereitet aber vorerst deaktiviert
// skip: results.length || 0,
// take: 50,
};
// Im Zeitraum von 6 Monaten in der Vergangenheit und 6 Monate in der Zukunft abfragen
const start = this.dateAdapter.addCalendarMonths(this.dateAdapter.today(), -6);
const stop = this.dateAdapter.addCalendarMonths(this.dateAdapter.today(), 6);
return this.domainTaskCalendarService
.getInfos({
...querytoken,
filter: {
timespan: `"${start.toISOString()}"-"${stop?.toISOString()}"`,
...querytoken?.filter,
branch_id: String(branchId),
},
})
.pipe(
tapResponse(
(response) => {
if (!response.error) {
response = this.preparePreInfos(response);
const results = this.get((s) => s.searchResults);
const searchResults = results.length > 0 && !options?.clear ? [...results, ...response.result] : [...response.result];
const sorted = searchResults.sort((a, b) =>
this.dateAdapter.findClosestDate(new Date(a.taskDate || a.publicationDate), new Date(b.taskDate || b.publicationDate))
);
this.patchState({
searchResults,
searchTarget:
sorted?.length > 0 && response.skip === 0
? this.domainTaskCalendarService.getDateGroupKey(sorted[0].taskDate || sorted[0].publicationDate)
: '',
isSearching: false,
message: undefined,
hits: response.hits,
});
} else {
this.uiModal.open({ content: UiMessageModalComponent, data: response });
this.patchState({ searchResults: [], isSearching: false, message: 'Keine Suchergebnisse' });
}
},
(error) => {
console.error(error);
this.patchState({ searchResults: [], isSearching: false, message: 'Keine Suchergebnisse' });
}
)
);
})
)
);
resetSearch() {
this.patchState({ searchResults: [], hits: undefined });
}
readonly loadItems = this.effect(($: Observable<void>) => readonly loadItems = this.effect(($: Observable<void>) =>
$.pipe( $.pipe(
tap(() => this.patchState({ fetching: true })), tap(() => this.patchState({ fetching: true })),
debounceTime(500), debounceTime(500),
withLatestFrom(this.domainTaskCalendarService.currentBranchId$, this.selectStartStop, this.selectFilter), withLatestFrom(this.domainTaskCalendarService.currentBranchId$, this.selectStartStop, this.selectInitialFilter),
switchMap(([_, branchId, date, filter]) => { switchMap(([_, branchId, date, filter]) => {
const querytoken = { ...filter?.getQueryToken() }; const querytoken = { ...filter?.getQueryToken() };
return this.domainTaskCalendarService return this.domainTaskCalendarService
@@ -146,13 +243,7 @@ export class TaskCalendarStore extends ComponentStore<TaskCalendarState> {
tapResponse( tapResponse(
(response) => { (response) => {
if (!response.error) { if (!response.error) {
const preInfos = response.result.filter((info) => this.domainTaskCalendarService.getInfoType(info) === 'PreInfo'); response = this.preparePreInfos(response);
preInfos.forEach((info) => {
const preInfoTask = clone(info);
delete preInfoTask.publicationDate;
response.result.push(preInfoTask);
});
this.patchState({ displayInfos: response.result, fetching: false, message: undefined }); this.patchState({ displayInfos: response.result, fetching: false, message: undefined });
} else { } else {
this.uiModal.open({ this.uiModal.open({
@@ -172,6 +263,16 @@ export class TaskCalendarStore extends ComponentStore<TaskCalendarState> {
) )
); );
preparePreInfos(response) {
const preInfos = response.result.filter((info) => this.domainTaskCalendarService.getInfoType(info) === 'PreInfo');
preInfos.forEach((info) => {
const preInfoTask = clone(info);
delete preInfoTask.publicationDate;
response.result.push(preInfoTask);
});
return response;
}
readonly loadFilter = this.effect(($: Observable<void>) => readonly loadFilter = this.effect(($: Observable<void>) =>
$.pipe( $.pipe(
tap(() => this.patchState({ fetching: true })), tap(() => this.patchState({ fetching: true })),
@@ -227,7 +328,20 @@ export class TaskCalendarStore extends ComponentStore<TaskCalendarState> {
} }
} }
}, },
complete: () => this.loadItems(), });
let hasChanged = false;
taskModalRef?.afterChanged$.subscribe({
next: (changed) => (hasChanged = changed),
complete: async () => {
if (hasChanged) {
this.loadItems();
const searchResults = await this.searchResults$.pipe(first()).toPromise();
if (searchResults?.length > 0) {
this.search({ clear: true });
}
}
},
}); });
} }

View File

@@ -1,5 +1,5 @@
:host { :host {
@apply grid grid-cols-8; @apply grid grid-cols-8 pr-4;
} }
.cell { .cell {
@@ -11,7 +11,8 @@
} }
.cell-day-of-week:not(.cell-first) { .cell-day-of-week:not(.cell-first) {
@apply border-0 border-t-2 border-solid border-gray-200; @apply border-0 border-t-2 border-solid;
border-color: #edeff0;
} }
.cell-first { .cell-first {

View File

@@ -1,15 +1,26 @@
<div></div> <div></div>
<div class="nav-wrapper"> <div class="nav-wrapper">
<button class="prev" type="button" (click)="prev()"> <div class="flex flex-row">
<ui-icon icon="arrow_head" size="20px"></ui-icon> <button class="prev" type="button" (click)="prev()">
</button> <ui-icon icon="arrow_head" size="12px"></ui-icon>
</button>
<div class="display-year-month"> <div class="display-year-month">
{{ calendar.displayed | date: 'MMMM yyyy' }} {{ calendar.displayed | date: 'MMMM yyyy' }}
</div>
<button class="next" type="button" (click)="next()">
<ui-icon icon="arrow_head" size="12px"></ui-icon>
</button>
</div> </div>
<button class="today" type="button" (click)="today()">Heute</button> <div class="grid grid-flow-col gap-4 col-start-8">
<button class="next" type="button" (click)="next()"> <button class="today" type="button" (click)="today()">Heute</button>
<ui-icon icon="arrow_head" size="20px"></ui-icon> <a *ngIf="calendar.mode === 'week'" class="calendar-nav" [routerLink]="['/filiale/task-calendar/calendar']">
</button> <span>zum Kalender</span>
<ui-icon icon="calendar" size="18px"></ui-icon>
</a>
<a *ngIf="calendar.mode === 'month'" class="task-nav" [routerLink]="['/filiale/task-calendar/tasks']">
<span>zur Liste</span>
<ui-icon icon="tasks" size="18px"></ui-icon>
</a>
</div>
</div> </div>

View File

@@ -1,26 +1,51 @@
:host { :host {
@apply grid grid-cols-8 box-border items-center; @apply grid grid-cols-8 box-border items-center pt-2 pr-4;
}
a {
@apply flex flex-row items-center py-2 px-4 no-underline border-2 border-solid font-bold text-lg whitespace-nowrap;
border: 1px solid #aeb7c1;
border-radius: 5px;
&.task-nav {
width: 141px;
}
&.calendar-nav {
width: 188px;
}
ui-icon {
@apply ml-2;
color: #596470;
}
} }
button { button {
@apply outline-none border-none bg-transparent text-xl text-black; @apply grid grid-flow-col items-center py-2 px-4 outline-none font-bold rounded-lg text-lg;
&.today { &.today {
@apply mr-11; border: 1px solid #aeb7c1;
border-radius: 5px;
} }
} }
.nav-wrapper { .nav-wrapper {
@apply flex flex-row items-center col-span-7; @apply grid grid-flow-col items-center col-span-7;
} }
.display-year-month { .display-year-month {
@apply flex-grow text-center font-bold text-xl; @apply flex flex-row items-center justify-center text-center font-bold whitespace-nowrap text-2xl;
width: 200px;
} }
.prev, .prev,
.next { .next {
@apply text-ucla-blue; @apply grid justify-center w-12 h-8;
color: #596470;
background: #ffffff;
box-shadow: 0px 0px 10px rgba(220, 226, 233, 0.5);
border-radius: 5px;
} }
.prev { .prev {

View File

@@ -1,5 +1,4 @@
import { Component, ChangeDetectionStrategy, Host } from '@angular/core'; import { Component, ChangeDetectionStrategy, Host } from '@angular/core';
import { DateAdapter } from '@ui/common';
import { UiCalendarComponent } from '../ui-calendar.component'; import { UiCalendarComponent } from '../ui-calendar.component';
@Component({ @Component({

View File

@@ -1,98 +1,2 @@
<ui-calendar-header></ui-calendar-header> <ui-calendar-header></ui-calendar-header>
<ui-calendar-body></ui-calendar-body> <ui-calendar-body></ui-calendar-body>
<!-- <div class="calendar" [ngClass]="mode">
<div class="header">
<ng-container>
<div class="placeholder"></div>
<div class="navigation">
<button class="button back" (click)="navigateBack()">
<ui-icon icon="arrow_head" size="18px" rotate="180deg" alt="back button"></ui-icon>
</button>
<div class="title">{{ dateAdapter.getMonthName(dateAdapter.getMonth(selected)) }} {{ dateAdapter.getYear(selected) }}</div>
<button class="button next" (click)="navigateForth()">
<ui-icon icon="arrow_head" size="18px" alt="back button"></ui-icon>
</button>
</div>
</ng-container>
</div>
<table class="body">
<thead>
<tr class="row">
<th></th>
<th>M</th>
<th>D</th>
<th>M</th>
<th>D</th>
<th>F</th>
<th>S</th>
<th>S</th>
</tr>
</thead>
<tbody class="table-body">
<ng-container *ngIf="mode === 'month'">
<tr class="row" *ngFor="let kw of dateAdapter.getDatesAndCalendarWeeksForMonth(selected)">
<td (click)="selectedChange.emit(kw.dates[0])" class="kw-cell" [attr.week]="kw.week">KW {{ kw.week }}</td>
<td
class="day-cell"
[attr.day]="day.getDate()"
[class.out-of-month]="day.getMonth() !== dateAdapter.getMonth(selected)"
*ngFor="let day of kw.dates"
>
<div class="day" [class.is-sunday]="day.getDay() === 0" (click)="selectDay(day)">
<span [class.today]="dateAdapter.equals(today, day)" [class.today]="dateAdapter.equals(selected, day)">
{{ day.getDate() }}
</span>
</div>
<div class="items">
<ng-container *ngFor="let item of items | calendarItemsFilter: day | calendarItemsFilterUniqueStatusType">
<span
*ngIf="item.type === 'Task'"
class="indicator"
[style.backgroundColor]="item.color"
[title]="item.label"
(click)="itemSelected.emit(item)"
>
</span>
<span
*ngIf="item.type === 'Info'"
class="indicator info"
[style.backgroundColor]="item.color"
[title]="item.label"
(click)="itemSelected.emit(item)"
>i</span
>
</ng-container>
</div>
</td>
</tr>
</ng-container>
<ng-container *ngIf="mode === 'week'">
<tr class="row" *ngIf="dateAdapter.getCalendarWeek(selected); let kw">
<td class="kw-cell" [attr.week]="kw">KW {{ kw }}</td>
<td
class="day-cell"
[attr.day]="day.getDate()"
*ngFor="let day of dateAdapter.getDatesForCalendarWeek(dateAdapter.getYear(selected), kw)"
>
<div class="day" (click)="selectDay(day)">
<span [class.today]="dateAdapter.equals(today, day)" [class.selected]="dateAdapter.equals(selected, day)">
{{ day.getDate() }}
</span>
</div>
<div class="items">
<span
class="indicator"
[style.backgroundColor]="item.color"
[title]="item.label"
*ngFor="let item of items | calendarItemsFilter: day | calendarItemsFilterUniqueStatusType"
(click)="itemSelected.emit(item)"
>
</span>
</div>
</td>
</tr>
</ng-container>
</tbody>
</table>
</div> -->

View File

@@ -1,125 +1,3 @@
:host {
}
ui-calendar-header { ui-calendar-header {
@apply my-7; @apply my-4;
} }
// :host {
// @apply block;
// }
// .calendar {
// @apply my-0 mx-auto;
// width: 95%;
// .header {
// @apply grid items-center;
// grid-template-columns: 2.5fr 14fr;
// .navigation {
// @apply flex justify-between items-center mb-5;
// .button {
// @apply appearance-none border-none bg-transparent text-ucla-blue;
// &:focus {
// @apply outline-none;
// }
// }
// }
// .title {
// @apply text-lg font-bold;
// }
// }
// .body {
// @apply w-full;
// > *:not(:empty) {
// @apply cursor-pointer;
// }
// .row {
// @apply grid;
// grid-template-columns: 2fr repeat(7, 2fr);
// grid-template-rows: 5rem;
// &:only-child {
// grid-template-rows: 6rem;
// }
// .kw-cell {
// @apply font-bold;
// color: rgba(89, 100, 112, 1);
// }
// th {
// @apply flex justify-center items-center;
// color: rgba(89, 100, 112, 1);
// border-top: 1px solid #555;
// }
// th:first-child {
// @apply border-t-0;
// }
// th:first-child,
// td:first-child {
// border-right: 1px solid #555;
// }
// th,
// td {
// @apply text-center p-2;
// &:first-child {
// @apply text-left;
// }
// }
// .day-cell.out-of-month {
// .day,
// .items,
// .indicator {
// @apply hidden;
// }
// }
// .day-cell {
// .day {
// @apply text-lg;
// color: rgba(0, 0, 0, 1);
// &.is-sunday {
// color: rgba(137, 148, 158, 1);
// }
// .selected {
// @apply border rounded-full bg-active-branch text-white p-3;
// }
// .today {
// @apply border rounded-full bg-cool-grey text-white p-3;
// }
// }
// .items {
// @apply flex justify-center;
// .indicator {
// @apply block m-1 text-white rounded-full;
// height: 0.7rem;
// width: 0.7rem;
// &.info {
// @apply flex items-center justify-center;
// font-size: 0.6rem;
// }
// }
// }
// }
// }
// }
// }

View File

@@ -1,5 +1,6 @@
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { UiIconModule } from '@ui/icon'; import { UiIconModule } from '@ui/icon';
import { UiCalendarBodyComponent } from './calendar-body/calendar-body.component'; import { UiCalendarBodyComponent } from './calendar-body/calendar-body.component';
import { UiCalendarCellDirective } from './calendar-body/calendar-cell.directive'; import { UiCalendarCellDirective } from './calendar-body/calendar-cell.directive';
@@ -15,7 +16,7 @@ import { UiCalendarComponent } from './ui-calendar.component';
UiCalendarCellDirective, UiCalendarCellDirective,
UiFilterIndicatorsByDatePipe, UiFilterIndicatorsByDatePipe,
], ],
imports: [CommonModule, UiIconModule], imports: [CommonModule, UiIconModule, RouterModule],
exports: [UiCalendarComponent], exports: [UiCalendarComponent],
}) })
export class UiCalendarModule {} export class UiCalendarModule {}

View File

@@ -193,6 +193,13 @@ export class DateAdapter {
return this.getYear(first) - this.getYear(second) || this.getMonth(first) - this.getMonth(second); return this.getYear(first) - this.getYear(second) || this.getMonth(first) - this.getMonth(second);
} }
findClosestDate(a: Date, b: Date, compareDate?: Date) {
const date = compareDate || this.today();
var distancea = Math.abs(date.getTime() - a.getTime());
var distanceb = Math.abs(date.getTime() - b.getTime());
return distancea - distanceb;
}
isLessThan({ first, second, precision }: { first: Date; second: Date; precision?: Precision }): boolean { isLessThan({ first, second, precision }: { first: Date; second: Date; precision?: Precision }): boolean {
return this.formatDate(first, precision) < this.formatDate(second, precision); return this.formatDate(first, precision) < this.formatDate(second, precision);
} }

View File

@@ -1,6 +1,7 @@
import { QueryTokenDTO } from '@swagger/oms'; import { QueryTokenDTO } from '@swagger/oms';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { IUiInputGroup, UiInputGroup } from './ui-input-group'; import { IUiInputGroup, UiInputGroup } from './ui-input-group';
import { UiInputType } from './ui-input-type.enum';
import { IUiOrderBy, UiOrderBy } from './ui-order-by'; import { IUiOrderBy, UiOrderBy } from './ui-order-by';
export interface IUiFilter { export interface IUiFilter {
@@ -55,7 +56,13 @@ export class UiFilter implements IUiFilter {
inputGroup.input.forEach((input) => { inputGroup.input.forEach((input) => {
const key = input.key; const key = input.key;
params[`${group}_${key}`] = input.toStringValue(); const values = inputGroup.input
?.filter((i) => i.key === key)
?.map((i) => i.toStringValue())
?.filter((i) => !!i)
?.join(';');
params[`${group}_${key}`] = values;
}); });
}); });
@@ -90,13 +97,21 @@ export class UiFilter implements IUiFilter {
.forEach((inputGroup) => { .forEach((inputGroup) => {
inputGroup.input inputGroup.input
.filter((i) => i.key === key) .filter((i) => i.key === key)
.forEach((input) => { .forEach((input, index) => {
input.fromStringValue(key, params[gk]); let value = params[gk];
if (input.type === UiInputType.Text && value?.includes(';')) {
const splitted = value?.split(';');
if (splitted?.length >= index) {
value = splitted[index];
}
}
input.fromStringValue(key, value);
}); });
}); });
}); });
this.orderBy.forEach((ob) => { this.orderBy?.forEach((ob) => {
const order = params[`order_by_${ob.by}`]; const order = params[`order_by_${ob.by}`];
const selected = (order && ob.desc && order === 'desc') || (!ob.desc && order === 'asc'); const selected = (order && ob.desc && order === 'desc') || (!ob.desc && order === 'asc');
ob.setSelected(selected); ob.setSelected(selected);
@@ -133,7 +148,11 @@ export class UiFilter implements IUiFilter {
for (const inputGroup of this.filter) { for (const inputGroup of this.filter) {
for (const input of inputGroup.input.filter((i) => i.target === 'filter')) { for (const input of inputGroup.input.filter((i) => i.target === 'filter')) {
const key = input.key; const key = input.key;
const value = input.toStringValue(); const value = inputGroup.input
?.filter((i) => i.key === key)
?.map((i) => i.toStringValue())
?.filter((i) => !!i)
?.join(';');
if (!!value) { if (!!value) {
filter[key] = value; filter[key] = value;

View File

@@ -1,4 +1,6 @@
<div class="spinner" *ngIf="loading"> <div class="spinner" *ngIf="loading">
<ui-icon icon="spinner" [size]="size"></ui-icon> <ui-icon icon="spinner" [size]="spinnerSize"></ui-icon>
</div>
<div class="content">
<ng-content></ng-content>
</div> </div>
<ng-content></ng-content>

View File

@@ -0,0 +1,13 @@
:host {
@apply flex flex-col h-full mt-12 text-gray-500;
}
.spinner {
@apply animate-spin absolute;
top: 4rem;
left: calc(50% - 14px);
}
.content {
@apply mt-16;
}

View File

@@ -1,11 +1,20 @@
import { OverlayRef } from '@angular/cdk/overlay'; import { OverlayRef } from '@angular/cdk/overlay';
import { TemplateRef, Type } from '@angular/core'; import { TemplateRef, Type } from '@angular/core';
import { UiModalConfig } from '@ui/modal'; import { UiModalConfig } from '@ui/modal';
import { Subject } from 'rxjs'; import { Subject, BehaviorSubject } from 'rxjs';
import { UiModalResult } from './modal-result'; import { UiModalResult } from './modal-result';
export class UiModalRef<TR = any, TD = any> { export class UiModalRef<TR = any, TD = any> {
afterClosed$ = new Subject<UiModalResult<TR>>(); afterClosed$ = new Subject<UiModalResult<TR>>();
afterChanged$ = new BehaviorSubject<boolean>(false);
private _result: TR;
get result(): TR {
return this._result;
}
set result(value) {
this._result = value;
}
constructor( constructor(
public overlay: OverlayRef, public overlay: OverlayRef,
@@ -22,10 +31,15 @@ export class UiModalRef<TR = any, TD = any> {
data, data,
}); });
this.afterChanged$.complete();
this.afterClosed$.complete(); this.afterClosed$.complete();
} }
close(data?: TR): void { close(data?: TR): void {
this._close({ type: 'close', data }); this._close({ type: 'close', data });
} }
markChanged() {
this.afterChanged$.next(true);
}
} }

View File

@@ -13,8 +13,8 @@
<ng-template #loader> <ng-template #loader>
<ng-container *ngIf="useLoadAnimation; else contentLoader"> <ng-container *ngIf="useLoadAnimation; else contentLoader">
<ui-skeleton-loader></ui-skeleton-loader> <ui-skeleton-loader [template]="skeletonTemplate"></ui-skeleton-loader>
<ui-skeleton-loader *ngFor="let skeletons of createSkeletons()"></ui-skeleton-loader> <ui-skeleton-loader [template]="skeletonTemplate" *ngFor="let skeletons of createSkeletons()"></ui-skeleton-loader>
</ng-container> </ng-container>
<ng-template #contentLoader> <ng-template #contentLoader>

View File

@@ -48,6 +48,8 @@ export class UiScrollContainerComponent implements OnInit {
@Input() initialScroll: number; @Input() initialScroll: number;
@Input() skeletonTemplate?: string;
get containerHeightString() { get containerHeightString() {
return `calc(100vh - ${this.containerHeight}rem)`; return `calc(100vh - ${this.containerHeight}rem)`;
} }

View File

@@ -1,28 +1,48 @@
<div class="header"> <ng-container [ngSwitch]="template">
<div class="left animation"></div> <div *ngSwitchCase="'task-calendar'" class="task-calendar">
<div class="right animation"></div> <div class="task-calendar-header">
</div> <div class="left animation"></div>
<div class="right animation"></div>
</div>
<div class="row"> <div class="task-calendar-details">
<div class="thumbnail animation"></div> <div class="date animation"></div>
<div class="icon animation"></div>
<div> <div class="col">
<div class="title animation"></div> <div class="item animation"></div>
<div class="sub-row"> <div class="item animation"></div>
<div class="item animation"></div> <div class="item animation"></div>
<div class="item animation"></div> </div>
</div>
<div class="sub-row">
<div class="item animation"></div>
<div class="item animation"></div>
</div>
<div class="sub-row">
<div class="item animation"></div>
<div class="item animation"></div>
</div>
<div class="sub-row">
<div class="item animation"></div>
<div class="item animation"></div>
</div> </div>
</div> </div>
</div>
<div *ngSwitchDefault class="default">
<div class="header">
<div class="left animation"></div>
<div class="right animation"></div>
</div>
<div class="row">
<div class="thumbnail animation"></div>
<div>
<div class="title animation"></div>
<div class="sub-row">
<div class="item animation"></div>
<div class="item animation"></div>
</div>
<div class="sub-row">
<div class="item animation"></div>
<div class="item animation"></div>
</div>
<div class="sub-row">
<div class="item animation"></div>
<div class="item animation"></div>
</div>
<div class="sub-row">
<div class="item animation"></div>
<div class="item animation"></div>
</div>
</div>
</div>
</div>
</ng-container>

View File

@@ -1,8 +1,44 @@
:host { .default {
@apply flex flex-col pt-4 pb-6 pl-1 pr-4 bg-white; @apply flex flex-col pt-4 pb-6 pl-1 pr-4 bg-white;
height: 300px; height: 300px;
} }
.task-calendar {
@apply h-40 p-6;
.task-calendar-header {
@apply flex flex-row justify-between w-full;
.left {
@apply h-6 bg-ucla-blue;
width: 240px;
}
.right {
@apply h-6 bg-ucla-blue;
width: 130px;
}
}
.task-calendar-details {
@apply grid mt-6;
grid-template-columns: 170px 40px 1fr;
.date {
@apply col-start-1 h-4 bg-ucla-blue;
width: 120px;
}
.icon {
@apply col-start-2 w-6 h-4 bg-ucla-blue;
}
.col {
@apply flex flex-col col-start-3;
}
}
}
.header { .header {
@apply w-full flex flex-row justify-between mt-5; @apply w-full flex flex-row justify-between mt-5;

View File

@@ -1,4 +1,4 @@
import { Component, ChangeDetectionStrategy } from '@angular/core'; import { Component, ChangeDetectionStrategy, Input } from '@angular/core';
@Component({ @Component({
selector: 'ui-skeleton-loader', selector: 'ui-skeleton-loader',
@@ -7,5 +7,8 @@ import { Component, ChangeDetectionStrategy } from '@angular/core';
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class UiSkeletonLoaderComponent { export class UiSkeletonLoaderComponent {
@Input()
template?: string;
constructor() {} constructor() {}
} }

View File

@@ -17,6 +17,7 @@ variables:
value: "$[counter(format('{0}.{1}', variables['Major'], variables['Minor']),0)]" value: "$[counter(format('{0}.{1}', variables['Major'], variables['Minor']),0)]"
- name: 'BuildUniqueID' - name: 'BuildUniqueID'
value: '$(Build.BuildID)-$(Agent.Id)-$(System.DefinitionId)-$(System.JobId)' value: '$(Build.BuildID)-$(Agent.Id)-$(System.DefinitionId)-$(System.JobId)'
- group: 'GithubCMF'
jobs: jobs:
# - job: cibuild_fast # - job: cibuild_fast
@@ -52,6 +53,7 @@ jobs:
displayName: 'npm auth' displayName: 'npm auth'
inputs: inputs:
workingFile: .npmrc workingFile: .npmrc
customEndpoint: GitHub-read-packages
- bash: | - bash: |
echo Build and Run Tests in docker echo Build and Run Tests in docker
docker build . \ docker build . \
@@ -110,6 +112,7 @@ jobs:
displayName: 'npm auth' displayName: 'npm auth'
inputs: inputs:
workingFile: .npmrc workingFile: .npmrc
customEndpoint: GitHub-read-packages
- task: Docker@2 - task: Docker@2
displayName: 'build ISAClient Debug' displayName: 'build ISAClient Debug'
inputs: inputs:
@@ -170,6 +173,7 @@ jobs:
displayName: 'npm auth' displayName: 'npm auth'
inputs: inputs:
workingFile: .npmrc workingFile: .npmrc
customEndpoint: GitHub-read-packages
- task: Docker@2 - task: Docker@2
displayName: 'build ISAClient Prod' displayName: 'build ISAClient Prod'
inputs: inputs:

1065
package-lock.json generated
View File

File diff suppressed because it is too large Load Diff

View File

@@ -59,6 +59,7 @@
"@ngrx/entity": "~12.5.1", "@ngrx/entity": "~12.5.1",
"@ngrx/store": "~12.5.1", "@ngrx/store": "~12.5.1",
"@ngrx/store-devtools": "~12.5.1", "@ngrx/store-devtools": "~12.5.1",
"@paragondata/ngx-ui": "^12.0.0-beta.23",
"angular-oauth2-oidc": "^13.0.1", "angular-oauth2-oidc": "^13.0.1",
"angular-oauth2-oidc-jwks": "^13.0.1", "angular-oauth2-oidc-jwks": "^13.0.1",
"core-js": "^2.6.5", "core-js": "^2.6.5",

View File

@@ -304,7 +304,10 @@
], ],
"@ui/branch-dropdown": [ "@ui/branch-dropdown": [
"apps/ui/branch-dropdown/src/public-api.ts" "apps/ui/branch-dropdown/src/public-api.ts"
] ],
"@paragondata/ngx-ui/form-field": [
"node_modules/@paragondata/ngx-ui/form-field"
],
} }
} }
} }