(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 { CurrencyPipe, NgClass, NgIf } from '@angular/common';
import { CurrencyPipe, NgClass } from '@angular/common';
import {
AfterViewInit,
ChangeDetectionStrategy,
@ -36,19 +36,20 @@ import {
<span>{{ tour.ordinanceType }}</span>
</div>
<div>
@if (accountingState !== AccountingState.Billed) {
<span
[title]="!tour.healthInsurance ? 'Keine Krankenkasse hinterlegt' : ''"
[ngClass]="{ 'bg-yellow-200': !tour.healthInsurance }"
*ngIf="accountingState !== AccountingState.Billed; else diffTmpl"
>
{{ tour.revenue | currency : 'EUR' : 'symbol' : '1.2-2' : 'de-DE' }}
</span>
</div>
<ng-template #diffTmpl>
} @else {
{{
tour.revenueDeviation | currency : 'EUR' : 'symbol' : '1.2-2' : 'de-DE'
}}
</ng-template>`,
}
</div>
`,
host: {
class: 'flex flex-col rounded border p-2 m-1 bg-white cursor-pointer',
},
@ -69,9 +70,8 @@ import {
MatIconModule,
MatBadgeModule,
ClipboardModule,
NgIf,
NgClass,
CurrencyPipe,
CurrencyPipe
],
})
export class AccountingItemComponent implements AfterViewInit {

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

View File

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

View File

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

View File

@ -3,7 +3,7 @@
[state]="accountingState"
(filterChange)="filterChanged($event)"
></dks-accounting-lane-header>
<ng-container *ngIf="tours$ | async as tours">
@if (tours$ | async; as tours) {
<cdk-virtual-scroll-viewport
itemSize="86"
(scrolledIndexChange)="fetchNextPage()"
@ -25,4 +25,4 @@
[withRevenueSum]="accountingState !== AccountingState.Billed"
></dks-accounting-lane-footer>
</ng-container> -->
</ng-container>
}

View File

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

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 { MatDividerModule } from '@angular/material/divider';
import { MatIconModule } from '@angular/material/icon';
@ -18,37 +18,46 @@ import { Tour } from '../../../core/data-access/graphql/generated/generated';
</span>
</div>
<div class="flex flex-col text-gray-600 text-sm">
<span class="font-medium" *ngIf="tour.createdAt">Erstellt am</span>
<span *ngIf="tour.createdAt">
@if (tour.createdAt) {
<span class="font-medium">Erstellt am</span>
}
@if (tour.createdAt) {
<span>
{{ tour.createdAt | date : 'dd.MM.yyyy' }}
{{ tour.createdAt | date : 'HH:mm' }} Uhr
</span>
}
</div>
</div>
<mat-divider></mat-divider>
@if (tour.driverName || tour.codriverName || tour.carName) {
<div
class="flex flex-wrap md:flex-nowrap justify-between px-4 py-2"
*ngIf="tour.driverName || tour.codriverName || tour.carName"
>
@if (tour.carName) {
<div
class="flex flex-col pb-1 md:pb-0"
*ngIf="tour.carName"
>
<span class="font-semibold pb-1">Fahrzeug</span>
{{ tour.carName }}
</div>
}
@if (tour.driverName) {
<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">
}
@if (tour.codriverName) {
<div class="flex flex-col">
<span class="font-semibold pb-1">Beifahrer</span>
<span>{{ tour.codriverName }}</span>
</div>
}
</div>
}
<div class="flex flex-col px-4 py-2">
<span class="font-semibold pb-1">Patient</span>
<span>{{ tour.patientName }} {{ tour.patientSurname }}</span>
@ -71,7 +80,7 @@ import { Tour } from '../../../core/data-access/graphql/generated/generated';
</div>
`,
styles: [],
imports: [MatTabsModule, MatIconModule, MatDividerModule, NgIf, DatePipe]
imports: [MatTabsModule, MatIconModule, MatDividerModule, DatePipe]
})
export class AccountingTourOverviewComponent {
@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 { MatDividerModule } from '@angular/material/divider';
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>
<mat-list-item role="listitem">
Status:
<ng-container [ngSwitch]="tour.ticket.currentState">
@switch (tour.ticket.currentState) {
@case (TicketValidationState.Created) {
<span
class="font-bold"
*ngSwitchCase="TicketValidationState.Created"
>Touren angenommen
</span>
}
@case (TicketValidationState.UserInteractionRequired) {
<span
class="font-bold"
*ngSwitchCase="TicketValidationState.UserInteractionRequired"
>Genehmigung / Transportschein
</span>
}
@case (TicketValidationState.TdUncertain) {
<span
class="font-bold"
*ngSwitchCase="TicketValidationState.TdUncertain"
>Transportschein unklar
</span>
}
@case (TicketValidationState.DocumentsMissing) {
<span
class="font-bold"
*ngSwitchCase="TicketValidationState.DocumentsMissing"
>Dokumente fehlen
</span>
}
@case (TicketValidationState.Archived) {
<span
class="font-bold"
*ngSwitchCase="TicketValidationState.Archived"
>Archiviert
</span>
</ng-container>
}
}
</mat-list-item>
<mat-list-item role="listitem">
Typ:
@ -55,14 +60,14 @@ import { Ticket, TicketValidationState, Tour } from '../../../core/data-access/g
</span>
</mat-list-item>
</mat-list>
@if (tour.ticket.errors.length > 0 || tour.ticket.notes.length > 0) {
<mat-list
class="col-span-2 max-h-96 overflow-y-auto"
*ngIf="tour.ticket.errors.length > 0 || tour.ticket.notes.length > 0"
role="list"
>
<div mat-subheader class="text-sm">Ticket Anmerkungen</div>
@for (error of tour.ticket.errors; track error) {
<mat-list-item
*ngFor="let error of tour.ticket.errors"
lines="2"
role="listitem"
>
@ -72,8 +77,9 @@ import { Ticket, TicketValidationState, Tour } from '../../../core/data-access/g
error.split(':').at(1)
}}</span>
</mat-list-item>
}
@for (note of tour.ticket.notes; track note) {
<mat-list-item
*ngFor="let note of tour.ticket.notes"
lines="2"
role="listitem"
>
@ -83,18 +89,16 @@ import { Ticket, TicketValidationState, Tour } from '../../../core/data-access/g
note.split(':').at(1)
}}</span>
</mat-list-item>
}
</mat-list>
}
</div>
`,
styles: [],
imports: [
MatIconModule,
MatListModule,
MatDividerModule,
NgIf,
NgFor,
NgSwitch,
NgSwitchCase,
MatDividerModule
]
})
export class AccountingTourTicketComponent {

View File

@ -1,7 +1,8 @@
<mat-list role="list" *ngIf="anomalies$ | async as anomalies">
<ng-container *ngIf="anomalies.length; else emptyListTmpl">
@if (anomalies$ | async; as anomalies) {
<mat-list role="list">
@if (anomalies.length) {
@for (anomaly of anomalies; track anomaly) {
<mat-list-item
*ngFor="let anomaly of anomalies"
class="flex"
role="listitem"
>
@ -11,13 +12,14 @@
(anomalyAccepted)="acceptAnomaly($event)"
></dks-anomaly-list-item>
</mat-list-item>
</ng-container>
<ng-template #emptyListTmpl>
}
} @else {
<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"

View File

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

View File

@ -1,6 +1,6 @@
<ng-container *ngIf="anomalies$ | async as anomalies">
@if (anomalies$ | async; as anomalies) {
@if (anomalies.length) {
<mat-tree
*ngIf="anomalies.length; else emptyListTmpl"
[dataSource]="dataSource"
[treeControl]="treeControl"
>
@ -11,7 +11,6 @@
(anomalyAccepted)="acceptAnomaly($event)"
></dks-anomaly-list-item>
</mat-tree-node>
<mat-tree-node
class="flex"
*matTreeNodeDef="let node; when: hasChild"
@ -30,12 +29,12 @@
<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>
<ng-template #emptyListTmpl>
} @else {
<mat-list-item
>Herzlichen Glückwunsch, es liegen keine Anomalien vor!</mat-list-item
>
</ng-template>
</ng-container>
}
}
<mat-paginator
#paginator
class="mt-auto"

View File

@ -2,10 +2,11 @@
<mat-card-header class="flex justify-center">
<mat-card-title>{{ ordinanceType }}</mat-card-title>
</mat-card-header>
<mat-card-content *ngIf="toursPerTimeKPI$ | async as kpis">
@if (toursPerTimeKPI$ | async; as kpis) {
<mat-card-content>
<div class="flex">
@for (kpi of kpis; track kpi; let idx = $index) {
<div
*ngFor="let kpi of kpis; index as idx"
class="w-1/3 flex flex-col justify-center items-center"
style="border-color: var(--mat-sys-primary);"
[ngClass]="{
@ -20,6 +21,8 @@
{{ kpi.interval.end | date : "HH:mm" }}
</div>
</div>
}
</div>
</mat-card-content>
}
</mat-card>

View File

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

View File

@ -1,19 +1,20 @@
@if (upcomingTours$ | async; as upcomingTours) {
<div
class="flex flex-col justify-between flex-wrap m-2 xl:m-4"
*ngIf="upcomingTours$ | async as upcomingTours"
>
<ng-container *ngIf="upcomingTours.length; else emptyTmpl">
@if (upcomingTours.length) {
@for (tour of upcomingTours; track tour) {
<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>
<div class="cursor-pointer" (click)="openTourViewDialog(tour)">
{{ tour.operationId }} - {{ tour.startDate | date: 'HH:mm' }} Uhr
</div>
</div>
</ng-container>
<ng-template #emptyTmpl
>Keine Touren in den nächsten 20 Minuten ohne Anfahrt</ng-template
>
}
} @else {
Keine Touren in den nächsten 20 Minuten ohne Anfahrt
}
</div>
}

View File

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

View File

@ -1,4 +1,5 @@
<div class="m-auto flex flex-col" *ngIf="metricsSummary$ | async as summary">
@if (metricsSummary$ | async; as summary) {
<div class="m-auto flex flex-col">
<div class="flex items-center justify-end">
<span class="text-base">{{
summary.averageRevenue | currency : 'EUR' : 'symbol' : '1.2-2'
@ -10,3 +11,4 @@
}} Index</span>
</div>
</div>
}

View File

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

View File

@ -1,4 +1,5 @@
<div class="m-auto flex flex-col" *ngIf="metricsSummary$ | async as summary">
@if (metricsSummary$ | async; as summary) {
<div class="m-auto flex flex-col">
<div class="flex items-center justify-end">
<span class="text-base">{{
summary.revenue | currency : 'EUR' : 'symbol' : '1.2-2'
@ -43,3 +44,4 @@
</div>
</div> -->
</div>
}

View File

@ -1,6 +1,7 @@
<div class="grid grid-cols-1 md:grid-cols-4" *ngIf="vm$ | async as vm">
<ng-container [ngSwitch]="selectedMetric">
<ng-container *ngSwitchCase="DiagrammType.TimePerTour">
@if (vm$ | async; as vm) {
<div class="grid grid-cols-1 md:grid-cols-4">
@switch (selectedMetric) {
@case (DiagrammType.TimePerTour) {
<dks-time-per-tour-metric
class="col-span-3"
[labelFormat]="labelFormat"
@ -17,8 +18,8 @@
[metrics]="vm.metrics"
></dks-time-per-tour-summary>
</div>
</ng-container>
<ng-container *ngSwitchCase="DiagrammType.Revenue">
}
@case (DiagrammType.Revenue) {
<dks-breakeven-metric
class="col-span-3"
[labelFormat]="getLabelFormatForMetricTimeScale(metricTimeScale)"
@ -26,8 +27,8 @@
[metricTimeScale]="metricTimeScale"
></dks-breakeven-metric>
<dks-breakeven-summary [metrics]="vm.metrics"></dks-breakeven-summary>
</ng-container>
<ng-container *ngSwitchCase="DiagrammType.AverageRevenue">
}
@case (DiagrammType.AverageRevenue) {
<dks-average-revenue-metric
class="col-span-3"
[labelFormat]="getLabelFormatForMetricTimeScale(metricTimeScale)"
@ -36,6 +37,7 @@
<dks-average-revenue-summary
[metrics]="vm.metrics"
></dks-average-revenue-summary>
</ng-container>
</ng-container>
}
}
</div>
}

View File

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

View File

@ -1,4 +1,5 @@
<div class="m-auto flex flex-col" *ngIf="metricsSummary$ | async as summary">
@if (metricsSummary$ | async; as summary) {
<div class="m-auto flex flex-col">
<div class="flex items-center justify-end">
<span class="text-base">{{ summary.tourAmount | number : '1.2-2' }}</span>
<div class="p-1 text-sm" title="Menge Touren">
@ -40,3 +41,4 @@
</div>
</div> -->
</div>
}

View File

@ -1,4 +1,5 @@
<!-- 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" -->
@if (metricSummary$ | async; as summary) {
<mat-card
appearance="raised"
class="cursor-pointer transition ease-in-out hover:scale-105 duration-200"
@ -7,13 +8,11 @@
}"
style="border-color: var(--mat-sys-primary)"
(click)="setSelectedViewType()"
*ngIf="metricSummary$ | async as summary"
>
<mat-card-header class="!pb-2 justify-center">
<mat-card-title>{{ viewType }}</mat-card-title>
</mat-card-header>
<!-- <span class="text-lg pb-4">{{ viewType }}</span> -->
<mat-card-content class="!flex justify-center">
@switch (currentMetric) { @case (DiagrammType.TimePerTour) {
<div class="w-1/4 flex flex-col items-end">
@ -51,3 +50,4 @@
} }
</mat-card-content>
</mat-card>
}

View File

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

View File

@ -1,5 +1,6 @@
<ng-container *ngIf="vm$ | async as vm; else loadingTmpl">
<div class="flex flex-col m-0 md:m-4" *ngIf="!vm.isLoading; else loadingTmpl">
@if (vm$ | async; as vm) {
@if (!vm.isLoading) {
<div class="flex flex-col m-0 md:m-4">
<!-- Top row with user input elements -->
<div class="flex flex-wrap md:flex-nowrap justify-between items-center">
<div class="flex items-center m-2">
@ -31,7 +32,6 @@
<mat-icon>chevron_right</mat-icon>
</button>
</div>
<mat-chip-listbox (change)="changeSelectedType($event)">
<mat-chip-option
[value]="DiagrammType.Revenue"
@ -135,7 +135,9 @@
</mat-card-content>
</mat-card>
</div>
</ng-container>
<ng-template #loadingTmpl>
} @else {
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
</ng-template>
}
} @else {
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
}

View File

@ -1,6 +1,6 @@
@if (vm$ | async; as vm) {
<div
class="flex flex-col m-0 md:m-4"
*ngIf="vm$ | async as vm; else loadingTmpl"
>
<div class="flex flex-wrap md:flex-nowrap">
<!-- KPI -->
@ -35,37 +35,41 @@
(click)="setSelectedOrdinanceType(OrdinanceTypes.KTW)"
></dks-time-per-tour-kpi>
</div>
@if (vm.selectedOrdinanceType) {
<div
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">
@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"
*ngSwitchCase="OrdinanceTypes.BTW"
></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"
*ngSwitchCase="OrdinanceTypes.TSW"
></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"
*ngSwitchCase="OrdinanceTypes.KTW"
></dks-time-per-tour-by-car>
</ng-container>
}
}
</mat-card-content>
</mat-card>
</div>
}
</div>
<ng-template #loadingTmpl>
} @else {
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
</ng-template>
}

View File

@ -7,15 +7,18 @@
matSort
aria-label="Fahrzeuge"
>
<ng-container matColumnDef="{{ column.id }}" *ngFor="let column of columns">
@for (column of columns; track column) {
<ng-container matColumnDef="{{ column.id }}">
<th mat-header-cell *matHeaderCellDef>{{ column.title }}</th>
<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">
<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="flex" *ngFor="let detail of carData.additionalData; index as idx">
@for (detail of carData.additionalData; track detail; let idx = $index) {
<div class="flex">
<div class="pr-2 md:px-4">
<span class="text-base font-semibold">{{getSlotTitleByIndex(idx)}}</span>
</div>
@ -29,6 +32,7 @@
<span class="pr-2 text-base">{{detail.breakEven}}€</span><mat-icon title="Deckungsbeitrag">account_balance</mat-icon>
</div>
</div>
}
</div>
</td>
</ng-container>

View File

@ -9,8 +9,8 @@
<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>
@for (kpi of kpis; track kpi; let idx = $index) {
<div
*ngFor="let kpi of kpis; index as idx"
class="w-1/3 flex flex-col justify-center items-center"
>
<div
@ -34,6 +34,7 @@
{{ kpi.kpi.meta.breakEven}}€
</div>
</div>
}
</div>
</mat-card-content>
</mat-card>

View File

@ -1,11 +1,11 @@
<ng-container *ngIf="ticket">
@if (ticket) {
<div class="flex justify-between">
<span class="text-base">
{{ ticket.tours.at(0)?.ordinanceType }}
</span>
<div>
@if (ticket.errors.length) {
<mat-icon
*ngIf="ticket.errors.length"
[matBadge]="ticket.errors.length"
matBadgeColor="warn"
[title]="ticket.errors.length + ' Anmerkungen'"
@ -13,6 +13,7 @@
>
error
</mat-icon>
}
</div>
</div>
<span
@ -25,4 +26,4 @@
}}</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 {
ChangeDetectionStrategy,
Component,
@ -32,7 +32,7 @@ import CategoryColors from './category-colors';
}
`,
],
imports: [MatIconModule, MatBadgeModule, NgIf],
imports: [MatIconModule, MatBadgeModule],
})
export class TicketItemComponent implements OnInit {
@Input() ticket!: Ticket & { tours: Tour[] };

View File

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

View File

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

View File

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

View File

@ -3,7 +3,7 @@
[state]="ticketState"
(filterChange)="filterChanged($event)"
></dks-ticket-lane-header>
<ng-container *ngIf="tickets$ | async as tickets">
@if (tickets$ | async; as tickets) {
<cdk-virtual-scroll-viewport
itemSize="104"
(scrolledIndexChange)="fetchNextPage()"
@ -33,4 +33,4 @@
[ticketStateMeta]="ticketStateMeta"
></dks-ticket-lane-footer
></ng-container> -->
</ng-container>
}

View File

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

View File

@ -1,4 +1,4 @@
import { NgIf } from '@angular/common';
import {
ChangeDetectionStrategy,
Component,
@ -39,12 +39,13 @@ import {
</div>
<div class="flex flex-col p-2">
<label>Alles vorhanden?</label>
@if (ticket.approvalState !== ApprovalState.Free) {
<mat-checkbox
*ngIf="ticket.approvalState !== ApprovalState.Free"
[(ngModel)]="formModel.approval.location"
name="approval"
>Genehmigung</mat-checkbox
>
}
<mat-checkbox
[(ngModel)]="formModel.tdLocation"
name="transport_document"
@ -54,13 +55,12 @@ import {
</form>
</div>`,
imports: [
NgIf,
MatButtonModule,
MatIconModule,
MatFormFieldModule,
MatRadioModule,
MatCheckboxModule,
FormsModule,
FormsModule
]
})
export class TicketDocumentsMissingComponent {

View File

@ -1,10 +1,4 @@
import {
NgClass,
NgForOf,
NgIf,
NgSwitch,
NgSwitchCase,
} from '@angular/common';
import { NgClass } from '@angular/common';
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { MatIconModule } from '@angular/material/icon';
import { MatListModule } from '@angular/material/list';
@ -33,36 +27,47 @@ import {
</span>
</mat-list-item>
</mat-list>
<mat-list class="col-span-2" role="list" *ngIf="ticket.approvalState">
@if (ticket.approvalState) {
<mat-list class="col-span-2" role="list">
<div mat-subheader class="text-sm">Gehnemigungsinformationen</div>
<mat-list-item role="listitem">
<ng-container [ngSwitch]="ticket.approvalState">
<span *ngSwitchCase="ApprovalState.Free">Genehmigungsfrei</span>
@switch (ticket.approvalState) {
@case (ApprovalState.Free) {
<span>Genehmigungsfrei</span>
}
@case (ApprovalState.Unknown) {
<span
*ngSwitchCase="ApprovalState.Unknown"
title="Automatische Prüfung nicht möglich"
>Automatische Prüfung nicht möglich</span
>
<span *ngSwitchCase="ApprovalState.Required"
}
@case (ApprovalState.Required) {
<span
>Genehmigung benötigt</span
>
<span *ngSwitchCase="ApprovalState.Requested"
}
@case (ApprovalState.Requested) {
<span
>Genehmigung angefragt</span
>
<span *ngSwitchCase="ApprovalState.Approved"
}
@case (ApprovalState.Approved) {
<span
>Genehmigung eingegangen</span
>
</ng-container>
}
}
</mat-list-item>
</mat-list>
}
@if (ticket.errors.length > 0 || ticket.notes.length > 0) {
<mat-list
class="col-span-2 max-h-96 overflow-y-auto"
*ngIf="ticket.errors.length > 0 || ticket.notes.length > 0"
role="list"
>
<div mat-subheader class="text-sm">Ticket Anmerkungen</div>
@for (error of ticket.errors; track error) {
<mat-list-item
*ngFor="let error of ticket.errors"
lines="2"
role="listitem"
>
@ -72,8 +77,9 @@ import {
error.split(':').at(1)
}}</span>
</mat-list-item>
}
@for (note of ticket.notes; track note) {
<mat-list-item
*ngFor="let note of ticket.notes"
lines="2"
role="listitem"
>
@ -81,15 +87,13 @@ import {
<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: [
MatListModule,
MatIconModule,
NgIf,
NgForOf,
NgSwitch,
NgSwitchCase,
MatIconModule
]
})
export class TicketOverviewComponent {

View File

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

View File

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

View File

@ -1,4 +1,4 @@
import { NgIf } from '@angular/common';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
@ -57,14 +57,12 @@ import { AuthService } from '../../../core/components/ms-authentication/auth.ser
<mat-radio-button [value]="true"> Ja </mat-radio-button>
</mat-radio-group>
</div>
<ng-container
*ngIf="
@if (
(ticket.approvalState === ApprovalState.Required &&
formModel().approval.isNeeded) ||
(ticket.approvalState === ApprovalState.Unknown &&
formModel().approval.isNeeded)
"
>
) {
<div class="flex flex-col p-2">
<label>Wer fragte die Genehmigung an?</label>
<mat-radio-group
@ -76,7 +74,6 @@ import { AuthService } from '../../../core/components/ms-authentication/auth.ser
<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
@ -88,7 +85,7 @@ import { AuthService } from '../../../core/components/ms-authentication/auth.ser
required
/>
</mat-form-field>
</ng-container>
}
<div class="flex flex-col p-2">
<label>Wo befindet sich der Transportschein?</label>
@ -110,13 +107,12 @@ import { AuthService } from '../../../core/components/ms-authentication/auth.ser
</form>
</div>`,
imports: [
NgIf,
MatButtonModule,
MatIconModule,
MatFormFieldModule,
MatRadioModule,
MatInputModule,
FormsModule,
FormsModule
],
})
export class TicketUserActionRequiredComponent implements OnInit {

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 { FormControl, ReactiveFormsModule } from '@angular/forms';
import { MatFormFieldModule } from '@angular/material/form-field';
@ -28,19 +28,20 @@ import { TicketsService } from '../tickets.service';
</mat-form-field>
<hr />
<div class="w-full">
@for (ticket of tickets$ | async; track ticket) {
<div
class="flex flex-col justify-between items-center py-2 border-b border-gray-200 cursor-pointer"
*ngFor="let ticket of tickets$ | async"
(click)="showTicketView(ticket)"
>
<div class="w-full">
{{ ticket.tours.at(0)?.ordinanceType }}
@if (ticket.tours.at(0)?.category) {
<span
*ngIf="ticket.tours.at(0)?.category"
class="text-sm text-gray-600"
>
({{ ticket.tours.at(0)?.category }})
</span>
}
</div>
<div class="w-full flex justify-between">
<span>
@ -54,15 +55,14 @@ import { TicketsService } from '../tickets.service';
</span>
</div>
</div>
}
</div>
</div>`,
imports: [
MatFormFieldModule,
MatInputModule,
ReactiveFormsModule,
AsyncPipe,
NgFor,
NgIf,
AsyncPipe
],
changeDetection: ChangeDetectionStrategy.OnPush
})