This commit is contained in:
2024-11-27 11:40:43 +01:00
parent 53e7db435c
commit 95fe01ad86
51 changed files with 4175 additions and 456 deletions

1
.gitignore vendored
View File

@@ -45,6 +45,7 @@ yarn-error.log
*.sublime-workspace *.sublime-workspace
# Visual Studio Code # Visual Studio Code
.vs/*
.vscode/* .vscode/*
!.vscode/settings.json !.vscode/settings.json
!.vscode/tasks.json !.vscode/tasks.json

39
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,39 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "localhost (Chrome)",
"type": "chrome",
"request": "launch",
"url": "http://localhost:8100",
"webRoot": "${workspaceFolder}/www",
"preLaunchTask": "ng serve",
"sourceMaps": true,
"trace": true
},
{
"name": "Debug node process",
"type": "node",
"request": "launch",
"cwd": "${workspaceFolder}",
"program": "${workspaceFolder}/capacitor.config.ts",
"skipFiles": [
"<node_internals/**"
],
"stopOnEntry": true,
"console": "externalTerminal",
"env": {
"port": "5000"
}
}
],
"compounds": [
{
"name": "Launch Node and Browser",
"configurations": [
"Debug node process",
"localhost (Chrome)"
]
}
]
}

View File

@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.VisualStudio.JavaScript.Sdk/0.5.89-alpha">
<PropertyGroup>
<!--Command to run on project build-->
<BuildCommand>ionic build</BuildCommand>
<!--Command to run on project clean-->
<CleanCommand>
</CleanCommand>
<StartupCommand>
</StartupCommand>
</PropertyGroup>
</Project>

10
PROJECT_MIGRATION_LOG.txt Normal file
View File

@@ -0,0 +1,10 @@
Hello! Thank you for trying out our new JavaScript and TypeScript project experience. We've added the below list of files to your project directory in order to enable the new experience:
C:\Users\m.ilhan\source\repos\verag\.vscode\launch.json
C:\Users\m.ilhan\source\repos\verag\nuget.config
C:\Users\m.ilhan\source\repos\verag\MIGRATED_PROJECT.esproj
C:\Users\m.ilhan\source\repos\verag\PROJECT_MIGRATION_LOG.txt
We'd love to get your feedback! Please submit any bugs or improvements that need to be made by going to Help -> Send Feedback.
If you'd like to revert to your original project, you can right-click on the project and click on 'Revert Project To Old Experience'.

36
VeragTVAppFrontend.sln Normal file
View File

@@ -0,0 +1,36 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.11.35327.3
MinimumVisualStudioVersion = 10.0.40219.1
Project("{54A90642-561A-4BB1-A94E-469ADEE60C69}") = "VeragTvApp.client", "VeragTvApp.client.esproj", "{1650CC57-EA5F-4ACF-BD98-B8A5DED15956}"
ProjectSection(ProjectDependencies) = postProject
{C085CCA8-3E7E-4AA3-A0C5-D41DDAF8192C} = {C085CCA8-3E7E-4AA3-A0C5-D41DDAF8192C}
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VeragTvApp.server", "..\VeragTvApp\VeragTvApp.server.csproj", "{C085CCA8-3E7E-4AA3-A0C5-D41DDAF8192C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{1650CC57-EA5F-4ACF-BD98-B8A5DED15956}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1650CC57-EA5F-4ACF-BD98-B8A5DED15956}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1650CC57-EA5F-4ACF-BD98-B8A5DED15956}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
{1650CC57-EA5F-4ACF-BD98-B8A5DED15956}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1650CC57-EA5F-4ACF-BD98-B8A5DED15956}.Release|Any CPU.Build.0 = Release|Any CPU
{1650CC57-EA5F-4ACF-BD98-B8A5DED15956}.Release|Any CPU.Deploy.0 = Release|Any CPU
{C085CCA8-3E7E-4AA3-A0C5-D41DDAF8192C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C085CCA8-3E7E-4AA3-A0C5-D41DDAF8192C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C085CCA8-3E7E-4AA3-A0C5-D41DDAF8192C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C085CCA8-3E7E-4AA3-A0C5-D41DDAF8192C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {9B2DABB1-ADAB-46C3-B82D-B9BB96C00E15}
EndGlobalSection
EndGlobal

9
VeragTvApp.client.esproj Normal file
View File

@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.VisualStudio.JavaScript.Sdk/0.5.89-alpha">
<PropertyGroup>
<!--Command to run on project build-->
<!--Command to run on project clean-->
<CleanCommand>
</CleanCommand>
<StartupCommand>ionic serve</StartupCommand>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup />
</Project>

10
nuget.config Normal file
View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
<disabledPackageSources>
<clear />
</disabledPackageSources>
</configuration>

View File

@@ -0,0 +1 @@
C:\Users\m.ilhan\source\repos\verag\obj\Debug\MIGRATED_PROJECT.esproj.CoreCompileInputs.cache

View File

@@ -0,0 +1 @@
C:\Users\m.ilhan\source\repos\verag\obj\Debug\VeragTvApp.client.esproj.CoreCompileInputs.cache

21
package-lock.json generated
View File

@@ -21,9 +21,10 @@
"@capacitor/haptics": "6.0.1", "@capacitor/haptics": "6.0.1",
"@capacitor/keyboard": "6.0.2", "@capacitor/keyboard": "6.0.2",
"@capacitor/status-bar": "6.0.1", "@capacitor/status-bar": "6.0.1",
"@ionic/angular": "^8.0.0", "@ionic/angular": "^8.4.0",
"ionicons": "^7.0.0", "ionicons": "^7.0.0",
"rxjs": "~7.8.0", "rxjs": "~7.8.0",
"swiper": "^11.1.15",
"tslib": "^2.3.0", "tslib": "^2.3.0",
"zone.js": "~0.14.2" "zone.js": "~0.14.2"
}, },
@@ -14801,6 +14802,24 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/swiper": {
"version": "11.1.15",
"resolved": "https://registry.npmjs.org/swiper/-/swiper-11.1.15.tgz",
"integrity": "sha512-IzWeU34WwC7gbhjKsjkImTuCRf+lRbO6cnxMGs88iVNKDwV+xQpBCJxZ4bNH6gSrIbbyVJ1kuGzo3JTtz//CBw==",
"funding": [
{
"type": "patreon",
"url": "https://www.patreon.com/swiperjs"
},
{
"type": "open_collective",
"url": "http://opencollective.com/swiper"
}
],
"engines": {
"node": ">= 4.7.0"
}
},
"node_modules/symbol-observable": { "node_modules/symbol-observable": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz",

View File

@@ -26,9 +26,10 @@
"@capacitor/haptics": "6.0.1", "@capacitor/haptics": "6.0.1",
"@capacitor/keyboard": "6.0.2", "@capacitor/keyboard": "6.0.2",
"@capacitor/status-bar": "6.0.1", "@capacitor/status-bar": "6.0.1",
"@ionic/angular": "^8.0.0", "@ionic/angular": "^8.4.0",
"ionicons": "^7.0.0", "ionicons": "^7.0.0",
"rxjs": "~7.8.0", "rxjs": "~7.8.0",
"swiper": "^11.1.15",
"tslib": "^2.3.0", "tslib": "^2.3.0",
"zone.js": "~0.14.2" "zone.js": "~0.14.2"
}, },

View File

@@ -4,17 +4,17 @@ import { PreloadAllModules, RouterModule, Routes } from '@angular/router';
const routes: Routes = [ const routes: Routes = [
{ {
path: 'home', path: 'home',
loadChildren: () => import('./home/home.module').then( m => m.HomePageModule) loadChildren: () => import('./pages/home/home.module').then( m => m.HomePageModule)
}, },
{ {
path: 'message/:id', path: 'aviso',
loadChildren: () => import('./view-message/view-message.module').then( m => m.ViewMessagePageModule) loadChildren: () => import('./pages/aviso/aviso.module').then( m => m.AvisoPageModule)
}, },
{ {
path: '', path: '',
redirectTo: 'home', redirectTo: 'aviso',
pathMatch: 'full' pathMatch: 'full'
}, }
]; ];
@NgModule({ @NgModule({

View File

@@ -1,16 +1,21 @@
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { RouteReuseStrategy } from '@angular/router'; import { RouteReuseStrategy } from '@angular/router';
import { HttpClientModule } from '@angular/common/http';
import { IonicModule, IonicRouteStrategy } from '@ionic/angular'; import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module'; import { AppRoutingModule } from './app-routing.module';
@NgModule({ @NgModule({
declarations: [AppComponent], declarations: [AppComponent],
imports: [BrowserModule, IonicModule.forRoot(), AppRoutingModule], imports: [
BrowserModule,
IonicModule.forRoot(),
AppRoutingModule,
HttpClientModule // Hinzufügen
],
providers: [{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }], providers: [{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }],
bootstrap: [AppComponent], bootstrap: [AppComponent],
}) })
export class AppModule {} export class AppModule { }

View File

@@ -0,0 +1,128 @@
import { Directive, ElementRef, AfterViewInit, OnDestroy } from '@angular/core';
import { Subject } from 'rxjs';
import { debounceTime, takeUntil } from 'rxjs/operators';
@Directive({
selector: '[autoResizeText]'
})
export class AutoResizeTextDirective implements AfterViewInit, OnDestroy {
private resizeObserver!: ResizeObserver;
private resize$ = new Subject<void>();
private destroy$ = new Subject<void>();
constructor(private el: ElementRef) { }
ngAfterViewInit() {
this.adjustFontSizes();
this.resizeObserver = new ResizeObserver(() => {
this.resize$.next();
});
this.resizeObserver.observe(this.el.nativeElement);
// Debounce der Resize-Events
this.resize$.pipe(
debounceTime(100), // Warte 100ms nach dem letzten Resize-Event
takeUntil(this.destroy$)
).subscribe(() => {
this.adjustFontSizes();
});
}
ngOnDestroy() {
if (this.resizeObserver) {
this.resizeObserver.disconnect();
}
this.destroy$.next();
this.destroy$.complete();
}
/**
* Passt die Schriftgrößen der einzelnen Spans individuell an.
*/
private adjustFontSizes() {
const container: HTMLElement = this.el.nativeElement;
const spans: HTMLElement[] = Array.from(container.querySelectorAll('div.dynamic-font-size'));
// Store the original font sizes
spans.forEach(span => {
if (!span.dataset['originalFontSizePx']) {
const computedStyle = window.getComputedStyle(span);
const originalFontSizePx = parseFloat(computedStyle.fontSize);
span.dataset['originalFontSizePx'] = originalFontSizePx.toString();
}
});
// Reset font sizes to original
spans.forEach(span => {
const originalFontSizePx = parseFloat(span.dataset['originalFontSizePx'] || '16');
span.style.fontSize = `${originalFontSizePx}px`;
});
// If content overflows, adjust font sizes
if (this.isOverflowing(container)) {
// Find the scaling factor needed to fit the content
let scalingFactor = this.calculateScalingFactor(container, spans);
// Apply the scaling factor to all spans
spans.forEach(span => {
const originalFontSizePx = parseFloat(span.dataset['originalFontSizePx'] || '16');
let newFontSizePx = originalFontSizePx * scalingFactor;
// Ensure the font size doesn't go below a minimum value
const minFontSizePx = 12;
if (newFontSizePx < minFontSizePx) {
newFontSizePx = minFontSizePx;
}
span.style.fontSize = `${newFontSizePx}px`;
});
}
}
/**
* Calculates the scaling factor needed to fit all content within the container.
* @param container The container element.
* @param spans The list of span elements.
* @returns The scaling factor as a decimal.
*/
private calculateScalingFactor(container: HTMLElement, spans: HTMLElement[]): number {
const minFontSizePx = 12; // Minimum font size
let low = 0.1; // 10% scaling
let high = 1; // 100% scaling
let mid = 1;
// Binary search to find the optimal scaling factor
while (high - low > 0.01) {
mid = (low + high) / 2;
// Apply scaling factor
spans.forEach(span => {
const originalFontSizePx = parseFloat(span.dataset['originalFontSizePx'] || '16');
let newFontSizePx = originalFontSizePx * mid;
if (newFontSizePx < minFontSizePx) {
newFontSizePx = minFontSizePx;
}
span.style.fontSize = `${newFontSizePx}px`;
});
// Check if content overflows
if (this.isOverflowing(container)) {
high = mid;
} else {
low = mid;
}
}
return low;
}
/**
* Überprüft, ob der Inhalt des Containers überläuft.
* @param container Das zu überprüfende HTMLElement.
* @returns Wahr, wenn ein Überlauf vorliegt, sonst falsch.
*/
private isOverflowing(container: HTMLElement): boolean {
return container.scrollWidth > container.clientWidth || container.scrollHeight > container.clientHeight;
}
}

