From 241293162c541e6e8f1eabddc1a226fc0875c497 Mon Sep 17 00:00:00 2001 From: ancplua Date: Tue, 12 May 2026 12:21:11 +0200 Subject: [PATCH] feat(angular): reshape PaperlessUI.Angular to TourPlanner-Angular layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the empty Angular 21 scaffold with the layered structure used in TourPlanner-Angular: src/app/ app.component.{ts,html,css,spec.ts} # root with shell app.config.ts # HttpClient + Router + API_BASE_URL app.routes.ts # lazy /documents route core/ api/api-client.service.ts # base HTTP wrapper, baseUrl from token api/generated/ # openapi-typescript output target config/api-base-url.token.ts # InjectionToken layout/ shell/app-shell.component.{ts,html,css} # navbar + navbar/app-navbar.component.{ts,html,css} # documents / upload / search features/ documents/pages/documents-page.component.{ts,html,css} # stub proxy.conf.json # /api -> http://localhost:5057 Every component uses ChangeDetectionStrategy.OnPush and standalone imports (no NgModules) — same conventions as TourPlanner-Angular. package.json gains generate:openapi / generate:types / generate scripts so PaperlessREST's OpenAPI doc can be flowed into src/app/core/api/generated/ once the backend side lands. pnpm stays as the package manager to keep the existing CI job intact. Build verified: ng build emits the lazy `documents-page-component` chunk (683 B) confirming the lazy-loading pattern wires through. No new deps introduced. Follow-ups: - Wire the actual /documents service + viewmodel against the generated API - Repeat for /upload and /search features - Tailwind + Leaflet adoption (TourPlanner-Angular parity) - React equivalent (PaperlessUI.React -> qyl.dashboard layout) Co-Authored-By: Claude Opus 4.7 (1M context) --- PaperlessUI.Angular/package.json | 8 +- PaperlessUI.Angular/proxy.conf.json | 6 + PaperlessUI.Angular/src/app/app.component.css | 4 + .../src/app/app.component.html | 1 + .../src/app/app.component.spec.ts | 19 + PaperlessUI.Angular/src/app/app.component.ts | 11 + PaperlessUI.Angular/src/app/app.config.ts | 8 +- PaperlessUI.Angular/src/app/app.css | 0 PaperlessUI.Angular/src/app/app.html | 344 ------------------ PaperlessUI.Angular/src/app/app.routes.ts | 12 +- PaperlessUI.Angular/src/app/app.spec.ts | 23 -- PaperlessUI.Angular/src/app/app.ts | 12 - .../src/app/core/api/api-client.service.ts | 32 ++ .../src/app/core/api/generated/.gitkeep | 1 + .../src/app/core/config/api-base-url.token.ts | 3 + .../pages/documents-page.component.css | 8 + .../pages/documents-page.component.html | 4 + .../pages/documents-page.component.ts | 9 + .../layout/navbar/app-navbar.component.css | 32 ++ .../layout/navbar/app-navbar.component.html | 14 + .../app/layout/navbar/app-navbar.component.ts | 11 + .../app/layout/shell/app-shell.component.css | 10 + .../app/layout/shell/app-shell.component.html | 4 + .../app/layout/shell/app-shell.component.ts | 12 + PaperlessUI.Angular/src/index.html | 2 +- PaperlessUI.Angular/src/main.ts | 4 +- 26 files changed, 207 insertions(+), 387 deletions(-) create mode 100644 PaperlessUI.Angular/proxy.conf.json create mode 100644 PaperlessUI.Angular/src/app/app.component.css create mode 100644 PaperlessUI.Angular/src/app/app.component.html create mode 100644 PaperlessUI.Angular/src/app/app.component.spec.ts create mode 100644 PaperlessUI.Angular/src/app/app.component.ts delete mode 100644 PaperlessUI.Angular/src/app/app.css delete mode 100644 PaperlessUI.Angular/src/app/app.html delete mode 100644 PaperlessUI.Angular/src/app/app.spec.ts delete mode 100644 PaperlessUI.Angular/src/app/app.ts create mode 100644 PaperlessUI.Angular/src/app/core/api/api-client.service.ts create mode 100644 PaperlessUI.Angular/src/app/core/api/generated/.gitkeep create mode 100644 PaperlessUI.Angular/src/app/core/config/api-base-url.token.ts create mode 100644 PaperlessUI.Angular/src/app/features/documents/pages/documents-page.component.css create mode 100644 PaperlessUI.Angular/src/app/features/documents/pages/documents-page.component.html create mode 100644 PaperlessUI.Angular/src/app/features/documents/pages/documents-page.component.ts create mode 100644 PaperlessUI.Angular/src/app/layout/navbar/app-navbar.component.css create mode 100644 PaperlessUI.Angular/src/app/layout/navbar/app-navbar.component.html create mode 100644 PaperlessUI.Angular/src/app/layout/navbar/app-navbar.component.ts create mode 100644 PaperlessUI.Angular/src/app/layout/shell/app-shell.component.css create mode 100644 PaperlessUI.Angular/src/app/layout/shell/app-shell.component.html create mode 100644 PaperlessUI.Angular/src/app/layout/shell/app-shell.component.ts diff --git a/PaperlessUI.Angular/package.json b/PaperlessUI.Angular/package.json index d5e298e..f48ab23 100644 --- a/PaperlessUI.Angular/package.json +++ b/PaperlessUI.Angular/package.json @@ -3,10 +3,14 @@ "version": "0.0.0", "scripts": { "ng": "ng", - "start": "ng serve", + "start": "ng serve --port 4200 --proxy-config proxy.conf.json", "build": "ng build", "watch": "ng build --watch --configuration development", - "test": "ng test" + "test": "ng test --watch=false", + "test:watch": "ng test", + "generate:openapi": "node -e \"const fs=require('node:fs');fs.rmSync('../PaperlessREST/openapi/paperless.json',{force:true})\" && dotnet build ../PaperlessREST/PaperlessREST.csproj -tlp:ErrorsOnly && node -e \"if(!require('node:fs').existsSync('../PaperlessREST/openapi/paperless.json')){console.error('paperless.json not generated');process.exit(1)}\"", + "generate:types": "openapi-typescript ../PaperlessREST/openapi/paperless.json -o src/app/core/api/generated/api-types.ts", + "generate": "pnpm run generate:openapi && pnpm run generate:types" }, "private": true, "packageManager": "pnpm@10.30.2", diff --git a/PaperlessUI.Angular/proxy.conf.json b/PaperlessUI.Angular/proxy.conf.json new file mode 100644 index 0000000..d3d8c2c --- /dev/null +++ b/PaperlessUI.Angular/proxy.conf.json @@ -0,0 +1,6 @@ +{ + "/api": { + "target": "http://localhost:5057", + "secure": false + } +} diff --git a/PaperlessUI.Angular/src/app/app.component.css b/PaperlessUI.Angular/src/app/app.component.css new file mode 100644 index 0000000..3d856ad --- /dev/null +++ b/PaperlessUI.Angular/src/app/app.component.css @@ -0,0 +1,4 @@ +:host { + display: block; + min-height: 100vh; +} diff --git a/PaperlessUI.Angular/src/app/app.component.html b/PaperlessUI.Angular/src/app/app.component.html new file mode 100644 index 0000000..eff5fda --- /dev/null +++ b/PaperlessUI.Angular/src/app/app.component.html @@ -0,0 +1 @@ + diff --git a/PaperlessUI.Angular/src/app/app.component.spec.ts b/PaperlessUI.Angular/src/app/app.component.spec.ts new file mode 100644 index 0000000..c73cc6b --- /dev/null +++ b/PaperlessUI.Angular/src/app/app.component.spec.ts @@ -0,0 +1,19 @@ +import { TestBed } from '@angular/core/testing'; +import { provideRouter } from '@angular/router'; +import { AppComponent } from './app.component'; + +describe('AppComponent', () => { + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [AppComponent], + providers: [provideRouter([])], + }).compileComponents(); + }); + + it('renders the app shell', () => { + const fixture = TestBed.createComponent(AppComponent); + fixture.detectChanges(); + const host = fixture.nativeElement as HTMLElement; + expect(host.querySelector('app-shell')).toBeTruthy(); + }); +}); diff --git a/PaperlessUI.Angular/src/app/app.component.ts b/PaperlessUI.Angular/src/app/app.component.ts new file mode 100644 index 0000000..be40086 --- /dev/null +++ b/PaperlessUI.Angular/src/app/app.component.ts @@ -0,0 +1,11 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { AppShellComponent } from './layout/shell/app-shell.component'; + +@Component({ + selector: 'app-root', + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [AppShellComponent], + templateUrl: './app.component.html', + styleUrl: './app.component.css', +}) +export class AppComponent {} diff --git a/PaperlessUI.Angular/src/app/app.config.ts b/PaperlessUI.Angular/src/app/app.config.ts index cb1270e..e117860 100644 --- a/PaperlessUI.Angular/src/app/app.config.ts +++ b/PaperlessUI.Angular/src/app/app.config.ts @@ -1,11 +1,15 @@ +import { provideHttpClient, withFetch } from '@angular/common/http'; import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core'; import { provideRouter } from '@angular/router'; +import { API_BASE_URL } from './core/config/api-base-url.token'; import { routes } from './app.routes'; export const appConfig: ApplicationConfig = { providers: [ provideBrowserGlobalErrorListeners(), - provideRouter(routes) - ] + provideHttpClient(withFetch()), + provideRouter(routes), + { provide: API_BASE_URL, useFactory: () => location.origin + '/' }, + ], }; diff --git a/PaperlessUI.Angular/src/app/app.css b/PaperlessUI.Angular/src/app/app.css deleted file mode 100644 index e69de29..0000000 diff --git a/PaperlessUI.Angular/src/app/app.html b/PaperlessUI.Angular/src/app/app.html deleted file mode 100644 index a1c4296..0000000 --- a/PaperlessUI.Angular/src/app/app.html +++ /dev/null @@ -1,344 +0,0 @@ - - - - - - - - - - - -
-
-
- -

