diff --git a/src/angular/frontend/package.json b/src/angular/frontend/package.json
index 101fed7..8762983 100644
--- a/src/angular/frontend/package.json
+++ b/src/angular/frontend/package.json
@@ -3,7 +3,7 @@
"version": "0.0.0",
"scripts": {
"ng": "ng",
- "start": "ng serve",
+ "start": "ng serve --host 0.0.0.0",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test"
@@ -38,4 +38,4 @@
"karma-jasmine-html-reporter": "~2.1.0",
"typescript": "~5.9.3"
}
-}
\ No newline at end of file
+}
diff --git a/src/angular/frontend/src/app/app.routes.ts b/src/angular/frontend/src/app/app.routes.ts
index e340292..7e5f923 100644
--- a/src/angular/frontend/src/app/app.routes.ts
+++ b/src/angular/frontend/src/app/app.routes.ts
@@ -19,12 +19,26 @@ export const routes: Routes = [
title: 'Home',
path: '',
component: HomeComponent,
+ data: { showInNav: true }
+ },
+ {
+ title: 'Dashboard',
+ path: 'dashboard',
+ component: HomeComponent,
+ data: { showInNav: true },
+ children: [
+ {
+ title: 'Test',
+ path: 'test',
+ component: HomeComponent,
+ }
+ ]
},
],
canActivate: [authGuard],
resolve: {
user: subjectResolver,
- }
+ },
},
{
path: '**',
diff --git a/src/angular/frontend/src/app/components/home/home.component.html b/src/angular/frontend/src/app/components/home/home.component.html
index 5f2c53f..c4dd070 100644
--- a/src/angular/frontend/src/app/components/home/home.component.html
+++ b/src/angular/frontend/src/app/components/home/home.component.html
@@ -1 +1,121 @@
+test
+
home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
+home works!
home works!
diff --git a/src/angular/frontend/src/app/components/home/home.component.ts b/src/angular/frontend/src/app/components/home/home.component.ts
index 2692e8b..4101bca 100644
--- a/src/angular/frontend/src/app/components/home/home.component.ts
+++ b/src/angular/frontend/src/app/components/home/home.component.ts
@@ -1,8 +1,11 @@
import { Component } from '@angular/core';
+import {LinkComponent} from '../link/link.component';
@Component({
selector: 'app-home',
- imports: [],
+ imports: [
+ LinkComponent
+ ],
templateUrl: './home.component.html',
styleUrl: './home.component.scss',
standalone: true,
diff --git a/src/angular/frontend/src/app/components/link/link.component.html b/src/angular/frontend/src/app/components/link/link.component.html
new file mode 100644
index 0000000..1a73114
--- /dev/null
+++ b/src/angular/frontend/src/app/components/link/link.component.html
@@ -0,0 +1 @@
+
diff --git a/src/angular/frontend/src/app/components/link/link.component.scss b/src/angular/frontend/src/app/components/link/link.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/angular/frontend/src/app/components/link/link.component.spec.ts b/src/angular/frontend/src/app/components/link/link.component.spec.ts
new file mode 100644
index 0000000..b106be3
--- /dev/null
+++ b/src/angular/frontend/src/app/components/link/link.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { LinkComponent } from './link.component';
+
+describe('LinkComponent', () => {
+ let component: LinkComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [LinkComponent]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(LinkComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/angular/frontend/src/app/components/link/link.component.ts b/src/angular/frontend/src/app/components/link/link.component.ts
new file mode 100644
index 0000000..fb88ac6
--- /dev/null
+++ b/src/angular/frontend/src/app/components/link/link.component.ts
@@ -0,0 +1,24 @@
+import {Component, inject, input} from '@angular/core';
+import {Router} from '@angular/router';
+
+@Component({
+ selector: 'app-link',
+ imports: [],
+ templateUrl: './link.component.html',
+ styleUrl: './link.component.scss'
+})
+export class LinkComponent {
+
+ public href = input.required();
+ public external = input(false);
+
+ private router = inject(Router);
+
+ click($event: PointerEvent) {
+ if (!this.external()) {
+ $event.preventDefault();
+ this.router.navigate([this.href()]).then();
+ }
+ }
+
+}
diff --git a/src/angular/frontend/src/app/components/shell/drawer/drawer.component.html b/src/angular/frontend/src/app/components/shell/drawer/drawer.component.html
new file mode 100644
index 0000000..6a1ac2a
--- /dev/null
+++ b/src/angular/frontend/src/app/components/shell/drawer/drawer.component.html
@@ -0,0 +1,14 @@
+
+
+
+
diff --git a/src/angular/frontend/src/app/components/shell/drawer/drawer.component.scss b/src/angular/frontend/src/app/components/shell/drawer/drawer.component.scss
new file mode 100644
index 0000000..e43a9de
--- /dev/null
+++ b/src/angular/frontend/src/app/components/shell/drawer/drawer.component.scss
@@ -0,0 +1,47 @@
+:host {
+ display: flex;
+
+ height: 100%;
+ overflow: auto;
+
+ .drawer-container {
+ display: flex;
+ flex-direction: column;
+ padding: 1rem;
+ }
+
+ .main-content-container{
+ display: flex;
+ flex-direction: column;
+ padding: 1rem 0 0 1rem;
+ }
+
+ .drawer-container {
+ background-color: var(--neutral-70);
+ }
+
+ .main-content-container {
+ flex-grow: 1;
+
+ .top-nav-breadcrumb-container, .top-nav-container {
+ margin-right: 1rem;
+ }
+
+ .top-nav-breadcrumb-container {
+ margin-top: 1rem;
+ padding: 0.5rem 0;
+ border-bottom: 1px solid var(--neutral-70);
+ border-top: 1px solid var(--neutral-70);
+ }
+
+ .content-container {
+ padding-top: 1rem;
+ padding-right: 1rem;
+ flex-grow: 1;
+ overflow: auto;
+
+ }
+ }
+}
+
+
diff --git a/src/angular/frontend/src/app/components/shell/drawer/drawer.component.spec.ts b/src/angular/frontend/src/app/components/shell/drawer/drawer.component.spec.ts
new file mode 100644
index 0000000..0ab22bd
--- /dev/null
+++ b/src/angular/frontend/src/app/components/shell/drawer/drawer.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { DrawerComponent } from './drawer.component';
+
+describe('DrawerComponent', () => {
+ let component: DrawerComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [DrawerComponent]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(DrawerComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/angular/frontend/src/app/components/shell/drawer/drawer.component.ts b/src/angular/frontend/src/app/components/shell/drawer/drawer.component.ts
new file mode 100644
index 0000000..735899f
--- /dev/null
+++ b/src/angular/frontend/src/app/components/shell/drawer/drawer.component.ts
@@ -0,0 +1,14 @@
+import { Component } from '@angular/core';
+import {RouterOutlet} from '@angular/router';
+
+@Component({
+ selector: 'app-drawer',
+ imports: [
+ RouterOutlet
+ ],
+ templateUrl: './drawer.component.html',
+ styleUrl: './drawer.component.scss'
+})
+export class DrawerComponent {
+
+}
diff --git a/src/angular/frontend/src/app/components/shell/nav-profile/nav-profile.component.html b/src/angular/frontend/src/app/components/shell/nav-profile/nav-profile.component.html
index 403d35c..aff10bb 100644
--- a/src/angular/frontend/src/app/components/shell/nav-profile/nav-profile.component.html
+++ b/src/angular/frontend/src/app/components/shell/nav-profile/nav-profile.component.html
@@ -2,4 +2,4 @@
{{user()}}
Logout
-
+
diff --git a/src/angular/frontend/src/app/components/shell/nav-profile/nav-profile.component.scss b/src/angular/frontend/src/app/components/shell/nav-profile/nav-profile.component.scss
index 834910e..a458ae1 100644
--- a/src/angular/frontend/src/app/components/shell/nav-profile/nav-profile.component.scss
+++ b/src/angular/frontend/src/app/components/shell/nav-profile/nav-profile.component.scss
@@ -15,5 +15,6 @@
height: 3rem;
overflow: hidden;
background-color: var(--bg-color);
+ border-radius: 0.5rem;
}
}
diff --git a/src/angular/frontend/src/app/components/shell/shell.component.html b/src/angular/frontend/src/app/components/shell/shell.component.html
index 025c400..21e2c33 100644
--- a/src/angular/frontend/src/app/components/shell/shell.component.html
+++ b/src/angular/frontend/src/app/components/shell/shell.component.html
@@ -1,4 +1,36 @@
-
-
-
-
+
+
+
+
+
+
+ @for (link of navLinks(); track link.label) {
+
+ {{link.label}}
+
+ }
+
+
+
+
+
+
+
/
+
+ @for (crumb of breadcrumbs(); let last = $last; track crumb.label) {
+
+ @if (crumb.url && !last) {
+
{{ crumb.label }}
+ } @else {
+
{{ crumb.label }}
+ }
+ @if (!last) {
+
/
+ }
+ }
+
+
+
diff --git a/src/angular/frontend/src/app/components/shell/shell.component.scss b/src/angular/frontend/src/app/components/shell/shell.component.scss
index f96a889..858d656 100644
--- a/src/angular/frontend/src/app/components/shell/shell.component.scss
+++ b/src/angular/frontend/src/app/components/shell/shell.component.scss
@@ -1,9 +1,62 @@
-.shell-nav-bar {
- .logo {
- $height: 3rem;
- height: $height;
- width: #{$height * 3.356876171875};
+:host {
+ height: 100vh;
+ width: 100vw;
+ display: block;
+
+ .top-nav-container {
+ display: flex;
+
+ .title {
+ font-size: 2rem;
+ font-weight: bold;
+ }
+ app-nav-profile {
+ margin-left: auto;
+ }
}
+
+ .drawer-content {
+ display: flex;
+ flex-direction: column;
+ .logo {
+ $height: 3rem;
+ height: $height;
+ width: #{$height * 3.356876171875};
+ padding-bottom: 1rem;
+ border-bottom: 1px solid var(--neutral-60);
+ }
+
+ .nav-links {
+ margin-top: 1rem;
+ display: flex;
+ flex-direction: column;
+
+ .nav-link {
+ width: 100%;
+
+ padding: 0.5rem;
+ border-radius: 0.25rem;
+
+ &:hover, &.active {
+ background-color: var(--neutral-60);
+ }
+ }
+
+ ::ng-deep app-link {
+ a {
+ display: flex;
+ width: 100%;
+ color: unset;
+ text-decoration: none;
+ }
+
+ }
+ }
+ }
+
+}
+.shell-nav-bar {
+
app-nav-profile {
margin-left: auto;
}
diff --git a/src/angular/frontend/src/app/components/shell/shell.component.ts b/src/angular/frontend/src/app/components/shell/shell.component.ts
index a117ff9..8a5c17f 100644
--- a/src/angular/frontend/src/app/components/shell/shell.component.ts
+++ b/src/angular/frontend/src/app/components/shell/shell.component.ts
@@ -1,21 +1,111 @@
-import {Component, computed, inject} from '@angular/core';
+import {Component, computed, OnInit, signal} from '@angular/core';
import {NavProfileComponent} from './nav-profile/nav-profile.component';
-import {ActivatedRoute, RouterOutlet} from '@angular/router';
-import {PanelComponent} from '../panel/panel.component';
+import {ActivatedRoute, NavigationEnd, Route, Router, RouterLink} from '@angular/router';
import {IconComponent} from '../icon/icon.component';
-import {toSignal} from '@angular/core/rxjs-interop';
+import {DrawerComponent} from './drawer/drawer.component';
+import {filter} from 'rxjs';
+import {NgClass} from '@angular/common';
+import {LinkComponent} from '../link/link.component';
@Component({
selector: 'app-shell',
imports: [
NavProfileComponent,
- PanelComponent,
- IconComponent
+ IconComponent,
+ DrawerComponent,
+ RouterLink,
+ NgClass,
+ LinkComponent
],
templateUrl: './shell.component.html',
styleUrl: './shell.component.scss',
standalone: true,
})
-export class ShellComponent {
+export class ShellComponent implements OnInit {
+ protected breadcrumbs = signal([]);
+ protected navLinks = signal([]);
+
+ protected title = computed(() => {
+ return this.breadcrumbs()[this.breadcrumbs().length - 1]?.label || 'Undefined';
+ });
+
+ protected activeTopLevelUrl = computed(() => {
+ const breadcrumbs = this.breadcrumbs();
+
+ if (breadcrumbs.length === 0) {
+ return null;
+ }
+
+ return this.breadcrumbs()[0]?.url;
+ });
+
+ constructor(private router: Router, private route: ActivatedRoute) {
+
+ }
+
+ ngOnInit() {
+
+ this.initNavigation();
+ this.initBreadcrumbs();
+
+
+ this.router.events
+ .pipe(filter(event => event instanceof NavigationEnd))
+ .subscribe(() => {
+ this.initBreadcrumbs();
+ });
+ }
+
+ private initBreadcrumbs() {
+ this.breadcrumbs.set(this.buildBreadcrumbs(this.route.root));
+ console.log('breadcrumbs', this.breadcrumbs());
+ }
+
+ private initNavigation() {
+ this.navLinks.set(this.extractNavLinks(this.router.config));
+ console.log('navLinks', this.navLinks())
+ }
+
+ private extractNavLinks(routes: Route[]): Breadcrumb[] {
+ return routes
+ .filter(r => r.path === '')
+ .flatMap(r => r.children ?? [])
+ .filter(r => r.data?.['showInNav'])
+ .map(r => ({
+ label: r.data?.['title'] || r.title || '',
+ url: '/' + (r.path ?? '')
+ }));
+ }
+
+ private buildBreadcrumbs(route: ActivatedRoute, url: string = '/', breadcrumbs: Breadcrumb[] = []): Breadcrumb[] {
+ const children = route.children;
+
+ if (children.length === 0) {
+ return breadcrumbs;
+ }
+
+ const child = children[0];
+
+ const routeURL = child.snapshot.url.map(segment => segment.path).join('/');
+ if (routeURL) {
+ url += `${routeURL}`;
+ }
+
+ const label =
+ child.snapshot.data['title'] ||
+ child.snapshot.routeConfig?.title ||
+ '';
+
+ if (label) {
+ breadcrumbs.push({ label, url });
+ }
+
+ return this.buildBreadcrumbs(child, url, breadcrumbs);
+ }
+}
+
+export class Breadcrumb {
+ label: string = 'undefined';
+ url: string = '';
}