(web): use angulars built-in control flow everywhere

This commit is contained in:
Marcel Arndt 2025-06-05 15:26:13 +02:00
parent 08e3b3247c
commit 3302dd7e32
40 changed files with 1257 additions and 1177 deletions

View File

@ -1,5 +1,5 @@
import { ClipboardModule } from '@angular/cdk/clipboard'; import { ClipboardModule } from '@angular/cdk/clipboard';
import { CurrencyPipe, NgClass, NgIf } from '@angular/common'; import { CurrencyPipe, NgClass } from '@angular/common';
import { import {
AfterViewInit, AfterViewInit,
ChangeDetectionStrategy, ChangeDetectionStrategy,
@ -23,32 +23,33 @@ import {
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'dks-accounting-item', selector: 'dks-accounting-item',
template: ` <div class="flex justify-between"> template: ` <div class="flex justify-between">
<div class="flex items-center"> <div class="flex items-center">
<button <button
mat-icon-button mat-icon-button
(click)="notifyCopy($event)" (click)="notifyCopy($event)"
[cdkCopyToClipboard]="tour.operationId" [cdkCopyToClipboard]="tour.operationId"
> >
<mat-icon class="text-sm">content_copy</mat-icon> <mat-icon class="text-sm">content_copy</mat-icon>
</button> </button>
{{ tour.operationId }} {{ tour.operationId }}
</div>
<span>{{ tour.ordinanceType }}</span>
</div> </div>
<span>{{ tour.ordinanceType }}</span> <div>
</div> @if (accountingState !== AccountingState.Billed) {
<div> <span
<span [title]="!tour.healthInsurance ? 'Keine Krankenkasse hinterlegt' : ''"
[title]="!tour.healthInsurance ? 'Keine Krankenkasse hinterlegt' : ''" [ngClass]="{ 'bg-yellow-200': !tour.healthInsurance }"
[ngClass]="{ 'bg-yellow-200': !tour.healthInsurance }" >
*ngIf="accountingState !== AccountingState.Billed; else diffTmpl" {{ tour.revenue | currency : 'EUR' : 'symbol' : '1.2-2' : 'de-DE' }}
> </span>
{{ tour.revenue | currency : 'EUR' : 'symbol' : '1.2-2' : 'de-DE' }} } @else {
</span> {{
</div> tour.revenueDeviation | currency : 'EUR' : 'symbol' : '1.2-2' : 'de-DE'
<ng-template #diffTmpl> }}
{{ }
tour.revenueDeviation | currency : 'EUR' : 'symbol' : '1.2-2' : 'de-DE' </div>
}} `,
</ng-template>`,
host: { host: {
class: 'flex flex-col rounded border p-2 m-1 bg-white cursor-pointer', class: 'flex flex-col rounded border p-2 m-1 bg-white cursor-pointer',
}, },
@ -69,10 +70,9 @@ import {
MatIconModule, MatIconModule,
MatBadgeModule, MatBadgeModule,
ClipboardModule, ClipboardModule,
NgIf,
NgClass, NgClass,
CurrencyPipe, CurrencyPipe
], ],
}) })
export class AccountingItemComponent implements AfterViewInit { export class AccountingItemComponent implements AfterViewInit {
toast = inject(HotToastService); toast = inject(HotToastService);

View File

@ -1,24 +1,28 @@
import { AsyncPipe, CurrencyPipe, NgIf } from '@angular/common'; import { AsyncPipe, CurrencyPipe } from '@angular/common';
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { TourStateMeta } from '../../../core/data-access/graphql/generated/generated'; import { TourStateMeta } from '../../../core/data-access/graphql/generated/generated';
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'dks-accounting-lane-footer', selector: 'dks-accounting-lane-footer',
template: `<span *ngIf="withTourCount" class="text-left text-xs"> template: `@if (withTourCount) {
{{ tourStateMeta.count }} Touren <span class="text-left text-xs">
</span> {{ tourStateMeta.count }} Touren
<span *ngIf="withRevenueSum" class="text-left text-xs"> </span>
{{ }
tourStateMeta.revenueSum @if (withRevenueSum) {
| currency : 'EUR' : 'symbol' : '1.2-2' : 'de-DE' <span class="text-left text-xs">
}} {{
</span>`, tourStateMeta.revenueSum
| currency : 'EUR' : 'symbol' : '1.2-2' : 'de-DE'
}}
</span>
}`,
host: { host: {
class: 'flex flex-col items-center justify-center p-2', class: 'flex flex-col items-center justify-center p-2',
}, },
styles: [``], styles: [``],
imports: [NgIf, CurrencyPipe], imports: [CurrencyPipe],
}) })
export class AccountingLaneFooterComponent { export class AccountingLaneFooterComponent {
@Input() tourStateMeta!: TourStateMeta; @Input() tourStateMeta!: TourStateMeta;

View File

@ -1,4 +1,4 @@
import { NgFor, NgSwitch, NgSwitchCase } from '@angular/common';
import { import {
AfterViewInit, AfterViewInit,
ChangeDetectionStrategy, ChangeDetectionStrategy,
@ -23,68 +23,70 @@ import {
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'dks-accounting-lane-header-filter', selector: 'dks-accounting-lane-header-filter',
template: ` template: `
<ng-container [ngSwitch]="state"> @switch (state) {
<button @case (AccountingState.Billable) {
mat-icon-button <button
[matMenuTriggerFor]="beforeBilledMenu" mat-icon-button
aria-label="Abrechenbar Filter button" [matMenuTriggerFor]="beforeBilledMenu"
*ngSwitchCase="AccountingState.Billable" aria-label="Abrechenbar Filter button"
> >
<mat-icon>filter_list</mat-icon> <mat-icon>filter_list</mat-icon>
</button> </button>
<button }
mat-icon-button @case (AccountingState.PreparedForBilling) {
[matMenuTriggerFor]="beforeBilledMenu" <button
aria-label="Abrechnung vorbereitet Filter button" mat-icon-button
*ngSwitchCase="AccountingState.PreparedForBilling" [matMenuTriggerFor]="beforeBilledMenu"
aria-label="Abrechnung vorbereitet Filter button"
> >
<mat-icon>filter_list</mat-icon> <mat-icon>filter_list</mat-icon>
</button> </button>
<button }
mat-icon-button @case (AccountingState.Billed) {
[matMenuTriggerFor]="afterBilledMenu" <button
aria-label="Abgerechnet Filter button" mat-icon-button
*ngSwitchCase="AccountingState.Billed" [matMenuTriggerFor]="afterBilledMenu"
aria-label="Abgerechnet Filter button"
> >
<mat-icon>filter_list</mat-icon> <mat-icon>filter_list</mat-icon>
</button> </button>
</ng-container> }
<form #form="ngForm"> }
<mat-menu #beforeBilledMenu="matMenu"> <form #form="ngForm">
<section <mat-menu #beforeBilledMenu="matMenu">
mat-menu-item @for (item of filter.beforeBilled; track item) {
(click)="$event.stopPropagation()" <section
*ngFor="let item of filter.beforeBilled" mat-menu-item
(click)="$event.stopPropagation()"
> >
<mat-checkbox [(ngModel)]="item.checked" [name]="item.value">{{ <mat-checkbox [(ngModel)]="item.checked" [name]="item.value">{{
item.text item.text
}}</mat-checkbox> }}</mat-checkbox>
</section> </section>
</mat-menu> }
<mat-menu #afterBilledMenu="matMenu"> </mat-menu>
<section <mat-menu #afterBilledMenu="matMenu">
mat-menu-item @for (item of filter.afterBilled; track item) {
(click)="$event.stopPropagation()" <section
*ngFor="let item of filter.afterBilled" mat-menu-item
(click)="$event.stopPropagation()"
> >
<mat-checkbox [(ngModel)]="item.checked" [name]="item.value">{{ <mat-checkbox [(ngModel)]="item.checked" [name]="item.value">{{
item.text item.text
}}</mat-checkbox> }}</mat-checkbox>
</section> </section>
</mat-menu> }
</form> </mat-menu>
`, </form>
`,
styles: [], styles: [],
imports: [ imports: [
NgSwitch, FormsModule,
NgSwitchCase, MatButtonModule,
NgFor, MatIconModule,
FormsModule, MatMenuModule,
MatButtonModule, MatCheckboxModule
MatIconModule, ]
MatMenuModule,
MatCheckboxModule,
]
}) })
export class AccountingLaneHeaderFilterComponent implements AfterViewInit { export class AccountingLaneHeaderFilterComponent implements AfterViewInit {
@Input() state!: AccountingState; @Input() state!: AccountingState;

View File

@ -1,4 +1,4 @@
import { NgSwitch, NgSwitchCase } from '@angular/common';
import { import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
Component, Component,
@ -16,19 +16,25 @@ import { AccountingLaneHeaderFilterComponent } from './accounting-lane-header-fi
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'dks-accounting-lane-header', selector: 'dks-accounting-lane-header',
template: `<div></div> template: `<div></div>
<ng-container [ngSwitch]="state"> @switch (state) {
<div *ngSwitchCase="AccountingState.Billable">Abrechenbar</div> @case (AccountingState.Billable) {
<div *ngSwitchCase="AccountingState.PreparedForBilling"> <div>Abrechenbar</div>
für Abrechnung vorbereitet }
</div> @case (AccountingState.PreparedForBilling) {
<div *ngSwitchCase="AccountingState.Billed"> <div>
Abgerechnet (mit Abweichung) für Abrechnung vorbereitet
</div> </div>
</ng-container> }
<dks-accounting-lane-header-filter @case (AccountingState.Billed) {
[state]="state" <div>
(filterChange)="filterChanged($event)" Abgerechnet (mit Abweichung)
></dks-accounting-lane-header-filter>`, </div>
}
}
<dks-accounting-lane-header-filter
[state]="state"
(filterChange)="filterChanged($event)"
></dks-accounting-lane-header-filter>`,
host: { host: {
class: 'flex items-center justify-between p-2 bg-secondary-container', class: 'flex items-center justify-between p-2 bg-secondary-container',
}, },
@ -39,7 +45,7 @@ import { AccountingLaneHeaderFilterComponent } from './accounting-lane-header-fi
} }
`, `,
], ],
imports: [NgSwitch, NgSwitchCase, AccountingLaneHeaderFilterComponent], imports: [AccountingLaneHeaderFilterComponent],
}) })
export class AccountingLaneHeaderComponent { export class AccountingLaneHeaderComponent {
@Input() state!: AccountingState; @Input() state!: AccountingState;

View File

@ -3,11 +3,11 @@
[state]="accountingState" [state]="accountingState"
(filterChange)="filterChanged($event)" (filterChange)="filterChanged($event)"
></dks-accounting-lane-header> ></dks-accounting-lane-header>
<ng-container *ngIf="tours$ | async as tours"> @if (tours$ | async; as tours) {
<cdk-virtual-scroll-viewport <cdk-virtual-scroll-viewport
itemSize="86" itemSize="86"
(scrolledIndexChange)="fetchNextPage()" (scrolledIndexChange)="fetchNextPage()"
> >
<dks-accounting-item <dks-accounting-item
*cdkVirtualFor="let tour of tours; trackBy: trackByFn" *cdkVirtualFor="let tour of tours; trackBy: trackByFn"
class="animate-fadeIn" class="animate-fadeIn"
@ -15,14 +15,14 @@
[accountingState]="accountingState" [accountingState]="accountingState"
[tour]="tour" [tour]="tour"
(click)="openTourView(tour)" (click)="openTourView(tour)"
> >
</dks-accounting-item> </dks-accounting-item>
</cdk-virtual-scroll-viewport> </cdk-virtual-scroll-viewport>
<!-- <ng-container *ngIf="accountingStateMeta$ | async as accountingStateMeta"> <!-- <ng-container *ngIf="accountingStateMeta$ | async as accountingStateMeta">
<dks-accounting-lane-footer <dks-accounting-lane-footer
[tourStateMeta]="accountingStateMeta" [tourStateMeta]="accountingStateMeta"
[withTourCount]="true" [withTourCount]="true"
[withRevenueSum]="accountingState !== AccountingState.Billed" [withRevenueSum]="accountingState !== AccountingState.Billed"
></dks-accounting-lane-footer> ></dks-accounting-lane-footer>
</ng-container> --> </ng-container> -->
</ng-container> }

View File

@ -2,7 +2,7 @@ import {
CdkVirtualScrollViewport, CdkVirtualScrollViewport,
ScrollingModule, ScrollingModule,
} from '@angular/cdk/scrolling'; } from '@angular/cdk/scrolling';
import { AsyncPipe, NgIf } from '@angular/common'; import { AsyncPipe } from '@angular/common';
import { import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
Component, Component,
@ -53,10 +53,9 @@ import { AccountingLaneHeaderComponent } from './accounting-lane-header.componen
imports: [ imports: [
AccountingLaneHeaderComponent, AccountingLaneHeaderComponent,
AccountingItemComponent, AccountingItemComponent,
NgIf,
AsyncPipe, AsyncPipe,
ScrollingModule, ScrollingModule
], ],
}) })
export class AccountingLaneComponent { export class AccountingLaneComponent {
private dialog = inject(DialogService); private dialog = inject(DialogService);

View File

@ -1,4 +1,4 @@
import { DatePipe, NgIf } from '@angular/common'; import { DatePipe } from '@angular/common';
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { MatDividerModule } from '@angular/material/divider'; import { MatDividerModule } from '@angular/material/divider';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
@ -18,37 +18,46 @@ import { Tour } from '../../../core/data-access/graphql/generated/generated';
</span> </span>
</div> </div>
<div class="flex flex-col text-gray-600 text-sm"> <div class="flex flex-col text-gray-600 text-sm">
<span class="font-medium" *ngIf="tour.createdAt">Erstellt am</span> @if (tour.createdAt) {
<span *ngIf="tour.createdAt"> <span class="font-medium">Erstellt am</span>
{{ tour.createdAt | date : 'dd.MM.yyyy' }} }
{{ tour.createdAt | date : 'HH:mm' }} Uhr @if (tour.createdAt) {
</span> <span>
{{ tour.createdAt | date : 'dd.MM.yyyy' }}
{{ tour.createdAt | date : 'HH:mm' }} Uhr
</span>
}
</div> </div>
</div> </div>
<mat-divider></mat-divider> <mat-divider></mat-divider>
<div @if (tour.driverName || tour.codriverName || tour.carName) {
class="flex flex-wrap md:flex-nowrap justify-between px-4 py-2"
*ngIf="tour.driverName || tour.codriverName || tour.carName"
>
<div <div
class="flex flex-col pb-1 md:pb-0" class="flex flex-wrap md:flex-nowrap justify-between px-4 py-2"
*ngIf="tour.carName" >
> @if (tour.carName) {
<span class="font-semibold pb-1">Fahrzeug</span> <div
{{ tour.carName }} class="flex flex-col pb-1 md:pb-0"
>
<span class="font-semibold pb-1">Fahrzeug</span>
{{ tour.carName }}
</div>
}
@if (tour.driverName) {
<div
class="flex flex-col pb-1 md:pb-0"
>
<span class="font-semibold pb-1">Fahrer</span>
<span>{{ tour.driverName }}</span>
</div>
}
@if (tour.codriverName) {
<div class="flex flex-col">
<span class="font-semibold pb-1">Beifahrer</span>
<span>{{ tour.codriverName }}</span>
</div>
}
</div> </div>
<div }
class="flex flex-col pb-1 md:pb-0"
*ngIf="tour.driverName"
>
<span class="font-semibold pb-1">Fahrer</span>
<span>{{ tour.driverName }}</span>
</div>
<div class="flex flex-col" *ngIf="tour.codriverName">
<span class="font-semibold pb-1">Beifahrer</span>
<span>{{ tour.codriverName }}</span>
</div>
</div>
<div class="flex flex-col px-4 py-2"> <div class="flex flex-col px-4 py-2">
<span class="font-semibold pb-1">Patient</span> <span class="font-semibold pb-1">Patient</span>
<span>{{ tour.patientName }} {{ tour.patientSurname }}</span> <span>{{ tour.patientName }} {{ tour.patientSurname }}</span>
@ -69,9 +78,9 @@ import { Tour } from '../../../core/data-access/graphql/generated/generated';
<span>{{ tour.targetZip }} {{ tour.targetCity }}</span> <span>{{ tour.targetZip }} {{ tour.targetCity }}</span>
</div> </div>
</div> </div>
`, `,
styles: [], styles: [],
imports: [MatTabsModule, MatIconModule, MatDividerModule, NgIf, DatePipe] imports: [MatTabsModule, MatIconModule, MatDividerModule, DatePipe]
}) })
export class AccountingTourOverviewComponent { export class AccountingTourOverviewComponent {
@Input() tour!: Tour; @Input() tour!: Tour;

View File

@ -1,4 +1,4 @@
import { DatePipe, NgFor, NgIf, NgSwitch, NgSwitchCase } from '@angular/common'; import { DatePipe } from '@angular/common';
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { MatDividerModule } from '@angular/material/divider'; import { MatDividerModule } from '@angular/material/divider';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
@ -14,33 +14,38 @@ import { Ticket, TicketValidationState, Tour } from '../../../core/data-access/g
<div mat-subheader class="text-sm">Ticket Informationen</div> <div mat-subheader class="text-sm">Ticket Informationen</div>
<mat-list-item role="listitem"> <mat-list-item role="listitem">
Status: Status:
<ng-container [ngSwitch]="tour.ticket.currentState"> @switch (tour.ticket.currentState) {
<span @case (TicketValidationState.Created) {
class="font-bold" <span
*ngSwitchCase="TicketValidationState.Created" class="font-bold"
>Touren angenommen >Touren angenommen
</span> </span>
<span }
class="font-bold" @case (TicketValidationState.UserInteractionRequired) {
*ngSwitchCase="TicketValidationState.UserInteractionRequired" <span
>Genehmigung / Transportschein class="font-bold"
</span> >Genehmigung / Transportschein
<span </span>
class="font-bold" }
*ngSwitchCase="TicketValidationState.TdUncertain" @case (TicketValidationState.TdUncertain) {
>Transportschein unklar <span
</span> class="font-bold"
<span >Transportschein unklar
class="font-bold" </span>
*ngSwitchCase="TicketValidationState.DocumentsMissing" }
>Dokumente fehlen @case (TicketValidationState.DocumentsMissing) {
</span> <span
<span class="font-bold"
class="font-bold" >Dokumente fehlen
*ngSwitchCase="TicketValidationState.Archived" </span>
>Archiviert }
</span> @case (TicketValidationState.Archived) {
</ng-container> <span
class="font-bold"
>Archiviert
</span>
}
}
</mat-list-item> </mat-list-item>
<mat-list-item role="listitem"> <mat-list-item role="listitem">
Typ: Typ:
@ -55,47 +60,46 @@ import { Ticket, TicketValidationState, Tour } from '../../../core/data-access/g
</span> </span>
</mat-list-item> </mat-list-item>
</mat-list> </mat-list>
<mat-list @if (tour.ticket.errors.length > 0 || tour.ticket.notes.length > 0) {
class="col-span-2 max-h-96 overflow-y-auto" <mat-list
*ngIf="tour.ticket.errors.length > 0 || tour.ticket.notes.length > 0" class="col-span-2 max-h-96 overflow-y-auto"
role="list" role="list"
> >
<div mat-subheader class="text-sm">Ticket Anmerkungen</div> <div mat-subheader class="text-sm">Ticket Anmerkungen</div>
<mat-list-item @for (error of tour.ticket.errors; track error) {
*ngFor="let error of tour.ticket.errors" <mat-list-item
lines="2" lines="2"
role="listitem" role="listitem"
> >
<mat-icon matListItemIcon class="!text-red-600">error</mat-icon> <mat-icon matListItemIcon class="!text-red-600">error</mat-icon>
<span matListItemTitle>{{ error.split(':').at(0) }}</span> <span matListItemTitle>{{ error.split(':').at(0) }}</span>
<span [title]="error.split(':').at(1)">{{ <span [title]="error.split(':').at(1)">{{
error.split(':').at(1) error.split(':').at(1)
}}</span> }}</span>
</mat-list-item> </mat-list-item>
<mat-list-item }
*ngFor="let note of tour.ticket.notes" @for (note of tour.ticket.notes; track note) {
lines="2" <mat-list-item
role="listitem" lines="2"
> role="listitem"
<mat-icon matListItemIcon class="!text-yellow-400">warning</mat-icon> >
<span matListItemTitle>{{ note.split(':').at(0) }}</span> <mat-icon matListItemIcon class="!text-yellow-400">warning</mat-icon>
<span [title]="note.split(':').at(1)">{{ <span matListItemTitle>{{ note.split(':').at(0) }}</span>
note.split(':').at(1) <span [title]="note.split(':').at(1)">{{
}}</span> note.split(':').at(1)
</mat-list-item> }}</span>
</mat-list> </mat-list-item>
}
</mat-list>
}
</div> </div>
`, `,
styles: [], styles: [],
imports: [ imports: [
MatIconModule, MatIconModule,
MatListModule, MatListModule,
MatDividerModule, MatDividerModule
NgIf, ]
NgFor,
NgSwitch,
NgSwitchCase,
]
}) })
export class AccountingTourTicketComponent { export class AccountingTourTicketComponent {
@Input() tour!: Tour & { ticket: Ticket }; @Input() tour!: Tour & { ticket: Ticket };

View File

@ -1,29 +1,31 @@
<mat-list role="list" *ngIf="anomalies$ | async as anomalies"> @if (anomalies$ | async; as anomalies) {
<ng-container *ngIf="anomalies.length; else emptyListTmpl"> <mat-list role="list">
<mat-list-item @if (anomalies.length) {
*ngFor="let anomaly of anomalies" @for (anomaly of anomalies; track anomaly) {
class="flex" <mat-list-item
role="listitem" class="flex"
role="listitem"
>
<dks-anomaly-list-item
[anomaly]="anomaly"
(anomalySetToSleep)="setAnomalyToSleep($event)"
(anomalyAccepted)="acceptAnomaly($event)"
></dks-anomaly-list-item>
</mat-list-item>
}
} @else {
<mat-list-item
>Alle Mitarbeiter sind auf einem Fahrzeug angemeldet!</mat-list-item
>
}
</mat-list>
}
<mat-paginator
class="mt-auto"
[length]="totalCount$ | async"
[pageSize]="pageSize"
[pageSizeOptions]="[]"
(page)="changePage($event)"
aria-label="Select page"
> >
<dks-anomaly-list-item </mat-paginator>
[anomaly]="anomaly"
(anomalySetToSleep)="setAnomalyToSleep($event)"
(anomalyAccepted)="acceptAnomaly($event)"
></dks-anomaly-list-item>
</mat-list-item>
</ng-container>
<ng-template #emptyListTmpl>
<mat-list-item
>Alle Mitarbeiter sind auf einem Fahrzeug angemeldet!</mat-list-item
>
</ng-template>
</mat-list>
<mat-paginator
class="mt-auto"
[length]="totalCount$ | async"
[pageSize]="pageSize"
[pageSizeOptions]="[]"
(page)="changePage($event)"
aria-label="Select page"
>
</mat-paginator>

View File

@ -1,14 +1,18 @@
<h2>{{ ref.data.title }}</h2> <h2>{{ ref.data.title }}</h2>
<mat-divider></mat-divider> <mat-divider></mat-divider>
<article class="my-4 mx-2"> <article class="my-4 mx-2">
<div class="pb-4" *ngFor="let target of ref.data.solution"> @for (target of ref.data.solution; track target) {
<span class="font-bold text-base">{{ target.name }}</span> <div class="pb-4">
<ul class="ml-4"> <span class="font-bold text-base">{{ target.name }}</span>
<li class="list-disc" *ngFor="let step of target.steps"> <ul class="ml-4">
{{ step.description }} @for (step of target.steps; track step) {
</li> <li class="list-disc">
</ul> {{ step.description }}
</div> </li>
}
</ul>
</div>
}
</article> </article>
<mat-divider></mat-divider> <mat-divider></mat-divider>
<div class="flex justify-end mt-2"> <div class="flex justify-end mt-2">

View File

@ -1,48 +1,47 @@
<ng-container *ngIf="anomalies$ | async as anomalies"> @if (anomalies$ | async; as anomalies) {
<mat-tree @if (anomalies.length) {
*ngIf="anomalies.length; else emptyListTmpl" <mat-tree
[dataSource]="dataSource" [dataSource]="dataSource"
[treeControl]="treeControl" [treeControl]="treeControl"
>
<mat-tree-node *matTreeNodeDef="let node" matTreeNodePadding>
<dks-anomaly-list-item
[anomaly]="node.anomaly"
(anomalySetToSleep)="setAnomalyToSleep($event)"
(anomalyAccepted)="acceptAnomaly($event)"
></dks-anomaly-list-item>
</mat-tree-node>
<mat-tree-node
class="flex"
*matTreeNodeDef="let node; when: hasChild"
matTreeNodePadding
>
<button
mat-icon-button
matTreeNodeToggle
[attr.aria-label]="'Toggle ' + node.name"
> >
<mat-icon class="mat-icon-rtl-mirror"> <mat-tree-node *matTreeNodeDef="let node" matTreeNodePadding>
{{ treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right' }} <dks-anomaly-list-item
</mat-icon> [anomaly]="node.anomaly"
</button> (anomalySetToSleep)="setAnomalyToSleep($event)"
{{ node.name }} (anomalyAccepted)="acceptAnomaly($event)"
<div class="ml-auto rounded-full w-5 h-5 flex justify-center items-center font-bold" style="background-color: var(--mat-sys-primary-container); color: var(--mat-sys-on-primary-container)">{{ node?.childCount }}</div> ></dks-anomaly-list-item>
</mat-tree-node> </mat-tree-node>
</mat-tree> <mat-tree-node
<ng-template #emptyListTmpl> class="flex"
*matTreeNodeDef="let node; when: hasChild"
matTreeNodePadding
>
<button
mat-icon-button
matTreeNodeToggle
[attr.aria-label]="'Toggle ' + node.name"
>
<mat-icon class="mat-icon-rtl-mirror">
{{ treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right' }}
</mat-icon>
</button>
{{ node.name }}
<div class="ml-auto rounded-full w-5 h-5 flex justify-center items-center font-bold" style="background-color: var(--mat-sys-primary-container); color: var(--mat-sys-on-primary-container)">{{ node?.childCount }}</div>
</mat-tree-node>
</mat-tree>
} @else {
<mat-list-item <mat-list-item
>Herzlichen Glückwunsch, es liegen keine Anomalien vor!</mat-list-item >Herzlichen Glückwunsch, es liegen keine Anomalien vor!</mat-list-item
>
}
}
<mat-paginator
#paginator
class="mt-auto"
[length]="totalCount$ | async"
[pageSize]="pageSize"
[pageSizeOptions]="[]"
(page)="changePage($event)"
aria-label="Select page"
> >
</ng-template> </mat-paginator>
</ng-container>
<mat-paginator
#paginator
class="mt-auto"
[length]="totalCount$ | async"
[pageSize]="pageSize"
[pageSizeOptions]="[]"
(page)="changePage($event)"
aria-label="Select page"
>
</mat-paginator>

View File

@ -2,24 +2,27 @@
<mat-card-header class="flex justify-center"> <mat-card-header class="flex justify-center">
<mat-card-title>{{ ordinanceType }}</mat-card-title> <mat-card-title>{{ ordinanceType }}</mat-card-title>
</mat-card-header> </mat-card-header>
<mat-card-content *ngIf="toursPerTimeKPI$ | async as kpis"> @if (toursPerTimeKPI$ | async; as kpis) {
<div class="flex"> <mat-card-content>
<div <div class="flex">
*ngFor="let kpi of kpis; index as idx" @for (kpi of kpis; track kpi; let idx = $index) {
class="w-1/3 flex flex-col justify-center items-center" <div
style="border-color: var(--mat-sys-primary);" class="w-1/3 flex flex-col justify-center items-center"
style="border-color: var(--mat-sys-primary);"
[ngClass]="{ [ngClass]="{
'border-b-2': (currentTimeSlot$ | async) === idx, 'border-b-2': (currentTimeSlot$ | async) === idx,
}" }"
> >
<div class="p-4 text-base"> <div class="p-4 text-base">
{{ kpi.kpi.value.toString().replace(".", ",") }} {{ kpi.kpi.value.toString().replace(".", ",") }}
</div> </div>
<div class="font-bold"> <div class="font-bold">
{{ kpi.interval.start | date : "HH:mm" }} - {{ kpi.interval.start | date : "HH:mm" }} -
{{ kpi.interval.end | date : "HH:mm" }} {{ kpi.interval.end | date : "HH:mm" }}
</div> </div>
</div>
}
</div> </div>
</div> </mat-card-content>
</mat-card-content> }
</mat-card> </mat-card>

View File

@ -8,7 +8,9 @@
</span> </span>
</div> </div>
<div class="flex flex-col w-1/2 text-gray-600 text-sm"> <div class="flex flex-col w-1/2 text-gray-600 text-sm">
<span class="font-medium" *ngIf="ref.data.createdAt">Erstellt am</span> @if (ref.data.createdAt) {
<span class="font-medium">Erstellt am</span>
}
<span> <span>
{{ ref.data.createdAt | date: 'HH:mm' }} Uhr {{ ref.data.createdAt | date: 'HH:mm' }} Uhr
{{ ref.data.createdAt | date: 'dd.MM.yyyy' }} {{ ref.data.createdAt | date: 'dd.MM.yyyy' }}
@ -16,29 +18,34 @@
</div> </div>
</div> </div>
<mat-divider></mat-divider> <mat-divider></mat-divider>
<div @if (ref.data.driverName || ref.data.codriverName || ref.data.carName) {
class="flex flex-wrap md:flex-nowrap p-2"
*ngIf="ref.data.driverName || ref.data.codriverName || ref.data.carName"
>
<div <div
class="w-full md:w-1/3 flex flex-col pb-1 md:pb-0" class="flex flex-wrap md:flex-nowrap p-2"
*ngIf="ref.data.carName" >
> @if (ref.data.carName) {
<span class="font-semibold pb-1">Fahrzeug</span> <div
{{ ref.data.carName }} class="w-full md:w-1/3 flex flex-col pb-1 md:pb-0"
>
<span class="font-semibold pb-1">Fahrzeug</span>
{{ ref.data.carName }}
</div>
}
@if (ref.data.driverName) {
<div
class="w-full md:w-1/3 flex flex-col pb-1 md:pb-0"
>
<span class="font-semibold pb-1">Fahrer</span>
<span>{{ ref.data.driverName }}</span>
</div>
}
@if (ref.data.codriverName) {
<div class="w-full md:w-1/3 flex flex-col">
<span class="font-semibold pb-1">Beifahrer</span>
<span>{{ ref.data.codriverName }}</span>
</div>
}
</div> </div>
<div }
class="w-full md:w-1/3 flex flex-col pb-1 md:pb-0"
*ngIf="ref.data.driverName"
>
<span class="font-semibold pb-1">Fahrer</span>
<span>{{ ref.data.driverName }}</span>
</div>
<div class="w-full md:w-1/3 flex flex-col" *ngIf="ref.data.codriverName">
<span class="font-semibold pb-1">Beifahrer</span>
<span>{{ ref.data.codriverName }}</span>
</div>
</div>
<div class="flex flex-col p-2"> <div class="flex flex-col p-2">
<span class="font-semibold pb-1">Patient</span> <span class="font-semibold pb-1">Patient</span>
<span>{{ ref.data.patientName }} {{ ref.data.patientSurname }}</span> <span>{{ ref.data.patientName }} {{ ref.data.patientSurname }}</span>

View File

@ -1,19 +1,20 @@
<div @if (upcomingTours$ | async; as upcomingTours) {
class="flex flex-col justify-between flex-wrap m-2 xl:m-4" <div
*ngIf="upcomingTours$ | async as upcomingTours" class="flex flex-col justify-between flex-wrap m-2 xl:m-4"
>
<ng-container *ngIf="upcomingTours.length; else emptyTmpl">
<div
class="py-2 xl:px-2 flex flex-nowrap items-center"
*ngFor="let tour of upcomingTours"
> >
<div class="h-4 w-4 mr-2 rounded-full" [ngClass]="tour.colorClass"></div> @if (upcomingTours.length) {
<div class="cursor-pointer" (click)="openTourViewDialog(tour)"> @for (tour of upcomingTours; track tour) {
{{ tour.operationId }} - {{ tour.startDate | date: 'HH:mm' }} Uhr <div
</div> class="py-2 xl:px-2 flex flex-nowrap items-center"
</div> >
</ng-container> <div class="h-4 w-4 mr-2 rounded-full" [ngClass]="tour.colorClass"></div>
<ng-template #emptyTmpl <div class="cursor-pointer" (click)="openTourViewDialog(tour)">
>Keine Touren in den nächsten 20 Minuten ohne Anfahrt</ng-template {{ tour.operationId }} - {{ tour.startDate | date: 'HH:mm' }} Uhr
> </div>
</div> </div>
}
} @else {
Keine Touren in den nächsten 20 Minuten ohne Anfahrt
}
</div>
}

View File

@ -1,7 +1,8 @@
<div @if (echartsOptions$ | async; as echartsOptions) {
*ngIf="echartsOptions$ | async as echartsOptions" <div
dksEcharts dksEcharts
[defaultHeight]="300" [defaultHeight]="300"
[options]="echartsOptions" [options]="echartsOptions"
[extentions]="echartsExtensions" [extentions]="echartsExtensions"
></div> ></div>
}

View File

@ -1,12 +1,14 @@
<div class="m-auto flex flex-col" *ngIf="metricsSummary$ | async as summary"> @if (metricsSummary$ | async; as summary) {
<div class="flex items-center justify-end"> <div class="m-auto flex flex-col">
<span class="text-base">{{ <div class="flex items-center justify-end">
summary.averageRevenue | currency : 'EUR' : 'symbol' : '1.2-2' <span class="text-base">{{
}} Ø Umsatz</span> summary.averageRevenue | currency : 'EUR' : 'symbol' : '1.2-2'
}} Ø Umsatz</span>
</div>
<div class="flex items-center justify-end">
<span class="text-base">{{
(summary.availableTime / summary.tourAmount) | number : '1.2-2'
}} Index</span>
</div>
</div> </div>
<div class="flex items-center justify-end"> }
<span class="text-base">{{
(summary.availableTime / summary.tourAmount) | number : '1.2-2'
}} Index</span>
</div>
</div>

View File

@ -1,7 +1,8 @@
<div @if (echartsOptions$ | async; as echartsOptions) {
*ngIf="echartsOptions$ | async as echartsOptions" <div
dksEcharts dksEcharts
[defaultHeight]="300" [defaultHeight]="300"
[options]="echartsOptions" [options]="echartsOptions"
[extentions]="echartsExtensions" [extentions]="echartsExtensions"
></div> ></div>
}

View File

@ -1,45 +1,47 @@
<div class="m-auto flex flex-col" *ngIf="metricsSummary$ | async as summary"> @if (metricsSummary$ | async; as summary) {
<div class="flex items-center justify-end"> <div class="m-auto flex flex-col">
<span class="text-base">{{ <div class="flex items-center justify-end">
summary.revenue | currency : 'EUR' : 'symbol' : '1.2-2' <span class="text-base">{{
}}</span> summary.revenue | currency : 'EUR' : 'symbol' : '1.2-2'
<div class="p-1 text-sm" title="Umsatz"> }}</span>
<mat-icon>euro</mat-icon> <div class="p-1 text-sm" title="Umsatz">
<mat-icon>euro</mat-icon>
</div>
</div> </div>
</div> <div class="flex items-center justify-end">
<div class="flex items-center justify-end"> <span class="text-base">{{
<span class="text-base">{{ summary.personnelCost | currency : 'EUR' : 'symbol' : '1.2-2'
summary.personnelCost | currency : 'EUR' : 'symbol' : '1.2-2' }}</span>
}}</span> <div class="p-1 text-sm" title="Personalkosten">
<div class="p-1 text-sm" title="Personalkosten"> <mat-icon>people</mat-icon>
<mat-icon>people</mat-icon> </div>
</div> </div>
</div> <div class="flex items-center justify-end">
<div class="flex items-center justify-end"> <span class="text-base">{{
<span class="text-base">{{ summary.consumptionCost | currency : 'EUR' : 'symbol' : '1.2-2'
summary.consumptionCost | currency : 'EUR' : 'symbol' : '1.2-2' }}</span>
}}</span> <div class="p-1 text-sm" title="Verbrauch">
<div class="p-1 text-sm" title="Verbrauch"> <mat-icon>local_shipping</mat-icon>
<mat-icon>local_shipping</mat-icon> </div>
</div> </div>
</div> <div class="flex items-center justify-end">
<div class="flex items-center justify-end"> <span class="text-base">{{
<span class="text-base">{{ summary.breakEven | currency : 'EUR' : 'symbol' : '1.2-2'
summary.breakEven | currency : 'EUR' : 'symbol' : '1.2-2' }}</span>
}}</span> <div class="p-1 text-sm" title="Deckungsbeitrag">
<div class="p-1 text-sm" title="Deckungsbeitrag"> <mat-icon>account_balance</mat-icon>
<mat-icon>account_balance</mat-icon> </div>
</div> </div>
</div> <!-- TODO: Add feature toggle for forecast -->
<!-- TODO: Add feature toggle for forecast --> <!-- <div class="flex items-center justify-end text-yellow-500">
<!-- <div class="flex items-center justify-end text-yellow-500">
<span class="text-base" <span class="text-base"
>+{{ >+{{
summary.forecastRevenue | currency : 'EUR' : 'symbol' : '1.2-2' summary.forecastRevenue | currency : 'EUR' : 'symbol' : '1.2-2'
}}</span }}</span
> >
<div class="p-1 text-sm" title="Umsatz Vorausschau"> <div class="p-1 text-sm" title="Umsatz Vorausschau">
<mat-icon>account_balance</mat-icon> <mat-icon>account_balance</mat-icon>
</div> </div>
</div> --> </div> -->
</div> </div>
}

View File

@ -1,41 +1,43 @@
<div class="grid grid-cols-1 md:grid-cols-4" *ngIf="vm$ | async as vm"> @if (vm$ | async; as vm) {
<ng-container [ngSwitch]="selectedMetric"> <div class="grid grid-cols-1 md:grid-cols-4">
<ng-container *ngSwitchCase="DiagrammType.TimePerTour"> @switch (selectedMetric) {
<dks-time-per-tour-metric @case (DiagrammType.TimePerTour) {
class="col-span-3" <dks-time-per-tour-metric
[labelFormat]="labelFormat" class="col-span-3"
[metrics]="vm.metrics" [labelFormat]="labelFormat"
[metricTimeScale]="metricTimeScale"
[showCategories]="showCategories"
></dks-time-per-tour-metric>
<div class="flex flex-col justify-center items-center">
<mat-slide-toggle (toggleChange)="showCategories = !showCategories"
>Zeige Kategorien</mat-slide-toggle
>
<dks-time-per-tour-summary
class="m-auto"
[metrics]="vm.metrics" [metrics]="vm.metrics"
></dks-time-per-tour-summary> [metricTimeScale]="metricTimeScale"
</div> [showCategories]="showCategories"
</ng-container> ></dks-time-per-tour-metric>
<ng-container *ngSwitchCase="DiagrammType.Revenue"> <div class="flex flex-col justify-center items-center">
<dks-breakeven-metric <mat-slide-toggle (toggleChange)="showCategories = !showCategories"
class="col-span-3" >Zeige Kategorien</mat-slide-toggle
[labelFormat]="getLabelFormatForMetricTimeScale(metricTimeScale)" >
[metrics]="vm.metrics" <dks-time-per-tour-summary
[metricTimeScale]="metricTimeScale" class="m-auto"
></dks-breakeven-metric> [metrics]="vm.metrics"
<dks-breakeven-summary [metrics]="vm.metrics"></dks-breakeven-summary> ></dks-time-per-tour-summary>
</ng-container> </div>
<ng-container *ngSwitchCase="DiagrammType.AverageRevenue"> }
<dks-average-revenue-metric @case (DiagrammType.Revenue) {
class="col-span-3" <dks-breakeven-metric
[labelFormat]="getLabelFormatForMetricTimeScale(metricTimeScale)" class="col-span-3"
[metrics]="vm.metrics" [labelFormat]="getLabelFormatForMetricTimeScale(metricTimeScale)"
></dks-average-revenue-metric> [metrics]="vm.metrics"
<dks-average-revenue-summary [metricTimeScale]="metricTimeScale"
[metrics]="vm.metrics" ></dks-breakeven-metric>
></dks-average-revenue-summary> <dks-breakeven-summary [metrics]="vm.metrics"></dks-breakeven-summary>
</ng-container> }
</ng-container> @case (DiagrammType.AverageRevenue) {
</div> <dks-average-revenue-metric
class="col-span-3"
[labelFormat]="getLabelFormatForMetricTimeScale(metricTimeScale)"
[metrics]="vm.metrics"
></dks-average-revenue-metric>
<dks-average-revenue-summary
[metrics]="vm.metrics"
></dks-average-revenue-summary>
}
}
</div>
}

View File

@ -1,7 +1,8 @@
<div @if (echartsOptions$ | async; as echartsOptions) {
*ngIf="echartsOptions$ | async as echartsOptions" <div
dksEcharts dksEcharts
[defaultHeight]="300" [defaultHeight]="300"
[options]="echartsOptions" [options]="echartsOptions"
[extentions]="echartsExtensions" [extentions]="echartsExtensions"
></div> ></div>
}

View File

@ -1,12 +1,13 @@
<div class="m-auto flex flex-col" *ngIf="metricsSummary$ | async as summary"> @if (metricsSummary$ | async; as summary) {
<div class="flex items-center justify-end"> <div class="m-auto flex flex-col">
<span class="text-base">{{ summary.tourAmount | number : '1.2-2' }}</span> <div class="flex items-center justify-end">
<div class="p-1 text-sm" title="Menge Touren"> <span class="text-base">{{ summary.tourAmount | number : '1.2-2' }}</span>
<mat-icon>local_shipping</mat-icon> <div class="p-1 text-sm" title="Menge Touren">
<mat-icon>local_shipping</mat-icon>
</div>
</div> </div>
</div> <!-- TODO: Add feature toggle for forecast -->
<!-- TODO: Add feature toggle for forecast --> <!-- <div class="flex items-center justify-end text-yellow-500">
<!-- <div class="flex items-center justify-end text-yellow-500">
<span class="text-base">+{{ summary.forecast | number : '1.0-0' }}</span> <span class="text-base">+{{ summary.forecast | number : '1.0-0' }}</span>
<div class="p-1 text-sm" title="Vorausschau"> <div class="p-1 text-sm" title="Vorausschau">
<mat-icon>local_shipping</mat-icon> <mat-icon>local_shipping</mat-icon>
@ -15,28 +16,29 @@
<div class="flex items-center justify-end"> <div class="flex items-center justify-end">
<span class="text-base" <span class="text-base"
>{{ summary.availableTime | number : '1.2-2' }} h</span >{{ summary.availableTime | number : '1.2-2' }} h</span
> >
<div class="p-1 text-sm" title="Verfügbare Zeit"> <div class="p-1 text-sm" title="Verfügbare Zeit">
<mat-icon>departure_board</mat-icon> <mat-icon>departure_board</mat-icon>
</div>
</div> </div>
</div> <div class="flex items-center justify-end">
<div class="flex items-center justify-end"> <span class="text-base">{{
<span class="text-base">{{ summary.breakEven | currency : 'EUR' : 'symbol' : '1.2-2'
summary.breakEven | currency : 'EUR' : 'symbol' : '1.2-2' }}</span>
}}</span> <div class="p-1 text-sm" title="Deckungsbeitrag">
<div class="p-1 text-sm" title="Deckungsbeitrag"> <mat-icon>account_balance</mat-icon>
<mat-icon>account_balance</mat-icon> </div>
</div> </div>
</div> <!-- TODO: Add feature toggle for forecast -->
<!-- TODO: Add feature toggle for forecast --> <!-- <div class="flex items-center justify-end text-yellow-500">
<!-- <div class="flex items-center justify-end text-yellow-500">
<span class="text-base" <span class="text-base"
>+{{ >+{{
summary.forecastRevenue | currency : 'EUR' : 'symbol' : '1.2-2' summary.forecastRevenue | currency : 'EUR' : 'symbol' : '1.2-2'
}}</span }}</span
> >
<div class="p-1 text-sm" title="Vorausschau"> <div class="p-1 text-sm" title="Vorausschau">
<mat-icon>account_balance</mat-icon> <mat-icon>account_balance</mat-icon>
</div> </div>
</div> --> </div> -->
</div> </div>
}

View File

@ -1,53 +1,53 @@
<!-- class="py-5 w-full flex flex-col justify-center items-center bg-white shadow rounded cursor-pointer transition ease-in-out hover:shadow-md hover:scale-105 duration-200" --> <!-- class="py-5 w-full flex flex-col justify-center items-center bg-white shadow rounded cursor-pointer transition ease-in-out hover:shadow-md hover:scale-105 duration-200" -->
<mat-card @if (metricSummary$ | async; as summary) {
appearance="raised" <mat-card
class="cursor-pointer transition ease-in-out hover:scale-105 duration-200" appearance="raised"
class="cursor-pointer transition ease-in-out hover:scale-105 duration-200"
[ngClass]="{ [ngClass]="{
'!border': selected '!border': selected
}" }"
style="border-color: var(--mat-sys-primary)" style="border-color: var(--mat-sys-primary)"
(click)="setSelectedViewType()" (click)="setSelectedViewType()"
*ngIf="metricSummary$ | async as summary" >
> <mat-card-header class="!pb-2 justify-center">
<mat-card-header class="!pb-2 justify-center"> <mat-card-title>{{ viewType }}</mat-card-title>
<mat-card-title>{{ viewType }}</mat-card-title> </mat-card-header>
</mat-card-header> <!-- <span class="text-lg pb-4">{{ viewType }}</span> -->
<!-- <span class="text-lg pb-4">{{ viewType }}</span> --> <mat-card-content class="!flex justify-center">
@switch (currentMetric) { @case (DiagrammType.TimePerTour) {
<mat-card-content class="!flex justify-center"> <div class="w-1/4 flex flex-col items-end">
@switch (currentMetric) { @case (DiagrammType.TimePerTour) { <div class="flex items-center pb-2" title="Touren Menge">
<div class="w-1/4 flex flex-col items-end"> {{ summary.tourAmount.toFixed(2) }}
<div class="flex items-center pb-2" title="Touren Menge"> <mat-icon class="mx-2">local_shipping</mat-icon>
{{ summary.tourAmount.toFixed(2) }} </div>
<mat-icon class="mx-2">local_shipping</mat-icon> <div class="flex items-center" title="Verfügbare Zeit">
</div> {{ summary.availableTime.toFixed(2) }}
<div class="flex items-center" title="Verfügbare Zeit"> <mat-icon class="mx-2">departure_board</mat-icon>
{{ summary.availableTime.toFixed(2) }} </div>
<mat-icon class="mx-2">departure_board</mat-icon> </div>
</div> } @case (DiagrammType.Revenue) {
</div> <div class="w-1/4 flex flex-col items-end">
} @case (DiagrammType.Revenue) { <div class="flex items-center pb-2" title="Umsatz">
<div class="w-1/4 flex flex-col items-end"> {{ summary.revenue | currency : "EUR" : "symbol" : ".2" }}
<div class="flex items-center pb-2" title="Umsatz"> <mat-icon class="mx-2">euro</mat-icon>
{{ summary.revenue | currency : "EUR" : "symbol" : ".2" }} </div>
<mat-icon class="mx-2">euro</mat-icon> <div class="flex items-center" title="Deckungsbeitrag">
</div> {{ summary.breakEven | currency : "EUR" }}
<div class="flex items-center" title="Deckungsbeitrag"> <mat-icon class="mx-2">account_balance</mat-icon>
{{ summary.breakEven | currency : "EUR" }} </div>
<mat-icon class="mx-2">account_balance</mat-icon> </div>
</div> } @case (DiagrammType.AverageRevenue) {
</div> <div class="w-1/4 flex flex-col items-end">
} @case (DiagrammType.AverageRevenue) { <div class="flex items-center pb-2" title="Durchschnittlicher Umsatz">
<div class="w-1/4 flex flex-col items-end"> <span class="mr-1">Ø</span>
<div class="flex items-center pb-2" title="Durchschnittlicher Umsatz"> {{ summary.averageRevenue | currency : "EUR" }}
<span class="mr-1">Ø</span> </div>
{{ summary.averageRevenue | currency : "EUR" }} <div class="flex items-center" title="Zeit pro Tour Index">
</div> {{ calculateIndex(summary).toFixed(2) }}
<div class="flex items-center" title="Zeit pro Tour Index"> <span class="mx-2">Index</span>
{{ calculateIndex(summary).toFixed(2) }} </div>
<span class="mx-2">Index</span> </div>
</div> } }
</div> </mat-card-content>
} } </mat-card>
</mat-card-content> }
</mat-card>

View File

@ -1,9 +1,4 @@
import { import { AsyncPipe, CurrencyPipe, NgClass } from '@angular/common';
AsyncPipe,
CurrencyPipe,
NgClass,
NgIf
} from '@angular/common';
import { Component, EventEmitter, Input, Output } from '@angular/core'; import { Component, EventEmitter, Input, Output } from '@angular/core';
import { MatCardModule } from '@angular/material/card'; import { MatCardModule } from '@angular/material/card';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
@ -15,13 +10,12 @@ import { summarizeMetrics } from '../../util/summarize-metrics';
@Component({ @Component({
selector: 'dks-view-type-selector', selector: 'dks-view-type-selector',
imports: [ imports: [
NgIf,
NgClass, NgClass,
AsyncPipe, AsyncPipe,
CurrencyPipe, CurrencyPipe,
MatIconModule, MatIconModule,
MatCardModule, MatCardModule
], ],
templateUrl: './view-type-selector.component.html', templateUrl: './view-type-selector.component.html',
styleUrls: ['./view-type-selector.component.css'], styleUrls: ['./view-type-selector.component.css'],
}) })

View File

@ -1,141 +1,143 @@
<ng-container *ngIf="vm$ | async as vm; else loadingTmpl"> @if (vm$ | async; as vm) {
<div class="flex flex-col m-0 md:m-4" *ngIf="!vm.isLoading; else loadingTmpl"> @if (!vm.isLoading) {
<!-- Top row with user input elements --> <div class="flex flex-col m-0 md:m-4">
<div class="flex flex-wrap md:flex-nowrap justify-between items-center"> <!-- Top row with user input elements -->
<div class="flex items-center m-2"> <div class="flex flex-wrap md:flex-nowrap justify-between items-center">
<button mat-icon-button (click)="addMonthToSelectedDate(-1)"> <div class="flex items-center m-2">
<mat-icon>chevron_left</mat-icon> <button mat-icon-button (click)="addMonthToSelectedDate(-1)">
</button> <mat-icon>chevron_left</mat-icon>
<div class="flex items-center"> </button>
<span class="pr-2"> <div class="flex items-center">
{{ vm.selectedDate | date : "MM.yyyy" }} <span class="pr-2">
</span> {{ vm.selectedDate | date : "MM.yyyy" }}
<input </span>
[formControl]="selectedDate" <input
[matDatepicker]="picker" [formControl]="selectedDate"
[matDatepickerFilter]="notSunday" [matDatepicker]="picker"
[min]="minSelectableDate" [matDatepickerFilter]="notSunday"
class="invisible w-0" [min]="minSelectableDate"
/> class="invisible w-0"
<button mat-icon-button (click)="picker.open()"> />
<mat-icon>today</mat-icon> <button mat-icon-button (click)="picker.open()">
<mat-icon>today</mat-icon>
</button>
<mat-datepicker
#picker
startView="multi-year"
(monthSelected)="setMonthAndYear($event, picker)"
class="month-picker"
></mat-datepicker>
</div>
<button mat-icon-button (click)="addMonthToSelectedDate(1)">
<mat-icon>chevron_right</mat-icon>
</button> </button>
<mat-datepicker
#picker
startView="multi-year"
(monthSelected)="setMonthAndYear($event, picker)"
class="month-picker"
></mat-datepicker>
</div> </div>
<button mat-icon-button (click)="addMonthToSelectedDate(1)"> <mat-chip-listbox (change)="changeSelectedType($event)">
<mat-icon>chevron_right</mat-icon> <mat-chip-option
</button> [value]="DiagrammType.Revenue"
[selected]="vm.selectedMetric === DiagrammType.Revenue"
>
Umsatz
</mat-chip-option>
<mat-chip-option
[value]="DiagrammType.TimePerTour"
[selected]="vm.selectedMetric === DiagrammType.TimePerTour"
>
Kapazität pro Tour
</mat-chip-option>
<mat-chip-option
[value]="DiagrammType.AverageRevenue"
[selected]="vm.selectedMetric === DiagrammType.AverageRevenue"
>
Durchschnittsumsatz
</mat-chip-option>
</mat-chip-listbox>
</div> </div>
<div
<mat-chip-listbox (change)="changeSelectedType($event)"> class="my-5 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-2 lg:gap-4"
<mat-chip-option
[value]="DiagrammType.Revenue"
[selected]="vm.selectedMetric === DiagrammType.Revenue"
> >
Umsatz <dks-view-type-selector
</mat-chip-option> [viewType]="DashboardViewType.BTW"
<mat-chip-option [currentMetric]="vm.selectedMetric"
[value]="DiagrammType.TimePerTour" [metrics]="vm.btwMetrics"
[selected]="vm.selectedMetric === DiagrammType.TimePerTour" [selected]="vm.selectedViewType === DashboardViewType.BTW"
(viewTypeSelected)="setSelectedViewType(DashboardViewType.BTW)"
></dks-view-type-selector>
<dks-view-type-selector
[viewType]="DashboardViewType.TSW"
[currentMetric]="vm.selectedMetric"
[metrics]="vm.tswMetrics"
[selected]="vm.selectedViewType === DashboardViewType.TSW"
(viewTypeSelected)="setSelectedViewType(DashboardViewType.TSW)"
></dks-view-type-selector>
<dks-view-type-selector
[viewType]="DashboardViewType.KTW"
[currentMetric]="vm.selectedMetric"
[metrics]="vm.ktwMetrics"
[selected]="vm.selectedViewType === DashboardViewType.KTW"
(viewTypeSelected)="setSelectedViewType(DashboardViewType.KTW)"
></dks-view-type-selector>
<dks-view-type-selector
[viewType]="DashboardViewType.Gesamt"
[currentMetric]="vm.selectedMetric"
[metrics]="vm.combinedMetrics"
[selected]="vm.selectedViewType === DashboardViewType.Gesamt"
(viewTypeSelected)="setSelectedViewType(DashboardViewType.Gesamt)"
></dks-view-type-selector>
</div>
<!-- Visualisation -->
<mat-card
appearance="outlined"
class="grid grid-cols-2 lg:grid-cols-4 gap-2 lg:gap-4"
> >
Kapazität pro Tour <mat-card-content>
</mat-chip-option> @switch (vm.selectedViewType) { @case (DashboardViewType.BTW) {
<mat-chip-option <div class="col-span-2 lg:col-span-4 w-full">
[value]="DiagrammType.AverageRevenue" <dks-metric-summary
[selected]="vm.selectedMetric === DiagrammType.AverageRevenue" [metrics]="vm.btwMetrics"
> [metricTimeScale]="metricTimeScale"
Durchschnittsumsatz [selectedMetric]="vm.selectedMetric"
</mat-chip-option> [labelFormat]="'d.M.yy'"
</mat-chip-listbox> ></dks-metric-summary>
</div> </div>
<div } @case (DashboardViewType.TSW) {
class="my-5 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-2 lg:gap-4" <div class="col-span-2 lg:col-span-4 w-full">
> <dks-metric-summary
<dks-view-type-selector [metrics]="vm.tswMetrics"
[viewType]="DashboardViewType.BTW" [metricTimeScale]="metricTimeScale"
[currentMetric]="vm.selectedMetric" [selectedMetric]="vm.selectedMetric"
[metrics]="vm.btwMetrics" [labelFormat]="'d.M.yy'"
[selected]="vm.selectedViewType === DashboardViewType.BTW" ></dks-metric-summary>
(viewTypeSelected)="setSelectedViewType(DashboardViewType.BTW)" </div>
></dks-view-type-selector> } @case (DashboardViewType.KTW) {
<dks-view-type-selector <div
[viewType]="DashboardViewType.TSW" class="col-span-2 lg:col-span-4 w-full rounded"
[currentMetric]="vm.selectedMetric" >
[metrics]="vm.tswMetrics" <dks-metric-summary
[selected]="vm.selectedViewType === DashboardViewType.TSW" [metrics]="vm.ktwMetrics"
(viewTypeSelected)="setSelectedViewType(DashboardViewType.TSW)" [metricTimeScale]="metricTimeScale"
></dks-view-type-selector> [selectedMetric]="vm.selectedMetric"
<dks-view-type-selector [labelFormat]="'d.M.yy'"
[viewType]="DashboardViewType.KTW" ></dks-metric-summary>
[currentMetric]="vm.selectedMetric" </div>
[metrics]="vm.ktwMetrics" } @case (DashboardViewType.Gesamt) {
[selected]="vm.selectedViewType === DashboardViewType.KTW" <div
(viewTypeSelected)="setSelectedViewType(DashboardViewType.KTW)" class="col-span-2 lg:col-span-4 w-full"
></dks-view-type-selector> >
<dks-view-type-selector <dks-metric-summary
[viewType]="DashboardViewType.Gesamt" [metrics]="vm.combinedMetrics"
[currentMetric]="vm.selectedMetric" [metricTimeScale]="metricTimeScale"
[metrics]="vm.combinedMetrics" [selectedMetric]="vm.selectedMetric"
[selected]="vm.selectedViewType === DashboardViewType.Gesamt" [labelFormat]="'d.M.yy'"
(viewTypeSelected)="setSelectedViewType(DashboardViewType.Gesamt)" ></dks-metric-summary>
></dks-view-type-selector> </div>
</div> } }
<!-- Visualisation --> </mat-card-content>
<mat-card </mat-card>
appearance="outlined" </div>
class="grid grid-cols-2 lg:grid-cols-4 gap-2 lg:gap-4" } @else {
> <mat-progress-bar mode="indeterminate"></mat-progress-bar>
<mat-card-content> }
@switch (vm.selectedViewType) { @case (DashboardViewType.BTW) { } @else {
<div class="col-span-2 lg:col-span-4 w-full"> <mat-progress-bar mode="indeterminate"></mat-progress-bar>
<dks-metric-summary }
[metrics]="vm.btwMetrics"
[metricTimeScale]="metricTimeScale"
[selectedMetric]="vm.selectedMetric"
[labelFormat]="'d.M.yy'"
></dks-metric-summary>
</div>
} @case (DashboardViewType.TSW) {
<div class="col-span-2 lg:col-span-4 w-full">
<dks-metric-summary
[metrics]="vm.tswMetrics"
[metricTimeScale]="metricTimeScale"
[selectedMetric]="vm.selectedMetric"
[labelFormat]="'d.M.yy'"
></dks-metric-summary>
</div>
} @case (DashboardViewType.KTW) {
<div
class="col-span-2 lg:col-span-4 w-full rounded"
>
<dks-metric-summary
[metrics]="vm.ktwMetrics"
[metricTimeScale]="metricTimeScale"
[selectedMetric]="vm.selectedMetric"
[labelFormat]="'d.M.yy'"
></dks-metric-summary>
</div>
} @case (DashboardViewType.Gesamt) {
<div
class="col-span-2 lg:col-span-4 w-full"
>
<dks-metric-summary
[metrics]="vm.combinedMetrics"
[metricTimeScale]="metricTimeScale"
[selectedMetric]="vm.selectedMetric"
[labelFormat]="'d.M.yy'"
></dks-metric-summary>
</div>
} }
</mat-card-content>
</mat-card>
</div>
</ng-container>
<ng-template #loadingTmpl>
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
</ng-template>

View File

@ -1,71 +1,75 @@
<div @if (vm$ | async; as vm) {
class="flex flex-col m-0 md:m-4" <div
*ngIf="vm$ | async as vm; else loadingTmpl" class="flex flex-col m-0 md:m-4"
> >
<div class="flex flex-wrap md:flex-nowrap"> <div class="flex flex-wrap md:flex-nowrap">
<!-- KPI --> <!-- KPI -->
<dks-time-per-tour-kpi <dks-time-per-tour-kpi
class="w-full md:w-1/3 m-2 cursor-pointer rounded-xl transition ease-in-out hover:scale-105 duration-200" class="w-full md:w-1/3 m-2 cursor-pointer rounded-xl transition ease-in-out hover:scale-105 duration-200"
[ngClass]="{ [ngClass]="{
'border': vm.selectedOrdinanceType === OrdinanceTypes.BTW 'border': vm.selectedOrdinanceType === OrdinanceTypes.BTW
}" }"
style="border-color: var(--mat-sys-primary);" style="border-color: var(--mat-sys-primary);"
[text]="OrdinanceTypes.BTW" [text]="OrdinanceTypes.BTW"
[kpis]="vm.btwKPI.summaries" [kpis]="vm.btwKPI.summaries"
(click)="setSelectedOrdinanceType(OrdinanceTypes.BTW)" (click)="setSelectedOrdinanceType(OrdinanceTypes.BTW)"
></dks-time-per-tour-kpi> ></dks-time-per-tour-kpi>
<dks-time-per-tour-kpi <dks-time-per-tour-kpi
class="w-full md:w-1/3 m-2 cursor-pointer rounded-xl transition ease-in-out hover:scale-105 duration-200" class="w-full md:w-1/3 m-2 cursor-pointer rounded-xl transition ease-in-out hover:scale-105 duration-200"
[ngClass]="{ [ngClass]="{
'border': vm.selectedOrdinanceType === OrdinanceTypes.TSW 'border': vm.selectedOrdinanceType === OrdinanceTypes.TSW
}" }"
style="border-color: var(--mat-sys-primary);" style="border-color: var(--mat-sys-primary);"
[text]="OrdinanceTypes.TSW" [text]="OrdinanceTypes.TSW"
[kpis]="vm.tswKPI.summaries" [kpis]="vm.tswKPI.summaries"
(click)="setSelectedOrdinanceType(OrdinanceTypes.TSW)" (click)="setSelectedOrdinanceType(OrdinanceTypes.TSW)"
></dks-time-per-tour-kpi> ></dks-time-per-tour-kpi>
<dks-time-per-tour-kpi <dks-time-per-tour-kpi
class="w-full md:w-1/3 m-2 cursor-pointer rounded-xl transition ease-in-out hover:scale-105 duration-200" class="w-full md:w-1/3 m-2 cursor-pointer rounded-xl transition ease-in-out hover:scale-105 duration-200"
[ngClass]="{ [ngClass]="{
'border': vm.selectedOrdinanceType === OrdinanceTypes.KTW 'border': vm.selectedOrdinanceType === OrdinanceTypes.KTW
}" }"
style="border-color: var(--mat-sys-primary);" style="border-color: var(--mat-sys-primary);"
[text]="OrdinanceTypes.KTW" [text]="OrdinanceTypes.KTW"
[kpis]="vm.ktwKPI.summaries" [kpis]="vm.ktwKPI.summaries"
(click)="setSelectedOrdinanceType(OrdinanceTypes.KTW)" (click)="setSelectedOrdinanceType(OrdinanceTypes.KTW)"
></dks-time-per-tour-kpi> ></dks-time-per-tour-kpi>
</div>
@if (vm.selectedOrdinanceType) {
<div
class="md:p-2 flex flex-wrap md:flex-nowrap"
>
<!-- Data Table -->
<mat-card appearance="outlined" class="w-full">
<mat-card-content>
@switch (vm.selectedOrdinanceType) {
@case (OrdinanceTypes.BTW) {
<dks-time-per-tour-by-car
[columns]="vm.btwKPI.cars.columns"
[data]="vm.btwKPI.cars.data"
class="w-full"
></dks-time-per-tour-by-car>
}
@case (OrdinanceTypes.TSW) {
<dks-time-per-tour-by-car
[columns]="vm.tswKPI.cars.columns"
[data]="vm.tswKPI.cars.data"
class="w-full"
></dks-time-per-tour-by-car>
}
@case (OrdinanceTypes.KTW) {
<dks-time-per-tour-by-car
[columns]="vm.ktwKPI.cars.columns"
[data]="vm.ktwKPI.cars.data"
class="w-full"
></dks-time-per-tour-by-car>
}
}
</mat-card-content>
</mat-card>
</div>
}
</div> </div>
<div } @else {
class="md:p-2 flex flex-wrap md:flex-nowrap"
*ngIf="vm.selectedOrdinanceType"
>
<!-- Data Table -->
<mat-card appearance="outlined" class="w-full">
<mat-card-content>
<ng-container [ngSwitch]="vm.selectedOrdinanceType">
<dks-time-per-tour-by-car
[columns]="vm.btwKPI.cars.columns"
[data]="vm.btwKPI.cars.data"
class="w-full"
*ngSwitchCase="OrdinanceTypes.BTW"
></dks-time-per-tour-by-car>
<dks-time-per-tour-by-car
[columns]="vm.tswKPI.cars.columns"
[data]="vm.tswKPI.cars.data"
class="w-full"
*ngSwitchCase="OrdinanceTypes.TSW"
></dks-time-per-tour-by-car>
<dks-time-per-tour-by-car
[columns]="vm.ktwKPI.cars.columns"
[data]="vm.ktwKPI.cars.data"
class="w-full"
*ngSwitchCase="OrdinanceTypes.KTW"
></dks-time-per-tour-by-car>
</ng-container>
</mat-card-content>
</mat-card>
</div>
</div>
<ng-template #loadingTmpl>
<mat-progress-bar mode="indeterminate"></mat-progress-bar> <mat-progress-bar mode="indeterminate"></mat-progress-bar>
</ng-template> }

View File

@ -6,29 +6,33 @@
class="full-width-table" class="full-width-table"
matSort matSort
aria-label="Fahrzeuge" aria-label="Fahrzeuge"
> >
<ng-container matColumnDef="{{ column.id }}" *ngFor="let column of columns"> @for (column of columns; track column) {
<th mat-header-cell *matHeaderCellDef>{{ column.title }}</th> <ng-container matColumnDef="{{ column.id }}">
<td mat-cell *matCellDef="let carData" [ngClass]="{'bg-gray-50': carData.carName === 'Touren ohne Zuweisung'}">{{ carData[column.id] }}{{(column.id !=='carName' && carData.carName === 'Touren ohne Zuweisung') ? 'T' : ''}}</td> <th mat-header-cell *matHeaderCellDef>{{ column.title }}</th>
</ng-container> <td mat-cell *matCellDef="let carData" [ngClass]="{'bg-gray-50': carData.carName === 'Touren ohne Zuweisung'}">{{ carData[column.id] }}{{(column.id !=='carName' && carData.carName === 'Touren ohne Zuweisung') ? 'T' : ''}}</td>
</ng-container>
}
<ng-container matColumnDef="expandedDetail"> <ng-container matColumnDef="expandedDetail">
<td mat-cell *matCellDef="let carData" [attr.colspan]="displayedColumns.length"> <td mat-cell *matCellDef="let carData" [attr.colspan]="displayedColumns.length">
<div class="overflow-hidden flex flex-col items-start pl-0 md:pl-8" [@detailExpand]="carData === expandedCarData ? 'expanded' : 'collapsed'"> <div class="overflow-hidden flex flex-col items-start pl-0 md:pl-8" [@detailExpand]="carData === expandedCarData ? 'expanded' : 'collapsed'">
<div class="flex" *ngFor="let detail of carData.additionalData; index as idx"> @for (detail of carData.additionalData; track detail; let idx = $index) {
<div class="pr-2 md:px-4"> <div class="flex">
<span class="text-base font-semibold">{{getSlotTitleByIndex(idx)}}</span> <div class="pr-2 md:px-4">
<span class="text-base font-semibold">{{getSlotTitleByIndex(idx)}}</span>
</div>
<div class="px-2 md:px-4 w-20 md:w-32 flex items-center justify-end">
<span class="pr-2 text-base">{{detail.availableTime}}</span><mat-icon title="Verfügbare Zeit">departure_board</mat-icon>
</div>
<div class="px-2 md:px-4 w-20 md:w-32 flex items-center justify-end">
<span class="pr-2 text-base">{{detail.tourAmount}}</span><mat-icon title="Menge Touren">local_shipping</mat-icon>
</div>
<div class="px-2 md:px-4 w-20 md:w-32 flex items-center justify-end">
<span class="pr-2 text-base">{{detail.breakEven}}€</span><mat-icon title="Deckungsbeitrag">account_balance</mat-icon>
</div>
</div> </div>
<div class="px-2 md:px-4 w-20 md:w-32 flex items-center justify-end"> }
<span class="pr-2 text-base">{{detail.availableTime}}</span><mat-icon title="Verfügbare Zeit">departure_board</mat-icon>
</div>
<div class="px-2 md:px-4 w-20 md:w-32 flex items-center justify-end">
<span class="pr-2 text-base">{{detail.tourAmount}}</span><mat-icon title="Menge Touren">local_shipping</mat-icon>
</div>
<div class="px-2 md:px-4 w-20 md:w-32 flex items-center justify-end">
<span class="pr-2 text-base">{{detail.breakEven}}€</span><mat-icon title="Deckungsbeitrag">account_balance</mat-icon>
</div>
</div>
</div> </div>
</td> </td>
</ng-container> </ng-container>

View File

@ -9,31 +9,32 @@
<div class="p-1 text-sm" title="Menge Touren"><mat-icon>local_shipping</mat-icon></div> <div class="p-1 text-sm" title="Menge Touren"><mat-icon>local_shipping</mat-icon></div>
<div class="p-1 text-sm" title="Deckungsbeitrag"><mat-icon>account_balance</mat-icon></div> <div class="p-1 text-sm" title="Deckungsbeitrag"><mat-icon>account_balance</mat-icon></div>
</div> </div>
<div @for (kpi of kpis; track kpi; let idx = $index) {
*ngFor="let kpi of kpis; index as idx"
class="w-1/3 flex flex-col justify-center items-center"
>
<div <div
class="font-bold" class="w-1/3 flex flex-col justify-center items-center"
style="border-color: var(--mat-sys-primary);" >
[ngClass]="{ 'border-b-2': (currentTimeSlot$ | async) === idx }" <div
> class="font-bold"
{{ kpi.interval.start | date: 'HH:mm' }} - style="border-color: var(--mat-sys-primary);"
{{ kpi.interval.end | date: 'HH:mm' }} [ngClass]="{ 'border-b-2': (currentTimeSlot$ | async) === idx }"
>
{{ kpi.interval.start | date: 'HH:mm' }} -
{{ kpi.interval.end | date: 'HH:mm' }}
</div>
<div class="p-4 text-base">
{{ kpi.kpi.value.toString().replace('.', ',') }}
</div>
<div class="p-2 flex justify-center items-center text-sm">
{{ kpi.kpi.meta.availableTime }}
</div>
<div class="p-2 flex justify-center items-center text-sm">
{{ kpi.kpi.meta.tourAmount }}
</div>
<div class="p-2 flex justify-center items-center text-sm">
{{ kpi.kpi.meta.breakEven}}€
</div>
</div> </div>
<div class="p-4 text-base"> }
{{ kpi.kpi.value.toString().replace('.', ',') }}
</div>
<div class="p-2 flex justify-center items-center text-sm">
{{ kpi.kpi.meta.availableTime }}
</div>
<div class="p-2 flex justify-center items-center text-sm">
{{ kpi.kpi.meta.tourAmount }}
</div>
<div class="p-2 flex justify-center items-center text-sm">
{{ kpi.kpi.meta.breakEven}}€
</div>
</div>
</div> </div>
</mat-card-content> </mat-card-content>
</mat-card> </mat-card>

View File

@ -1,28 +1,29 @@
<ng-container *ngIf="ticket"> @if (ticket) {
<div class="flex justify-between"> <div class="flex justify-between">
<span class="text-base"> <span class="text-base">
{{ ticket.tours.at(0)?.ordinanceType }} {{ ticket.tours.at(0)?.ordinanceType }}
</span> </span>
<div> <div>
<mat-icon @if (ticket.errors.length) {
*ngIf="ticket.errors.length" <mat-icon
[matBadge]="ticket.errors.length" [matBadge]="ticket.errors.length"
matBadgeColor="warn" matBadgeColor="warn"
[title]="ticket.errors.length + ' Anmerkungen'" [title]="ticket.errors.length + ' Anmerkungen'"
aria-hidden="false" aria-hidden="false"
> >
error error
</mat-icon> </mat-icon>
}
</div> </div>
</div> </div>
<span <span
>{{ ticket.tours.at(0)?.patientName }} >{{ ticket.tours.at(0)?.patientName }}
{{ ticket.tours.at(0)?.patientSurname }}</span {{ ticket.tours.at(0)?.patientSurname }}</span
> >
<span <span
>{{ getToursLength() }} Tour{{ >{{ getToursLength() }} Tour{{
getToursLength() > 1 ? 'en' : '' getToursLength() > 1 ? 'en' : ''
}}</span }}</span
> >
<span class="text-xs self-end">{{ getDistance(ticket.createdAt) }}</span> <span class="text-xs self-end">{{ getDistance(ticket.createdAt) }}</span>
</ng-container> }

View File

@ -1,4 +1,4 @@
import { NgIf } from '@angular/common';
import { import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
Component, Component,
@ -32,7 +32,7 @@ import CategoryColors from './category-colors';
} }
`, `,
], ],
imports: [MatIconModule, MatBadgeModule, NgIf], imports: [MatIconModule, MatBadgeModule],
}) })
export class TicketItemComponent implements OnInit { export class TicketItemComponent implements OnInit {
@Input() ticket!: Ticket & { tours: Tour[] }; @Input() ticket!: Ticket & { tours: Tour[] };

View File

@ -1,31 +1,41 @@
import { NgIf } from '@angular/common';
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { TicketStateMeta } from '../../../core/data-access/graphql/generated/generated'; import { TicketStateMeta } from '../../../core/data-access/graphql/generated/generated';
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'dks-ticket-lane-footer', selector: 'dks-ticket-lane-footer',
template: `<span *ngIf="withTicketCount" class="text-left text-xs"> template: `@if (withTicketCount) {
{{ ticketStateMeta.count }} Tickets <span class="text-left text-xs">
</span> {{ ticketStateMeta.count }} Tickets
<span *ngIf="withApprovalRequiredCount" class="text-left text-xs"> </span>
{{ ticketStateMeta.approvalRequiredCount }} davon genehmigungspflichtig }
</span> @if (withApprovalRequiredCount) {
<span *ngIf="withApprovalUnknownCount" class="text-left text-xs"> <span class="text-left text-xs">
{{ ticketStateMeta.approvalUnknownCount }} davon auf Genehmigungspflicht {{ ticketStateMeta.approvalRequiredCount }} davon genehmigungspflichtig
zu prüfen </span>
</span> }
<span *ngIf="withApprovalNotCompanyCount" class="text-left text-xs"> @if (withApprovalUnknownCount) {
{{ ticketStateMeta.approvalNotCompanyCount }} davon fehlende Genehmigung <span class="text-left text-xs">
</span> {{ ticketStateMeta.approvalUnknownCount }} davon auf Genehmigungspflicht
<span *ngIf="withTdNotCompanyCount" class="text-left text-xs"> zu prüfen
{{ ticketStateMeta.tdNotCompanyCount }} davon fehlender Transportschein </span>
</span> `, }
@if (withApprovalNotCompanyCount) {
<span class="text-left text-xs">
{{ ticketStateMeta.approvalNotCompanyCount }} davon fehlende Genehmigung
</span>
}
@if (withTdNotCompanyCount) {
<span class="text-left text-xs">
{{ ticketStateMeta.tdNotCompanyCount }} davon fehlender Transportschein
</span>
}`,
host: { host: {
class: 'flex flex-col items-center justify-center p-2', class: 'flex flex-col items-center justify-center p-2',
}, },
styles: [``], styles: [``],
imports: [NgIf], imports: [],
}) })
export class TicketLaneFooterComponent { export class TicketLaneFooterComponent {
@Input() ticketStateMeta!: TicketStateMeta; @Input() ticketStateMeta!: TicketStateMeta;

View File

@ -1,4 +1,4 @@
import { NgFor, NgSwitch, NgSwitchCase } from '@angular/common';
import { import {
AfterViewInit, AfterViewInit,
ChangeDetectionStrategy, ChangeDetectionStrategy,
@ -24,92 +24,96 @@ import {
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'dks-ticket-lane-header-filter', selector: 'dks-ticket-lane-header-filter',
template: ` template: `
<ng-container [ngSwitch]="state"> @switch (state) {
<button @case (TicketValidationState.Created) {
mat-icon-button <button
[matMenuTriggerFor]="createdMenu" mat-icon-button
aria-label="Touren angenommen Filter button" [matMenuTriggerFor]="createdMenu"
*ngSwitchCase="TicketValidationState.Created" aria-label="Touren angenommen Filter button"
> >
<mat-icon>filter_list</mat-icon> <mat-icon>filter_list</mat-icon>
</button> </button>
<button }
mat-icon-button @case (TicketValidationState.UserInteractionRequired) {
[matMenuTriggerFor]="userInteractionMenu" <button
aria-label="Touren angenommen Filter button" mat-icon-button
*ngSwitchCase="TicketValidationState.UserInteractionRequired" [matMenuTriggerFor]="userInteractionMenu"
aria-label="Touren angenommen Filter button"
> >
<mat-icon>filter_list</mat-icon> <mat-icon>filter_list</mat-icon>
</button> </button>
<button }
mat-icon-button @case (TicketValidationState.TdUncertain) {
[matMenuTriggerFor]="tdUncertainMenu" <button
aria-label="Touren angenommen Filter button" mat-icon-button
*ngSwitchCase="TicketValidationState.TdUncertain" [matMenuTriggerFor]="tdUncertainMenu"
aria-label="Touren angenommen Filter button"
> >
<mat-icon>filter_list</mat-icon> <mat-icon>filter_list</mat-icon>
</button> </button>
<button }
mat-icon-button @case (TicketValidationState.DocumentsMissing) {
[matMenuTriggerFor]="documentsMenu" <button
aria-label="Touren angenommen Filter button" mat-icon-button
*ngSwitchCase="TicketValidationState.DocumentsMissing" [matMenuTriggerFor]="documentsMenu"
aria-label="Touren angenommen Filter button"
> >
<mat-icon>filter_list</mat-icon> <mat-icon>filter_list</mat-icon>
</button> </button>
</ng-container> }
<form #form="ngForm"> }
<mat-menu #createdMenu="matMenu"> <form #form="ngForm">
<section <mat-menu #createdMenu="matMenu">
mat-menu-item @for (item of filter.errors; track item) {
(click)="$event.stopPropagation()" <section
*ngFor="let item of filter.errors" mat-menu-item
(click)="$event.stopPropagation()"
> >
<mat-checkbox [(ngModel)]="item.checked" [name]="item.value">{{ <mat-checkbox [(ngModel)]="item.checked" [name]="item.value">{{
item.text item.text
}}</mat-checkbox> }}</mat-checkbox>
</section> </section>
</mat-menu> }
<mat-menu #userInteractionMenu="matMenu"> </mat-menu>
<section <mat-menu #userInteractionMenu="matMenu">
mat-menu-item @for (item of filter.approvalStates; track item) {
(click)="$event.stopPropagation()" <section
*ngFor="let item of filter.approvalStates" mat-menu-item
(click)="$event.stopPropagation()"
> >
<mat-checkbox [(ngModel)]="item.checked" [name]="item.value">{{ <mat-checkbox [(ngModel)]="item.checked" [name]="item.value">{{
item.text item.text
}}</mat-checkbox> }}</mat-checkbox>
</section> </section>
</mat-menu> }
<mat-menu #tdUncertainMenu="matMenu"> </mat-menu>
<section mat-menu-item> <mat-menu #tdUncertainMenu="matMenu">
<mat-checkbox checked="true" disabled="true">Alle</mat-checkbox> <section mat-menu-item>
</section> <mat-checkbox checked="true" disabled="true">Alle</mat-checkbox>
</mat-menu> </section>
<mat-menu #documentsMenu="matMenu"> </mat-menu>
<section <mat-menu #documentsMenu="matMenu">
mat-menu-item @for (item of filter.documentInfo; track item) {
(click)="$event.stopPropagation()" <section
*ngFor="let item of filter.documentInfo" mat-menu-item
(click)="$event.stopPropagation()"
> >
<mat-checkbox [(ngModel)]="item.checked" [name]="item.value">{{ <mat-checkbox [(ngModel)]="item.checked" [name]="item.value">{{
item.text item.text
}}</mat-checkbox> }}</mat-checkbox>
</section> </section>
</mat-menu> }
</form> </mat-menu>
`, </form>
`,
styles: [], styles: [],
imports: [ imports: [
NgSwitch, FormsModule,
NgSwitchCase, MatButtonModule,
NgFor, MatIconModule,
FormsModule, MatMenuModule,
MatButtonModule, MatCheckboxModule
MatIconModule, ]
MatMenuModule,
MatCheckboxModule,
]
}) })
export class TicketLaneHeaderFilterComponent implements AfterViewInit { export class TicketLaneHeaderFilterComponent implements AfterViewInit {
@Input() state!: TicketValidationState; @Input() state!: TicketValidationState;

View File

@ -1,4 +1,4 @@
import { NgSwitch, NgSwitchCase } from '@angular/common';
import { import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
Component, Component,
@ -16,23 +16,33 @@ import { TicketLaneHeaderFilterComponent } from './ticket-lane-header-filter.com
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'dks-ticket-lane-header', selector: 'dks-ticket-lane-header',
template: `<div></div> template: `<div></div>
<ng-container [ngSwitch]="state"> @switch (state) {
<div *ngSwitchCase="TicketValidationState.Created">Touren angenommen</div> @case (TicketValidationState.Created) {
<div *ngSwitchCase="TicketValidationState.UserInteractionRequired"> <div>Touren angenommen</div>
Genehmigung/Transportschein }
</div> @case (TicketValidationState.UserInteractionRequired) {
<div *ngSwitchCase="TicketValidationState.TdUncertain"> <div>
Transportschein unklar Genehmigung/Transportschein
</div> </div>
<div *ngSwitchCase="TicketValidationState.DocumentsMissing"> }
Dokumente fehlen @case (TicketValidationState.TdUncertain) {
</div> <div>
<div *ngSwitchCase="TicketValidationState.Archived">Archiv</div> Transportschein unklar
</ng-container> </div>
<dks-ticket-lane-header-filter }
[state]="state" @case (TicketValidationState.DocumentsMissing) {
(filterChange)="filterChanged($event)" <div>
></dks-ticket-lane-header-filter>`, Dokumente fehlen
</div>
}
@case (TicketValidationState.Archived) {
<div>Archiv</div>
}
}
<dks-ticket-lane-header-filter
[state]="state"
(filterChange)="filterChanged($event)"
></dks-ticket-lane-header-filter>`,
host: { host: {
class: 'flex items-center justify-between p-2', class: 'flex items-center justify-between p-2',
}, },
@ -44,7 +54,7 @@ import { TicketLaneHeaderFilterComponent } from './ticket-lane-header-filter.com
} }
`, `,
], ],
imports: [NgSwitch, NgSwitchCase, TicketLaneHeaderFilterComponent] imports: [TicketLaneHeaderFilterComponent]
}) })
export class TicketLaneHeaderComponent { export class TicketLaneHeaderComponent {
@Input() state!: TicketValidationState; @Input() state!: TicketValidationState;

View File

@ -3,34 +3,34 @@
[state]="ticketState" [state]="ticketState"
(filterChange)="filterChanged($event)" (filterChange)="filterChanged($event)"
></dks-ticket-lane-header> ></dks-ticket-lane-header>
<ng-container *ngIf="tickets$ | async as tickets"> @if (tickets$ | async; as tickets) {
<cdk-virtual-scroll-viewport <cdk-virtual-scroll-viewport
itemSize="104" itemSize="104"
(scrolledIndexChange)="fetchNextPage()" (scrolledIndexChange)="fetchNextPage()"
> >
<dks-ticket-item <dks-ticket-item
*cdkVirtualFor="let ticket of tickets; trackBy: trackByFn" *cdkVirtualFor="let ticket of tickets; trackBy: trackByFn"
class="mat-elevation-z4 animate-fadeIn" class="mat-elevation-z4 animate-fadeIn"
(click)="openTicketView(ticket)" (click)="openTicketView(ticket)"
[ticket]="ticket" [ticket]="ticket"
> >
</dks-ticket-item> </dks-ticket-item>
</cdk-virtual-scroll-viewport> </cdk-virtual-scroll-viewport>
<!-- <ng-container *ngIf="ticketStateMeta$ | async as ticketStateMeta"> <!-- <ng-container *ngIf="ticketStateMeta$ | async as ticketStateMeta">
<dks-ticket-lane-footer <dks-ticket-lane-footer
*dksRole="['manager', 'admin']" *dksRole="['manager', 'admin']"
[ticketStateMeta]="ticketStateMeta" [ticketStateMeta]="ticketStateMeta"
[withTicketCount]="true" [withTicketCount]="true"
[withApprovalRequiredCount]=" [withApprovalRequiredCount]="
ticketState === TicketState.UserInteractionRequired ticketState === TicketState.UserInteractionRequired
" "
[withApprovalUnknownCount]=" [withApprovalUnknownCount]="
ticketState === TicketState.UserInteractionRequired ticketState === TicketState.UserInteractionRequired
" "
></dks-ticket-lane-footer> ></dks-ticket-lane-footer>
<dks-ticket-lane-footer <dks-ticket-lane-footer
*dksRole="['controlcenter']" *dksRole="['controlcenter']"
[ticketStateMeta]="ticketStateMeta" [ticketStateMeta]="ticketStateMeta"
></dks-ticket-lane-footer ></dks-ticket-lane-footer
></ng-container> --> ></ng-container> -->
</ng-container> }

View File

@ -2,7 +2,7 @@ import {
CdkVirtualScrollViewport, CdkVirtualScrollViewport,
ScrollingModule, ScrollingModule,
} from '@angular/cdk/scrolling'; } from '@angular/cdk/scrolling';
import { AsyncPipe, NgIf } from '@angular/common'; import { AsyncPipe } from '@angular/common';
import { import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
Component, Component,
@ -51,10 +51,9 @@ import { TicketLaneHeaderComponent } from './ticket-lane-header.component';
imports: [ imports: [
TicketLaneHeaderComponent, TicketLaneHeaderComponent,
TicketItemComponent, TicketItemComponent,
NgIf,
AsyncPipe, AsyncPipe,
ScrollingModule, ScrollingModule
], ],
}) })
export class TicketLaneComponent { export class TicketLaneComponent {
private dialog = inject(DialogService); private dialog = inject(DialogService);

View File

@ -1,4 +1,4 @@
import { NgIf } from '@angular/common';
import { import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
Component, Component,
@ -23,45 +23,45 @@ import {
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'dks-ticket-documents-missing', selector: 'dks-ticket-documents-missing',
template: `<div class="flex items-center p-3"> template: `<div class="flex items-center p-3">
<form #form="ngForm" class="flex flex-col w-full"> <form #form="ngForm" class="flex flex-col w-full">
<div class="flex justify-end pt-4"> <div class="flex justify-end pt-4">
<button mat-stroked-button class="mr-2" (click)="form.resetForm()"> <button mat-stroked-button class="mr-2" (click)="form.resetForm()">
Zurücksetzen Zurücksetzen
</button> </button>
<button <button
mat-flat-button mat-flat-button
color="primary" color="primary"
[disabled]="form.invalid" [disabled]="form.invalid"
(click)="saveForm()" (click)="saveForm()"
> >
Speichern Speichern
</button> </button>
</div> </div>
<div class="flex flex-col p-2"> <div class="flex flex-col p-2">
<label>Alles vorhanden?</label> <label>Alles vorhanden?</label>
<mat-checkbox @if (ticket.approvalState !== ApprovalState.Free) {
*ngIf="ticket.approvalState !== ApprovalState.Free" <mat-checkbox
[(ngModel)]="formModel.approval.location" [(ngModel)]="formModel.approval.location"
name="approval" name="approval"
>Genehmigung</mat-checkbox >Genehmigung</mat-checkbox
> >
<mat-checkbox }
[(ngModel)]="formModel.tdLocation" <mat-checkbox
name="transport_document" [(ngModel)]="formModel.tdLocation"
>Transportschein</mat-checkbox name="transport_document"
> >Transportschein</mat-checkbox
</div> >
</form> </div>
</div>`, </form>
</div>`,
imports: [ imports: [
NgIf, MatButtonModule,
MatButtonModule, MatIconModule,
MatIconModule, MatFormFieldModule,
MatFormFieldModule, MatRadioModule,
MatRadioModule, MatCheckboxModule,
MatCheckboxModule, FormsModule
FormsModule, ]
]
}) })
export class TicketDocumentsMissingComponent { export class TicketDocumentsMissingComponent {
@Input() ticket!: Ticket & { tours: Tour[] }; @Input() ticket!: Ticket & { tours: Tour[] };

View File

@ -1,10 +1,4 @@
import { import { NgClass } from '@angular/common';
NgClass,
NgForOf,
NgIf,
NgSwitch,
NgSwitchCase,
} from '@angular/common';
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { MatListModule } from '@angular/material/list'; import { MatListModule } from '@angular/material/list';
@ -18,79 +12,89 @@ import {
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'dks-ticket-overview', selector: 'dks-ticket-overview',
template: `<div class="grid grid-cols-4"> template: `<div class="grid grid-cols-4">
<mat-list class="col-span-2" role="list"> <mat-list class="col-span-2" role="list">
<div mat-subheader class="text-sm">Ticket Informationen</div> <div mat-subheader class="text-sm">Ticket Informationen</div>
<mat-list-item role="listitem"> <mat-list-item role="listitem">
Typ: Typ:
<span class="font-bold"> <span class="font-bold">
{{ ticket.tours[0].type }} {{ ticket.tours[0].type }}
</span> </span>
</mat-list-item> </mat-list-item>
<mat-list-item role="listitem"> <mat-list-item role="listitem">
Kategorie: Kategorie:
<span class="font-bold"> <span class="font-bold">
{{ ticket.tours[0].category }} {{ ticket.tours[0].category }}
</span> </span>
</mat-list-item> </mat-list-item>
</mat-list> </mat-list>
<mat-list class="col-span-2" role="list" *ngIf="ticket.approvalState"> @if (ticket.approvalState) {
<div mat-subheader class="text-sm">Gehnemigungsinformationen</div> <mat-list class="col-span-2" role="list">
<mat-list-item role="listitem"> <div mat-subheader class="text-sm">Gehnemigungsinformationen</div>
<ng-container [ngSwitch]="ticket.approvalState"> <mat-list-item role="listitem">
<span *ngSwitchCase="ApprovalState.Free">Genehmigungsfrei</span> @switch (ticket.approvalState) {
<span @case (ApprovalState.Free) {
*ngSwitchCase="ApprovalState.Unknown" <span>Genehmigungsfrei</span>
title="Automatische Prüfung nicht möglich" }
>Automatische Prüfung nicht möglich</span @case (ApprovalState.Unknown) {
> <span
<span *ngSwitchCase="ApprovalState.Required" title="Automatische Prüfung nicht möglich"
>Genehmigung benötigt</span >Automatische Prüfung nicht möglich</span
> >
<span *ngSwitchCase="ApprovalState.Requested" }
>Genehmigung angefragt</span @case (ApprovalState.Required) {
> <span
<span *ngSwitchCase="ApprovalState.Approved" >Genehmigung benötigt</span
>Genehmigung eingegangen</span >
> }
</ng-container> @case (ApprovalState.Requested) {
</mat-list-item> <span
</mat-list> >Genehmigung angefragt</span
<mat-list >
class="col-span-2 max-h-96 overflow-y-auto" }
*ngIf="ticket.errors.length > 0 || ticket.notes.length > 0" @case (ApprovalState.Approved) {
role="list" <span
> >Genehmigung eingegangen</span
<div mat-subheader class="text-sm">Ticket Anmerkungen</div> >
<mat-list-item }
*ngFor="let error of ticket.errors" }
lines="2" </mat-list-item>
role="listitem" </mat-list>
> }
<mat-icon matListItemIcon class="!text-red-600">error</mat-icon> @if (ticket.errors.length > 0 || ticket.notes.length > 0) {
<span matListItemTitle>{{ error.split(':').at(0) }}</span> <mat-list
<span [title]="error.split(':').at(1)">{{ class="col-span-2 max-h-96 overflow-y-auto"
error.split(':').at(1) role="list"
}}</span> >
</mat-list-item> <div mat-subheader class="text-sm">Ticket Anmerkungen</div>
<mat-list-item @for (error of ticket.errors; track error) {
*ngFor="let note of ticket.notes" <mat-list-item
lines="2" lines="2"
role="listitem" role="listitem"
> >
<mat-icon matListItemIcon class="!text-yellow-400">warning</mat-icon> <mat-icon matListItemIcon class="!text-red-600">error</mat-icon>
<span matListItemTitle>{{ note.split(':').at(0) }}</span> <span matListItemTitle>{{ error.split(':').at(0) }}</span>
<span [title]="note.split(':').at(1)">{{ note.split(':').at(1) }}</span> <span [title]="error.split(':').at(1)">{{
</mat-list-item> error.split(':').at(1)
</mat-list> }}</span>
</div>`, </mat-list-item>
}
@for (note of ticket.notes; track note) {
<mat-list-item
lines="2"
role="listitem"
>
<mat-icon matListItemIcon class="!text-yellow-400">warning</mat-icon>
<span matListItemTitle>{{ note.split(':').at(0) }}</span>
<span [title]="note.split(':').at(1)">{{ note.split(':').at(1) }}</span>
</mat-list-item>
}
</mat-list>
}
</div>`,
imports: [ imports: [
MatListModule, MatListModule,
MatIconModule, MatIconModule
NgIf, ]
NgForOf,
NgSwitch,
NgSwitchCase,
]
}) })
export class TicketOverviewComponent { export class TicketOverviewComponent {
@Input() ticket!: Ticket & { tours: Tour[] }; @Input() ticket!: Ticket & { tours: Tour[] };

View File

@ -1,4 +1,4 @@
import { NgIf } from '@angular/common';
import { import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
Component, Component,

View File

@ -1,5 +1,5 @@
import { SelectionModel } from '@angular/cdk/collections'; import { SelectionModel } from '@angular/cdk/collections';
import { DatePipe, NgClass, NgIf } from '@angular/common'; import { DatePipe, NgClass } from '@angular/common';
import { import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
Component, Component,

View File

@ -1,4 +1,4 @@
import { NgIf } from '@angular/common';
import { import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
ChangeDetectorRef, ChangeDetectorRef,
@ -32,92 +32,88 @@ import { AuthService } from '../../../core/components/ms-authentication/auth.ser
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'dks-ticket-user-action-required', selector: 'dks-ticket-user-action-required',
template: `<div class="flex flex-col p-3"> template: `<div class="flex flex-col p-3">
<form #form="ngForm" class="flex flex-col w-full"> <form #form="ngForm" class="flex flex-col w-full">
<div class="flex justify-end pt-4"> <div class="flex justify-end pt-4">
<button mat-stroked-button class="mr-2" (click)="form.resetForm()"> <button mat-stroked-button class="mr-2" (click)="form.resetForm()">
Zurücksetzen Zurücksetzen
</button> </button>
<button <button
mat-flat-button mat-flat-button
color="primary" color="primary"
[disabled]="form.invalid" [disabled]="form.invalid"
(click)="saveForm()" (click)="saveForm()"
> >
Speichern Speichern
</button> </button>
</div> </div>
<div class="flex flex-col p-2">
<label>Wird eine Genehmigung benötigt?</label>
<mat-radio-group
[(ngModel)]="formModel().approval.isNeeded"
name="approvalNeeded"
required
>
<mat-radio-button [value]="false"> Nein </mat-radio-button>
<mat-radio-button [value]="true"> Ja </mat-radio-button>
</mat-radio-group>
</div>
<ng-container
*ngIf="
(ticket.approvalState === ApprovalState.Required &&
formModel().approval.isNeeded) ||
(ticket.approvalState === ApprovalState.Unknown &&
formModel().approval.isNeeded)
"
>
<div class="flex flex-col p-2"> <div class="flex flex-col p-2">
<label>Wer fragte die Genehmigung an?</label> <label>Wird eine Genehmigung benötigt?</label>
<mat-radio-group <mat-radio-group
[(ngModel)]="formModel().approval.whoRequested" [(ngModel)]="formModel().approval.isNeeded"
name="who" name="approvalNeeded"
required required
> >
<mat-radio-button value="Avicenna"> Avicenna </mat-radio-button> <mat-radio-button [value]="false"> Nein </mat-radio-button>
<mat-radio-button value="Kunde"> Kunde </mat-radio-button> <mat-radio-button [value]="true"> Ja </mat-radio-button>
</mat-radio-group> </mat-radio-group>
</div> </div>
@if (
<mat-form-field class="p-2"> (ticket.approvalState === ApprovalState.Required &&
<mat-label>Name des Sachbearbeiters?</mat-label> formModel().approval.isNeeded) ||
<input (ticket.approvalState === ApprovalState.Unknown &&
type="text" formModel().approval.isNeeded)
matInput ) {
id="staff" <div class="flex flex-col p-2">
[(ngModel)]="formModel().approval.answeredFrom" <label>Wer fragte die Genehmigung an?</label>
name="staff" <mat-radio-group
[(ngModel)]="formModel().approval.whoRequested"
name="who"
required
>
<mat-radio-button value="Avicenna"> Avicenna </mat-radio-button>
<mat-radio-button value="Kunde"> Kunde </mat-radio-button>
</mat-radio-group>
</div>
<mat-form-field class="p-2">
<mat-label>Name des Sachbearbeiters?</mat-label>
<input
type="text"
matInput
id="staff"
[(ngModel)]="formModel().approval.answeredFrom"
name="staff"
required
/>
</mat-form-field>
}
<div class="flex flex-col p-2">
<label>Wo befindet sich der Transportschein?</label>
<mat-radio-group
[(ngModel)]="formModel().tdLocation"
name="tdLocation"
required required
/> >
</mat-form-field> <mat-radio-button [value]="'company'"> Im Betrieb </mat-radio-button>
</ng-container> <mat-radio-button [value]="'practice'">
In der Praxis
<div class="flex flex-col p-2"> </mat-radio-button>
<label>Wo befindet sich der Transportschein?</label> <mat-radio-button [value]="'customer'">
<mat-radio-group Beim Kunden
[(ngModel)]="formModel().tdLocation" </mat-radio-button>
name="tdLocation" <mat-radio-button [value]="'uncertain'"> Unklar </mat-radio-button>
required </mat-radio-group>
> </div>
<mat-radio-button [value]="'company'"> Im Betrieb </mat-radio-button> </form>
<mat-radio-button [value]="'practice'"> </div>`,
In der Praxis
</mat-radio-button>
<mat-radio-button [value]="'customer'">
Beim Kunden
</mat-radio-button>
<mat-radio-button [value]="'uncertain'"> Unklar </mat-radio-button>
</mat-radio-group>
</div>
</form>
</div>`,
imports: [ imports: [
NgIf,
MatButtonModule, MatButtonModule,
MatIconModule, MatIconModule,
MatFormFieldModule, MatFormFieldModule,
MatRadioModule, MatRadioModule,
MatInputModule, MatInputModule,
FormsModule, FormsModule
], ],
}) })
export class TicketUserActionRequiredComponent implements OnInit { export class TicketUserActionRequiredComponent implements OnInit {
@Input() ticket!: Ticket & { tours: Tour[] }; @Input() ticket!: Ticket & { tours: Tour[] };

View File

@ -1,4 +1,4 @@
import { AsyncPipe, NgFor, NgIf } from '@angular/common'; import { AsyncPipe } from '@angular/common';
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms'; import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { MatFormFieldModule } from '@angular/material/form-field'; import { MatFormFieldModule } from '@angular/material/form-field';
@ -20,50 +20,50 @@ import { TicketsService } from '../tickets.service';
@Component({ @Component({
template: `<div class="flex flex-col items-center justify-start p-4"> template: `<div class="flex flex-col items-center justify-start p-4">
<div class="text-lg font-bold pb-2">Ticket suchen</div> <div class="text-lg font-bold pb-2">Ticket suchen</div>
<hr /> <hr />
<mat-form-field class="w-full"> <mat-form-field class="w-full">
<mat-label>Einsatznummer oder Patient</mat-label> <mat-label>Einsatznummer oder Patient</mat-label>
<input class="w-full" matInput [formControl]="searchCtrl" name="search" /> <input class="w-full" matInput [formControl]="searchCtrl" name="search" />
</mat-form-field> </mat-form-field>
<hr /> <hr />
<div class="w-full"> <div class="w-full">
<div @for (ticket of tickets$ | async; track ticket) {
class="flex flex-col justify-between items-center py-2 border-b border-gray-200 cursor-pointer" <div
*ngFor="let ticket of tickets$ | async" class="flex flex-col justify-between items-center py-2 border-b border-gray-200 cursor-pointer"
(click)="showTicketView(ticket)" (click)="showTicketView(ticket)"
> >
<div class="w-full"> <div class="w-full">
{{ ticket.tours.at(0)?.ordinanceType }} {{ ticket.tours.at(0)?.ordinanceType }}
<span @if (ticket.tours.at(0)?.category) {
*ngIf="ticket.tours.at(0)?.category" <span
class="text-sm text-gray-600" class="text-sm text-gray-600"
> >
({{ ticket.tours.at(0)?.category }}) ({{ ticket.tours.at(0)?.category }})
</span> </span>
</div> }
<div class="w-full flex justify-between"> </div>
<span> <div class="w-full flex justify-between">
{{ ticket.tours.at(0)?.patientName }} <span>
{{ ticket.tours.at(0)?.patientSurname }} {{ ticket.tours.at(0)?.patientName }}
</span> {{ ticket.tours.at(0)?.patientSurname }}
<span> </span>
{{ ticket.tours.length }} Tour{{ <span>
ticket.tours.length > 1 ? 'en' : '' {{ ticket.tours.length }} Tour{{
}} ticket.tours.length > 1 ? 'en' : ''
</span> }}
</div> </span>
</div>
</div>
}
</div> </div>
</div> </div>`,
</div>`,
imports: [ imports: [
MatFormFieldModule, MatFormFieldModule,
MatInputModule, MatInputModule,
ReactiveFormsModule, ReactiveFormsModule,
AsyncPipe, AsyncPipe
NgFor, ],
NgIf,
],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class TicketsSearchDialogComponent { export class TicketsSearchDialogComponent {