Merged PR 660: TK Feature merge

Related work items: #10, #15, #16, #1591, #1592, #1604, #1611, #1617, #1618, #1628, #1632, #1637, #1644, #1655, #1676, #1681, #1682, #1683
This commit is contained in:
Lorenz Hilpert
2021-05-10 16:16:40 +00:00
parent 2294cd2788
commit b6b78245e1
468 changed files with 6858 additions and 5117 deletions

View File

@@ -2096,6 +2096,206 @@
}
}
}
},
"@page/task-calendar": {
"projectType": "library",
"root": "apps/page/task-calendar",
"sourceRoot": "apps/page/task-calendar/src",
"prefix": "page",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:ng-packagr",
"options": {
"tsConfig": "apps/page/task-calendar/tsconfig.lib.json",
"project": "apps/page/task-calendar/ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "apps/page/task-calendar/tsconfig.lib.prod.json"
}
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "apps/page/task-calendar/src/test.ts",
"tsConfig": "apps/page/task-calendar/tsconfig.spec.json",
"karmaConfig": "apps/page/task-calendar/karma.conf.js"
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"apps/page/task-calendar/tsconfig.lib.json",
"apps/page/task-calendar/tsconfig.spec.json"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
},
"@domain/task-calendar": {
"projectType": "library",
"root": "apps/domain/task-calendar",
"sourceRoot": "apps/domain/task-calendar/src",
"prefix": "lib",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:ng-packagr",
"options": {
"tsConfig": "apps/domain/task-calendar/tsconfig.lib.json",
"project": "apps/domain/task-calendar/ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "apps/domain/task-calendar/tsconfig.lib.prod.json"
}
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "apps/domain/task-calendar/src/test.ts",
"tsConfig": "apps/domain/task-calendar/tsconfig.spec.json",
"karmaConfig": "apps/domain/task-calendar/karma.conf.js"
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"apps/domain/task-calendar/tsconfig.lib.json",
"apps/domain/task-calendar/tsconfig.spec.json"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
},
"@ui/calendar": {
"projectType": "library",
"root": "apps/ui/calendar",
"sourceRoot": "apps/ui/calendar/src",
"prefix": "lib",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:ng-packagr",
"options": {
"tsConfig": "apps/ui/calendar/tsconfig.lib.json",
"project": "apps/ui/calendar/ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "apps/ui/calendar/tsconfig.lib.prod.json"
}
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "apps/ui/calendar/src/test.ts",
"tsConfig": "apps/ui/calendar/tsconfig.spec.json",
"karmaConfig": "apps/ui/calendar/karma.conf.js"
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"apps/ui/calendar/tsconfig.lib.json",
"apps/ui/calendar/tsconfig.spec.json"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
},
"@ui/notes": {
"projectType": "library",
"root": "apps/ui/notes",
"sourceRoot": "apps/ui/notes/src",
"prefix": "ui",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:ng-packagr",
"options": {
"tsConfig": "apps/ui/notes/tsconfig.lib.json",
"project": "apps/ui/notes/ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "apps/ui/notes/tsconfig.lib.prod.json"
}
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "apps/ui/notes/src/test.ts",
"tsConfig": "apps/ui/notes/tsconfig.spec.json",
"karmaConfig": "apps/ui/notes/karma.conf.js"
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"apps/ui/notes/tsconfig.lib.json",
"apps/ui/notes/tsconfig.spec.json"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
},
"@ui/tshirt": {
"projectType": "library",
"root": "apps/ui/tshirt",
"sourceRoot": "apps/ui/tshirt/src",
"prefix": "ui",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:ng-packagr",
"options": {
"tsConfig": "apps/ui/tshirt/tsconfig.lib.json",
"project": "apps/ui/tshirt/ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "apps/ui/tshirt/tsconfig.lib.prod.json"
}
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "apps/ui/tshirt/src/test.ts",
"tsConfig": "apps/ui/tshirt/tsconfig.spec.json",
"karmaConfig": "apps/ui/tshirt/karma.conf.js"
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"apps/ui/tshirt/tsconfig.lib.json",
"apps/ui/tshirt/tsconfig.spec.json"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
}
},
"defaultProject": "sales"

View File

