mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 14:32:10 +01:00
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:
committed by
Lorenz Hilpert
parent
fdaceb9bf8
commit
9dd0954967
4
.npmrc
4
.npmrc
@@ -1,3 +1 @@
|
||||
@isa:registry=https://pkgs.dev.azure.com/hugendubel/_packaging/hugendubel%40Local/npm/registry/
|
||||
@cmf:registry=https://pkgs.dev.azure.com/hugendubel/_packaging/hugendubel%40Local/npm/registry/
|
||||
always-auth=true
|
||||
@paragondata:registry=https://npm.pkg.github.com
|
||||
@@ -1,5 +1,5 @@
|
||||
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 { memorize } from '@utils/common';
|
||||
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()}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,4 +212,7 @@
|
||||
<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);"/>
|
||||
</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>
|
||||
|
||||
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 105 KiB |
@@ -46,8 +46,7 @@ export class GoodsOutSearchFilterComponent implements OnInit, OnDestroy {
|
||||
private _goodsOutSearchStore: GoodsOutSearchStore,
|
||||
private _breadcrumb: BreadcrumbService,
|
||||
private _cdr: ChangeDetectorRef,
|
||||
private _router: Router,
|
||||
private readonly _config: Config
|
||||
private _router: Router
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
import { Clipboard } from '@angular/cdk/clipboard';
|
||||
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 { FileDTO } from '@swagger/checkout';
|
||||
import { DisplayInfoDTO } from '@swagger/eis';
|
||||
@@ -27,6 +37,9 @@ export class TaskInfoComponent implements OnChanges {
|
||||
@Input()
|
||||
info: DisplayInfoDTO;
|
||||
|
||||
@Output()
|
||||
changed = new EventEmitter<boolean>();
|
||||
|
||||
@Input()
|
||||
showTaskDate: boolean;
|
||||
|
||||
@@ -210,5 +223,6 @@ export class TaskInfoComponent implements OnChanges {
|
||||
await this.domainTaskCalendarService.addComment({ infoId: this.info.id, text: note }).toPromise();
|
||||
sessionStorage.removeItem(`INFO_NOTE_${this.info?.id}`);
|
||||
this.noteAdded$.next();
|
||||
this.changed.emit(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +1,45 @@
|
||||
<div class="date">
|
||||
<ng-container *ngIf="showDate">
|
||||
{{ 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
|
||||
<div class="task-list-item-wrapper">
|
||||
<div
|
||||
class="indicator invisible"
|
||||
[class.show]="(isTask$ | async) && !(hasUpdate$ | async)"
|
||||
[style.backgroundColor]="indicatorColor$ | async"
|
||||
></div>
|
||||
<div class="date">
|
||||
<ng-container *ngIf="showDate">
|
||||
{{ 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>
|
||||
</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>
|
||||
</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>
|
||||
<div class="task-content">
|
||||
<div class="task-content-title">
|
||||
<b>{{ item?.title }}</b>
|
||||
<ui-icon class="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>
|
||||
<ui-tshirt class="shirt-size" [effort]="item?.effort"></ui-tshirt>
|
||||
<div class="task-content">
|
||||
<div class="task-content-title">
|
||||
<b>{{ item?.title }}</b>
|
||||
<ui-icon
|
||||
class="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>{{ item?.category | replace: '##':' / ' }}</div>
|
||||
<div class="task-content-text">{{ item?.text | stripHtmlTags | substr: 70 }}</div>
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
:host {
|
||||
@apply flex flex-row px-4 py-3;
|
||||
.task-list-item-wrapper {
|
||||
@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 {
|
||||
@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;
|
||||
}
|
||||
|
||||
.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 {
|
||||
@@ -41,3 +48,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.show {
|
||||
@apply visible;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Component, ChangeDetectionStrategy, Input, OnChanges, SimpleChanges, Ho
|
||||
import { DomainTaskCalendarService } from '@domain/task-calendar';
|
||||
import { DisplayInfoDTO } from '@swagger/eis';
|
||||
import { combineLatest, ReplaySubject } from 'rxjs';
|
||||
import { catchError, map, shareReplay, switchMap } from 'rxjs/operators';
|
||||
import { map, shareReplay } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'page-task-list-item',
|
||||
|
||||
@@ -1,52 +1,55 @@
|
||||
<!-- <div class="task-list overdue-list" *ngIf="items | filterType: ['Task'] | filterStatus: ['Overdue']; let overdieItems"> -->
|
||||
<ng-container *ngIf="isToday$ | async">
|
||||
<div class="task-list overdue-list" *ngIf="overdueItems$ | async; let overdieItems">
|
||||
<div class="head-row">
|
||||
<h4>Überfällig</h4>
|
||||
<span class="muted"> {{ overdieItems?.length }} Aufgaben </span>
|
||||
</div>
|
||||
<hr />
|
||||
<ng-container *ngFor="let item of overdieItems">
|
||||
<page-task-list-item [item]="item" (click)="select.emit(item)"></page-task-list-item>
|
||||
<ui-scroll-container [loading]="fetching$ | async" [useLoadAnimation]="false">
|
||||
<ng-container *ngIf="isToday$ | async">
|
||||
<div class="task-list overdue-list" *ngIf="overdueItems$ | async; let overdieItems">
|
||||
<div class="head-row">
|
||||
<div class="head-row-title">
|
||||
<h4>Überfällig</h4>
|
||||
</div>
|
||||
<span class="muted"> {{ overdieItems?.length }} Aufgaben </span>
|
||||
</div>
|
||||
<hr />
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngFor="let item of overdieItems">
|
||||
<page-task-list-item [item]="item" (click)="select.emit(item)"></page-task-list-item>
|
||||
<hr />
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="selectedItems$ | async; let itemsForSelectedDate">
|
||||
<div class="task-list today-list" *ngIf="itemsForSelectedDate?.length">
|
||||
<div class="head-row">
|
||||
<div class="head-row-title">
|
||||
<h4>{{ selected | date: 'EEEE' }}</h4>
|
||||
<div class="muted">{{ selected | date }}</div>
|
||||
<ng-container *ngIf="selectedItems$ | async; let itemsForSelectedDate">
|
||||
<div class="task-list today-list" *ngIf="itemsForSelectedDate?.length">
|
||||
<div class="head-row">
|
||||
<div class="head-row-title">
|
||||
<h4>{{ selected | date: 'EEEE' }}, {{ selected | date }}</h4>
|
||||
</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 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 />
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngFor="let item of itemsForSelectedDate">
|
||||
<page-task-list-item [item]="item" (click)="select.emit(item)" [showDate]="false"></page-task-list-item>
|
||||
<hr />
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="isToday$ | async">
|
||||
<div class="task-list ongoing-list" *ngIf="ongoingItems$ | async; let ongoingItems">
|
||||
<div class="head-row">
|
||||
<div class="head-row-title">
|
||||
<h4>Laufende Aufgaben</h4>
|
||||
<ng-container *ngIf="isToday$ | async">
|
||||
<div class="task-list ongoing-list" *ngIf="ongoingItems$ | async; let ongoingItems">
|
||||
<div class="head-row">
|
||||
<div class="head-row-title">
|
||||
<h4>Laufende Aufgaben</h4>
|
||||
</div>
|
||||
<div class="head-row-info">
|
||||
<span class="muted"> {{ ongoingItems?.length }} Aufgaben </span>
|
||||
</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 />
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngFor="let item of ongoingItems">
|
||||
<page-task-list-item [item]="item" (click)="select.emit(item)"></page-task-list-item>
|
||||
<hr />
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ui-scroll-container>
|
||||
|
||||
@@ -3,13 +3,17 @@
|
||||
}
|
||||
|
||||
.task-list {
|
||||
@apply pt-6;
|
||||
@apply pt-4;
|
||||
|
||||
.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 {
|
||||
@apply flex flex-row self-end;
|
||||
@apply flex flex-row text-lg;
|
||||
|
||||
.muted {
|
||||
@apply ml-4 self-end;
|
||||
@@ -17,10 +21,10 @@
|
||||
}
|
||||
|
||||
.head-row-info {
|
||||
@apply flex flex-col text-right items-end;
|
||||
@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 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;
|
||||
}
|
||||
}
|
||||
|
||||
:host ::ng-deep ui-scroll-container .scroll-container {
|
||||
@apply flex flex-col;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { Component, ChangeDetectionStrategy, Input, EventEmitter, Output } from '@angular/core';
|
||||
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 { DisplayInfoDTO } from '@swagger/eis';
|
||||
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' })));
|
||||
|
||||
overdueItems$ = combineLatest([this.items$, this.selected$]).pipe(
|
||||
@@ -65,7 +78,7 @@ export class TaskListComponent {
|
||||
: 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(
|
||||
@@ -95,8 +108,8 @@ export class TaskListComponent {
|
||||
: item
|
||||
)
|
||||
),
|
||||
map((list) => this.sort(list, ['Overdue', 'InProcess', 'Approved', 'Completed', 'Removed'])),
|
||||
map((list) => list.sort(this.moveRemovedToEnd.bind(this)))
|
||||
map((list) => this.domainTaskCalendarService.sort(list, ['Overdue', 'InProcess', 'Approved', 'Completed', 'Removed'])),
|
||||
map((list) => list.sort(this.domainTaskCalendarService.moveRemovedToEnd))
|
||||
);
|
||||
|
||||
selectedItems$ = combineLatest([this.items$, this.selected$]).pipe(
|
||||
@@ -116,8 +129,8 @@ export class TaskListComponent {
|
||||
)
|
||||
),
|
||||
// Sortierung der aufgaben nach Rot => Gelb => Grau => Grün
|
||||
map((list) => this.sort(list, ['Overdue', 'InProcess', 'Approved', 'Completed', 'Removed'])),
|
||||
map((list) => list.sort(this.moveRemovedToEnd.bind(this)))
|
||||
map((list) => this.domainTaskCalendarService.sort(list, ['Overdue', 'InProcess', 'Approved', 'Completed', 'Removed'])),
|
||||
map((list) => list.sort((a, b) => this.domainTaskCalendarService.moveRemovedToEnd(a, b)))
|
||||
);
|
||||
|
||||
@Output()
|
||||
@@ -131,76 +144,6 @@ export class TaskListComponent {
|
||||
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() {
|
||||
let displayInfos = await this.selectedItems$.pipe(first()).toPromise();
|
||||
displayInfos = displayInfos.filter((di) => !this.domainTaskCalendarService.getProcessingStatusList(di).includes('Completed'));
|
||||
|
||||
@@ -7,10 +7,13 @@ import { FilterStatusPipe, FilterTypePipe } from './pipes';
|
||||
import { UiCommonModule } from '@ui/common';
|
||||
import { UiTshirtModule } from '@ui/tshirt';
|
||||
import { UiIconModule } from '@ui/icon';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { UiSpinnerModule } from '@ui/spinner';
|
||||
import { UiScrollContainerModule } from '@ui/scroll-container';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, UiCommonModule, UiTshirtModule, UiIconModule],
|
||||
exports: [TaskListComponent],
|
||||
imports: [CommonModule, UiCommonModule, UiSpinnerModule, UiTshirtModule, UiIconModule, RouterModule, UiScrollContainerModule],
|
||||
exports: [TaskListComponent, TaskListItemComponent],
|
||||
declarations: [TaskListComponent, TaskListItemComponent, FilterStatusPipe, FilterTypePipe],
|
||||
})
|
||||
export class TaskListModule {}
|
||||
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
@@ -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 {}
|
||||
@@ -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 { isEqual } from 'lodash';
|
||||
import { BehaviorSubject, Subject } from 'rxjs';
|
||||
import { first, takeUntil } from 'rxjs/operators';
|
||||
import { TaskCalendarStore } from '../../task-calendar.store';
|
||||
|
||||
@Component({
|
||||
@@ -8,24 +12,66 @@ import { TaskCalendarStore } from '../../task-calendar.store';
|
||||
styleUrls: ['task-calendar-filter.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class TaskCalendarFilterComponent {
|
||||
export class TaskCalendarFilterComponent implements OnInit, OnDestroy {
|
||||
private _onDestroy$ = new Subject();
|
||||
@Output() exitFilter = new EventEmitter<void>();
|
||||
|
||||
filter$ = this.taskCalendarStore.selectFilter;
|
||||
filter$ = new BehaviorSubject<UiFilter>(undefined);
|
||||
|
||||
fetching$ = this.taskCalendarStore.selectFetching;
|
||||
|
||||
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.loadItems();
|
||||
this.exitFilter.emit();
|
||||
this.taskCalendarStore.setMessage({ message: '' });
|
||||
|
||||
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() {
|
||||
this.taskCalendarStore.loadFilter();
|
||||
}
|
||||
|
||||
navigate(uri: string, queryParams?: Params) {
|
||||
this._router.navigate([uri], { queryParams });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<h1>{{ info?.title }}</h1>
|
||||
<ui-icon *ngIf="showUpdateIcon$ | async" icon="refresh" size="22px"></ui-icon>
|
||||
</div>
|
||||
<page-task-info [info]="info"></page-task-info>
|
||||
<page-task-info [info]="info" (changed)="changed()"></page-task-info>
|
||||
|
||||
<div class="actions">
|
||||
<button *ngIf="showOpenSuccessorCta$ | async" class="btn-cta" type="button" (click)="close(info.successor?.id)">
|
||||
|
||||
@@ -13,7 +13,6 @@ import { map, shareReplay } from 'rxjs/operators';
|
||||
})
|
||||
export class InfoModalComponent {
|
||||
info: DisplayInfoDTO;
|
||||
|
||||
info$ = new ReplaySubject<DisplayInfoDTO>();
|
||||
|
||||
processingStatus$ = this.info$.pipe(
|
||||
@@ -42,6 +41,10 @@ export class InfoModalComponent {
|
||||
this.info$.next(this.info);
|
||||
}
|
||||
|
||||
changed() {
|
||||
this.modalRef.markChanged();
|
||||
}
|
||||
|
||||
close(successorId: number = undefined) {
|
||||
this.modalRef.close({ successorId });
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<h1>{{ info?.title }}</h1>
|
||||
<ui-icon *ngIf="showUpdateIcon$ | async" icon="refresh" size="22px"></ui-icon>
|
||||
</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">
|
||||
<button *ngIf="showOpenSuccessorCta$ | async" class="btn-cta" type="button" (click)="close(info.successor?.id)">
|
||||
|
||||
@@ -42,6 +42,10 @@ export class PreInfoModalComponent {
|
||||
this.info$.next(this.info);
|
||||
}
|
||||
|
||||
changed() {
|
||||
this.modalRef.markChanged();
|
||||
}
|
||||
|
||||
close(successorId: number = undefined) {
|
||||
this.modalRef.close({ successorId });
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
</div>
|
||||
|
||||
<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 class="camera-preview" *ngIf="cameraPreview$ | async; let cameraPreview">
|
||||
|
||||
@@ -122,19 +122,26 @@ export class TaskModalComponent {
|
||||
this.modalRef.close({ successorId });
|
||||
}
|
||||
|
||||
changed() {
|
||||
this.modalRef.markChanged();
|
||||
}
|
||||
|
||||
async startEdit() {
|
||||
await this.domainTaskCalendarService.setToEdit({ infoId: this.info.id }).toPromise();
|
||||
this.reloadInfo();
|
||||
this.changed();
|
||||
}
|
||||
|
||||
async completeEdit() {
|
||||
await this.domainTaskCalendarService.complete({ infoId: this.info.id }).toPromise();
|
||||
this.changed();
|
||||
this.close();
|
||||
}
|
||||
|
||||
async edit() {
|
||||
await this.domainTaskCalendarService.setToEdit({ infoId: this.info.id }).toPromise();
|
||||
this.reloadInfo();
|
||||
this.changed();
|
||||
}
|
||||
|
||||
reloadInfo() {
|
||||
@@ -159,6 +166,7 @@ export class TaskModalComponent {
|
||||
if (result.data === '') {
|
||||
this.captureInput?.nativeElement?.click();
|
||||
} else if (result.data?.length > 0) {
|
||||
this.changed();
|
||||
this.close();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ import { RouterModule, Routes } from '@angular/router';
|
||||
import { PageTaskCalendarComponent } from './page-task-calendar.component';
|
||||
import { CalendarComponent } from './pages/calendar/calendar.component';
|
||||
import { CalendarModule } from './pages/calendar/calendar.module';
|
||||
import { TaskSearchComponent } from './pages/task-search';
|
||||
import { TasksComponent } from './pages/tasks/tasks.component';
|
||||
import { TasksModule } from './pages/tasks/tasks.module';
|
||||
|
||||
@@ -13,7 +14,8 @@ const routes: Routes = [
|
||||
children: [
|
||||
{ path: 'calendar', component: CalendarComponent },
|
||||
{ path: 'tasks', component: TasksComponent },
|
||||
{ path: '', pathMatch: 'full', redirectTo: 'calendar' },
|
||||
{ path: 'search', component: TaskSearchComponent },
|
||||
{ path: '', pathMatch: 'full', redirectTo: 'tasks' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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()">
|
||||
<ui-icon size="20px" icon="filter_alit"></ui-icon>
|
||||
<span class="label">Filter</span>
|
||||
</button>
|
||||
<div class="content-header">
|
||||
<page-task-searchbar></page-task-searchbar>
|
||||
<button class="filter" [class.active]="hasFilter$ | async" (click)="filterActive$.next(true); filterOverlay.open()">
|
||||
<ui-icon size="16px" icon="filter_new"></ui-icon>
|
||||
<span class="label">Filter</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,20 +1,27 @@
|
||||
:host {
|
||||
@apply flex flex-col w-full box-content relative;
|
||||
height: calc(100vh - 14rem);
|
||||
}
|
||||
|
||||
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 {
|
||||
@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;
|
||||
|
||||
right: 0;
|
||||
top: 10px;
|
||||
min-width: 106px;
|
||||
@apply font-sans flex items-center font-bold text-lg py-2 px-4 justify-center ml-3 h-14;
|
||||
width: 108px;
|
||||
background-color: #aeb7c1;
|
||||
box-shadow: 0px 0px 10px rgba(220, 226, 233, 0.5);
|
||||
border-radius: 5px;
|
||||
|
||||
.label {
|
||||
@apply ml-px-5;
|
||||
@apply ml-2;
|
||||
}
|
||||
|
||||
ui-icon {
|
||||
width: 18px;
|
||||
}
|
||||
|
||||
&.active {
|
||||
@@ -22,35 +29,27 @@ shell-breadcrumb {
|
||||
}
|
||||
}
|
||||
|
||||
.content-container {
|
||||
max-height: calc(100vh - 267px);
|
||||
overflow: scroll;
|
||||
@apply bg-white rounded-card;
|
||||
.content-header {
|
||||
@apply grid items-center mt-4 shadow-input gap-1;
|
||||
height: 56px;
|
||||
grid-template-columns: 1fr 120px;
|
||||
}
|
||||
|
||||
.switch-group {
|
||||
@apply flex flex-row justify-center my-9;
|
||||
|
||||
a.calendar-switch {
|
||||
@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;
|
||||
}
|
||||
.content-container {
|
||||
@apply overflow-auto;
|
||||
max-height: calc(100vh - 267px);
|
||||
border-radius: 0px 0px 5px 5px;
|
||||
}
|
||||
|
||||
:host ::ng-deep ui-calendar .navigation .title {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, ViewChild, ElementRef } from '@angular/core';
|
||||
import { Config } from '@core/config';
|
||||
import { isEqual } from 'lodash';
|
||||
import { BehaviorSubject, combineLatest } from 'rxjs';
|
||||
@@ -12,6 +12,9 @@ import { TaskCalendarStore } from './task-calendar.store';
|
||||
providers: [TaskCalendarStore],
|
||||
})
|
||||
export class PageTaskCalendarComponent {
|
||||
@ViewChild('searchInput', { static: true })
|
||||
searchInput: ElementRef;
|
||||
|
||||
filterActive$ = new BehaviorSubject<boolean>(false);
|
||||
taskCalendarKey = this._config.get('process.ids.taskCalendar');
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { ShellBreadcrumbModule } from '@shell/breadcrumb';
|
||||
import { ShellFilterOverlayModule } from '@shell/filter-overlay';
|
||||
import { UiFilterNextModule } from '@ui/filter';
|
||||
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 { ModalsModule } from './modals/modals.module';
|
||||
import { PageTaskCalendarRoutingModule } from './page-task-calendar-routing.module';
|
||||
@@ -19,6 +20,7 @@ import { PageTaskCalendarComponent } from './page-task-calendar.component';
|
||||
ModalsModule,
|
||||
UiFilterNextModule,
|
||||
ShellFilterOverlayModule,
|
||||
TaskSearchbarModule,
|
||||
],
|
||||
exports: [PageTaskCalendarComponent],
|
||||
})
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
<ui-calendar
|
||||
mode="month"
|
||||
[selected]="selectedDate$ | async"
|
||||
[displayed]="displayedDate$ | async"
|
||||
[minDate]="minDate"
|
||||
[maxDate]="maxDate"
|
||||
[indicators]="indicators$ | async"
|
||||
(selectedChange)="setSelectedDate($event)"
|
||||
(displayedChange)="setDisplayedDate($event)"
|
||||
></ui-calendar>
|
||||
<div class="bg-white pb-8 rounded-lg">
|
||||
<ui-calendar
|
||||
mode="month"
|
||||
[selected]="selectedDate$ | async"
|
||||
[displayed]="displayedDate$ | async"
|
||||
[minDate]="minDate"
|
||||
[maxDate]="maxDate"
|
||||
[indicators]="indicators$ | async"
|
||||
(selectedChange)="setSelectedDate($event)"
|
||||
(displayedChange)="setDisplayedDate($event)"
|
||||
></ui-calendar>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
// start:ng42.barrel
|
||||
export * from './task-search.component';
|
||||
export * from './task-search.module';
|
||||
// end:ng42.barrel
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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 {}
|
||||
@@ -1,12 +1,19 @@
|
||||
<ui-calendar
|
||||
mode="week"
|
||||
[selected]="selectedDate$ | async"
|
||||
[displayed]="displayedDate$ | async"
|
||||
[minDate]="minDate"
|
||||
[maxDate]="maxDate"
|
||||
[indicators]="indicators$ | async"
|
||||
(selectedChange)="setSelectedAndDisplayedDate({ selectedDate: $event })"
|
||||
(displayedChange)="setSelectedAndDisplayedDate({ displayDate: $event })"
|
||||
></ui-calendar>
|
||||
<div class="bg-white pb-8 rounded-lg">
|
||||
<ui-calendar
|
||||
mode="week"
|
||||
[selected]="selectedDate$ | async"
|
||||
[displayed]="displayedDate$ | async"
|
||||
[minDate]="minDate"
|
||||
[maxDate]="maxDate"
|
||||
[indicators]="indicators$ | async"
|
||||
(selectedChange)="setSelectedAndDisplayedDate({ selectedDate: $event })"
|
||||
(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>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
:host {
|
||||
@apply block pb-24;
|
||||
@apply block;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,10 @@ export class TasksComponent implements OnInit {
|
||||
|
||||
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 maxDate = this.dateAdapter.addCalendarMonths(this.dateAdapter.today(), 6);
|
||||
@@ -46,8 +50,11 @@ export class TasksComponent implements OnInit {
|
||||
this.updateBreadcrumb({});
|
||||
}
|
||||
|
||||
this.taskCalendarStore.setMode({ mode: 'week' });
|
||||
this.taskCalendarStore.loadItems();
|
||||
this.removeSearchBreadcrumbs();
|
||||
|
||||
this.taskCalendarStore.resetSearch();
|
||||
this.taskCalendarStore.setMode({ mode: 'week' });
|
||||
}
|
||||
|
||||
setSelectedAndDisplayedDate({ displayDate, selectedDate }: { displayDate?: Date; selectedDate?: Date }) {
|
||||
@@ -56,7 +63,6 @@ export class TasksComponent implements OnInit {
|
||||
if (displayDate && selectedDate) {
|
||||
this.taskCalendarStore.setSelectedDate({ date: selectedDate });
|
||||
this.taskCalendarStore.setDisplayedDate({ date: displayDate });
|
||||
this.taskCalendarStore.loadItems();
|
||||
this.router.navigate([], {
|
||||
queryParams: { displayDate: displayDate?.toJSON(), selectedDate: selectedDate?.toJSON() },
|
||||
});
|
||||
@@ -74,7 +80,6 @@ export class TasksComponent implements OnInit {
|
||||
|
||||
if (displayDate) {
|
||||
this.taskCalendarStore.setDisplayedDate({ date: displayDate });
|
||||
this.taskCalendarStore.loadItems();
|
||||
this.router.navigate([], {
|
||||
queryParams: { ...queryParams, displayDate: displayDate?.toJSON() },
|
||||
});
|
||||
@@ -86,6 +91,10 @@ export class TasksComponent implements OnInit {
|
||||
this.taskCalendarStore.open(item);
|
||||
}
|
||||
|
||||
removeSearchBreadcrumbs() {
|
||||
this.breadcrumb.removeBreadcrumbsByKeyAndTags(this._config.get('process.ids.taskCalendar'), ['task-calendar', 'search']);
|
||||
}
|
||||
|
||||
updateBreadcrumb({ displayDate, selectedDate }: { displayDate?: Date; selectedDate?: Date }) {
|
||||
const queryParams = this.activatedRoute.snapshot.queryParams;
|
||||
this.breadcrumb.addOrUpdateBreadcrumbIfNotExists({
|
||||
|
||||
@@ -4,7 +4,6 @@ import { CommonModule } from '@angular/common';
|
||||
import { TasksComponent } from './tasks.component';
|
||||
import { UiCalendarModule } from '@ui/calendar';
|
||||
import { TaskListModule } from '../../components/task-list';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, UiCalendarModule, TaskListModule],
|
||||
exports: [TasksComponent],
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { DomainTaskCalendarService } from '@domain/task-calendar';
|
||||
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 { DateAdapter } from '@ui/common';
|
||||
import { Filter, FilterOption, SelectFilter, UiFilter, UiFilterMappingService } from '@ui/filter';
|
||||
import { UiMessageModalComponent, UiModalRef, UiModalResult, UiModalService } from '@ui/modal';
|
||||
import { clone } from 'lodash';
|
||||
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 { PreInfoModalComponent } from './modals/preinfo/preinfo-modal.component';
|
||||
import { TaskModalComponent } from './modals/task/task-modal.component';
|
||||
@@ -21,7 +21,11 @@ export interface TaskCalendarState {
|
||||
initialFilter: UiFilter;
|
||||
filter: UiFilter;
|
||||
message: string;
|
||||
searchResults: DisplayInfoDTO[];
|
||||
searchTarget: string;
|
||||
fetching: boolean;
|
||||
isSearching: boolean;
|
||||
hits: number;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
@@ -38,6 +42,14 @@ export class TaskCalendarStore extends ComponentStore<TaskCalendarState> {
|
||||
|
||||
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) =>
|
||||
displayItems.reduce<CalendarIndicator[]>((agg, item) => {
|
||||
const calendarIndicator = this.mapDisplayInfoToCalendarIndicator(item);
|
||||
@@ -60,6 +72,7 @@ export class TaskCalendarStore extends ComponentStore<TaskCalendarState> {
|
||||
readonly selectFilter = this.select((s) => s.filter);
|
||||
|
||||
readonly selectFetching = this.select((s) => s.fetching);
|
||||
readonly fetching = this.get((s) => s.fetching);
|
||||
|
||||
readonly selectMessage = this.select((s) => s.message);
|
||||
|
||||
@@ -88,9 +101,11 @@ export class TaskCalendarStore extends ComponentStore<TaskCalendarState> {
|
||||
}
|
||||
});
|
||||
|
||||
hits = this.get((s) => s.hits);
|
||||
|
||||
constructor(
|
||||
public domainTaskCalendarService: DomainTaskCalendarService,
|
||||
private dateAdapter: DateAdapter,
|
||||
private domainTaskCalendarService: DomainTaskCalendarService,
|
||||
private uiModal: UiModalService,
|
||||
private uiFilterMappingService: UiFilterMappingService
|
||||
) {
|
||||
@@ -103,6 +118,10 @@ export class TaskCalendarStore extends ComponentStore<TaskCalendarState> {
|
||||
filter: undefined,
|
||||
message: undefined,
|
||||
fetching: false,
|
||||
searchResults: [],
|
||||
isSearching: false,
|
||||
hits: undefined,
|
||||
searchTarget: '',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -126,11 +145,89 @@ export class TaskCalendarStore extends ComponentStore<TaskCalendarState> {
|
||||
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>) =>
|
||||
$.pipe(
|
||||
tap(() => this.patchState({ fetching: true })),
|
||||
debounceTime(500),
|
||||
withLatestFrom(this.domainTaskCalendarService.currentBranchId$, this.selectStartStop, this.selectFilter),
|
||||
withLatestFrom(this.domainTaskCalendarService.currentBranchId$, this.selectStartStop, this.selectInitialFilter),
|
||||
switchMap(([_, branchId, date, filter]) => {
|
||||
const querytoken = { ...filter?.getQueryToken() };
|
||||
return this.domainTaskCalendarService
|
||||
@@ -146,13 +243,7 @@ export class TaskCalendarStore extends ComponentStore<TaskCalendarState> {
|
||||
tapResponse(
|
||||
(response) => {
|
||||
if (!response.error) {
|
||||
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);
|
||||
});
|
||||
|
||||
response = this.preparePreInfos(response);
|
||||
this.patchState({ displayInfos: response.result, fetching: false, message: undefined });
|
||||
} else {
|
||||
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>) =>
|
||||
$.pipe(
|
||||
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 });
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
:host {
|
||||
@apply grid grid-cols-8;
|
||||
@apply grid grid-cols-8 pr-4;
|
||||
}
|
||||
|
||||
.cell {
|
||||
@@ -11,7 +11,8 @@
|
||||
}
|
||||
|
||||
.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 {
|
||||
|
||||
@@ -1,15 +1,26 @@
|
||||
<div></div>
|
||||
<div class="nav-wrapper">
|
||||
<button class="prev" type="button" (click)="prev()">
|
||||
<ui-icon icon="arrow_head" size="20px"></ui-icon>
|
||||
</button>
|
||||
|
||||
<div class="display-year-month">
|
||||
{{ calendar.displayed | date: 'MMMM yyyy' }}
|
||||
<div class="flex flex-row">
|
||||
<button class="prev" type="button" (click)="prev()">
|
||||
<ui-icon icon="arrow_head" size="12px"></ui-icon>
|
||||
</button>
|
||||
<div class="display-year-month">
|
||||
{{ calendar.displayed | date: 'MMMM yyyy' }}
|
||||
</div>
|
||||
<button class="next" type="button" (click)="next()">
|
||||
<ui-icon icon="arrow_head" size="12px"></ui-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button class="today" type="button" (click)="today()">Heute</button>
|
||||
<button class="next" type="button" (click)="next()">
|
||||
<ui-icon icon="arrow_head" size="20px"></ui-icon>
|
||||
</button>
|
||||
<div class="grid grid-flow-col gap-4 col-start-8">
|
||||
<button class="today" type="button" (click)="today()">Heute</button>
|
||||
<a *ngIf="calendar.mode === 'week'" class="calendar-nav" [routerLink]="['/filiale/task-calendar/calendar']">
|
||||
<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>
|
||||
|
||||
@@ -1,26 +1,51 @@
|
||||
: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 {
|
||||
@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 {
|
||||
@apply mr-11;
|
||||
border: 1px solid #aeb7c1;
|
||||
border-radius: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-wrapper {
|
||||
@apply flex flex-row items-center col-span-7;
|
||||
@apply grid grid-flow-col items-center col-span-7;
|
||||
}
|
||||
|
||||
.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,
|
||||
.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 {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Component, ChangeDetectionStrategy, Host } from '@angular/core';
|
||||
import { DateAdapter } from '@ui/common';
|
||||
import { UiCalendarComponent } from '../ui-calendar.component';
|
||||
|
||||
@Component({
|
||||
|
||||
@@ -1,98 +1,2 @@
|
||||
<ui-calendar-header></ui-calendar-header>
|
||||
<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> -->
|
||||
|
||||
@@ -1,125 +1,3 @@
|
||||
:host {
|
||||
}
|
||||
|
||||
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;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { UiIconModule } from '@ui/icon';
|
||||
import { UiCalendarBodyComponent } from './calendar-body/calendar-body.component';
|
||||
import { UiCalendarCellDirective } from './calendar-body/calendar-cell.directive';
|
||||
@@ -15,7 +16,7 @@ import { UiCalendarComponent } from './ui-calendar.component';
|
||||
UiCalendarCellDirective,
|
||||
UiFilterIndicatorsByDatePipe,
|
||||
],
|
||||
imports: [CommonModule, UiIconModule],
|
||||
imports: [CommonModule, UiIconModule, RouterModule],
|
||||
exports: [UiCalendarComponent],
|
||||
})
|
||||
export class UiCalendarModule {}
|
||||
|
||||
@@ -193,6 +193,13 @@ export class DateAdapter {
|
||||
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 {
|
||||
return this.formatDate(first, precision) < this.formatDate(second, precision);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { QueryTokenDTO } from '@swagger/oms';
|
||||
import { Subject } from 'rxjs';
|
||||
import { IUiInputGroup, UiInputGroup } from './ui-input-group';
|
||||
import { UiInputType } from './ui-input-type.enum';
|
||||
import { IUiOrderBy, UiOrderBy } from './ui-order-by';
|
||||
|
||||
export interface IUiFilter {
|
||||
@@ -55,7 +56,13 @@ export class UiFilter implements IUiFilter {
|
||||
inputGroup.input.forEach((input) => {
|
||||
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) => {
|
||||
inputGroup.input
|
||||
.filter((i) => i.key === key)
|
||||
.forEach((input) => {
|
||||
input.fromStringValue(key, params[gk]);
|
||||
.forEach((input, index) => {
|
||||
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 selected = (order && ob.desc && order === 'desc') || (!ob.desc && order === 'asc');
|
||||
ob.setSelected(selected);
|
||||
@@ -133,7 +148,11 @@ export class UiFilter implements IUiFilter {
|
||||
for (const inputGroup of this.filter) {
|
||||
for (const input of inputGroup.input.filter((i) => i.target === 'filter')) {
|
||||
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) {
|
||||
filter[key] = value;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<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>
|
||||
<ng-content></ng-content>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
import { OverlayRef } from '@angular/cdk/overlay';
|
||||
import { TemplateRef, Type } from '@angular/core';
|
||||
import { UiModalConfig } from '@ui/modal';
|
||||
import { Subject } from 'rxjs';
|
||||
import { Subject, BehaviorSubject } from 'rxjs';
|
||||
import { UiModalResult } from './modal-result';
|
||||
|
||||
export class UiModalRef<TR = any, TD = any> {
|
||||
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(
|
||||
public overlay: OverlayRef,
|
||||
@@ -22,10 +31,15 @@ export class UiModalRef<TR = any, TD = any> {
|
||||
data,
|
||||
});
|
||||
|
||||
this.afterChanged$.complete();
|
||||
this.afterClosed$.complete();
|
||||
}
|
||||
|
||||
close(data?: TR): void {
|
||||
this._close({ type: 'close', data });
|
||||
}
|
||||
|
||||
markChanged() {
|
||||
this.afterChanged$.next(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
|
||||
<ng-template #loader>
|
||||
<ng-container *ngIf="useLoadAnimation; else contentLoader">
|
||||
<ui-skeleton-loader></ui-skeleton-loader>
|
||||
<ui-skeleton-loader *ngFor="let skeletons of createSkeletons()"></ui-skeleton-loader>
|
||||
<ui-skeleton-loader [template]="skeletonTemplate"></ui-skeleton-loader>
|
||||
<ui-skeleton-loader [template]="skeletonTemplate" *ngFor="let skeletons of createSkeletons()"></ui-skeleton-loader>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #contentLoader>
|
||||
|
||||
@@ -48,6 +48,8 @@ export class UiScrollContainerComponent implements OnInit {
|
||||
|
||||
@Input() initialScroll: number;
|
||||
|
||||
@Input() skeletonTemplate?: string;
|
||||
|
||||
get containerHeightString() {
|
||||
return `calc(100vh - ${this.containerHeight}rem)`;
|
||||
}
|
||||
|
||||
@@ -1,28 +1,48 @@
|
||||
<div class="header">
|
||||
<div class="left animation"></div>
|
||||
<div class="right animation"></div>
|
||||
</div>
|
||||
<ng-container [ngSwitch]="template">
|
||||
<div *ngSwitchCase="'task-calendar'" class="task-calendar">
|
||||
<div class="task-calendar-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 class="task-calendar-details">
|
||||
<div class="date animation"></div>
|
||||
<div class="icon animation"></div>
|
||||
<div class="col">
|
||||
<div class="item animation"></div>
|
||||
<div class="item animation"></div>
|
||||
<div class="item animation"></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>
|
||||
|
||||
@@ -1,8 +1,44 @@
|
||||
:host {
|
||||
.default {
|
||||
@apply flex flex-col pt-4 pb-6 pl-1 pr-4 bg-white;
|
||||
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 {
|
||||
@apply w-full flex flex-row justify-between mt-5;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { Component, ChangeDetectionStrategy, Input } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'ui-skeleton-loader',
|
||||
@@ -7,5 +7,8 @@ import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class UiSkeletonLoaderComponent {
|
||||
@Input()
|
||||
template?: string;
|
||||
|
||||
constructor() {}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ variables:
|
||||
value: "$[counter(format('{0}.{1}', variables['Major'], variables['Minor']),0)]"
|
||||
- name: 'BuildUniqueID'
|
||||
value: '$(Build.BuildID)-$(Agent.Id)-$(System.DefinitionId)-$(System.JobId)'
|
||||
- group: 'GithubCMF'
|
||||
|
||||
jobs:
|
||||
# - job: cibuild_fast
|
||||
@@ -52,6 +53,7 @@ jobs:
|
||||
displayName: 'npm auth'
|
||||
inputs:
|
||||
workingFile: .npmrc
|
||||
customEndpoint: GitHub-read-packages
|
||||
- bash: |
|
||||
echo Build and Run Tests in docker
|
||||
docker build . \
|
||||
@@ -110,6 +112,7 @@ jobs:
|
||||
displayName: 'npm auth'
|
||||
inputs:
|
||||
workingFile: .npmrc
|
||||
customEndpoint: GitHub-read-packages
|
||||
- task: Docker@2
|
||||
displayName: 'build ISAClient Debug'
|
||||
inputs:
|
||||
@@ -170,6 +173,7 @@ jobs:
|
||||
displayName: 'npm auth'
|
||||
inputs:
|
||||
workingFile: .npmrc
|
||||
customEndpoint: GitHub-read-packages
|
||||
- task: Docker@2
|
||||
displayName: 'build ISAClient Prod'
|
||||
inputs:
|
||||
|
||||
1065
package-lock.json
generated
1065
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -59,6 +59,7 @@
|
||||
"@ngrx/entity": "~12.5.1",
|
||||
"@ngrx/store": "~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-jwks": "^13.0.1",
|
||||
"core-js": "^2.6.5",
|
||||
|
||||
@@ -304,7 +304,10 @@
|
||||
],
|
||||
"@ui/branch-dropdown": [
|
||||
"apps/ui/branch-dropdown/src/public-api.ts"
|
||||
]
|
||||
],
|
||||
"@paragondata/ngx-ui/form-field": [
|
||||
"node_modules/@paragondata/ngx-ui/form-field"
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user