Added basic structure for logged-in state

This commit is contained in:
Jim Martens 2023-11-14 17:31:33 +01:00
parent 01a294f0c0
commit d1f43f926c
22 changed files with 655 additions and 18 deletions

View File

@ -53,7 +53,8 @@ import {from, Observable, of, switchMap} from "rxjs";
styleUrls: ['app.component.scss'],
standalone: true,
imports: [RouterLink, RouterLinkActive, IonApp, IonSplitPane, IonMenu, IonContent, IonList,
IonListHeader, IonNote, IonMenuToggle, IonItem, IonIcon, IonLabel, IonRouterOutlet, NgIf, NgFor, AsyncPipe, IonItemDivider, IonHeader, IonToolbar, IonTitle],
IonListHeader, IonNote, IonMenuToggle, IonItem, IonIcon, IonLabel, IonRouterOutlet, NgIf, NgFor, AsyncPipe,
IonItemDivider, IonHeader, IonToolbar, IonTitle],
})
export class AppComponent {
public loggedInPages = [

View File

@ -33,6 +33,21 @@ export const ROOT_ROUTES: Routes = [
loadComponent: () => import("./auth/account/account.component").then(mod => mod.AccountComponent),
canActivate: [AppAuthGuard]
},
{
path: 'routes',
loadComponent: () => import("./routes/routes.component").then(mod => mod.RoutesComponent),
canActivate: [AppAuthGuard]
},
{
path: 'timetables',
loadComponent: () => import("./timetables/timetables.component").then(mod => mod.TimetablesComponent),
canActivate: [AppAuthGuard]
},
{
path: 'formations',
loadComponent: () => import("./formations/formations.component").then(mod => mod.FormationsComponent),
canActivate: [AppAuthGuard]
},
{
path: '',
loadComponent: () => import("./dashboard/dashboard.component").then(mod => mod.DashboardComponent),

View File

@ -1,22 +1,123 @@
<ion-header [translucent]="true">
<ion-toolbar>
<ion-buttons slot="start">
<ion-menu-button></ion-menu-button>
</ion-buttons>
<ion-title i18n="page title">Timetable</ion-title>
</ion-toolbar>
</ion-header>
<ion-content [fullscreen]="true">
<ion-header collapse="condense">
<ng-container *ngIf="loggedIn$ | async; else loggedOut">
<ion-header [translucent]="true">
<ion-toolbar>
<ion-title size="large" i18n="page title">Timetable</ion-title>
<ion-buttons slot="start">
<ion-menu-button></ion-menu-button>
</ion-buttons>
<ion-title i18n="page title">Dashboard</ion-title>
</ion-toolbar>
</ion-header>
<div id="container">
<ion-content [fullscreen]="true">
<ion-list>
<ion-list-header>
<ion-label i18n>Routes</ion-label>
</ion-list-header>
<ion-item>
<ion-label i18n class="bold">Name</ion-label>
<ion-label i18n class="bold">Country</ion-label>
<ion-label i18n class="bold">First Station</ion-label>
<ion-label i18n class="bold">Last Station</ion-label>
<ion-label i18n class="bold ion-text-end"># Stations</ion-label>
</ion-item>
<ion-item-sliding>
<ion-item *ngFor="let route of routes">
<ion-label>{{ route.name }}</ion-label>
<ion-label>{{ route.country.name }}</ion-label>
<ion-label>{{ route.firstStation.name }}</ion-label>
<ion-label>{{ route.lastStation.name }}</ion-label>
<ion-label class="ion-text-end">{{ route.numberOfStations }}</ion-label>
</ion-item>
<ion-item-options side="end">
<ion-item-option color="danger" i18n>
<ion-icon slot="start" [ios]="'trash-outline'" [md]="'trash-sharp'"></ion-icon>
Delete
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
</ion-list>
<ion-list>
<ion-list-header>
<ion-label i18n>Timetables</ion-label>
</ion-list-header>
<ion-item>
<ion-label i18n class="bold">Name</ion-label>
<ion-label i18n class="bold">Route</ion-label>
<ion-label i18n class="bold">Date</ion-label>
<ion-label i18n class="bold">State</ion-label>
<ion-label i18n class="bold ion-text-end"># Services</ion-label>
</ion-item>
<ion-item-sliding>
<ion-item *ngFor="let timetable of timetables">
<ion-label>{{ timetable.name }}</ion-label>
<ion-label>{{ timetable.route }}</ion-label>
<ion-label>{{ timetable.date }}</ion-label>
<ion-label>{{ timetable.state }}</ion-label>
<ion-label class="ion-text-end">{{ timetable.numberOfServices }}</ion-label>
</ion-item>
<ion-item-options side="end">
<ion-item-option color="danger" i18n>
<ion-icon slot="start" [ios]="'trash-outline'" [md]="'trash-sharp'"></ion-icon>
Delete
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
</ion-list>
<ion-list>
<ion-list-header>
<ion-label i18n>Formations</ion-label>
</ion-list-header>
<ion-item>
<ion-label i18n class="bold">Name</ion-label>
<ion-label i18n class="bold">Formation</ion-label>
<ion-label i18n class="bold ion-text-end">Length</ion-label>
</ion-item>
<ion-item-sliding>
<ion-item *ngFor="let formation of formations">
<ion-label>{{ formation.name }}</ion-label>
<ion-label>{{ formation.formation }}</ion-label>
<ion-label class="ion-text-end">{{ formation.length }}</ion-label>
</ion-item>
<ion-item-options side="end">
<ion-item-option color="danger" i18n>
<ion-icon slot="start" [ios]="'trash-outline'" [md]="'trash-sharp'"></ion-icon>
Delete
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
</ion-list>
<ion-fab slot="fixed" horizontal="end" vertical="bottom">
<ion-fab-button i18n-aria-label aria-label="">
<ion-icon [ios]="'add-outline'" [md]="'add-sharp'"></ion-icon>
</ion-fab-button>
<ion-fab-list side="top">
<ion-fab-button i18n-aria-label aria-label="Add formation" [show]="true">
<ion-icon [ios]="'train-outline'" [md]="'train-sharp'"></ion-icon>
</ion-fab-button>
<ion-fab-button i18n-aria-label aria-label="Add timetable" [show]="true">
<ion-icon [ios]="'time-outline'" [md]="'time-sharp'"></ion-icon>
</ion-fab-button>
<ion-fab-button i18n-aria-label aria-label="Add route" [show]="true">
<ion-icon [ios]="'map-outline'" [md]="'map-sharp'"></ion-icon>
</ion-fab-button>
</ion-fab-list>
</ion-fab>
</ion-content>
<ion-footer></ion-footer>
</ng-container>
{{ '' // logged out below }}
<ng-template #loggedOut>
<ion-header [translucent]="true">
<ion-toolbar>
<ion-buttons slot="start">
<ion-menu-button></ion-menu-button>
</ion-buttons>
<ion-title i18n="page title">Timetable</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<p i18n="welcome text|A welcome to users">
TODO
</p>
</div>
</ion-content>
</ion-content>
</ng-template>

View File

@ -3,3 +3,11 @@
p {
@include mixins.justifiedText();
}
.bold {
font-weight: bold;
}
ion-list {
margin-bottom: 1em;
}

View File

@ -1,13 +1,48 @@
import {Component} from '@angular/core';
import {
IonButton,
IonButtons,
IonCol,
IonContent,
IonFab,
IonFabButton,
IonFabList,
IonFooter,
IonHeader,
IonIcon,
IonItem,
IonItemOption,
IonItemOptions,
IonItemSliding,
IonLabel,
IonList,
IonListHeader,
IonMenuButton,
IonMenuToggle,
IonRow,
IonTitle,
IonToolbar
} from "@ionic/angular/standalone";
import {AsyncPipe, NgForOf, NgIf} from "@angular/common";
import {KeycloakService} from "keycloak-angular";
import {from, Observable} from "rxjs";
import {addIcons} from "ionicons";
import {
addOutline,
addSharp,
mapOutline,
mapSharp,
time,
timeOutline,
timeSharp,
trainOutline,
trainSharp,
trashOutline,
trashSharp
} from "ionicons/icons";
import {Route} from "../routes/model/route";
import {Formation} from "../formations/model/formation";
import {Timetable} from ".././timetables/model/timetable";
@Component({
selector: 'app-dashboard',
@ -21,9 +56,57 @@ import {
IonTitle,
IonToolbar,
IonContent,
IonMenuToggle
IonMenuToggle,
NgIf,
AsyncPipe,
IonButton,
IonIcon,
NgForOf,
IonList,
IonItem,
IonLabel,
IonRow,
IonCol,
IonItemSliding,
IonItemOptions,
IonItemOption,
IonListHeader,
IonFab,
IonFabButton,
IonFabList,
IonFooter
]
})
export class DashboardComponent {
loggedIn$: Observable<boolean>;
routes: Route[] = [
{
name: 'Köln-Aachen',
country: {name: $localize`Germany`, code: 'de'},
firstStation: {name: 'Köln Hbf'},
lastStation: {name: 'Aachen Hbf'},
numberOfStations: 30
}
];
timetables: Timetable[] = [];
formations: Formation[] = [];
constructor(private readonly keycloakService: KeycloakService) {
this.loggedIn$ = from(this.keycloakService.isLoggedIn());
addIcons({
addOutline,
addSharp,
trashOutline,
trashSharp,
mapOutline,
mapSharp,
timeOutline,
timeSharp,
trainOutline,
trainSharp,
});
}
protected readonly time = time;
}

View File

@ -0,0 +1,36 @@
<ion-header [translucent]="true">
<ion-toolbar>
<ion-buttons slot="start">
<ion-menu-button></ion-menu-button>
</ion-buttons>
<ion-title i18n="page title">Formations</ion-title>
</ion-toolbar>
</ion-header>
<ion-content [fullscreen]="true">
<ion-list>
<ion-item>
<ion-label i18n class="bold">Name</ion-label>
<ion-label i18n class="bold">Formation</ion-label>
<ion-label i18n class="bold ion-text-end">Length</ion-label>
</ion-item>
<ion-item-sliding>
<ion-item *ngFor="let formation of formations">
<ion-label>{{ formation.name }}</ion-label>
<ion-label>{{ formation.formation }}</ion-label>
<ion-label class="ion-text-end">{{ formation.length }}</ion-label>
</ion-item>
<ion-item-options side="end">
<ion-item-option color="danger" i18n>
<ion-icon slot="start" [ios]="'trash-outline'" [md]="'trash-sharp'"></ion-icon>
Delete
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
</ion-list>
<ion-fab slot="fixed" horizontal="end" vertical="bottom">
<ion-fab-button i18n-aria-label aria-label="Add route">
<ion-icon [ios]="'add-outline'" [md]="'add-sharp'"></ion-icon>
</ion-fab-button>
</ion-fab>
</ion-content>
<ion-footer></ion-footer>

View File

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

View File

@ -0,0 +1,65 @@
import {Component} from '@angular/core';
import {
IonButtons,
IonContent,
IonFab,
IonFabButton,
IonFooter,
IonHeader,
IonIcon,
IonItem,
IonItemOption,
IonItemOptions,
IonItemSliding,
IonLabel,
IonList,
IonListHeader,
IonMenuButton,
IonTitle,
IonToolbar
} from "@ionic/angular/standalone";
import {addIcons} from "ionicons";
import {addOutline, addSharp, trashOutline, trashSharp} from "ionicons/icons";
import {Formation} from "./model/formation";
import {NgForOf} from "@angular/common";
@Component({
selector: 'app-formations',
templateUrl: './formations.component.html',
styleUrls: ['./formations.component.scss'],
standalone: true,
imports: [
IonHeader,
IonToolbar,
IonButtons,
IonMenuButton,
IonTitle,
IonContent,
IonList,
IonListHeader,
IonLabel,
IonItem,
IonItemSliding,
IonItemOptions,
IonItemOption,
IonIcon,
IonFab,
IonFabButton,
IonFooter,
NgForOf
]
})
export class FormationsComponent {
formations: Formation[] = [];
constructor() {
addIcons({
addOutline,
addSharp,
trashOutline,
trashSharp,
});
}
}

View File

@ -0,0 +1,5 @@
export interface Formation {
name: string;
formation: string;
length: number;
}

View File

@ -0,0 +1,4 @@
export interface Country {
code: string;
name: string;
}

View File

@ -0,0 +1,14 @@
import {Station} from "./station";
import {Country} from "./country";
export interface Route {
name: string;
country: Country;
firstStation: Station;
lastStation: Station;
numberOfStations: number;
}
export interface EditedRoute extends Route {
stations: Station[];
}

View File

@ -0,0 +1,3 @@
export interface Station {
name: string;
}

View File

@ -0,0 +1,40 @@
<ion-header [translucent]="true">
<ion-toolbar>
<ion-buttons slot="start">
<ion-menu-button></ion-menu-button>
</ion-buttons>
<ion-title i18n="page title">Routes</ion-title>
</ion-toolbar>
</ion-header>
<ion-content [fullscreen]="true">
<ion-list>
<ion-item>
<ion-label i18n class="bold">Name</ion-label>
<ion-label i18n class="bold">Country</ion-label>
<ion-label i18n class="bold">First Station</ion-label>
<ion-label i18n class="bold">Last Station</ion-label>
<ion-label i18n class="bold"># Stations</ion-label>
</ion-item>
<ion-item-sliding>
<ion-item *ngFor="let route of routes">
<ion-label>{{ route.name }}</ion-label>
<ion-label>{{ route.country.name }}</ion-label>
<ion-label>{{ route.firstStation.name }}</ion-label>
<ion-label>{{ route.lastStation.name }}</ion-label>
<ion-label>{{ route.numberOfStations }}</ion-label>
</ion-item>
<ion-item-options side="end">
<ion-item-option color="danger" i18n>
<ion-icon slot="start" [ios]="'trash-outline'" [md]="'trash-sharp'"></ion-icon>
Delete
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
</ion-list>
<ion-fab slot="fixed" horizontal="end" vertical="bottom">
<ion-fab-button i18n-aria-label aria-label="Add route">
<ion-icon [ios]="'add-outline'" [md]="'add-sharp'"></ion-icon>
</ion-fab-button>
</ion-fab>
</ion-content>
<ion-footer></ion-footer>

View File

@ -0,0 +1,9 @@
@use 'mixins';
p {
@include mixins.justifiedText();
}
.bold {
font-weight: bold;
}

View File

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

View File

@ -0,0 +1,72 @@
import {Component} from '@angular/core';
import {Route} from "./model/route";
import {addIcons} from "ionicons";
import {addOutline, addSharp, trashOutline, trashSharp} from "ionicons/icons";
import {NgForOf} from "@angular/common";
import {
IonButtons,
IonContent,
IonFab,
IonFabButton,
IonFooter,
IonHeader,
IonIcon,
IonItem,
IonItemOption,
IonItemOptions,
IonItemSliding,
IonLabel,
IonList,
IonListHeader,
IonMenuButton,
IonTitle,
IonToolbar
} from "@ionic/angular/standalone";
@Component({
selector: 'app-routes',
templateUrl: './routes.component.html',
styleUrls: ['./routes.component.scss'],
standalone: true,
imports: [
IonHeader,
NgForOf,
IonToolbar,
IonButtons,
IonMenuButton,
IonTitle,
IonContent,
IonList,
IonListHeader,
IonLabel,
IonItem,
IonItemSliding,
IonItemOptions,
IonItemOption,
IonIcon,
IonFab,
IonFabButton,
IonFooter
]
})
export class RoutesComponent {
routes: Route[] = [
{
name: 'Köln-Aachen',
country: {name: $localize`Germany`, code: 'de'},
firstStation: {name: 'Köln Hbf'},
lastStation: {name: 'Aachen Hbf'},
numberOfStations: 30
}
];
constructor() {
addIcons({
addOutline,
addSharp,
trashOutline,
trashSharp,
});
}
}

View File

@ -0,0 +1,7 @@
export interface Timetable {
name: string;
route: string;
date: string;
state: string;
numberOfServices: number;
}

View File

@ -0,0 +1,40 @@
<ion-header [translucent]="true">
<ion-toolbar>
<ion-buttons slot="start">
<ion-menu-button></ion-menu-button>
</ion-buttons>
<ion-title i18n="page title">Timetables</ion-title>
</ion-toolbar>
</ion-header>
<ion-content [fullscreen]="true">
<ion-list>
<ion-item>
<ion-label i18n class="bold">Name</ion-label>
<ion-label i18n class="bold">Route</ion-label>
<ion-label i18n class="bold">Date</ion-label>
<ion-label i18n class="bold">State</ion-label>
<ion-label i18n class="bold ion-text-end"># Services</ion-label>
</ion-item>
<ion-item-sliding>
<ion-item *ngFor="let timetable of timetables">
<ion-label>{{ timetable.name }}</ion-label>
<ion-label>{{ timetable.route }}</ion-label>
<ion-label>{{ timetable.date }}</ion-label>
<ion-label>{{ timetable.state }}</ion-label>
<ion-label class="ion-text-end">{{ timetable.numberOfServices }}</ion-label>
</ion-item>
<ion-item-options side="end">
<ion-item-option color="danger" i18n>
<ion-icon slot="start" [ios]="'trash-outline'" [md]="'trash-sharp'"></ion-icon>
Delete
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
</ion-list>
<ion-fab slot="fixed" horizontal="end" vertical="bottom">
<ion-fab-button i18n-aria-label aria-label="Add route">
<ion-icon [ios]="'add-outline'" [md]="'add-sharp'"></ion-icon>
</ion-fab-button>
</ion-fab>
</ion-content>
<ion-footer></ion-footer>

View File

@ -0,0 +1,24 @@
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {IonicModule} from '@ionic/angular';
import {TimetablesComponent} from './timetables.component';
describe('TimetableComponent', () => {
let component: TimetablesComponent;
let fixture: ComponentFixture<TimetablesComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [TimetablesComponent],
imports: [IonicModule.forRoot()]
}).compileComponents();
fixture = TestBed.createComponent(TimetablesComponent);
component = fixture.componentInstance;
fixture.detectChanges();
}));
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,62 @@
import {Component} from '@angular/core';
import {Timetable} from "./model/timetable";
import {
IonButtons,
IonContent,
IonFab,
IonFabButton,
IonFooter,
IonHeader,
IonIcon,
IonItem,
IonItemOption,
IonItemOptions,
IonItemSliding,
IonLabel,
IonList,
IonListHeader,
IonMenuButton,
IonTitle,
IonToolbar
} from "@ionic/angular/standalone";
import {addIcons} from "ionicons";
import {addOutline, addSharp, trashOutline, trashSharp} from "ionicons/icons";
@Component({
selector: 'app-timetables',
templateUrl: './timetables.component.html',
styleUrls: ['./timetables.component.scss'],
standalone: true,
imports: [
IonHeader,
IonToolbar,
IonButtons,
IonMenuButton,
IonTitle,
IonContent,
IonList,
IonListHeader,
IonLabel,
IonItem,
IonItemSliding,
IonItemOptions,
IonItemOption,
IonIcon,
IonFab,
IonFabButton,
IonFooter
]
})
export class TimetablesComponent {
timetables: Timetable[] = [];
constructor() {
addIcons({
addOutline,
addSharp,
trashOutline,
trashSharp,
});
}
}