View File

@@ -1,25 +0,0 @@
<ion-header [translucent]="true">
<ion-toolbar>
<ion-title>
Inbox
</ion-title>
</ion-toolbar>
</ion-header>
<ion-content [fullscreen]="true">
<ion-refresher slot="fixed" (ionRefresh)="refresh($event)">
<ion-refresher-content></ion-refresher-content>
</ion-refresher>
<ion-header collapse="condense">
<ion-toolbar>
<ion-title size="large">
Inbox
</ion-title>
</ion-toolbar>
</ion-header>
<ion-list>
<app-message *ngFor="let message of getMessages()" [message]="message"></app-message>
</ion-list>
</ion-content>

View File

@@ -1,25 +0,0 @@
import { Component, inject } from '@angular/core';
import { RefresherCustomEvent } from '@ionic/angular';
import { MessageComponent } from '../message/message.component';
import { DataService, Message } from '../services/data.service';
@Component({
selector: 'app-home',
templateUrl: 'home.page.html',
styleUrls: ['home.page.scss'],
})
export class HomePage {
private data = inject(DataService);
constructor() {}
refresh(ev: any) {
setTimeout(() => {
(ev as RefresherCustomEvent).detail.complete();
}, 3000);
}
getMessages(): Message[] {
return this.data.getMessages();
}
}