@@ -1,5 +1,5 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { ArticleDTO, DisplayInfoDTO, EISPublicDocumentService } from '@swagger/eis';
import {
CatalogPrintService,
CheckoutPrintService,
@@ -17,18 +17,17 @@ import { Printer } from './defs/printer.model';
@Injectable({
providedIn: 'root',
})
export class PrinterService {
export class DomainPrinterService {
constructor(
private printService: PrintService,
private oMSPrintService: OMSPrintService,
private catalogPrintService: CatalogPrintService,
private checkoutPrintService: CheckoutPrintService,
private http: HttpClient
private eisPublicDocumentService: EISPublicDocumentService
) {}
getAvailablePrinters(): Observable<Printer[] | { error: string }> {
return this.printService.PrintPrinters().pipe(
timeout(20000),
catchError((error) => of({ error })),
map((response: any) => {
if (response.error && response.error.status === 503) {
@@ -58,12 +57,9 @@ export class PrinterService {
);
}
getAvailablePrintersDev() {
return this.http.get('http://127.0.0.1:5000/api/printers').pipe(
timeout(20000),
catchError((error) => {
return of({ error });
}),
getAvailableOfficePrinters(): Observable<Printer[] | { error: string }> {
return this.printService.PrintOfficePrinters().pipe(
catchError((error) => of({ error })),
map((response: any) => {
if (response.error && response.error.status === 503) {
return {
@@ -78,6 +74,7 @@ export class PrinterService {
error: response.message ? response.message : response.error.message ? response.error.message : 'Ein Fehler ist aufgetreten',
};
}
return response.result.map(
(t) =>
<Printer>{
@@ -91,34 +88,28 @@ export class PrinterService {
);
}
async printOrder(orderIds: number[], printer: string): Promise<ResponseArgs> {
printOrder({ orderIds, printer }: { orderIds: number[]; printer: string }): Observable<ResponseArgs> {
const params = <any>{
printer: printer,
data: orderIds,
};
const response = await this.oMSPrintService.OMSPrintAbholscheinById(params).pipe(timeout(20000)).toPromise();
return response;
return this.oMSPrintService.OMSPrintAbholscheinById(params).pipe(timeout(20000));
}
async printProduct(item: ItemDTO, printer: string): Promise<ResponseArgs> {
printProduct({ item, printer }: { item: ItemDTO; printer: string }): Observable<ResponseArgs> {
const params = <PrintRequestOfIEnumerableOfItemDTO>{
printer: printer,
data: [item],
};
const response = await this.catalogPrintService.CatalogPrintArtikelDetail(params).pipe(timeout(20000)).toPromise();
return response;
return this.catalogPrintService.CatalogPrintArtikelDetail(params).pipe(timeout(20000));
}
async printCart(cartId: number, printer: string): Promise<ResponseArgs> {
printCart({ cartId, printer }: { cartId: number; printer: string }): Observable<ResponseArgs> {
const params = <any>{
printer: printer,
data: cartId,
};
const response = await this.checkoutPrintService.CheckoutPrintWarenkorbById(params).pipe(timeout(20000)).toPromise();
return response;
return this.checkoutPrintService.CheckoutPrintWarenkorbById(params).pipe(timeout(20000));
}
async printGoodsInLabel(subsetItemIds: number[]): Promise<ResponseArgs> {
@@ -147,4 +138,35 @@ export class PrinterService {
return response;
}
printPdf({ printer, data }: { printer: string; data: string }) {
return this.printService.PrintPrintPdf({
printer,
data,
});
}
printDisplayInfoDTOArticles({ articles, printer, title }: { articles: ArticleDTO[]; printer: string; title?: string }) {
return this.eisPublicDocumentService
.EISPublicDocumentGetArticlesPdfAsBase64({
payload: { data: articles.map((article) => article.ean), title },
})
.pipe(switchMap((res) => this.printPdf({ printer, data: res.result })));
}
printDisplayInfoDTOList({ displayInfos, printer, title }: { displayInfos: DisplayInfoDTO[]; printer: string; title?: string }) {
return this.eisPublicDocumentService
.EISPublicDocumentGetInfosPdfAsBase64({
payload: { data: displayInfos, title },
})
.pipe(switchMap((res) => this.printPdf({ printer, data: res.result })));
}
getDefaultPrinter({ printerType }: { printerType: 'Office' | 'Label' }): string {
return localStorage.getItem(`Default_${printerType}_Printer`) ?? undefined;
}
setDefaultPrinter({ printerType, printer }: { printerType: 'Office' | 'Label'; printer: string }): void {
localStorage.setItem(`Default_${printerType}_Printer`, printer);
}
}

View File

@@ -0,0 +1,25 @@
# TaskCalendar
This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 10.1.2.
## Code scaffolding
Run `ng generate component component-name --project task-calendar` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project task-calendar`.
> Note: Don't forget to add `--project task-calendar` or else it will be added to the default project in your `angular.json` file.
## Build
Run `ng build task-calendar` to build the project. The build artifacts will be stored in the `dist/` directory.
## Publishing
After building your library with `ng build task-calendar`, go to the dist folder `cd dist/task-calendar` and run `npm publish`.
## Running unit tests
Run `ng test task-calendar` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).

View File

@@ -0,0 +1,32 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma'),
],
client: {
clearContext: false, // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, '../../../coverage/domain/task-calendar'),
reports: ['html', 'lcovonly', 'text-summary'],
fixWebpackSourcePaths: true,
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true,
});
};

View File

@@ -0,0 +1,7 @@
{
"$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../../dist/domain/task-calendar",
"lib": {
"entryFile": "src/public-api.ts"
}
}

View File

@@ -0,0 +1,11 @@
{
"name": "@domain/task-calendar",
"version": "0.0.1",
"peerDependencies": {
"@angular/common": "^10.1.2",
"@angular/core": "^10.1.2"
},
"dependencies": {
"tslib": "^2.0.0"
}
}

View File

@@ -0,0 +1,4 @@
// start:ng42.barrel
export * from './info.type';
export * from './processing-status-list.type';
// end:ng42.barrel

View File

@@ -0,0 +1 @@
export type InfoType = 'Task' | 'Info' | 'PreInfo';

View File

@@ -0,0 +1,3 @@
export type ProcessingStatusType = 'Approved' | 'InProcess' | 'Completed' | 'Overdue' | 'Archived';
export type ProcessingStatusList = ProcessingStatusType[];

View File

@@ -0,0 +1,8 @@
import { NgModule } from '@angular/core';
@NgModule({
declarations: [],
imports: [],
exports: [],
})
export class TaskCalendarModule {}

View File

@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { TaskCalendarService } from './task-calendar.service';
describe('TaskCalendarService', () => {
let service: TaskCalendarService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(TaskCalendarService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@@ -0,0 +1,268 @@
import { Injectable } from '@angular/core';
import { DisplayInfoRequest, EISPublicService, FileDTO, ProcessingStatus, QueryTokenDTO } from '@swagger/eis';
import { DateAdapter } from '@ui/common';
import { memorize } from '@utils/common';
import { combineLatest } from 'rxjs';
import { map, shareReplay, switchMap } from 'rxjs/operators';
import { InfoType, ProcessingStatusList } from './defs';
@Injectable({
providedIn: 'root',
})
export class DomainTaskCalendarService {
readonly currentBranchId$ = this.eisPublicService.EISPublicGetCurrentBranch({}).pipe(
map((response) => response.result?.id),
shareReplay()
);
constructor(private eisPublicService: EISPublicService, private dateAdapter: DateAdapter) {}
getFilters() {
return this.eisPublicService.EISPublicQueryDisplayInfoFilter();
}
getInfos(queryToken: QueryTokenDTO) {
return this.eisPublicService.EISPublicQueryDisplayInfo({
queryToken,
});
}
getInfoById({ infoId }: { infoId: number }) {
return this.eisPublicService.EISPublicGetDisplayInfoById({
infoId,
});
}
setToEdit({ infoId }: { infoId: number }) {
return this.eisPublicService.EISPublicSetInfoToEdit({
infoId,
});
}
complete({ infoId, file }: { infoId: number; file?: string | ArrayBuffer }) {
let fileDTO: FileDTO;
let _file: string;
if (typeof file === 'string') {
_file = file;
} else if (file instanceof ArrayBuffer) {
_file = this.arrayBufferToString(file);
}
if (_file) {
fileDTO = {
name: 'confirmation image',
mime: this.parseMimeTypeFromBase64String(_file),
content: this.removeMetaDataFromBase64(_file),
};
} else {
fileDTO = {};
}
return this.eisPublicService.EISPublicCompleteConfirmation({
infoId,
file: fileDTO,
});
}
/* @internal */
arrayBufferToString(arrayBuffer: ArrayBuffer): string {
return String.fromCharCode.apply(null, new Uint16Array(arrayBuffer));
}
/* @internal */
parseMimeTypeFromBase64String(base64: string): string {
return base64.substring('data:'.length, base64.indexOf(';'));
}
/* @internal */
removeMetaDataFromBase64(base64: string): string {
const stringFromWhichToStart = 'base64,';
return base64.substring(base64.indexOf(stringFromWhichToStart) + stringFromWhichToStart.length, base64.length);
}
resetConfirmation({ infoId }: { infoId: number }) {
return this.eisPublicService.EISPublicResetConfirmation({
infoId,
});
}
@memorize({ ttl: 60000 })
getFile({ fileId, download }: { fileId: number; download?: boolean }) {
return this.eisPublicService
.EISPublicGetFileContent({
fileId,
download,
})
.pipe(shareReplay());
}
@memorize({ ttl: 60000 })
getFiles({ infoId }: { infoId: number }) {
return this.eisPublicService
.EISPublicGetFiles({
infoId,
})
.pipe(shareReplay());
}
@memorize({ ttl: 60000 })
getImageFiles({ infoId }: { infoId: number }) {
return this.eisPublicService
.EISPublicGetImages({
infoId,
})
.pipe(shareReplay());
}
@memorize({ ttl: 60000 })
getTeaserFile({ infoId }: { infoId: number }) {
return this.eisPublicService
.EISPublicGetTeaserImage({
infoId,
})
.pipe(shareReplay());
}
getConfirmationFiles({ infoId }: { infoId: number }) {
return this.currentBranchId$.pipe(
switchMap((branchId) => this.eisPublicService.EISPublicGetConfirmationFilesByBranchId({ infoId, branchId }))
);
}
getArticles({ infoId }: { infoId: number }) {
return this.eisPublicService.EISPublicGetArticles({
infoId,
});
}
getProcessingStatusList({ processingStatus = 0 }: { processingStatus?: ProcessingStatus }): ProcessingStatusList {
/**
* ProcessingStatus
* 1 = Approved
* 2 = InProcess
* 4 = Completed
* 8 = Overdue
* 16 = Archived
*/
const processingStatusList: ProcessingStatusList = [];
if (!!(processingStatus & 1)) {
processingStatusList.push('Approved');
}
if (!!(processingStatus & 2)) {
processingStatusList.push('InProcess');
}
if (!!(processingStatus & 4)) {
processingStatusList.push('Completed');
}
if (!!(processingStatus & 8)) {
processingStatusList.push('Overdue');
}
if (!!(processingStatus & 16)) {
processingStatusList.push('Archived');
}
return processingStatusList;
}
getProcessingStatusColorCode({
processingStatus,
hasTask,
publicationDate,
announcementDate,
}: {
processingStatus?: ProcessingStatus;
publicationDate?: string;
announcementDate?: string;
hasTask?: boolean;
}) {
const type = this.getInfoType({ hasTask, publicationDate, announcementDate });
if (type === ('Info' || 'PreInfo')) {
return '#8A949E';
}
const processingStatusList = this.getProcessingStatusList({ processingStatus });
if (processingStatusList.includes('Archived')) {
return '#44942e';
}
if (processingStatusList.includes('Completed')) {
return '#44942e';
}
if (processingStatusList.includes('InProcess')) {
return '#FFCC01';
}
if (processingStatusList.includes('Overdue')) {
return '#f70400';
}
if (processingStatusList.includes('Approved')) {
return '#8A949E';
}
return '#8A949E';
}
getInfoType({
hasTask,
publicationDate,
announcementDate,
}: {
publicationDate?: string;
announcementDate?: string;
hasTask?: boolean;
}): InfoType {
if (hasTask) {
return 'Task';
}
if (!publicationDate || !announcementDate) {
return 'Info';
}
const _publicationDate = new Date(publicationDate);
const _announcmentDate = new Date(announcementDate);
return this.dateAdapter.compareDate(_announcmentDate, _publicationDate) > 0 ? 'PreInfo' : 'Info';
}
getDisplayInfoDate(source: { taskDate?: string; publicationDate?: string; announcementDate?: string }) {
// Wenn announcementDate abgelaufen ist wird der taskDate/publicationDate genommen
let displayDate = new Date(source.taskDate || source.publicationDate);
if (source.announcementDate && new Date(source.announcementDate) >= this.dateAdapter.today()) {
displayDate = new Date(source.announcementDate);
}
return displayDate;
}
getComments({ infoId }: { infoId: number }) {
return this.currentBranchId$.pipe(
switchMap((branchId) =>
this.eisPublicService.EISPublicGetCommentsByBranchId({
branchId,
infoId,
})
)
);
}
addComment({ infoId, text }: { infoId: number; text: string }) {
return this.currentBranchId$.pipe(
switchMap((branchId) =>
this.eisPublicService.EISPublicAddComment({
infoId,
payload: { text, branch: { id: branchId } },
})
)
);
}
}

View File

@@ -0,0 +1,7 @@
/*
* Public API Surface of task-calendar
*/
export * from './lib/task-calendar.service';
export * from './lib/task-calendar.module';
export * from './lib/defs';

View File

@@ -0,0 +1,24 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js/dist/zone';
import 'zone.js/dist/zone-testing';
import { getTestBed } from '@angular/core/testing';
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
declare const require: {
context(
path: string,
deep?: boolean,
filter?: RegExp
): {
keys(): string[];
<T>(id: string): T;
};
};
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);

View File

@@ -0,0 +1,25 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"outDir": "../../../out-tsc/lib",
"target": "es2015",
"declaration": true,
"declarationMap": true,
"inlineSources": true,
"types": [],
"lib": [
"dom",
"es2018"
]
},
"angularCompilerOptions": {
"skipTemplateCodegen": true,
"strictMetadataEmit": true,
"enableResourceInlining": true
},
"exclude": [
"src/test.ts",
"**/*.spec.ts"
]
}

View File

@@ -0,0 +1,10 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.lib.json",
"compilerOptions": {
"declarationMap": false
},
"angularCompilerOptions": {
"enableIvy": false
}
}

View File

@@ -0,0 +1,17 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"outDir": "../../../out-tsc/spec",
"types": [
"jasmine"
]
},
"files": [
"src/test.ts"
],
"include": [
"**/*.spec.ts",
"**/*.d.ts"
]
}

View File

@@ -0,0 +1,17 @@
{
"extends": "../../../tslint.json",
"rules": {
"directive-selector": [
true,
"attribute",
"lib",
"camelCase"
],
"component-selector": [
true,
"element",
"lib",
"kebab-case"
]
}
}

View File

@@ -1,22 +0,0 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { PrinterComponent } from './modal-printer.component';
@Component({
selector: 'modal-print-cart',
templateUrl: 'modal-printer.component.html',
styleUrls: ['modal-printer.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PrintCartComponent extends PrinterComponent {
async print() {
this.loaded = false;
const printResponse = await this.printerService.printCart(this.modalRef.data, this.selectedPrinterValue);
if (!printResponse.error) {
this.modalRef.close();
} else {
this.setError(printResponse.message);
}
this.loaded = true;
}
}

View File

@@ -1,22 +0,0 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { PrinterComponent } from './modal-printer.component';
@Component({
selector: 'modal-print-order',
templateUrl: 'modal-printer.component.html',
styleUrls: ['modal-printer.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PrintOrderComponent extends PrinterComponent {
async print() {
this.loaded = false;
const printResponse = await this.printerService.printOrder(this.modalRef.data, this.selectedPrinterValue);
if (!printResponse.error) {
this.modalRef.close();
} else {
this.setError(printResponse.message);
}
this.loaded = true;
}
}

View File

@@ -1,22 +0,0 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { PrinterComponent } from './modal-printer.component';
@Component({
selector: 'modal-print-product',
templateUrl: 'modal-printer.component.html',
styleUrls: ['modal-printer.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PrintProductComponent extends PrinterComponent {
async print() {
this.loaded = false;
const printResponse = await this.printerService.printProduct(this.modalRef.data, this.selectedPrinterValue);
if (!printResponse.error) {
this.modalRef.close();
} else {
this.setError(printResponse.message);
}
this.loaded = true;
}
}

View File

@@ -15,7 +15,7 @@
<div class="body">
<ui-spinner [show]="!loaded">
<ui-select class="select" [(ngModel)]="selectedPrinterValue">
<ui-select-option *ngFor="let printer of printers" [label]="printer.text" [value]="printer.key"></ui-select-option>
<ui-select-option *ngFor="let printer of printers$ | async" [label]="printer.text" [value]="printer.key"></ui-select-option>
</ui-select>
</ui-spinner>
</div>

View File

@@ -1,7 +1,9 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { Printer, PrinterService } from '@domain/printer';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core';
import { Printer, DomainPrinterService } from '@domain/printer';
import { UiModalRef } from '@ui/modal';
import { Subscription } from 'rxjs';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { PrintModalData } from './modal-printer.data';
@Component({
selector: 'modal-print-cart',
@@ -9,39 +11,50 @@ import { Subscription } from 'rxjs';
styleUrls: ['modal-printer.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PrinterComponent implements OnInit, OnDestroy {
export class PrintModalComponent implements OnInit {
protected selectedPrinterValue: string;
printers: { key: string; text: string; selected: boolean }[] = [];
printerSubscription: Subscription;
printers$: Observable<{ key: string; text: string; selected: boolean }[]>;
error = false;
errorMessage = 'Der Druckauftrag konnte nicht ausgeführt werden.';
loaded = false;
constructor(protected printerService: PrinterService, public modalRef: UiModalRef, private cdr: ChangeDetectorRef) {}
constructor(
protected printerService: DomainPrinterService,
public modalRef: UiModalRef<string, PrintModalData>,
private cdr: ChangeDetectorRef
) {}
ngOnInit() {
this.loadPrinters();
}
loadPrinters() {
let printerResult$: Observable<Printer[] | { error: string }>;
this.loaded = false;
this.error = false;
this.printerSubscription = this.printerService.getAvailablePrinters().subscribe((response) => {
if ((response as { error: string }).error) {
const errorResponse = response as { error: string };
this.error = true;
this.errorMessage = errorResponse.error;
return;
}
if (this.modalRef.data.printerType === 'Office') {
printerResult$ = this.printerService.getAvailableOfficePrinters();
} else if (this.modalRef.data.printerType === 'Label') {
printerResult$ = this.printerService.getAvailablePrinters();
}
this.printers$ = printerResult$.pipe(map((printers) => this.loadPrinterHelper(printers)));
}
loadPrinterHelper(response: Printer[] | { error: string }): { key: string; text: string; selected: boolean }[] {
if ((response as { error: string }).error) {
const errorResponse = response as { error: string };
this.error = true;
this.errorMessage = errorResponse.error;
} else {
const result = response as Printer[];
this.printers = result.map((t) => {
const printers = result.map((t) => {
return { key: t.key, text: t.description, selected: t.selected };
});
const selectedPrinter = this.printers.find((printer) => printer.selected);
this.selectedPrinterValue = selectedPrinter ? selectedPrinter.key : this.printers[0].key;
const defaultPrinter = this.printerService.getDefaultPrinter({ printerType: this.modalRef.data.printerType });
const selectedPrinter = printers.find((printer) => (printer.selected || defaultPrinter ? printer.key === defaultPrinter : false));
this.selectedPrinterValue = selectedPrinter ? selectedPrinter.key : printers[0].key;
this.error = false;
this.loaded = true;
@@ -51,7 +64,9 @@ export class PrinterComponent implements OnInit, OnDestroy {
this.errorMessage = 'Keine Drucker verfügbar.';
}
this.cdr.markForCheck();
});
return printers;
}
return [];
}
setError(errorMessage?: string) {
@@ -61,13 +76,15 @@ export class PrinterComponent implements OnInit, OnDestroy {
}
}
print() {
this.modalRef.close(this.selectedPrinterValue);
}
ngOnDestroy(): void {
if (this.printerSubscription) {
this.printerSubscription.unsubscribe();
async print() {
this.loaded = false;
const printResponse = await this.modalRef.data.print(this.selectedPrinterValue);
if (!printResponse.error) {
this.printerService.setDefaultPrinter({ printerType: this.modalRef.data.printerType, printer: this.selectedPrinterValue });
this.modalRef.close();
} else {
this.setError(printResponse.message);
}
this.loaded = true;
}
}

View File

@@ -0,0 +1,4 @@
export interface PrintModalData {
printerType: 'Office' | 'Label';
print: (printer: string) => Promise<{ error?: boolean; message?: string } | null>;
}

View File

@@ -5,14 +5,11 @@ import { UiIconModule } from '@ui/icon';
import { UiModalModule } from '@ui/modal';
import { UiSelectModule } from '@ui/select';
import { UiSpinnerModule } from 'apps/ui/spinner/src/lib/ui-spinner.module';
import { PrintCartComponent } from './modal-print-cart.component';
import { PrintOrderComponent } from './modal-print-order.component';
import { PrintProductComponent } from './modal-print-product.component';
import { PrinterComponent } from './modal-printer.component';
import { PrintModalComponent } from './modal-printer.component';
@NgModule({
declarations: [PrinterComponent, PrintOrderComponent, PrintCartComponent, PrintProductComponent],
declarations: [PrintModalComponent],
imports: [CommonModule, FormsModule, UiModalModule, UiIconModule, UiSelectModule, UiSpinnerModule],
exports: [PrinterComponent, PrintOrderComponent, PrintCartComponent, PrintProductComponent],
exports: [PrintModalComponent],
})
export class ModalPrinterModule {}

View File

@@ -4,6 +4,4 @@
export * from './lib/modal-printer.module';
export * from './lib/modal-printer.component';
export * from './lib/modal-print-product.component';
export * from './lib/modal-print-cart.component';
export * from './lib/modal-print-order.component';
export * from './lib/modal-printer.data';

View File

@@ -5,8 +5,8 @@ import { AvailabilityService } from '@domain/availability';
import { DomainCheckoutService } from '@domain/checkout';
import { AvailabilityDTO, DestinationDTO, ShoppingCartItemDTO } from '@swagger/checkout';
import { UiMessageModalComponent, UiModalService } from '@ui/modal';
import { PrintCartComponent } from '@modal/printer';
import { first, map, shareReplay, switchMap, takeUntil, tap } from 'rxjs/operators';
import { PrintModalData, PrintModalComponent } from '@modal/printer';
import { first, map, shareReplay, switchMap, takeUntil } from 'rxjs/operators';
import { SsoService } from 'sso';
import { PurchasingOptionsModalComponent, PurchasingOptionsModalData } from '../modals/purchasing-options-modal';
import { PurchasingOptions } from '../modals/purchasing-options-modal/purchasing-options-modal.store';
@@ -14,6 +14,7 @@ import { Subject, NEVER } from 'rxjs';
import { StringDictionary } from '@cmf/core';
import { DomainCatalogService } from '@domain/catalog';
import { BreadcrumbService } from '@core/breadcrumb';
import { DomainPrinterService } from '@domain/printer';
@Component({
selector: 'page-checkout-review',
@@ -122,7 +123,8 @@ export class CheckoutReviewComponent {
private router: Router,
private cdr: ChangeDetectorRef,
private domainCatalogService: DomainCatalogService,
private breadcrumb: BreadcrumbService
private breadcrumb: BreadcrumbService,
private domainPrinterService: DomainPrinterService
) {
this.breadcrumb
.getBreadcrumbsByKeyAndTag$(this.applicationService.activatedProcessId, 'checkout')
@@ -308,8 +310,11 @@ export class CheckoutReviewComponent {
async openPrintModal() {
let shoppingCart = await this.shoppingCart$.pipe(first()).toPromise();
this.uiModal.open({
content: PrintCartComponent,
data: shoppingCart.id,
content: PrintModalComponent,
data: {
printerType: 'Office',
print: (printer) => this.domainPrinterService.printCart({ cartId: shoppingCart.id, printer }).toPromise(),
} as PrintModalData,
});
}

View File

@@ -1,7 +1,7 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { DomainCheckoutService } from '@domain/checkout';
import { UiModalService } from '@ui/modal';
import { PrintOrderComponent } from '@modal/printer';
import { PrintModalComponent, PrintModalData } from '@modal/printer';
import { first, map, switchMap } from 'rxjs/operators';
import { CrmCustomerService } from '@domain/crm';
import { Router } from '@angular/router';
@@ -10,6 +10,7 @@ import { DomainCatalogService } from '@domain/catalog';
import { DisplayOrderItemDTO } from '@swagger/oms';
import { BreadcrumbService } from '@core/breadcrumb';
import { ApplicationService } from '@core/application';
import { DomainPrinterService } from '@domain/printer';
@Component({
selector: 'page-checkout-summary',
@@ -84,7 +85,8 @@ export class CheckoutSummaryComponent {
private omsService: OmsService,
private uiModal: UiModalService,
private breadcrumb: BreadcrumbService,
private applicationService: ApplicationService
private applicationService: ApplicationService,
private domainPrinterService: DomainPrinterService
) {
this.breadcrumb
.getBreadcrumbsByKeyAndTags$(this.applicationService.activatedProcessId, ['checkout'])
@@ -104,8 +106,11 @@ export class CheckoutSummaryComponent {
openPrintModal(id: number) {
this.uiModal.open({
content: PrintOrderComponent,
data: [id],
content: PrintModalComponent,
data: {
printerType: 'Office',
print: (printer) => this.domainPrinterService.printOrder({ orderIds: [id], printer }).toPromise(),
} as PrintModalData,
});
}

View File

@@ -0,0 +1,25 @@
# TaskCalendar
This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 10.1.2.
## Code scaffolding
Run `ng generate component component-name --project task-calendar` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project task-calendar`.
> Note: Don't forget to add `--project task-calendar` or else it will be added to the default project in your `angular.json` file.
## Build
Run `ng build task-calendar` to build the project. The build artifacts will be stored in the `dist/` directory.
## Publishing
After building your library with `ng build task-calendar`, go to the dist folder `cd dist/task-calendar` and run `npm publish`.
## Running unit tests
Run `ng test task-calendar` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).

View File

@@ -0,0 +1,32 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma'),
],
client: {
clearContext: false, // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, '../../../coverage/page/task-calendar'),
reports: ['html', 'lcovonly', 'text-summary'],
fixWebpackSourcePaths: true,
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true,
});
};

View File

@@ -0,0 +1,7 @@
{
"$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../../dist/page/task-calendar",
"lib": {
"entryFile": "src/public-api.ts"
}
}

View File

@@ -0,0 +1,11 @@
{
"name": "@page/task-calendar",
"version": "0.0.1",
"peerDependencies": {
"@angular/common": "^10.1.2",
"@angular/core": "^10.1.2"
},
"dependencies": {
"tslib": "^2.0.0"
}
}

View File

@@ -0,0 +1,92 @@
<div class="info-header">
<h5>
{{ dayOfWeek }}
</h5>
<span class="muted">
<ng-container *ngIf="checkSameDay(); else notToday">{{ date | date: 'EEEE, dd. MMMM yyyy' }}</ng-container>
<ng-template #notToday>{{ date | date: 'dd. MMMM yyyy' }}</ng-template>
</span>
</div>
<hr />
<div class="info-body">
<div class="time-span">{{ timeSpan }}</div>
<div class="indicator" *ngIf="type$ | async; let type" [ngClass]="type">
<div class="task-indicator" *ngIf="type === 'Task'" [style.backgroundColor]="indicatorColor$ | async">&nbsp;</div>
<ui-icon icon="info" size="16px" *ngIf="type !== 'Task'"></ui-icon>
</div>
<div class="teaser" *ngIf="teaser$ | async; let teaser">
<img [isaBlobImage]="teaser" />
</div>
<div class="content">
<div>{{ info.text | stripHtmlTags }}</div>
<div class="task-date" *ngIf="showTaskDate">{{ taskDate }}</div>
</div>
</div>
<ng-container *ngIf="info?.link">
<hr />
<div class="link">
<div class="link-heading">Titelinformationen</div>
<hr />
<div class="link-item">
<ui-icon icon="vlbtix" size="19px" *ngIf="showVlbtixIcon$ | async"></ui-icon>
<div class="grow">Digitale Titelübersicht</div>
<button class="link-cta" type="button" (click)="openUrl(info.link)">
Öffnen
</button>
</div>
</div>
</ng-container>
<ng-container *ngIf="info?.attachments > 0 || info?.articles?.length > 0">
<hr />
<div class="attachments">
<div class="attachments-heading">Dateianhang</div>
<hr />
<ng-container *ngFor="let attachment of attachments$ | async">
<div class="attachments-item">
<ui-icon icon="attachment" size="19px"></ui-icon>
<div class="grow">
{{ attachment.name }}
</div>
<button class="attachments-cta" type="button" (click)="openFile(attachment)">
Öffnen
</button>
</div>
<hr />
</ng-container>
<ng-container *ngIf="info?.articles?.length > 0">
<div class="attachments-item">
<ui-icon icon="attachment" size="19px"></ui-icon>
<div class="grow">
Artikelliste
</div>
<button class="copy-article-cta" type="button" (click)="copyArticleList()">
EAN-Liste kopieren
</button>
<button class="open-article-cta" type="button" (click)="openArticleList()">
Öffnen
</button>
</div>
</ng-container>
</div>
</ng-container>
<ng-container *ngIf="showNotes$ | async">
<hr />
<div class="notes">
<div class="notes-heading">
<div>Notizen</div>
<div class="muted">{{ (notes$ | async)?.length }} Notizen</div>
</div>
<hr />
<ui-notes [notes]="notes$ | async" (add)="addNote($event)" [value]="noteValue" (valueChange)="setNoteValue($event)"></ui-notes>
</div>
</ng-container>
<hr *ngIf="(type$ | async) === 'Task'" />

View File

@@ -0,0 +1,107 @@
:host {
@apply flex flex-col;
}
.info-header {
@apply flex flex-row items-baseline;
h5 {
@apply font-bold m-0 text-lg;
}
.muted {
@apply ml-4 text-cool-grey text-base font-semibold;
}
}
hr {
@apply bg-disabled-branch h-px-2 -ml-4 my-4;
width: calc(100% + 2rem);
}
.info-body {
@apply flex flex-row items-center;
.time-span {
@apply text-lg font-bold mr-2 whitespace-nowrap;
}
.indicator {
@apply mx-2;
ui-icon {
@apply text-cool-grey;
}
}
.task-indicator {
@apply w-px-2 h-full;
}
.indicator.Task {
@apply mx-2 self-stretch;
}
.teaser {
@apply mx-2;
img {
@apply h-px-50;
}
}
.content {
@apply overflow-y-auto ml-2 font-semibold;
max-height: 19rem;
}
.time-frame {
@apply mt-3;
}
}
.attachments,
.notes,
.link {
@apply mt-3;
.attachments-heading,
.notes-heading,
.link-heading {
@apply flex flex-row items-center text-lg font-bold;
.muted {
@apply ml-4 text-cool-grey font-semibold;
}
}
.attachments-item,
.link-item {
@apply flex flex-row items-center text-lg;
ui-icon {
@apply mr-3 text-cool-grey;
}
.attachments-cta,
.copy-article-cta,
.open-article-cta,
.link-cta {
@apply bg-transparent justify-self-end border-none outline-none text-brand text-cta-l font-bold p-0;
}
.copy-article-cta {
@apply mr-7;
}
}
.link-item {
ui-icon {
@apply mt-1;
}
}
}
.grow {
@apply flex-grow;
}

View File

@@ -0,0 +1,177 @@
import { Clipboard } from '@angular/cdk/clipboard';
import { DatePipe } from '@angular/common';
import { Component, ChangeDetectionStrategy, Input, OnChanges, SimpleChanges, Optional } from '@angular/core';
import { DomainTaskCalendarService } from '@domain/task-calendar';
import { FileDTO } from '@swagger/checkout';
import { DisplayInfoDTO } from '@swagger/eis';
import { DateAdapter } from '@ui/common';
import { UiModalRef, UiModalService } from '@ui/modal';
import { isNullOrUndefined } from '@utils/common';
import { combineLatest, ReplaySubject, Subject } from 'rxjs';
import { catchError, filter, first, map, startWith, switchMap } from 'rxjs/operators';
import { ArticleListModalComponent } from '../../modals/article-list/article-list-modal.component';
import { ImageViewerModalComponent } from '../../modals/image-viewer/image-viewer-modal.component';
import { ImageViewerModalData } from '../../modals/image-viewer/image-viewer-modal.data';
import { PdfViewerModalComponent } from '../../modals/pdf-viewer/pdf-viewer-modal.component';
import { PdfViewerModalData } from '../../modals/pdf-viewer/pdf-viewer-modal.data';
import { WebViewerModalComponent } from '../../modals/web-viewer/web-viewer-modal.component';
@Component({
selector: 'page-task-info',
templateUrl: 'task-info.component.html',
styleUrls: ['task-info.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TaskInfoComponent implements OnChanges {
@Input()
info: DisplayInfoDTO;
@Input()
showTaskDate: boolean;
get dayOfWeek() {
if (this.checkSameDay()) {
return 'Heute';
}
return this.datePipe.transform(this.date, 'EEEE');
}
get date(): Date {
const taskDate = this.info.taskDate;
const publicationDate = this.info.publicationDate;
const announcementDate = this.info.announcementDate;
return this.domainTaskCalendarService.getDisplayInfoDate({ taskDate, publicationDate, announcementDate });
}
get timeSpan(): string {
if (this.info?.timeFrom && this.info?.timeTo) {
const dateFrom = new Date(this.info?.timeFrom);
const dateTo = new Date(this.info?.timeTo);
return `${this.datePipe.transform(dateFrom, 'HH:mm')} - ${this.datePipe.transform(dateTo, 'HH:mm')}`;
}
return 'Ganztägig';
}
get taskDate(): string {
if (this.info?.taskDate && this.info?.taskOverdueDate) {
const dateFrom = new Date(this.info?.taskDate);
const dateTo = new Date(this.info?.taskOverdueDate);
return `${this.datePipe.transform(dateFrom, 'dd.MM.yy HH:mm')} Uhr bis ${this.datePipe.transform(dateTo, 'dd.MM.yy HH:mm')} Uhr`;
}
}
get noteValue() {
return sessionStorage.getItem(`INFO_NOTE_${this.info?.id}`) || '';
}
private info$ = new ReplaySubject<DisplayInfoDTO>();
showVlbtixIcon$ = this.info$.pipe(map((info) => (info?.link ? new URL(info.link).hostname === 'www.vlbtix.de' : false)));
teaser$ = this.info$.pipe(
filter((info) => !isNullOrUndefined(info)),
switchMap((info) => this.domainTaskCalendarService.getTeaserFile({ infoId: info.id }).pipe(catchError((error) => [undefined])))
);
attachments$ = this.info$.pipe(
filter((info) => info.attachments > 0),
switchMap((info) => this.domainTaskCalendarService.getFiles({ infoId: info.id })),
map((response) => response.result)
);
type$ = this.info$.pipe(map((info) => this.domainTaskCalendarService.getInfoType(info)));
showNotes$ = this.type$.pipe(map((type) => type === 'Info' || type === 'Task'));
indicatorColor$ = this.info$.pipe(map((info) => this.domainTaskCalendarService.getProcessingStatusColorCode(info)));
private noteAdded$ = new Subject();
notes$ = combineLatest([this.info$, this.noteAdded$.pipe(startWith([undefined]))]).pipe(
switchMap(([info]) => this.domainTaskCalendarService.getComments({ infoId: info.id })),
map((res) =>
res.result.map((comment) => ({
date: new Date(comment.created),
note: comment.text,
}))
)
);
constructor(
private dateAdapter: DateAdapter,
private datePipe: DatePipe,
private domainTaskCalendarService: DomainTaskCalendarService,
private uiModal: UiModalService,
private clipboard: Clipboard,
@Optional() private modalRef: UiModalRef
) {}
ngOnChanges({ info }: SimpleChanges): void {
if (info) {
this.info$.next(this.info);
}
}
setNoteValue(note: string) {
sessionStorage.setItem(`INFO_NOTE_${this.info?.id}`, note);
}
async openFile(file: FileDTO) {
const content = await this.domainTaskCalendarService.getFile({ fileId: file.id }).toPromise();
// TODO: Logik um den richtigen Dialog zu öffnen
if (file.mime === 'application/pdf') {
this.uiModal.open({
content: PdfViewerModalComponent,
data: {
file,
content,
} as PdfViewerModalData,
});
} else if (['image/gif', 'image/jpeg', 'image/png', 'image/svg+xml'].includes(file.mime)) {
this.uiModal.open({
content: ImageViewerModalComponent,
data: {
file,
content,
} as ImageViewerModalData,
});
}
}
copyArticleList() {
const eanString = this.info.articles.map((article) => article.data.ean).join(',');
this.clipboard.copy(eanString);
}
checkSameDay(): boolean {
return this.dateAdapter.equals(this.date, this.dateAdapter.today());
}
async openArticleList() {
const articleListModal = this.uiModal.open({
content: ArticleListModalComponent,
data: this.info,
});
const result = await articleListModal.afterClosed$.pipe(first()).toPromise();
if (result.data === 'closeAll') {
this.modalRef?.close();
}
}
openUrl(url: string) {
this.uiModal.open({
content: WebViewerModalComponent,
data: url,
config: {
width: '1120px',
},
});
}
async addNote(note: string) {
await this.domainTaskCalendarService.addComment({ infoId: this.info.id, text: note }).toPromise();
sessionStorage.removeItem(`INFO_NOTE_${this.info?.id}`);
this.noteAdded$.next();
}
}

View File

@@ -0,0 +1,15 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { TaskInfoComponent } from './task-info.component';
import { UiCommonModule } from '@ui/common';
import { UiIconModule } from '@ui/icon';
import { ClipboardModule } from '@angular/cdk/clipboard';
import { UiNotesModule } from 'apps/ui/notes/src/lib/ui-notes.module';
@NgModule({
imports: [CommonModule, UiCommonModule, UiIconModule, ClipboardModule, UiNotesModule],
exports: [TaskInfoComponent],
declarations: [TaskInfoComponent],
})
export class TaskInfoModule {}

View File

@@ -1,5 +1,4 @@
// start:ng42.barrel
export * from './task-list-header.component';
export * from './task-list-item.component';
export * from './task-list.component';
export * from './task-list.module';

View File

@@ -0,0 +1,29 @@
import { Pipe, PipeTransform } from '@angular/core';
import { DomainTaskCalendarService, ProcessingStatusList } from '@domain/task-calendar';
import { DisplayInfoDTO } from '@swagger/eis';
@Pipe({
name: 'filterStatus',
})
export class FilterStatusPipe implements PipeTransform {
constructor(private domainTaskCalendarService: DomainTaskCalendarService) {}
transform(items: DisplayInfoDTO[], includes: ProcessingStatusList = [], excludes: ProcessingStatusList = []): DisplayInfoDTO[] {
return items?.filter((item) => {
const processingStatus = this.domainTaskCalendarService.getProcessingStatusList(item);
let isIncluded = false;
let isExcluded = false;
for (const include of includes) {
isIncluded = processingStatus.includes(include);
// Early Exit
if (!isIncluded) {
return false;
}
}
for (const exclude of excludes) {
isExcluded = !processingStatus.includes(exclude);
}
return isIncluded && !isExcluded;
});
}
}

View File

@@ -0,0 +1,18 @@
import { Pipe, PipeTransform } from '@angular/core';
import { DomainTaskCalendarService, InfoType } from '@domain/task-calendar';
import { DisplayInfoDTO } from '@swagger/eis';
import { isArray } from '@utils/common';
@Pipe({
name: 'filterType',
})
export class FilterTypePipe implements PipeTransform {
constructor(private domainTaskCalendarService: DomainTaskCalendarService) {}
transform(items: DisplayInfoDTO[], types: InfoType[]): any {
return items?.filter((item) => {
const type = this.domainTaskCalendarService.getInfoType(item);
return types.includes(type);
});
}
}

View File

@@ -0,0 +1,4 @@
// start:ng42.barrel
export * from './filter-status.pipe';
export * from './filter-type.pipe';
// end:ng42.barrel

View File

@@ -0,0 +1,32 @@
<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="indicator" *ngIf="isTask$ | 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" *ngIf="item?.effort" [effort]="item?.effort"></ui-tshirt>
<div class="task-content">
<div class="task-content-title">
<b>{{ item?.title }}</b>
<ui-icon class="icon" icon="camera" size="16px" *ngIf="item.requiresImageOnConfirmation"></ui-icon>
<ui-icon class="icon" icon="attachment" size="16px" *ngIf="hasAttachments$ | async"></ui-icon>
</div>
<div>{{ item?.text | stripHtmlTags | substr: 70 }}</div>
</div>

View File

@@ -0,0 +1,34 @@
:host {
@apply flex flex-row px-4 py-3;
.date {
@apply text-right font-bold mr-2 self-start w-24;
}
.indicator {
@apply w-px-2 bg-gray-100 self-stretch mx-4;
}
.icon {
@apply mx-2 self-start mt-px-3 text-inactive-branch flex flex-row;
ui-icon + ui-icon {
@apply ml-2;
}
}
.indicator + .icon {
@apply ml-0;
}
.shirt-size {
@apply mx-2 mt-px-3;
}
.task-content {
@apply flex flex-col ml-2;
.task-content-title {
@apply flex flex-row;
}
}
}

View File

@@ -0,0 +1,57 @@
import { Component, ChangeDetectionStrategy, Input, OnChanges, SimpleChanges, HostBinding } from '@angular/core';
import { DomainTaskCalendarService } from '@domain/task-calendar';
import { DisplayInfoDTO } from '@swagger/eis';
import { combineLatest, ReplaySubject } from 'rxjs';
import { catchError, map, shareReplay, switchMap } from 'rxjs/operators';
@Component({
selector: 'page-task-list-item',
templateUrl: 'task-list-item.component.html',
styleUrls: ['task-list-item.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TaskListItemComponent implements OnChanges {
@Input()
item: DisplayInfoDTO;
@Input()
showDate = true;
item$ = new ReplaySubject<DisplayInfoDTO>();
itemType$ = this.item$.pipe(
map((item) => this.domainTaskCalendarService.getInfoType(item)),
shareReplay()
);
isPreInfo$ = this.itemType$.pipe(map((type) => type === 'PreInfo'));
isTask$ = this.itemType$.pipe(map((type) => type === 'Task'));
isInfo$ = this.itemType$.pipe(map((type) => type === 'Info'));
isInfoOrPreInfo$ = combineLatest([this.isInfo$, this.isPreInfo$]).pipe(map(([info, preInfo]) => info || preInfo));
hasComments$ = this.item$.pipe(map((item) => item.comments > 0));
hasIcon$ = combineLatest([this.isInfoOrPreInfo$, this.hasComments$]).pipe(map(([infoOrPreInfo, comments]) => infoOrPreInfo || comments));
indicatorColor$ = this.item$.pipe(map((item) => this.domainTaskCalendarService.getProcessingStatusColorCode(item)));
hasAttachments$ = this.item$.pipe(map((info) => info.attachments > 0 || info.articles?.length > 0));
showTime$ = this.item$.pipe(map((item) => !!item?.timeFrom && !!item?.timeTo));
@HostBinding('class')
get completed() {
return this.domainTaskCalendarService.getProcessingStatusList(this.item);
}
constructor(private domainTaskCalendarService: DomainTaskCalendarService) {}
ngOnChanges({ item }: SimpleChanges): void {
if (item) {
this.item$.next(this.item);
}
}
}

View File

@@ -0,0 +1,32 @@
<!-- <div class="task-list overdue-list" *ngIf="items | filterType: ['Task'] | filterStatus: ['Overdue']; let overdieItems"> -->
<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>
<hr />
</ng-container>
</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' }}</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>
<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>

View File

@@ -0,0 +1,44 @@
:host {
@apply flex flex-col box-border;
}
.task-list {
@apply pt-6;
.head-row {
@apply flex flex-row justify-between items-baseline px-4 py-3;
.head-row-title {
@apply flex flex-row self-end;
.muted {
@apply ml-4 self-end;
}
}
.head-row-info {
@apply flex flex-col text-right;
.cta-print {
@apply border-none outline-none bg-transparent text-brand text-cta-l font-bold pr-0 mb-3;
}
}
h4 {
@apply m-0;
}
.muted {
@apply text-active-branch font-semibold text-sm;
}
}
}
.task-list.today-list ::ng-deep page-task-list-item.Completed {
.date,
.icon,
.shirt-size,
.task-content {
@apply opacity-30;
}
}

View File

@@ -0,0 +1,155 @@
import { DatePipe } from '@angular/common';
import {
Component,
ChangeDetectionStrategy,
Input,
EventEmitter,
Output,
OnChanges,
SimpleChanges,
ChangeDetectorRef,
} from '@angular/core';
import { DomainPrinterService } from '@domain/printer';
import { DomainTaskCalendarService, ProcessingStatusList } from '@domain/task-calendar';
import { PrintModalComponent, PrintModalData } from '@modal/printer';
import { DisplayInfoDTO } from '@swagger/eis';
import { DateAdapter } from '@ui/common';
import { UiModalService } from '@ui/modal';
import { BehaviorSubject, combineLatest } from 'rxjs';
import { first, map, tap } from 'rxjs/operators';
@Component({
selector: 'page-task-list',
templateUrl: 'task-list.component.html',
styleUrls: ['task-list.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TaskListComponent {
today = this.dateAdapter.today();
/* @internal */
items$ = new BehaviorSubject<DisplayInfoDTO[]>([]);
@Input()
get items() {
return this.items$.value;
}
set items(value) {
this.items$.next(value);
}
/* @internal */
selected$ = new BehaviorSubject<Date>(new Date());
@Input()
get selected() {
return this.selected$.value;
}
set selected(value) {
if (this.selected !== value) {
this.selected$.next(value);
}
}
overdueItems$ = this.items$.pipe(
map((items) => {
const last7Days = this.dateAdapter.addCalendarDays(this.today, -7);
items.filter((item) => {
const type = this.domainTaskCalendarService.getInfoType(item);
const processingStatus = this.domainTaskCalendarService.getProcessingStatusList(item);
if (type === 'Task' && processingStatus.includes('Overdue')) {
return new Date(item.taskDate || item.publicationDate) >= last7Days;
}
return false;
});
})
);
selectedItems$ = combineLatest([this.items$, this.selected$]).pipe(
map(([items, date]) =>
items.filter(
(item) => (item.taskDate || item.publicationDate) && this.dateAdapter.equals(new Date(item.taskDate || item.publicationDate), date)
)
),
// Sortierung der aufgaben nach Rot => Gelb => Grau => Grün
map((list) => this.sort(list, ['Overdue', 'InProcess', 'Approved', 'Completed']))
);
@Output()
select = new EventEmitter<DisplayInfoDTO>();
constructor(
private dateAdapter: DateAdapter,
private datePipe: DatePipe,
private domainTaskCalendarService: DomainTaskCalendarService,
private uiModal: UiModalService,
private domainPrinterService: DomainPrinterService
) {}
/**
* 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];
for (const status of [...order].reverse()) {
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;
}
}
return 0;
} else if (aHasStatus && !bHasStatus) {
return -1;
} else if (!aHasStatus && bHasStatus) {
return 1;
}
return 0;
});
}
return result;
}
async print() {
const displayInfos = await this.selectedItems$.pipe(first()).toPromise();
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(this.selected, 'EEEE, dd. MMMM yyyy')}`,
})
.toPromise(),
} as PrintModalData,
});
}
}

View File

@@ -0,0 +1,16 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { TaskListComponent } from './task-list.component';
import { TaskListItemComponent } from './task-list-item.component';
import { FilterStatusPipe, FilterTypePipe } from './pipes';
import { UiCommonModule } from '@ui/common';
import { UiTshirtModule } from '@ui/tshirt';
import { UiIconModule } from '@ui/icon';
@NgModule({
imports: [CommonModule, UiCommonModule, UiTshirtModule, UiIconModule],
exports: [TaskListComponent],
declarations: [TaskListComponent, TaskListItemComponent, FilterStatusPipe, FilterTypePipe],
})
export class TaskListModule {}

View File

@@ -0,0 +1,17 @@
<ui-selected-filter-options
*ngIf="!!selectedFilters"
[(value)]="selectedFilters"
[initialFilter]="initialFilter"
(valueChange)="updateFilter($event)"
>
</ui-selected-filter-options>
<ui-filter-group *ngIf="!!selectedFilters" [(value)]="selectedFilters" (valueChange)="updateFilter($event)"></ui-filter-group>
<div class="sticky-cta-wrapper">
<button class="apply-filter" (click)="applyFilters()">
<span>
Filter anwenden
</span>
</button>
</div>

View File

@@ -0,0 +1,39 @@
:host {
@apply flex flex-col box-border w-full;
ui-filter-group ::ng-deep .select-filter-options {
overflow: scroll;
max-height: 400px;
}
}
.sticky-cta-wrapper {
@apply fixed text-center inset-x-0 bottom-0;
bottom: 30px;
}
button.apply-filter {
@apply border-none bg-brand text-white rounded-full py-cta-y-l px-cta-x-l text-cta-l font-bold;
min-width: 201px;
}
button.apply-filter:disabled {
@apply bg-inactive-customer;
}
button.apply-filter.loading {
padding-top: 15px;
padding-bottom: 17px;
}
ui-selected-filter-options {
@apply my-px-8;
}
ui-icon {
@apply inline-flex;
}
.spin {
@apply animate-spin;
}

View File

@@ -0,0 +1,48 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, OnDestroy, OnInit, Output } from '@angular/core';
import { Filter } from '@ui/filter';
import { first } from 'rxjs/operators';
import { TaskCalendarStore } from '../../task-calendar.store';
@Component({
selector: 'page-task-calendar-filter',
templateUrl: 'task-calendar-filter.component.html',
styleUrls: ['task-calendar-filter.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TaskCalendarFilterComponent implements OnInit, OnDestroy {
@Output() exitFilter = new EventEmitter<void>();
selectedFilters: Filter[];
initialFilter: Filter[];
updatedFilter: Filter[];
readonly initialFilters$ = this.taskCalendarStore.selectInitialFilter;
readonly filters$ = this.taskCalendarStore.selectFilter;
readonly displayInfos$ = this.taskCalendarStore.selectDisplayInfos;
constructor(private cdr: ChangeDetectorRef, private taskCalendarStore: TaskCalendarStore) {}
async ngOnInit() {
this.initialFilter = await this.initialFilters$.pipe(first()).toPromise();
const filters = await this.filters$.pipe(first()).toPromise();
this.selectedFilters = filters;
this.updatedFilter = filters;
this.cdr.markForCheck();
}
ngOnDestroy() {
const filters = this.updatedFilter;
this.taskCalendarStore.setFilter({ filters });
}
applyFilters() {
this.taskCalendarStore.loadItems();
this.updatedFilter = this.selectedFilters;
this.exitFilter.emit();
}
updateFilter(filters: Filter[]) {
this.taskCalendarStore.setFilter({ filters });
this.selectedFilters = filters;
this.cdr.markForCheck();
}
}

View File

@@ -0,0 +1,12 @@
<h1>{{ article?.ean }}</h1>
<div class="details">
<img class="details-image" [src]="article?.ean | productImage: 150:150:true" alt="product-image" />
<div class="details-text">
<p>
{{ article?.text }}
</p>
<button (click)="closeDetails.emit()" class="btn-cta-less" type="button">
<ui-icon rotate="180deg" icon="arrow" size="16px"></ui-icon> Weniger
</button>
</div>
</div>

View File

@@ -0,0 +1,33 @@
:host {
@apply flex flex-col relative mb-8;
}
h1 {
@apply text-xl font-bold text-center mb-3;
}
.details {
@apply flex flex-row;
.details-image {
@apply rounded-customerCard mt-5;
width: 58px;
height: 95px;
}
.details-text {
@apply flex flex-col;
p {
@apply px-5 py-0;
}
.btn-cta-less {
@apply border-none bg-transparent outline-none text-regular text-ucla-blue font-bold px-5 self-start;
ui-icon {
@apply inline mx-1 align-middle;
}
}
}
}

View File

@@ -0,0 +1,17 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { ArticleDTO } from '@swagger/eis';
@Component({
selector: 'page-article-list-details',
templateUrl: 'article-list-details.component.html',
styleUrls: ['article-list-details.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ArticleListDetailsComponent implements OnInit {
@Input() article: ArticleDTO;
@Output() closeDetails = new EventEmitter<void>();
constructor() {}
ngOnInit() {}
}

View File

@@ -0,0 +1,38 @@
<button class="close" type="button"><ui-icon icon="close" size="16px" (click)="close()"></ui-icon></button>
<ng-container *ngIf="!expandedArticle">
<h1>Artikelliste</h1>
<div class="slider-wrapper">
<button class="cta-slider-prev" (click)="prev()" *ngIf="!!showSliderButtons">
<ui-icon class="slider-icon" rotate="180deg" icon="arrow" size="15px"></ui-icon>
</button>
<div #sliderContainer class="articles">
<div class="article" *ngFor="let article of articles$ | async">
<img [src]="article?.ean | productImage: 190:310:true" alt="product-image" />
<h2>{{ article?.ean }}</h2>
<p>
{{ article?.text | trim: 50 }}
<button (click)="expandedArticle = article" *ngIf="article?.text?.length > 53" class="btn-cta-more" type="button">
Mehr <ui-icon icon="arrow" size="16px"></ui-icon>
</button>
</p>
</div>
</div>
<button class="cta-slider-next" (click)="next()" *ngIf="!!showSliderButtons">
<ui-icon class="slider-icon" icon="arrow" size="15px"></ui-icon>
</button>
</div>
<div class="actions">
<button (click)="print()" class="btn-cta-secondary">
Drucken
</button>
<button (click)="eanCopy()" class="btn-cta-secondary">
EAN-Liste kopieren
</button>
<button (click)="articleSearch()" class="btn-cta">
Liste in Artikelsuche anzeigen
</button>
</div>
</ng-container>
<ng-container *ngIf="!!expandedArticle">
<page-article-list-details (closeDetails)="expandedArticle = undefined" [article]="expandedArticle"></page-article-list-details>
</ng-container>

View File

@@ -0,0 +1,80 @@
:host {
@apply flex flex-col relative;
}
h1 {
@apply text-xl font-bold text-center mb-7;
}
.slider-wrapper {
@apply relative flex flex-row items-center;
.cta-slider-next,
.cta-slider-prev {
@apply outline-none border-none bg-white rounded-card opacity-70;
height: 310px;
width: 55px;
box-shadow: 3px 0 14px 8px white;
}
.slider-icon {
@apply items-center justify-center;
width: 40px;
height: 40px;
border-radius: 50%;
background-color: #e9f0f8;
color: #0f1542;
}
}
.articles {
@apply flex flex-row overflow-scroll;
scroll-behavior: smooth;
width: calc(100% + 1rem);
.article {
@apply flex flex-col mr-5;
width: 190px;
img {
@apply rounded-customerCard;
width: 190px;
height: 310px;
}
h2 {
@apply text-lg font-bold m-0 mt-4 mb-2;
}
p {
@apply m-0 break-all;
}
.btn-cta-more {
@apply border-none bg-transparent outline-none text-regular text-ucla-blue font-bold p-0;
ui-icon {
@apply inline mx-1 align-middle;
}
}
}
}
.actions {
@apply flex flex-row justify-center p-8;
.btn-cta {
@apply bg-brand text-white font-bold text-lg outline-none border-none rounded-full px-6 py-3;
}
.btn-cta-secondary {
@apply text-brand border-2 border-none bg-white font-bold text-lg px-4 py-2 rounded-full mr-2;
}
}
.close {
@apply absolute top-0 right-0 outline-none border-none bg-transparent;
z-index: 10;
ui-icon {
@apply text-inactive-branch;
}
}

View File

@@ -0,0 +1,80 @@
import { Clipboard } from '@angular/cdk/clipboard';
import { ChangeDetectionStrategy, Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { Router } from '@angular/router';
import { DomainPrinterService } from '@domain/printer';
import { DomainTaskCalendarService } from '@domain/task-calendar';
import { PrintModalComponent, PrintModalData } from '@modal/printer';
import { ModuleSwitcherService } from '@sales/core-services';
import { ArticleDTO, DisplayInfoDTO } from '@swagger/eis';
import { UiModalRef, UiModalService } from '@ui/modal';
import { NativeContainerService } from 'native-container';
import { first, map } from 'rxjs/operators';
@Component({
selector: 'page-article-list-modal',
templateUrl: 'article-list-modal.component.html',
styleUrls: ['article-list-modal.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ArticleListModalComponent implements OnInit {
@ViewChild('sliderContainer', { static: false }) sliderContainer: ElementRef<HTMLDivElement>;
articles$ = this.domainTaskCalendarService.getArticles({ infoId: this.modalRef.data.id }).pipe(map((response) => response.result));
expandedArticle: ArticleDTO;
showSliderButtons: boolean;
constructor(
private modalRef: UiModalRef<any, DisplayInfoDTO>,
private router: Router,
private moduleSwitcherService: ModuleSwitcherService,
private clipboard: Clipboard,
private nativeContainer: NativeContainerService,
private domainTaskCalendarService: DomainTaskCalendarService,
private domainPrinterService: DomainPrinterService,
private uiModal: UiModalService
) {}
ngOnInit() {
if (!this.nativeContainer.isUiWebview()?.isNative) {
this.showSliderButtons = true;
}
}
close() {
this.modalRef.close();
}
async print() {
const articles = await this.articles$.pipe(first()).toPromise();
this.uiModal.open({
content: PrintModalComponent,
data: {
printerType: 'Office',
print: (printer) =>
this.domainPrinterService.printDisplayInfoDTOArticles({ articles, printer, title: `${this.modalRef.data.title}` }).toPromise(),
} as PrintModalData,
});
}
async eanCopy() {
const articles = await this.articles$.toPromise();
const eanString = articles.map((article: ArticleDTO) => article.ean).join(',');
this.clipboard.copy(eanString);
}
prev() {
this.sliderContainer.nativeElement.scrollLeft -= 190;
}
next() {
this.sliderContainer.nativeElement.scrollLeft += 190;
}
async articleSearch() {
const articles = await this.articles$.toPromise();
const taskCalendarSearch: string = articles.map((article: ArticleDTO) => article.ean).join(';');
this.modalRef.close('closeAll');
this.moduleSwitcherService.switchToCustomer();
this.router.navigate(['/product', 'search'], { queryParams: { taskCalendarSearch } });
}
}

View File

@@ -0,0 +1,16 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { UiIconModule } from '@ui/icon';
import { ProductImageModule } from 'apps/cdn/product-image/src/public-api';
import { ArticleListDetailsComponent } from './article-list-details/article-list-details.component';
import { ArticleListModalComponent } from './article-list-modal.component';
import { TrimPipe } from './pipes/trim.pipe';
import { ClipboardModule } from '@angular/cdk/clipboard';
@NgModule({
imports: [CommonModule, UiIconModule, ProductImageModule, ClipboardModule],
exports: [ArticleListModalComponent],
declarations: [ArticleListModalComponent, ArticleListDetailsComponent, TrimPipe],
providers: [],
})
export class ArticleListModalModule {}

View File

@@ -0,0 +1,14 @@
import { Pipe, PipeTransform } from '@angular/core';
import { isString } from '@utils/common';
@Pipe({
name: 'trim',
})
export class TrimPipe implements PipeTransform {
transform(value: string, length: number = 15): any {
if (isString(value) && value.length > length + 3) {
return value.substr(0, length - 1) + '...';
}
return value;
}
}

View File

@@ -0,0 +1,9 @@
<div class="picture-wrapper">
<img class="picture" [src]="image" />
</div>
<div class="actions">
<button class="cancel-btn" (click)="close()">Abbrechen</button>
<button class="repeat-btn" (click)="repeat()">Wiederholen</button>
<button class="send-btn" (click)="send()">Absenden</button>
</div>

View File

@@ -0,0 +1,21 @@
.picture-wrapper {
@apply flex flex-row justify-center;
.picture {
@apply w-auto h-auto max-w-full;
max-height: calc(100vh - 300px);
}
}
.actions {
@apply flex flex-row justify-center mt-4;
.send-btn {
@apply bg-brand text-white font-bold text-lg outline-none border-none rounded-full px-6 py-3 ml-2;
}
.cancel-btn,
.repeat-btn {
@apply text-brand border-none outline-none bg-white font-bold text-lg px-4 py-2 rounded-full ml-2;
}
}

View File

@@ -0,0 +1,28 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Output } from '@angular/core';
import { UiModalRef } from '@ui/modal';
@Component({
selector: 'camera-capture-modal',
templateUrl: 'camera-capture-modal.component.html',
styleUrls: ['camera-capture-modal.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CameraCaptureModalComponent {
image: string;
constructor(private modalRef: UiModalRef<string>) {
this.image = this.modalRef.data;
}
close() {
this.modalRef.close();
}
repeat() {
this.modalRef.close('');
}
send() {
this.modalRef.close(this.image);
}
}

View File

@@ -0,0 +1,10 @@
import { NgModule } from '@angular/core';
import { CameraCaptureModalComponent } from './camera-capture-modal.component';
@NgModule({
imports: [],
exports: [CameraCaptureModalComponent],
declarations: [CameraCaptureModalComponent],
})
export class CameraCaptureModalModule {}

View File

@@ -0,0 +1,5 @@
<button class="close" type="button"><ui-icon icon="close" size="16px" (click)="close()"></ui-icon></button>
<h1>{{ file?.name }}</h1>
<img alt="file?.name" [isaBlobImage]="content" />

View File

@@ -0,0 +1,19 @@
:host {
@apply flex flex-col relative;
}
h1 {
@apply my-6 text-card-heading text-center;
}
img {
@apply max-w-full;
}
button.close {
@apply absolute top-0 right-0 outline-none border-none bg-transparent;
ui-icon {
@apply text-inactive-branch;
}
}

View File

@@ -0,0 +1,25 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { FileDTO } from '@swagger/eis';
import { UiModalRef } from '@ui/modal';
import { ImageViewerModalData } from './image-viewer-modal.data';
@Component({
selector: 'page-image-viewer-modal',
templateUrl: 'image-viewer-modal.component.html',
styleUrls: ['image-viewer-modal.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ImageViewerModalComponent {
file: FileDTO;
content: Blob;
constructor(private modalRef: UiModalRef<ImageViewerModalData>) {
this.content = this.modalRef.data.content;
this.file = this.modalRef.data.file;
}
close() {
this.modalRef.close();
}
}

View File

@@ -0,0 +1,6 @@
import { FileDTO } from '@swagger/checkout';
export interface ImageViewerModalData {
file: FileDTO;
content: Blob;
}

View File

@@ -0,0 +1,13 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ImageViewerModalComponent } from './image-viewer-modal.component';
import { UiCommonModule } from '@ui/common';
import { UiIconModule } from '@ui/icon';
@NgModule({
imports: [CommonModule, UiCommonModule, UiIconModule],
exports: [ImageViewerModalComponent],
declarations: [ImageViewerModalComponent],
})
export class ImageViewerModalModule {}

View File

@@ -0,0 +1,3 @@
<h1>{{ info?.title }}</h1>
<page-task-info [info]="info"></page-task-info>
<button type="button"><ui-icon icon="close" size="16px" (click)="close()"></ui-icon></button>

View File

@@ -0,0 +1,15 @@
:host {
@apply flex flex-col relative;
}
h1 {
@apply text-xl font-bold text-center;
}
button {
@apply absolute top-0 right-0 outline-none border-none bg-transparent;
ui-icon {
@apply text-inactive-branch;
}
}

View File

@@ -0,0 +1,22 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { DisplayInfoDTO } from '@swagger/eis';
import { UiModalRef } from '@ui/modal';
import { Subscription } from 'rxjs';
@Component({
selector: 'page-info-modal',
templateUrl: 'info-modal.component.html',
styleUrls: ['info-modal.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class InfoModalComponent {
info: DisplayInfoDTO;
constructor(private modalRef: UiModalRef<DisplayInfoDTO>) {
this.info = this.modalRef.data;
}
close() {
this.modalRef.close();
}
}

View File

@@ -0,0 +1,13 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { InfoModalComponent } from './info-modal.component';
import { TaskInfoModule } from '../../components/task-info/task-info.module';
import { UiIconModule } from '@ui/icon';
@NgModule({
imports: [CommonModule, TaskInfoModule, UiIconModule],
exports: [InfoModalComponent],
declarations: [InfoModalComponent],
})
export class InfoModalModule {}

View File

@@ -0,0 +1,33 @@
import { NgModule } from '@angular/core';
import { ImageViewerModalModule } from './image-viewer/image-viewer-modal.module';
import { CameraCaptureModalModule } from './camera/camera-capture-modal.module';
import { InfoModalModule } from './info/info-modal.module';
import { PdfViewerModalModule } from './pdf-viewer/pdf-viewer-modal.module';
import { PreInfoModalModule } from './preinfo/preinfo-modal.module';
import { TaskModalModule } from './task/task-modal.module';
import { ArticleListModalModule } from './article-list/article-list-modal.module';
import { WebViewerModalModule } from './web-viewer/web-viewer-modal.module';
@NgModule({
imports: [
InfoModalModule,
PreInfoModalModule,
TaskModalModule,
PdfViewerModalModule,
ImageViewerModalModule,
CameraCaptureModalModule,
ArticleListModalModule,
WebViewerModalModule,
],
exports: [
InfoModalModule,
PreInfoModalModule,
TaskModalModule,
PdfViewerModalModule,
ImageViewerModalModule,
CameraCaptureModalModule,
ArticleListModalModule,
WebViewerModalModule,
],
})
export class ModalsModule {}

View File

@@ -0,0 +1,19 @@
<button class="close" type="button"><ui-icon icon="close" size="16px" (click)="close()"></ui-icon></button>
<h1>{{ file?.name }}</h1>
<h5>Seite {{ page }} / {{ totalPages }}</h5>
<pdf-viewer
[src]="objectURL"
[render-text]="true"
[(page)]="page"
(after-load-complete)="callBackFn($event)"
zoom-scale="page-height"
></pdf-viewer>
<div class="cta-wrapper">
<button (click)="print()" class="cta-print" type="button">
Drucken
</button>
</div>

View File

@@ -0,0 +1,35 @@
:host {
@apply grid grid-cols-1 relative;
max-height: calc(100vh - 4rem);
}
h1 {
@apply my-6 text-card-heading text-center;
}
h5 {
@apply mt-0 mb-6 text-lg text-center text-inactive-branch font-normal;
}
pdf-viewer {
@apply flex-grow overflow-y-auto max-h-full;
::ng-deep .canvasWrapper {
@apply border-2 border-gray-200 border-solid rounded-card;
}
}
button.close {
@apply absolute top-0 right-0 outline-none border-none bg-transparent;
ui-icon {
@apply text-inactive-branch;
}
}
.cta-wrapper {
@apply absolute bottom-6 left-0 right-0 text-center;
button.cta-print {
@apply bg-brand text-xl px-6 py-3 text-white rounded-full border-none outline-none font-bold shadow-cta;
}
}

View File

@@ -0,0 +1,63 @@
import { Component, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
import { DomainPrinterService } from '@domain/printer';
import { DomainTaskCalendarService } from '@domain/task-calendar';
import { PrintModalComponent, PrintModalData } from '@modal/printer';
import { FileDTO } from '@swagger/eis';
import { UiModalRef, UiModalService } from '@ui/modal';
import { PDFDocumentProxy } from 'ng2-pdf-viewer';
import { PdfViewerModalData } from './pdf-viewer-modal.data';
@Component({
selector: 'page-pdf-viewer-modal',
templateUrl: 'pdf-viewer-modal.component.html',
styleUrls: ['pdf-viewer-modal.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PdfViewerModalComponent {
objectURL: string;
file: FileDTO;
page = 1;
totalPages = 1;
constructor(
private modalRef: UiModalRef<any, PdfViewerModalData>,
private cdr: ChangeDetectorRef,
private uiModal: UiModalService,
private taskCalendarService: DomainTaskCalendarService,
private domainPrinterService: DomainPrinterService
) {
this.objectURL = URL.createObjectURL(this.modalRef.data.content);
this.file = this.modalRef.data.file;
}
close() {
this.modalRef.close();
}
callBackFn(pdf: PDFDocumentProxy) {
this.totalPages = pdf.numPages;
this.cdr.markForCheck();
}
print() {
const blob = this.modalRef.data.content;
const reader = new FileReader();
reader.readAsDataURL(blob);
reader.onloadend = () => {
const base64data = reader.result as string;
this.uiModal.open({
content: PrintModalComponent,
data: {
printerType: 'Office',
print: (printer) =>
this.domainPrinterService
.printPdf({ printer, data: this.taskCalendarService.removeMetaDataFromBase64(base64data) })
.toPromise(),
} as PrintModalData,
});
};
}
}

View File

@@ -0,0 +1,6 @@
import { FileDTO } from '@swagger/checkout';
export interface PdfViewerModalData {
file: FileDTO;
content: Blob;
}

View File

@@ -0,0 +1,13 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { PdfViewerModule } from 'ng2-pdf-viewer';
import { PdfViewerModalComponent } from './pdf-viewer-modal.component';
import { UiIconModule } from '@ui/icon';
@NgModule({
imports: [CommonModule, PdfViewerModule, UiIconModule],
exports: [PdfViewerModalComponent],
declarations: [PdfViewerModalComponent],
})
export class PdfViewerModalModule {}

View File

@@ -0,0 +1,4 @@
<h3>Vorabinfo</h3>
<h1>{{ info?.title }}</h1>
<page-task-info [info]="info" showTaskDate="true"></page-task-info>
<button type="button"><ui-icon icon="close" size="16px" (click)="close()"></ui-icon></button>

View File

@@ -0,0 +1,19 @@
:host {
@apply flex flex-col relative;
}
h1 {
@apply text-xl font-bold text-center;
}
h3 {
@apply absolute m-0 top-0 left-0 text-regular text-active-branch uppercase;
}
button {
@apply absolute top-0 right-0 outline-none border-none bg-transparent;
ui-icon {
@apply text-inactive-branch;
}
}

View File

@@ -0,0 +1,21 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { DisplayInfoDTO } from '@swagger/eis';
import { UiModalRef } from '@ui/modal';
@Component({
selector: 'page-preinfo-modal',
templateUrl: 'preinfo-modal.component.html',
styleUrls: ['preinfo-modal.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PreInfoModalComponent {
info: DisplayInfoDTO;
constructor(private modalRef: UiModalRef<DisplayInfoDTO>) {
this.info = this.modalRef.data;
}
close() {
this.modalRef.close();
}
}

View File

@@ -0,0 +1,13 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { UiIconModule } from '@ui/icon';
import { TaskInfoModule } from '../../components/task-info/task-info.module';
import { PreInfoModalComponent } from './preinfo-modal.component';
@NgModule({
imports: [CommonModule, TaskInfoModule, UiIconModule],
exports: [PreInfoModalComponent],
declarations: [PreInfoModalComponent],
})
export class PreInfoModalModule {}

View File

@@ -0,0 +1,40 @@
<button class="btn-close" type="button"><ui-icon icon="close" size="16px" (click)="close()"></ui-icon></button>
<div class="header">
<ui-tshirt *ngIf="info?.effort" [effort]="info?.effort"></ui-tshirt>
<h1>{{ info?.title }}</h1>
</div>
<div class="status">
<div class="indicator" [style.backgroundColor]="indicatorColor$ | async"></div>
<span class="status-text">{{ status$ | async }}</span>
</div>
<div class="body">
<page-task-info [info]="info"></page-task-info>
</div>
<div class="camera-preview" *ngIf="cameraPreview$ | async; let cameraPreview">
<img [isaBlobImage]="cameraPreview" />
</div>
<div class="actions">
<button *ngIf="showStartEditCta$ | async" class="btn-cta" type="button" (click)="startEdit()">Jetzt bearbeiten</button>
<button *ngIf="showCompleteEditCta$ | async" class="btn-cta" type="button" (click)="completeEdit()">
Bearbeitung abschließen
</button>
<div *ngIf="showCameraCta$ | async">
<ng-container>
<!-- *ngIf="isIpad" -->
<div class="camera-hint">Bitte fotografieren Sie die Präsentation, um die<br />Aufgabe abschließen zu können</div>
<input uiCapture #captureInput id="file-input" capture="camera" type="file" (fileChanged)="cameraCapture($event)" />
<label class="btn-camera" for="file-input">Kamera öffnen</label>
</ng-container>
<!-- <div class="camera-hint" *ngIf="!isIpad">
Bitte auf dem iPad fortfahren
</div> -->
</div>
<ng-container *ngIf="showResetEditCta$ | async">
<button class="btn-cta-secondary" type="button" (click)="resetEdit()">Erneut bearbeiten</button>
<button class="btn-cta" type="button" (click)="close()">Fenster schließen</button>
</ng-container>
</div>

View File

@@ -0,0 +1,63 @@
:host {
@apply flex flex-col relative;
}
.header {
@apply flex flex-row justify-center items-center mt-3;
h1 {
@apply text-xl font-bold text-center m-0 ml-4;
}
}
.status {
@apply flex flex-row justify-center items-center mt-px-10 mb-4;
.indicator {
@apply h-px-10 w-px-10 rounded-full bg-gray-50;
}
.status-text {
@apply text-lg font-bold ml-2;
}
}
.camera-preview {
@apply flex flex-row justify-center;
img {
height: 200px;
}
}
.btn-close {
@apply absolute top-0 right-0 outline-none border-none bg-transparent;
ui-icon {
@apply text-inactive-branch;
}
}
.actions {
@apply text-center mb-5 mt-6 sticky bottom-1;
.btn-cta {
@apply bg-brand text-white font-bold text-lg outline-none border-none rounded-full px-6 py-3;
}
.btn-cta-secondary {
@apply text-brand border-2 border-solid border-brand bg-white font-bold text-lg px-4 py-2 rounded-full mr-2;
}
#file-input {
@apply hidden;
}
.btn-camera {
@apply inline-block bg-brand text-white font-bold text-lg outline-none border-none rounded-full px-6 py-3 cursor-pointer;
}
.camera-hint {
@apply text-inactive-branch mb-4;
}
}

View File

@@ -0,0 +1,119 @@
import { ChangeDetectionStrategy, Component, ElementRef, ViewChild } from '@angular/core';
import { DomainTaskCalendarService } from '@domain/task-calendar';
import { DisplayInfoDTO } from '@swagger/eis';
import { UiModalRef, UiModalService } from '@ui/modal';
import { isNullOrUndefined } from '@utils/common';
import { NativeContainerService } from 'native-container';
import { combineLatest, ReplaySubject } from 'rxjs';
import { map, shareReplay, switchMap } from 'rxjs/operators';
import { CameraCaptureModalComponent } from '../camera/camera-capture-modal.component';
@Component({
selector: 'page-task-modal',
templateUrl: 'task-modal.component.html',
styleUrls: ['task-modal.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TaskModalComponent {
@ViewChild('captureInput')
captureInput: ElementRef<HTMLInputElement>;
info: DisplayInfoDTO;
isIpad = this.nativeContainer.isUiWebview()?.isNative;
info$ = new ReplaySubject<DisplayInfoDTO>();
processingStatus$ = this.info$.pipe(
map((info) => this.domainTaskCalendarService.getProcessingStatusList(info)),
shareReplay()
);
status$ = this.processingStatus$.pipe(
map((processingStatus) => {
if (processingStatus.includes('InProcess')) {
return 'In Bearbeitung';
}
if (processingStatus.includes('Completed')) {
return 'Erledigt';
}
return 'Offen';
})
);
indicatorColor$ = this.info$.pipe(map((info) => this.domainTaskCalendarService.getProcessingStatusColorCode(info)));
cameraPreview$ = this.info$.pipe(
switchMap((info) =>
this.domainTaskCalendarService.getConfirmationFiles({ infoId: info.id }).pipe(map((response) => response.result[0]))
),
switchMap((file) => (!isNullOrUndefined(file?.id) ? this.domainTaskCalendarService.getFile({ fileId: file.id }) : [undefined]))
);
showStartEditCta$ = this.processingStatus$.pipe(
map((processingStatus) => !processingStatus.includes('InProcess') && !processingStatus.includes('Completed'))
);
showCompleteEditCta$ = combineLatest([this.processingStatus$, this.info$]).pipe(
map(([processingStatus, info]) => processingStatus.includes('InProcess') && !info.requiresImageOnConfirmation)
);
showCameraCta$ = combineLatest([this.processingStatus$, this.info$]).pipe(
map(([processingStatus, info]) => processingStatus.includes('InProcess') && info.requiresImageOnConfirmation)
);
showResetEditCta$ = this.processingStatus$.pipe(map((processingStatus) => processingStatus.includes('Completed')));
constructor(
private modalRef: UiModalRef<DisplayInfoDTO>,
private domainTaskCalendarService: DomainTaskCalendarService,
private uiModal: UiModalService,
private nativeContainer: NativeContainerService
) {
this.info = this.modalRef.data;
this.info$.next(this.info);
}
close() {
this.modalRef.close();
}
async startEdit() {
await this.domainTaskCalendarService.setToEdit({ infoId: this.info.id }).toPromise();
this.reloadInfo();
}
async completeEdit() {
await this.domainTaskCalendarService.complete({ infoId: this.info.id }).toPromise();
this.close();
}
async resetEdit() {
await this.domainTaskCalendarService.resetConfirmation({ infoId: this.info.id }).toPromise();
this.reloadInfo();
}
reloadInfo() {
this.domainTaskCalendarService
.getInfoById({ infoId: this.info.id })
.pipe(map((response) => response.result))
.subscribe((info) => {
this.info$.next(info);
this.info = info;
});
}
cameraCapture(image: string) {
const modalRef = this.uiModal.open({
content: CameraCaptureModalComponent,
data: image,
});
modalRef.afterClosed$.subscribe(async (result) => {
if (result.data === '') {
this.captureInput?.nativeElement?.click();
} else if (result.data?.length > 0) {
await this.domainTaskCalendarService.complete({ infoId: this.info.id, file: result.data }).toPromise();
this.close();
}
});
}
}

View File

@@ -0,0 +1,15 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { UiCommonModule } from '@ui/common';
import { UiIconModule } from '@ui/icon';
import { UiInputModule } from '@ui/input';
import { UiTshirtModule } from '@ui/tshirt';
import { TaskInfoModule } from '../../components/task-info/task-info.module';
import { TaskModalComponent } from './task-modal.component';
@NgModule({
imports: [CommonModule, UiTshirtModule, UiIconModule, TaskInfoModule, UiInputModule, UiCommonModule],
exports: [TaskModalComponent],
declarations: [TaskModalComponent],
})
export class TaskModalModule {}

View File

@@ -0,0 +1,3 @@
<button class="close" type="button"><ui-icon icon="close" size="16px" (click)="close()"></ui-icon></button>
<h1>Digitale Titelvorschau</h1>
<iframe [src]="src"></iframe>

View File

@@ -0,0 +1,20 @@
:host {
@apply flex flex-col relative;
}
iframe {
@apply w-full;
height: calc(100vh - 8rem);
}
h1 {
@apply my-6 text-card-heading text-center;
}
button.close {
@apply absolute top-0 right-0 outline-none border-none bg-transparent;
ui-icon {
@apply text-inactive-branch;
}
}

View File

@@ -0,0 +1,20 @@
import { Component } from '@angular/core';
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
import { UiModalRef } from '@ui/modal';
@Component({
selector: 'page-web-viewer-modal',
templateUrl: 'web-viewer-modal.component.html',
styleUrls: ['./web-viewer-modal.component.scss'],
})
export class WebViewerModalComponent {
src: SafeUrl;
constructor(private sanitizer: DomSanitizer, private modalRef: UiModalRef<string>) {
this.src = this.sanitizer.bypassSecurityTrustResourceUrl(this.modalRef.data);
}
close() {
this.modalRef.close();
}
}

View File

@@ -0,0 +1,11 @@
import { NgModule } from '@angular/core';
import { UiIconModule } from '@ui/icon';
import { WebViewerModalComponent } from './web-viewer-modal.component';
@NgModule({
imports: [UiIconModule],
exports: [],
declarations: [WebViewerModalComponent],
})
export class WebViewerModalModule {}

View File

@@ -0,0 +1,25 @@
import { NgModule } from '@angular/core';
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 { TasksComponent } from './pages/tasks/tasks.component';
import { TasksModule } from './pages/tasks/tasks.module';
const routes: Routes = [
{
path: '',
component: PageTaskCalendarComponent,
children: [
{ path: 'calendar', component: CalendarComponent },
{ path: 'tasks', component: TasksComponent },
{ path: '', pathMatch: 'full', redirectTo: './calendar' },
],
},
];
@NgModule({
imports: [RouterModule.forChild(routes), CalendarModule, TasksModule],
exports: [RouterModule],
})
export class PageTaskCalendarRoutingModule {}

View File

@@ -0,0 +1,34 @@
<shell-breadcrumb key="task-calendar"></shell-breadcrumb>
<button class="filter" (click)="filterActive$.next(true)">
<ui-icon size="20px" icon="filter_alit"></ui-icon>
<span class="label">Filter</span>
</button>
<div class="content-container" *ngIf="showMainContent$ | async">
<div class="switch-group">
<a class="calendar-switch" [routerLink]="['./calendar']" routerLinkActive="active">
<ui-icon icon="calendar" size="16px"></ui-icon>
<span>Kalender</span>
</a>
<a class="calendar-switch" [routerLink]="['./tasks']" routerLinkActive="active">
<ui-icon icon="tasks" size="16px"></ui-icon>
<span>Tätigkeiten</span>
</a>
</div>
<router-outlet></router-outlet>
</div>
<div class="filter-overlay" [class.active]="filterActive$ | async">
<div class="filter-content">
<div class="filter-close-right">
<button class="filter-close" (click)="filterActive$.next(false)">
<ui-icon size="20px" icon="close"></ui-icon>
</button>
</div>
<h2 class="filter-header">
Filter
</h2>
<page-task-calendar-filter (exitFilter)="filterActive$.next(false)" *ngIf="filterActive$ | async"> </page-task-calendar-filter>
</div>
</div>

View File

@@ -0,0 +1,84 @@
shell-breadcrumb {
margin-top: -5px;
@apply mb-px-10 pb-px-10;
}
.filter {
@apply absolute font-sans flex items-center font-bold bg-gray-400 border-0 text-regular py-px-8 px-px-15 rounded-filter justify-center;
right: 0;
top: 10px;
min-width: 106px;
.label {
@apply ml-px-5;
}
&.active {
@apply bg-dark-cerulean text-white ml-px-5;
}
}
.filter-content {
@apply max-w-content mx-auto mt-px-25 px-px-15;
.filter-close-right {
@apply pr-px-10 text-right;
}
.filter-header {
@apply text-center text-page-heading mt-0;
}
button.filter-close {
@apply border-0 bg-transparent text-ucla-blue;
}
}
.filter-overlay {
@apply fixed bg-munsell z-fixed;
transform: translatex(100%);
transition: transform 0.5s ease-out;
top: 135px;
right: 0;
bottom: 0;
left: 0;
&.active {
transform: translatex(0%);
}
}
.content-container {
max-height: calc(100vh - 267px);
overflow: scroll;
@apply bg-white rounded-card;
}
.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;
}
}
:host ::ng-deep ui-calendar .navigation .title {
margin-left: -140px;
}

View File

@@ -0,0 +1,41 @@
import { Component, OnInit } from '@angular/core';
import { BreadcrumbService } from '@core/breadcrumb';
import { BehaviorSubject, Observable, of } from 'rxjs';
import { delay, switchMap } from 'rxjs/operators';
import { TaskCalendarStore } from './task-calendar.store';
@Component({
selector: 'page-task-calendar',
templateUrl: 'page-task-calendar.component.html',
styleUrls: ['page-task-calendar.component.scss'],
providers: [TaskCalendarStore],
})
export class PageTaskCalendarComponent implements OnInit {
filterActive$ = new BehaviorSubject<boolean>(false);
showMainContent$ = this.getShowMainContent();
constructor(private breadcrumb: BreadcrumbService, private taskCalendarStore: TaskCalendarStore) {
this.taskCalendarStore.loadFilter();
}
ngOnInit(): void {
this.breadcrumb.addBreadcrumbIfNotExists({
key: 'task-calendar',
name: 'Tätigkeitskalender',
path: '/task-calendar/calendar',
tags: ['task-calendar'],
});
}
getShowMainContent(animationDelayInMs: number = 500): Observable<boolean> {
return this.filterActive$.pipe(
switchMap((filterActive) => {
const onExitMainContent = filterActive;
if (onExitMainContent) {
return of(!filterActive).pipe(delay(animationDelayInMs));
}
return of(!filterActive);
})
);
}
}

View File

@@ -0,0 +1,16 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { ShellBreadcrumbModule } from '@shell/breadcrumb';
import { UiFilterModule } from '@ui/filter';
import { UiIconModule } from '@ui/icon';
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';
import { PageTaskCalendarComponent } from './page-task-calendar.component';
@NgModule({
declarations: [PageTaskCalendarComponent, TaskCalendarFilterComponent],
imports: [CommonModule, PageTaskCalendarRoutingModule, ShellBreadcrumbModule, UiIconModule, ModalsModule, UiFilterModule],
exports: [PageTaskCalendarComponent],
})
export class PageTaskCalendarModule {}

View File

@@ -0,0 +1,10 @@
<ui-calendar
mode="month"
[selected]="selectedDate$ | async"
[displayed]="displayedDate$ | async"
[minDate]="minDate$ | async"
[maxDate]="maxDate$ | async"
[indicators]="indicators$ | async"
(selectedChange)="setSelectedDate($event)"
(displayedChange)="setDisplayedDate($event)"
></ui-calendar>

View File

@@ -0,0 +1,35 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { TaskCalendarStore } from '../../task-calendar.store';
@Component({
selector: 'page-calendar',
templateUrl: 'calendar.component.html',
styleUrls: ['calendar.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CalendarComponent {
readonly selectedDate$ = this.taskCalendarStore.selectSelectedDate;
readonly displayedDate$ = this.taskCalendarStore.selectDisplayedDate;
readonly indicators$ = this.taskCalendarStore.selectCalendarIndicators;
readonly minDate$ = this.taskCalendarStore.selectMinDate;
readonly maxDate$ = this.taskCalendarStore.selectMaxDate;
constructor(private taskCalendarStore: TaskCalendarStore, private activatedRoute: ActivatedRoute, private router: Router) {
this.taskCalendarStore.loadItems();
}
setSelectedDate = (date: Date) => {
this.taskCalendarStore.setSelectedDate({ date });
this.taskCalendarStore.setDisplayedDate({ date });
this.router.navigate(['../tasks'], {
relativeTo: this.activatedRoute,
});
};
setDisplayedDate = (date: Date) => this.taskCalendarStore.setDisplayedDate({ date });
}

View File

@@ -1,12 +1,12 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { CalendarComponent } from './calendar.component';
import { TaskCalendarPipesModule } from '../../pipes';
import { UiCalendarModule } from '@ui/calendar';
@NgModule({
imports: [CommonModule, TaskCalendarPipesModule],
imports: [CommonModule, UiCalendarModule],
exports: [CalendarComponent],
declarations: [CalendarComponent],
providers: [],
})
export class CalendarModule {}

View File

@@ -0,0 +1,12 @@
<ui-calendar
mode="week"
[selected]="selectedDate$ | async"
[displayed]="displayedDate$ | async"
[minDate]="minDate$ | async"
[maxDate]="maxDate$ | async"
[indicators]="indicators$ | async"
(selectedChange)="setSelectedDate($event)"
(displayedChange)="setDisplayedDate($event)"
></ui-calendar>
<page-task-list [items]="items$ | async" [selected]="selectedDate$ | async" (select)="open($event)"></page-task-list>

Some files were not shown because too many files have changed in this diff Show More