Hello, {{ title() }}

-

Congratulations! Your app is running. 🎉

-
- -
-
- @for (item of [ - { title: 'Explore the Docs', link: 'https://angular.dev' }, - { title: 'Learn with Tutorials', link: 'https://angular.dev/tutorials' }, - { title: 'Prompt and best practices for AI', link: 'https://angular.dev/ai/develop-with-ai'}, - { title: 'CLI Docs', link: 'https://angular.dev/tools/cli' }, - { title: 'Angular Language Service', link: 'https://angular.dev/tools/language-service' }, - { title: 'Angular DevTools', link: 'https://angular.dev/tools/devtools' }, - ]; track item.title) { - - {{ item.title }} - - - - - } -
- -
-
-
- - - - - - - - - - - diff --git a/PaperlessUI.Angular/src/app/app.routes.ts b/PaperlessUI.Angular/src/app/app.routes.ts index dc39edb..f64af20 100644 --- a/PaperlessUI.Angular/src/app/app.routes.ts +++ b/PaperlessUI.Angular/src/app/app.routes.ts @@ -1,3 +1,13 @@ import { Routes } from '@angular/router'; -export const routes: Routes = []; +export const routes: Routes = [ + { path: '', pathMatch: 'full', redirectTo: 'documents' }, + { + path: 'documents', + loadComponent: () => + import('./features/documents/pages/documents-page.component').then( + (m) => m.DocumentsPageComponent, + ), + }, + { path: '**', redirectTo: 'documents' }, +]; diff --git a/PaperlessUI.Angular/src/app/app.spec.ts b/PaperlessUI.Angular/src/app/app.spec.ts deleted file mode 100644 index 427e977..0000000 --- a/PaperlessUI.Angular/src/app/app.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { TestBed } from '@angular/core/testing'; -import { App } from './app'; - -describe('App', () => { - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [App], - }).compileComponents(); - }); - - it('should create the app', () => { - const fixture = TestBed.createComponent(App); - const app = fixture.componentInstance; - expect(app).toBeTruthy(); - }); - - it('should render title', async () => { - const fixture = TestBed.createComponent(App); - await fixture.whenStable(); - const compiled = fixture.nativeElement as HTMLElement; - expect(compiled.querySelector('h1')?.textContent).toContain('Hello, PaperlessUI.Angular'); - }); -}); diff --git a/PaperlessUI.Angular/src/app/app.ts b/PaperlessUI.Angular/src/app/app.ts deleted file mode 100644 index 775af12..0000000 --- a/PaperlessUI.Angular/src/app/app.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Component, signal } from '@angular/core'; -import { RouterOutlet } from '@angular/router'; - -@Component({ - selector: 'app-root', - imports: [RouterOutlet], - templateUrl: './app.html', - styleUrl: './app.css' -}) -export class App { - protected readonly title = signal('PaperlessUI.Angular'); -} diff --git a/PaperlessUI.Angular/src/app/core/api/api-client.service.ts b/PaperlessUI.Angular/src/app/core/api/api-client.service.ts new file mode 100644 index 0000000..371febc --- /dev/null +++ b/PaperlessUI.Angular/src/app/core/api/api-client.service.ts @@ -0,0 +1,32 @@ +import { HttpClient } from '@angular/common/http'; +import { inject, Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { API_BASE_URL } from '../config/api-base-url.token'; + +@Injectable({ + providedIn: 'root', +}) +export class ApiClientService { + private readonly http = inject(HttpClient); + private readonly baseUrl = inject(API_BASE_URL); + + get(path: string): Observable { + return this.http.get(this.buildUrl(path)); + } + + post(path: string, body: TRequest): Observable { + return this.http.post(this.buildUrl(path), body); + } + + put(path: string, body: TRequest): Observable { + return this.http.put(this.buildUrl(path), body); + } + + delete(path: string): Observable { + return this.http.delete(this.buildUrl(path)); + } + + private buildUrl(path: string): string { + return new URL(path, this.baseUrl).toString(); + } +} diff --git a/PaperlessUI.Angular/src/app/core/api/generated/.gitkeep b/PaperlessUI.Angular/src/app/core/api/generated/.gitkeep new file mode 100644 index 0000000..2454c1a --- /dev/null +++ b/PaperlessUI.Angular/src/app/core/api/generated/.gitkeep @@ -0,0 +1 @@ +openapi-typescript output target — `npm run generate:types` writes api-types.ts here once the PaperlessREST OpenAPI document is wired up. diff --git a/PaperlessUI.Angular/src/app/core/config/api-base-url.token.ts b/PaperlessUI.Angular/src/app/core/config/api-base-url.token.ts new file mode 100644 index 0000000..90a29c9 --- /dev/null +++ b/PaperlessUI.Angular/src/app/core/config/api-base-url.token.ts @@ -0,0 +1,3 @@ +import { InjectionToken } from '@angular/core'; + +export const API_BASE_URL = new InjectionToken('API_BASE_URL'); diff --git a/PaperlessUI.Angular/src/app/features/documents/pages/documents-page.component.css b/PaperlessUI.Angular/src/app/features/documents/pages/documents-page.component.css new file mode 100644 index 0000000..5780a12 --- /dev/null +++ b/PaperlessUI.Angular/src/app/features/documents/pages/documents-page.component.css @@ -0,0 +1,8 @@ +.documents-page { + max-width: 64rem; + margin: 0 auto; +} + +.documents-page h1 { + margin-top: 0; +} diff --git a/PaperlessUI.Angular/src/app/features/documents/pages/documents-page.component.html b/PaperlessUI.Angular/src/app/features/documents/pages/documents-page.component.html new file mode 100644 index 0000000..53a69b5 --- /dev/null +++ b/PaperlessUI.Angular/src/app/features/documents/pages/documents-page.component.html @@ -0,0 +1,4 @@ +
+

Documents

+

Document list lands here. Backend wiring follows in the next PR once openapi-typescript generates the contract.

+
diff --git a/PaperlessUI.Angular/src/app/features/documents/pages/documents-page.component.ts b/PaperlessUI.Angular/src/app/features/documents/pages/documents-page.component.ts new file mode 100644 index 0000000..2d14b33 --- /dev/null +++ b/PaperlessUI.Angular/src/app/features/documents/pages/documents-page.component.ts @@ -0,0 +1,9 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; + +@Component({ + selector: 'app-documents-page', + changeDetection: ChangeDetectionStrategy.OnPush, + templateUrl: './documents-page.component.html', + styleUrl: './documents-page.component.css', +}) +export class DocumentsPageComponent {} diff --git a/PaperlessUI.Angular/src/app/layout/navbar/app-navbar.component.css b/PaperlessUI.Angular/src/app/layout/navbar/app-navbar.component.css new file mode 100644 index 0000000..ee60761 --- /dev/null +++ b/PaperlessUI.Angular/src/app/layout/navbar/app-navbar.component.css @@ -0,0 +1,32 @@ +.app-navbar { + display: flex; + align-items: center; + gap: 1.5rem; + padding: 0.75rem 1.5rem; + border-bottom: 1px solid color-mix(in srgb, currentColor 12%, transparent); +} + +.app-navbar__brand { + font-weight: 600; + text-decoration: none; + color: inherit; +} + +.app-navbar__links { + display: flex; + gap: 1rem; + list-style: none; + margin: 0; + padding: 0; +} + +.app-navbar__links a { + text-decoration: none; + color: inherit; + padding: 0.25rem 0.5rem; + border-radius: 0.375rem; +} + +.app-navbar__links a.is-active { + background: color-mix(in srgb, currentColor 8%, transparent); +} diff --git a/PaperlessUI.Angular/src/app/layout/navbar/app-navbar.component.html b/PaperlessUI.Angular/src/app/layout/navbar/app-navbar.component.html new file mode 100644 index 0000000..1a00e64 --- /dev/null +++ b/PaperlessUI.Angular/src/app/layout/navbar/app-navbar.component.html @@ -0,0 +1,14 @@ + diff --git a/PaperlessUI.Angular/src/app/layout/navbar/app-navbar.component.ts b/PaperlessUI.Angular/src/app/layout/navbar/app-navbar.component.ts new file mode 100644 index 0000000..01a945e --- /dev/null +++ b/PaperlessUI.Angular/src/app/layout/navbar/app-navbar.component.ts @@ -0,0 +1,11 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { RouterLink, RouterLinkActive } from '@angular/router'; + +@Component({ + selector: 'app-navbar', + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [RouterLink, RouterLinkActive], + templateUrl: './app-navbar.component.html', + styleUrl: './app-navbar.component.css', +}) +export class AppNavbarComponent {} diff --git a/PaperlessUI.Angular/src/app/layout/shell/app-shell.component.css b/PaperlessUI.Angular/src/app/layout/shell/app-shell.component.css new file mode 100644 index 0000000..5612e81 --- /dev/null +++ b/PaperlessUI.Angular/src/app/layout/shell/app-shell.component.css @@ -0,0 +1,10 @@ +:host { + display: flex; + flex-direction: column; + min-height: 100vh; +} + +.app-shell__content { + flex: 1; + padding: 1.5rem; +} diff --git a/PaperlessUI.Angular/src/app/layout/shell/app-shell.component.html b/PaperlessUI.Angular/src/app/layout/shell/app-shell.component.html new file mode 100644 index 0000000..01f5dec --- /dev/null +++ b/PaperlessUI.Angular/src/app/layout/shell/app-shell.component.html @@ -0,0 +1,4 @@ + +
+ +
diff --git a/PaperlessUI.Angular/src/app/layout/shell/app-shell.component.ts b/PaperlessUI.Angular/src/app/layout/shell/app-shell.component.ts new file mode 100644 index 0000000..0a891ed --- /dev/null +++ b/PaperlessUI.Angular/src/app/layout/shell/app-shell.component.ts @@ -0,0 +1,12 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { RouterOutlet } from '@angular/router'; +import { AppNavbarComponent } from '../navbar/app-navbar.component'; + +@Component({ + selector: 'app-shell', + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [RouterOutlet, AppNavbarComponent], + templateUrl: './app-shell.component.html', + styleUrl: './app-shell.component.css', +}) +export class AppShellComponent {} diff --git a/PaperlessUI.Angular/src/index.html b/PaperlessUI.Angular/src/index.html index 93d197b..1bdc4d6 100644 --- a/PaperlessUI.Angular/src/index.html +++ b/PaperlessUI.Angular/src/index.html @@ -2,7 +2,7 @@ - PaperlessUIAngular + Paperless diff --git a/PaperlessUI.Angular/src/main.ts b/PaperlessUI.Angular/src/main.ts index 5df75f9..35b00f3 100644 --- a/PaperlessUI.Angular/src/main.ts +++ b/PaperlessUI.Angular/src/main.ts @@ -1,6 +1,6 @@ import { bootstrapApplication } from '@angular/platform-browser'; import { appConfig } from './app/app.config'; -import { App } from './app/app'; +import { AppComponent } from './app/app.component'; -bootstrapApplication(App, appConfig) +bootstrapApplication(AppComponent, appConfig) .catch((err) => console.error(err));