View File

@@ -1,16 +0,0 @@
<ion-item *ngIf="message" [routerLink]="'/message/' + message.id" [detail]="false">
<div slot="start" [class]="!message.read ? 'dot dot-unread' : 'dot'"></div>
<ion-label class="ion-text-wrap">
<h2>
{{ message.fromName }}
<span class="date">
<ion-note>{{ message.date }}</ion-note>
<ion-icon aria-hidden="true" name="chevron-forward" size="small" *ngIf="isIos()"></ion-icon>
</span>
</h2>
<h3>{{ message.subject }}</h3>
<p>
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</p>
</ion-label>
</ion-item>

View File

@@ -1,69 +0,0 @@
ion-item {
--padding-start: 0;
--inner-padding-end: 0;
}
ion-label {
margin-top: 12px;
margin-bottom: 12px;
}
ion-item h2 {
font-weight: 600;
margin: 0;
/**
* With larger font scales
* the date/time should wrap to the next
* line. However, there should be
* space between the name and the date/time
* if they can appear on the same line.
*/
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
ion-item p {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
width: 95%;
}
ion-item .date {
align-items: center;
display: flex;
}
ion-item ion-icon {
color: #c9c9ca;
}
ion-item ion-note {
font-size: 0.9375rem;
margin-right: 8px;
font-weight: normal;
}
ion-item ion-note.md {
margin-right: 14px;
}
.dot {
display: block;
height: 12px;
width: 12px;
border-radius: 50%;
align-self: start;
margin: 16px 10px 16px 16px;
}
.dot-unread {
background: var(--ion-color-primary);
}
ion-footer ion-title {
font-size: 11px;
font-weight: normal;
}

View File

@@ -1,25 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RouterModule } from '@angular/router';
import { IonicModule } from '@ionic/angular';
import { MessageComponent } from './message.component';
describe('MessageComponent', () => {
let component: MessageComponent;
let fixture: ComponentFixture<MessageComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [MessageComponent],
imports: [IonicModule.forRoot(), RouterModule.forRoot([])]
}).compileComponents();
fixture = TestBed.createComponent(MessageComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -1,17 +0,0 @@
import { ChangeDetectionStrategy, Component, inject, Input } from '@angular/core';
import { Platform } from '@ionic/angular';
import { Message } from '../services/data.service';
@Component({
selector: 'app-message',
templateUrl: './message.component.html',
styleUrls: ['./message.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class MessageComponent {
private platform = inject(Platform);
@Input() message?: Message;
isIos() {
return this.platform.is('ios')
}
}

View File

@@ -1,15 +0,0 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { IonicModule } from '@ionic/angular';
import { MessageComponent } from './message.component';
@NgModule({
imports: [ CommonModule, FormsModule, IonicModule, RouterModule],
declarations: [MessageComponent],
exports: [MessageComponent]
})
export class MessageComponentModule {}

View File

@@ -1,12 +1,12 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router'; import { Routes, RouterModule } from '@angular/router';
import { ViewMessagePage } from './view-message.page'; import { AvisoPage } from './aviso.page';
const routes: Routes = [ const routes: Routes = [
{ {
path: '', path: '',
component: ViewMessagePage component: AvisoPage
} }
]; ];
@@ -14,4 +14,4 @@ const routes: Routes = [
imports: [RouterModule.forChild(routes)], imports: [RouterModule.forChild(routes)],
exports: [RouterModule], exports: [RouterModule],
}) })
export class ViewMessagePageRoutingModule {} export class AvisoPageRoutingModule {}

View File

@@ -0,0 +1,23 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { IonicModule } from '@ionic/angular';
import { AvisoPageRoutingModule } from './aviso-routing.module';
import { AutoResizeTextDirective } from '../../directives/auto-resize-text.directive';
import { AvisoPage } from './aviso.page';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
@NgModule({
imports: [
CommonModule,
FormsModule,
IonicModule,
AvisoPageRoutingModule
],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [AvisoPage, AutoResizeTextDirective]
})
export class AvisoPageModule {}

View File

@@ -0,0 +1,96 @@
<ion-content fullscreen>
<!-- Titel für Arrivals -->
<ion-header>
<ion-toolbar>
<ion-title class="ion-text-center">
Ankünfte ({{ totalArrivals }}) Seite {{ currentPageIndex + 1 }} von {{ pages.length }}
</ion-title>
</ion-toolbar>
</ion-header>
<!-- Fehlernachricht anzeigen -->
<ion-grid>
<ion-row *ngIf="errorMessage">
<ion-col size="12" class="ion-text-center">
<ion-text color="danger">
<p>{{ errorMessage }}</p>
</ion-text>
</ion-col>
</ion-row>
<!-- Ladeindikator -->
<ion-row *ngIf="loadingArrivals && arrivals.length === 0">
<ion-col size="12" class="ion-text-center">
<ion-spinner name="crescent"></ion-spinner>
</ion-col>
</ion-row>
</ion-grid>
<!-- Grid-Container für Arrivals Kacheln -->
<div class="grid-container" *ngIf="!loadingArrivals && pages.length > 0">
<ion-card *ngFor="let aviso of pages[currentPageIndex]"
class="arrival-card"
[ngClass]="getStatusClass(aviso.status)">
<ion-card-header>
<ion-card-title class="ion-text-center">
<strong>{{ aviso.lkW_Nr }}</strong>
</ion-card-title>
</ion-card-header>
<ion-card-content>
<!-- Zentrierter Absatz für Ankunft und Dauer -->
<div class="centered-content">
<p class="ion-text-center">
<ion-icon [name]="getStatusIcon(aviso.status)"
[color]="getStatusColor(aviso.status)"></ion-icon>
{{ aviso.ankunft }}
<ion-icon name="hourglass-outline"></ion-icon>
{{ aviso.dauer }}
</p>
<p class="ion-text-center">
<ion-icon name="person-outline"></ion-icon>
{{ aviso.letzterMitarbeiter }}
</p>
</div>
</ion-card-content>
</ion-card>
</div>
<!-- Nachricht, wenn keine Arrivals vorhanden sind -->
<ion-grid>
<ion-row *ngIf="!loadingArrivals && arrivals.length === 0 && !errorMessage">
<ion-col size="12" class="ion-text-center">
<ion-text>
<p>Keine Ankünfte gefunden.</p>
</ion-text>
</ion-col>
</ion-row>
</ion-grid>
<!-- Bedingtes Textfeld für TV-Einstellungen -->
</ion-content>
<ion-footer>
<ng-container *ngFor="let setting of avisoTvSettings; let i = index">
<ng-container *ngIf="setting.isActive && setting.fixeZeile1">
<ion-item>
<!-- Erstes Div, wird angezeigt, wenn showFirstDiv true ist -->
<div *ngIf="showFirstDiv"
[innerHTML]="sanitizeHtml(setting.fixeZeile1 || '',i)"
autoResizeText
[style.text-align]="getTextAlign(i)"
class="htmltext">
</div>
<!-- Zweites Div, wird angezeigt, wenn showFirstDiv false ist -->
<div *ngIf="!showFirstDiv"
[innerHTML]="sanitizeHtml(setting.fixeZeile2 || '',i)"
autoResizeText
[style.text-align]="getTextAlign(i)"
class="htmltext">
</div>
</ion-item>
</ng-container>
</ng-container>
</ion-footer>

View File

@@ -0,0 +1,149 @@
ion-content {
--background: #f0f0f0; /* Heller Hintergrund für besseren Kontrast */
}
.title-card {
background-color: #3880ff; /* Beispiel: Blaue Hintergrundfarbe */
color: white;
text-align: center;
}
ion-card-content p {
font-size: 1.8em; /* Größere Schriftgröße für die Inhalte */
margin: 10px 0;
}
.grid-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(15%, 1fr)); /* Feste Mindestbreite */
gap: 1%; /* Abstand zwischen den Kacheln */
padding: 0px; /* Optional: Innenabstand */
justify-content: center; /* Zentriert die Kacheln */
}
.arrival-card {
width: 95%; /* Feste Breite */
height: 24vh; /* Feste Höhe */
border-radius: 15px; /* Abgerundete Ecken für ein moderneres Aussehen */
transition: box-shadow 0.3s, transform 0.3s;
}
.htmltext {
height: 30vh;
width: 100%;
white-space: pre-line;
transition: box-shadow 0.3s, transform 0.3s;
}
ion-content {
ion-grid.htmltext {
height: 30vh;
}
}
ion-icon {
vertical-align: middle;
font-size: 1.2em; /* Größere Icons für bessere Sichtbarkeit */
}
/* Statusklassen */
.statusLKW_Erfasst {
border-left: 10px solid #2dd36f; /* Grün */
}
.statusLKW_Freigegeben {
border-left: 10px solid #3880ff; /* Blau */
}
.statusLKW_LKW_neu {
border-left: 10px solid #f78c40; /* Orange */
}
.statusLKW_Ankunft {
border-left: 10px solid #f78c40; /* Orange */
}
.statusLKW_Vorbereitet {
border-left: 10px solid #c8c8c8; /* Grau */
}
.statusLKW_Vorgeschrieben {
border-left: 10px solid #a259ff; /* Violett */
}
.status-default {
border-left: 10px solid #c8c8c8; /* Grau */
}
/* Animationen für Ankunftskarten */
.arrival-card {
animation: fadeIn 0.5s ease-in-out;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: scale(0.95);
}
to {
opacity: 1;
transform: scale(1);
}
}
@media (min-width: 1200px) {
ion-card-title {
font-size: 3.5em;
}
ion-card-content p {
font-size: 2em;
}
}
ion-text p {
font-size: 2em;
}
.card-title {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 2.5em;
font-weight: bold;
}
.card-title ion-icon {
margin-left: auto;
}
ion-footer {
background-color: #ffffff;
border-top: 1px solid #c8c8c8;
}
ion-toolbar {
--padding-start: 0;
--padding-end: 0;
}
ion-title {
font-size: 1.6em;
font-weight: bold;
color: #3880ff;
}
@media (max-width: 600px) {
ion-title {
font-size: 1.4em;
}
}

View File

@@ -0,0 +1,17 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AvisoPage } from './aviso.page';
describe('AvisoPage', () => {
let component: AvisoPage;
let fixture: ComponentFixture<AvisoPage>;
beforeEach(() => {
fixture = TestBed.createComponent(AvisoPage);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,484 @@
import {
Component,OnInit,OnDestroy,HostListener,ChangeDetectorRef
} from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { AvisoService, AvisoArrivalsResponse } from '../../services/aviso.service';
import { AvisoDto } from '../../services/aviso.dto';
import { AvisoTvSettingsDto } from '../../services/aviso-tv-settings.dto';
import { ToastController } from '@ionic/angular';
import { interval, Subject } from 'rxjs';
import { switchMap, takeUntil, startWith } from 'rxjs/operators';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
@Component({
selector: 'app-aviso',
templateUrl: './aviso.page.html',
styleUrls: ['./aviso.page.scss'],
})
export class AvisoPage implements OnInit, OnDestroy {
standort: string = '';
seiten: boolean = false;
onlyOK: boolean = false;
// Datenmodelle
arrivals: AvisoDto[] = [];
avisoTvSettings: AvisoTvSettingsDto[] = []; // Neues Datenmodell
// Paginierung
pages: AvisoDto[][] = [];
currentPageIndex: number = 0;
tilesPerPage: number = 0;
// Ladezustand
loadingArrivals: boolean = false;
loadingSettings: boolean = false; // Ladezustand für Einstellungen
// Gesamtanzahl
totalArrivals: number = 0;
totalSettings: number = 0; // Gesamtanzahl für Einstellungen
// Fehlernachricht
errorMessage: string = '';
settingsErrorMessage: string = ''; // Fehlernachricht für Einstellungen
// Subjects zum Beenden der Subscriptions
private destroy$ = new Subject<void>();
private pageRotation$ = new Subject<void>(); // Optional, für separate Page Rotation
// Feste Kachelgrößen (entsprechend den CSS-Einstellungen)
private readonly tileWidth: number = 450; // in px
private readonly tileHeight: number = 100; // in px
private readonly gutter: number = 0; // Abstand zwischen den Kacheln in px
private readonly containerPadding: number = 0; // Innenabstand des Grid-Containers in px
public fontSettings: {textAlign: string}[] = [];
private toggleDivInterval: any;
showFirstDiv: boolean = true;
constructor(
private route: ActivatedRoute,
private avisoService: AvisoService,
private toastController: ToastController,
private cdr: ChangeDetectorRef,
private sanitizer: DomSanitizer
) { }
/**
* Sanitizes HTML, extracts font size and text align from span elements,
* and stores them in dataset attributes and the fontSettings array.
* @param html Das zu sanitizende HTML
* @param index Index der Einstellung zur Zuordnung der Stilinformationen
* @returns Sanitized HTML als SafeHtml
*/
sanitizeHtml(html: string, index: number): SafeHtml {
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const spans = doc.querySelectorAll('div');
spans.forEach(span => {
const fontSize = span.style.fontSize;
if (fontSize) {
let fontSizePx: number;
if (fontSize.endsWith('pt')) {
fontSizePx = parseFloat(fontSize) * 1.333; // Umrechnung von pt zu px
} else if (fontSize.endsWith('px')) {
fontSizePx = parseFloat(fontSize);
} else {
// Andere Einheiten oder Standardfall
fontSizePx = parseFloat(fontSize);
}
const textAlign = span.style.textAlign || 'left';
// Speichern der ursprünglichen Schriftgröße und Textausrichtung
this.fontSettings[index] = {
textAlign: textAlign
};
// Speichern in dataset
span.dataset['originalFontSizePx'] = fontSizePx.toString();
span.dataset['originalTextAlign'] = textAlign;
// Debugging: Loggen der ursprünglichen Schriftgröße
console.log(`Original Font Size for span: "${span.textContent}" = ${fontSizePx}px`);
} else {
// Falls keine Schriftgröße definiert ist
console.log(`No inline font size found for span: "${span.textContent}"`);
}
// Entfernen der Inline-Schriftgröße
span.style.fontSize = '';
// Hinzufügen der Klasse für die dynamische Anpassung
span.classList.add('dynamic-font-size');
});
// Debugging: Loggen des bearbeiteten HTML
console.log('Processed HTML:', doc.body.innerHTML);
return this.sanitizer.bypassSecurityTrustHtml(doc.body.innerHTML);
}
getTextAlign(index: number): string {
const settings = this.fontSettings[index];
return settings ? settings.textAlign : 'left';
}
ngOnInit() {
this.route.queryParams.pipe(takeUntil(this.destroy$)).subscribe((params) => {
this.standort = params['standort'] || 'SUB';
this.seiten = params['seiten'] === 'true' || false;
this.onlyOK = params['onlyOK'] === 'true' || false;
// Initiales Laden der Daten
this.loadArrivals();
this.loadAvisoTvSettings();
this.startDivToggle();
});
interval(1000000) // 10000 Millisekunden = 10 Sekunden
.pipe(
startWith(0), // Sofortiger Start ohne Warten
switchMap(() => {
// Nur den Ladeindikator anzeigen, wenn keine Daten vorhanden sind
this.loadingArrivals = this.arrivals.length === 0;
this.errorMessage = '';
return this.avisoService.getArrivals(this.standort, this.seiten, this.onlyOK);
}),
takeUntil(this.destroy$)
)
.subscribe(
(data: AvisoArrivalsResponse) => { // Typisierung der empfangenen Daten
console.log('Received arrivals data:', data); // Zum Debuggen
this.arrivals = data.avisos;
this.totalArrivals = data.totalCount;
this.loadingArrivals = false;
this.paginateArrivals();
this.cdr.detectChanges(); // Manuelle Change Detection
},
(error) => {
console.error('Fehler beim Laden der Arrivals:', error);
this.presentToast(`Fehler beim Laden der Arrivals: ${error}`);
this.errorMessage = 'Fehler beim Laden der Arrivals. Bitte versuche es später erneut.';
this.loadingArrivals = false;
}
);
// Automatisches Aktualisieren der TV-Einstellungen alle 10 Sekunden
interval(1000000) // 10000 Millisekunden = 10 Sekunden
.pipe(
startWith(0),
switchMap(() => {
this.loadingSettings = this.avisoTvSettings.length === 0;
this.settingsErrorMessage = '';
return this.avisoService.getAvisoTvSettings(this.standort);
}),
takeUntil(this.destroy$)
)
.subscribe(
(data: AvisoTvSettingsDto[]) => { // Typisierung als Array
console.log('Received TV settings data:', data);
this.avisoTvSettings = data;
this.totalSettings = data.length; // Setze totalSettings auf die Länge des Arrays
this.loadingSettings = false;
this.cdr.detectChanges(); // Manuelle Change Detection
// Debugging: Überprüfe jede Einstellung
this.avisoTvSettings.forEach(setting => {
console.log(`Setting ID: ${setting.tvTextBezeichnungID}, isActive: ${setting.isActive}, position: ${setting.position}, fixeZeile1: ${setting.fixeZeile1}`);
});
},
(error) => {
console.error('Fehler beim Laden der TV Settings:', error);
this.presentToast(`Fehler beim Laden der TV Settings: ${error}`);
this.settingsErrorMessage = 'Fehler beim Laden der TV Settings. Bitte versuche es später erneut.';
this.loadingSettings = false;
}
);
// Starte die Page Rotation einmal nach dem Initialen Laden
// Optional: Falls du separate Subjects für Page Rotation verwendest
// this.startPageRotation();
}
ngOnDestroy() {
// Beenden aller Subscriptions, um Speicherlecks zu vermeiden
this.destroy$.next();
this.destroy$.complete();
// Beende auch die Page Rotation Subscription, falls du separate Subjects verwendest
// this.pageRotation$.next();
// this.pageRotation$.complete();
}
/**
* Lädt die Ankünfte (Arrivals) von der API.
*/
loadArrivals() {
this.loadingArrivals = true;
this.avisoService
.getArrivals(this.standort, this.seiten, this.onlyOK)
.pipe(takeUntil(this.destroy$))
.subscribe(
(data: AvisoArrivalsResponse) => {
console.log('Received arrivals data:', data);
this.arrivals = data.avisos;
this.totalArrivals = data.totalCount;
this.loadingArrivals = false;
this.paginateArrivals();
this.cdr.detectChanges();
},
(error) => {
console.error('Fehler beim Laden der Arrivals:', error);
this.presentToast(`Fehler beim Laden der Arrivals: ${error}`);
this.errorMessage = 'Fehler beim Laden der Arrivals. Bitte versuche es später erneut.';
this.loadingArrivals = false;
}
);
}
/**
* Lädt die Aviso TV Einstellungen von der API.
*/
loadAvisoTvSettings() {
this.loadingSettings = true;
this.avisoService
.getAvisoTvSettings(this.standort)
.pipe(takeUntil(this.destroy$))
.subscribe(
(data: AvisoTvSettingsDto[]) => { // Typisierung als Array
console.log('Received TV settings data:', data); // Zum Debuggen
this.avisoTvSettings = data;
this.totalSettings = data.length; // Setze totalSettings auf die Länge des Arrays
this.loadingSettings = false;
this.cdr.detectChanges(); // Manuelle Change Detection
showFirstDiv: true // Initialisiere alle auf true
// Debugging: Überprüfe jede Einstellung
this.avisoTvSettings.forEach(setting => {
console.log(`Setting ID: ${setting.tvTextBezeichnungID}, isActive: ${setting.isActive}, position: ${setting.position}, fixeZeile1: ${setting.fixeZeile1}`);
});
},
(error) => {
console.error('Fehler beim Laden der TV Settings:', error);
this.presentToast(`Fehler beim Laden der TV Settings: ${error}`);
this.settingsErrorMessage = 'Fehler beim Laden der TV Settings. Bitte versuche es später erneut.';
this.loadingSettings = false;
}
);
}
startDivToggle() {
this.toggleDivInterval = setInterval(() => {
this.showFirstDiv = !this.showFirstDiv;
this.cdr.detectChanges(); // Manuelle Change Detection, falls nötig
}, 5000); // 5000 Millisekunden = 5 Sekunden
}
/**
* Lädt Arrivals und TV-Einstellungen basierend auf aktuellen Filtern.
*/
applyFilters() {
this.loadArrivals();
this.loadAvisoTvSettings();
}
/**
* Zeigt eine Toast-Nachricht an.
* @param message Die Nachricht, die angezeigt werden soll.
*/
async presentToast(message: string) {
const toast = await this.toastController.create({
message: message,
duration: 3000,
position: 'bottom',
});
toast.present();
}
// Methoden zur Bestimmung von Statusklassen und -farben
getStatusClass(status: number): string {
switch (status) {
case 0:
return 'statusLKW_Erfasst';
case 1:
return 'statusLKW_Freigegeben';
case 2:
return 'statusLKW_LKW_neu';
case 3:
return 'statusLKW_Ankunft';
case 4:
return 'statusLKW_Vorbereitet';
case 5:
return 'statusLKW_Vorgeschrieben';
default:
return 'status-default';
}
}
getStatusIcon(status: number): string {
switch (status) {
case 0:
return 'checkmark-circle'; // Erfasst
case 1:
return 'checkmark-circle'; // Freigegeben
case 2:
return 'help-circle'; // LKW n.e.
case 3:
return 'time'; // Ankunft
case 4:
return 'construct'; // Vorbereitet
case 5:
return 'bookmarks'; // Vorgeschrieben
default:
return 'help-circle'; // Unbekannt
}
}
getStatusColor(status: number): string {
switch (status) {
case 0:
return 'success'; // Erfasst - Grün
case 1:
return 'primary'; // Freigegeben - Blau
case 2:
return 'warning'; // LKW n.e. - Gelb/Orange
case 3:
return 'warning'; // Ankunft - Gelb/Orange
case 4:
return 'secondary'; // Vorbereitet - Grau
case 5:
return 'tertiary'; // Vorgeschrieben - Violett
default:
return 'medium'; // Unbekannt - Grau
}
}
getStatusText(status: number): string {
switch (status) {
case 0:
return 'Erfasst';
case 1:
return 'Freigegeben';
case 2:
return 'LKW n.e.';
case 3:
return 'Ankunft';
case 4:
return 'Vorbereitet';
case 5:
return 'Vorgeschrieben';
default:
return 'Unbekannt';
}
}
/**
* Gibt die CSS-Klasse basierend auf dem Position-Wert zurück.
* @param position Die Position (Top, Middle, Left, Right)
* @returns Eine CSS-Klassen-String
*/
getPositionClass(position: string): string {
const pos = position.toLowerCase(); // Konvertiere zu Kleinbuchstaben
switch (pos) {
case 'top':
return 'position-top';
case 'middle':
return 'position-middle';
case 'left':
return 'position-left';
case 'right':
return 'position-right';
default:
return '';
}
}
// Paginierung der Arrivals
paginateArrivals() {
// Berechnung der verfügbaren Breite und Höhe
const windowWidth = window.innerWidth;
const windowHeight = window.innerHeight;
// Definiere die Prozentsätze für Layout-Parameter
const titleHeightPercent = 5; // z.B. 10% der Fensterhöhe
const htmltextPercent = this.totalArrivals > 0 ? 30 : 0;
const containerPaddingPercent = 0; // z.B. 5% der Fensterbreite
const gutterPercent = 1;
const tileWidthPercent = 15; // z.B. 20% der Fensterbreite
const tileHeightPercent = 23; // z.B. 25% der Fensterhöhe
const containerPadding = (containerPaddingPercent / 100) * windowWidth;
const gutter = (gutterPercent / 100) * windowWidth;
const tileWidth = (tileWidthPercent / 100) * windowWidth;
const tileHeight = (tileHeightPercent / 100) * windowHeight;
// Berechnung der verfügbaren Breite für die Kacheln
const containerPaddingTotal = containerPadding * 2; // Links und rechts
const columns = Math.floor((windowWidth - containerPaddingTotal + gutter) / (tileWidth + gutter));
// Berechnung der verfügbaren Höhe für die Kacheln
const titleHeight = (titleHeightPercent / 100) * windowHeight;
let htmltext = 0;
if (this.totalArrivals > 0) {
htmltext = (htmltextPercent / 100) * windowHeight;
}
const availableHeightForTiles = windowHeight - htmltext - titleHeight - containerPadding * 2 - gutter * (columns > 1 ? columns : 0);
// Berechnung der maximal möglichen Kacheln pro Spalte
const rows = Math.floor((availableHeightForTiles + gutter) / (tileHeight + gutter));
// Berechnung der Kacheln pro Seite
this.tilesPerPage = columns * rows;
// Sicherstellen, dass mindestens eine Kachel pro Seite angezeigt wird
this.tilesPerPage = this.tilesPerPage > 0 ? this.tilesPerPage : 1;
console.log('Columns:', columns);
console.log('Rows:', rows);
console.log('Tiles per Page:', this.tilesPerPage);
// Aufteilung der Arrivals in Seiten
this.pages = [];
for (let i = 0; i < this.arrivals.length; i += this.tilesPerPage) {
this.pages.push(this.arrivals.slice(i, i + this.tilesPerPage));
}
// Reset der aktuellen Seite, wenn nötig
if (this.currentPageIndex >= this.pages.length) {
this.currentPageIndex = 0;
}
console.log('Total Pages:', this.pages.length);
}
// Automatisches Wechseln der Seiten alle 10 Sekunden
startPageRotation() {
interval(10000) // 10000 Millisekunden = 10 Sekunden
.pipe(takeUntil(this.destroy$))
.subscribe(() => {
if (this.pages.length > 0) {
this.currentPageIndex = (this.currentPageIndex + 1) % this.pages.length;
console.log('Wechsel zu Seite:', this.currentPageIndex + 1);
}
});
}
/**
* Event Listener für Fenstergrößenänderungen, um die Paginierung anzupassen
*/
@HostListener('window:resize', ['$event'])
onResize(event: Event) {
this.paginateArrivals();
}
}

View File

@@ -5,14 +5,13 @@ import { FormsModule } from '@angular/forms';
import { HomePage } from './home.page'; import { HomePage } from './home.page';
import { HomePageRoutingModule } from './home-routing.module'; import { HomePageRoutingModule } from './home-routing.module';
import { MessageComponentModule } from '../message/message.module';
@NgModule({ @NgModule({
imports: [ imports: [
CommonModule, CommonModule,
FormsModule, FormsModule,
IonicModule, IonicModule,
MessageComponentModule,
HomePageRoutingModule HomePageRoutingModule
], ],
declarations: [HomePage] declarations: [HomePage]

View File

View File

View File

@@ -0,0 +1,15 @@
import { Component, inject } from '@angular/core';
import { RefresherCustomEvent } from '@ionic/angular';
@Component({
selector: 'app-home',
templateUrl: 'home.page.html',
styleUrls: ['home.page.scss'],
})
export class HomePage {
constructor() {}
}

View File

@@ -0,0 +1,24 @@
export interface AvisoTvSettingsDto {
tvTextBezeichnungID: number;
standortID: number;
tvTextBezeichnung: string;
standort: string;
position: string;
fixeZeile1?: string;
fixeZeile2?: string;
fixeZeile3?: string;
art?: string;
startDate?: string | null;
endDate?: string | null;
startTime?: string | null;
endTime?: string | null;
isRecurring: boolean;
isMonday: boolean;
isTuesday: boolean;
isWednesday: boolean;
isThursday: boolean;
isFriday: boolean;
isSaturday: boolean;
isSunday: boolean;
isActive: boolean;
}

View File

@@ -0,0 +1,13 @@
export interface AvisoDto {
avisoID: number;
status: number;
lkW_Nr: string;
ankunft: string | null;
dauer: string;
letzterMitarbeiter: string;
weiterleitungTextTV: string;
imEx: string;
zollDigitalEingereicht: boolean;
buero: string;
avisoTVHinweis: string;
}

View File

@@ -0,0 +1,88 @@
import { Injectable } from '@angular/core';
import { HttpClient, HttpParams, HttpErrorResponse } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError, tap } from 'rxjs/operators';
import { AvisoDto } from './aviso.dto'; // Importiere das Interface
import { AvisoTvSettingsDto } from './aviso-tv-settings.dto'; // Importiere das neue Interface
// Definiere die Response Interfaces
export interface AvisoArrivalsResponse {
avisos: AvisoDto[];
totalCount: number;
}
@Injectable({
providedIn: 'root'
})
export class AvisoService {
private baseUrl = 'https://localhost:7063/api/Aviso';
constructor(private http: HttpClient) { }
/**
* Holt die Ankünfte (Arrivals) basierend auf den übergebenen Parametern.
* @param standort Standort zur Filterung der Ankünfte.
* @param seiten Gibt an, ob nur Seiten zurückgegeben werden sollen.
* @param onlyOK Gibt an, ob nur gültige Ankünfte zurückgegeben werden sollen.
* @returns Ein Observable von AvisoArrivalsResponse.
*/
getArrivals(standort: string, seiten: boolean = false, onlyOK: boolean = false): Observable<AvisoArrivalsResponse> {
let params = new HttpParams()
.set('standort', standort)
.set('seiten', seiten.toString())
.set('onlyOK', onlyOK.toString());
return this.http.get<AvisoArrivalsResponse>(`${this.baseUrl}/arrivals`, { params })
.pipe(
tap(data => console.log('AvisoService received arrivals data:', data)), // Logge die empfangenen Daten
catchError(this.handleError)
);
}
/**
* Holt die Aviso TV Einstellungen basierend auf dem übergebenen Standort.
* @param standort Standort zur Filterung der Einstellungen.
* @returns Ein Observable von AvisoTvSettingsDto[].
*/
getAvisoTvSettings(standort: string): Observable<AvisoTvSettingsDto[]> {
return this.http.get<AvisoTvSettingsDto[]>(`${this.baseUrl}/${standort}`)
.pipe(
tap(data => console.log('AvisoService received TV settings data:', data)), // Logge die empfangenen Daten
catchError(this.handleError)
);
}
/**
* Holt die bereitstehenden Avisos basierend auf dem übergebenen Standort.
* @param standort Standort zur Filterung der bereitstehenden Avisos.
* @returns Ein Observable von AvisoDto[].
*/
getReadyAvisos(standort: string = ''): Observable<AvisoDto[]> {
let params = new HttpParams().set('standort', standort);
return this.http.get<AvisoDto[]>(`${this.baseUrl}/ready`, { params })
.pipe(
tap(data => console.log('AvisoService received ready avisos:', data)), // Logge die empfangenen Daten
catchError(this.handleError)
);
}
/**
* Behandelt Fehler, die während HTTP-Anfragen auftreten.
* @param error Das aufgetretene HttpErrorResponse-Objekt.
* @returns Ein Observable, das einen Fehler auswirft.
*/
private handleError(error: HttpErrorResponse) {
let errorMessage = 'Unbekannter Fehler!';
if (error.error instanceof ErrorEvent) {
// Client-seitiger Fehler
errorMessage = `Fehler: ${error.error.message}`;
} else {
// Server-seitiger Fehler
errorMessage = `Server returned code: ${error.status}, error message is: ${error.message}`;
}
return throwError(errorMessage);
}
}

View File

@@ -1,12 +0,0 @@
import { TestBed } from '@angular/core/testing';
import { DataService } from './data.service';
describe('DataService', () => {
beforeEach(() => TestBed.configureTestingModule({}));
it('should be created', () => {
const service: DataService = TestBed.get(DataService);
expect(service).toBeTruthy();
});
});

View File

@@ -1,83 +0,0 @@
import { Injectable } from '@angular/core';
export interface Message {
fromName: string;
subject: string;
date: string;
id: number;
read: boolean;
}
@Injectable({
providedIn: 'root'
})
export class DataService {
public messages: Message[] = [
{
fromName: 'Matt Chorsey',
subject: 'New event: Trip to Vegas',
date: '9:32 AM',
id: 0,
read: false
},
{
fromName: 'Lauren Ruthford',
subject: 'Long time no chat',
date: '6:12 AM',
id: 1,
read: false
},
{
fromName: 'Jordan Firth',
subject: 'Report Results',
date: '4:55 AM',
id: 2,
read: false
},
{
fromName: 'Bill Thomas',
subject: 'The situation',
date: 'Yesterday',
id: 3,
read: false
},
{
fromName: 'Joanne Pollan',
subject: 'Updated invitation: Swim lessons',
date: 'Yesterday',
id: 4,
read: false
},
{
fromName: 'Andrea Cornerston',
subject: 'Last minute ask',
date: 'Yesterday',
id: 5,
read: false
},
{
fromName: 'Moe Chamont',
subject: 'Family Calendar - Version 1',
date: 'Last Week',
id: 6,
read: false
},
{
fromName: 'Kelly Richardson',
subject: 'Placeholder Headhots',
date: 'Last Week',
id: 7,
read: false
}
];
constructor() { }
public getMessages(): Message[] {
return this.messages;
}
public getMessageById(id: number): Message {
return this.messages[id];
}
}

View File

@@ -1,19 +0,0 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { ViewMessagePage } from './view-message.page';
import { IonicModule } from '@ionic/angular';
import { ViewMessagePageRoutingModule } from './view-message-routing.module';
@NgModule({
imports: [
CommonModule,
FormsModule,
IonicModule,
ViewMessagePageRoutingModule
],
declarations: [ViewMessagePage]
})
export class ViewMessagePageModule {}

View File

@@ -1,29 +0,0 @@
<ion-header [translucent]="true">
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button [text]="getBackButtonText()" defaultHref="/"></ion-back-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content [fullscreen]="true" *ngIf="message">
<ion-item>
<ion-icon aria-hidden="true" name="person-circle" color="primary"></ion-icon>
<ion-label class="ion-text-wrap">
<h2>
{{ message.fromName }}
<span class="date">
<ion-note>{{ message.date }}</ion-note>
</span>
</h2>
<h3>To: <ion-note>Me</ion-note></h3>
</ion-label>
</ion-item>
<div class="ion-padding">
<h1>{{ message.subject }}</h1>
<p>
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</p>
</div>
</ion-content>

View File

@@ -1,50 +0,0 @@
ion-item {
--inner-padding-end: 0;
--background: transparent;
}
ion-label {
margin-top: 12px;
margin-bottom: 12px;
}
ion-item h2 {
font-weight: 600;
/**
* With larger font scales
* the date/time should wrap to the next
* line. However, there should be
* space between the name and the date/time
* if they can appear on the same line.
*/
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
ion-item .date {
align-items: center;
display: flex;
}
ion-item ion-icon {
font-size: 42px;
margin-right: 8px;
}
ion-item ion-note {
font-size: 0.9375rem;
margin-right: 12px;
font-weight: normal;
}
h1 {
margin: 0;
font-weight: bold;
font-size: 1.4rem;
}
p {
line-height: 1.4;
}

View File

@@ -1,26 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { IonicModule } from '@ionic/angular';
import { RouterModule } from '@angular/router';
import { ViewMessagePageRoutingModule } from './view-message-routing.module';
import { ViewMessagePage } from './view-message.page';
describe('ViewMessagePage', () => {
let component: ViewMessagePage;
let fixture: ComponentFixture<ViewMessagePage>;
beforeEach(async () => {
TestBed.configureTestingModule({
declarations: [ViewMessagePage],
imports: [IonicModule.forRoot(), ViewMessagePageRoutingModule, RouterModule.forRoot([])]
}).compileComponents();
fixture = TestBed.createComponent(ViewMessagePage);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -1,29 +0,0 @@
import { CommonModule } from '@angular/common';
import { Component, inject, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { IonicModule, Platform } from '@ionic/angular';
import { DataService, Message } from '../services/data.service';
@Component({
selector: 'app-view-message',
templateUrl: './view-message.page.html',
styleUrls: ['./view-message.page.scss'],
})
export class ViewMessagePage implements OnInit {
public message!: Message;
private data = inject(DataService);
private activatedRoute = inject(ActivatedRoute);
private platform = inject(Platform);
constructor() {}
ngOnInit() {
const id = this.activatedRoute.snapshot.paramMap.get('id') as string;
this.message = this.data.getMessageById(parseInt(id, 10));
}
getBackButtonText() {
const isIos = this.platform.is('ios')
return isIos ? 'Inbox' : '';
}
}

1480
verag.njsproj Normal file

File diff suppressed because it is too large Load Diff

6
verag.njsproj.user Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<LastActiveSolutionConfig>Debug|Any CPU</LastActiveSolutionConfig>
</PropertyGroup>
</Project>

0
verag.sln Normal file
View File

1481
verage.njsproj Normal file

File diff suppressed because it is too large Load Diff

9
verage.njsproj.user Normal file
View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<LastActiveSolutionConfig>Debug|Any CPU</LastActiveSolutionConfig>
</PropertyGroup>
<PropertyGroup>
<PromptedToMigrateToJsps />
</PropertyGroup>
</Project>