From 7531ea94530a699b03d88b42381be4e88feee258 Mon Sep 17 00:00:00 2001 From: Vincent Guillet Date: Fri, 31 Oct 2025 18:32:24 +0100 Subject: [PATCH] add admin navbar and brand/platform management components --- client/angular.json | 3 + client/package-lock.json | 34 ++++- client/src/app/app.config.ts | 2 +- client/src/app/app.routes.ts | 6 +- .../admin-navbar/admin-navbar.component.css | 0 .../admin-navbar/admin-navbar.component.html | 9 ++ .../admin-navbar/admin-navbar.component.ts | 28 ++++ .../brand-dialog/brand-dialog.component.css | 0 .../brand-dialog/brand-dialog.component.html | 13 ++ .../brand-dialog/brand-dialog.component.ts | 51 +++++++ .../brands-list/brands-list.component.css | 65 ++++++++ .../brands-list/brands-list.component.html | 44 ++++++ .../brands-list/brands-list.component.ts | 138 +++++++++++++++++ .../platform-dialog.component.css | 0 .../platform-dialog.component.html | 21 +++ .../platform-dialog.component.ts | 74 ++++++++++ .../platforms-list.component.css | 65 ++++++++ .../platforms-list.component.html | 50 +++++++ .../platforms-list.component.ts | 139 ++++++++++++++++++ .../{admin-only => }/admin-only.guard.ts | 2 +- .../admin-only/admin-only.guard.spec.ts | 17 --- .../guards/{auth-only => }/auth-only.guard.ts | 2 +- .../guards/auth-only/auth-only.guard.spec.ts | 17 --- .../{guest-only => }/guest-only.guard.ts | 2 +- .../guest-only/guest-only.guard.spec.ts | 17 --- .../{authToken => }/auth-token.interceptor.ts | 4 +- .../authToken/auth-token.interceptor.spec.ts | 17 --- client/src/app/interfaces/brand.ts | 4 + .../{credentials => }/credentials.ts | 0 .../credentials/credentials.spec.ts | 7 - client/src/app/interfaces/platform.ts | 7 + client/src/app/interfaces/user.ts | 7 + client/src/app/models/brand/brand.spec.ts | 7 - client/src/app/models/brand/brand.ts | 3 - client/src/app/models/user/user.model.spec.ts | 7 - client/src/app/models/user/user.model.ts | 7 - .../add-product/add-product.component.html | 11 +- .../add-product/add-product.component.spec.ts | 23 --- .../add-product/add-product.component.ts | 22 ++- .../src/app/pages/admin/admin.component.html | 2 +- .../app/pages/admin/admin.component.spec.ts | 23 --- client/src/app/pages/admin/admin.component.ts | 11 +- client/src/app/pages/login/login.component.ts | 4 +- .../app/pages/profile/profile.component.ts | 2 +- .../app/services/auth/auth.service.spec.ts | 16 -- client/src/app/services/auth/auth.service.ts | 4 +- .../app/services/brand/brand.service.spec.ts | 16 -- .../src/app/services/brand/brand.service.ts | 19 ++- .../app/services/platform/platform.service.ts | 31 ++++ .../services/product/product.service.spec.ts | 16 -- 50 files changed, 842 insertions(+), 227 deletions(-) create mode 100644 client/src/app/components/admin-navbar/admin-navbar.component.css create mode 100644 client/src/app/components/admin-navbar/admin-navbar.component.html create mode 100644 client/src/app/components/admin-navbar/admin-navbar.component.ts create mode 100644 client/src/app/components/brand-dialog/brand-dialog.component.css create mode 100644 client/src/app/components/brand-dialog/brand-dialog.component.html create mode 100644 client/src/app/components/brand-dialog/brand-dialog.component.ts create mode 100644 client/src/app/components/brands-list/brands-list.component.css create mode 100644 client/src/app/components/brands-list/brands-list.component.html create mode 100644 client/src/app/components/brands-list/brands-list.component.ts create mode 100644 client/src/app/components/platform-dialog/platform-dialog.component.css create mode 100644 client/src/app/components/platform-dialog/platform-dialog.component.html create mode 100644 client/src/app/components/platform-dialog/platform-dialog.component.ts create mode 100644 client/src/app/components/platforms-list/platforms-list.component.css create mode 100644 client/src/app/components/platforms-list/platforms-list.component.html create mode 100644 client/src/app/components/platforms-list/platforms-list.component.ts rename client/src/app/guards/{admin-only => }/admin-only.guard.ts (94%) delete mode 100644 client/src/app/guards/admin-only/admin-only.guard.spec.ts rename client/src/app/guards/{auth-only => }/auth-only.guard.ts (92%) delete mode 100644 client/src/app/guards/auth-only/auth-only.guard.spec.ts rename client/src/app/guards/{guest-only => }/guest-only.guard.ts (89%) delete mode 100644 client/src/app/guards/guest-only/guest-only.guard.spec.ts rename client/src/app/interceptors/{authToken => }/auth-token.interceptor.ts (94%) delete mode 100644 client/src/app/interceptors/authToken/auth-token.interceptor.spec.ts create mode 100644 client/src/app/interfaces/brand.ts rename client/src/app/interfaces/{credentials => }/credentials.ts (100%) delete mode 100644 client/src/app/interfaces/credentials/credentials.spec.ts create mode 100644 client/src/app/interfaces/platform.ts create mode 100644 client/src/app/interfaces/user.ts delete mode 100644 client/src/app/models/brand/brand.spec.ts delete mode 100644 client/src/app/models/brand/brand.ts delete mode 100644 client/src/app/models/user/user.model.spec.ts delete mode 100644 client/src/app/models/user/user.model.ts delete mode 100644 client/src/app/pages/add-product/add-product.component.spec.ts delete mode 100644 client/src/app/pages/admin/admin.component.spec.ts delete mode 100644 client/src/app/services/auth/auth.service.spec.ts delete mode 100644 client/src/app/services/brand/brand.service.spec.ts create mode 100644 client/src/app/services/platform/platform.service.ts delete mode 100644 client/src/app/services/product/product.service.spec.ts diff --git a/client/angular.json b/client/angular.json index d195332..6a13e6b 100644 --- a/client/angular.json +++ b/client/angular.json @@ -94,5 +94,8 @@ } } } + }, + "cli": { + "analytics": false } } diff --git a/client/package-lock.json b/client/package-lock.json index 197cac3..60f6aa2 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -322,6 +322,7 @@ "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-18.2.14.tgz", "integrity": "sha512-Kp/MWShoYYO+R3lrrZbZgszbbLGVXHB+39mdJZwnIuZMDkeL3JsIBlSOzyJRTnpS1vITc+9jgHvP/6uKbMrW1Q==", "license": "MIT", + "peer": true, "dependencies": { "tslib": "^2.3.0" }, @@ -467,6 +468,7 @@ "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-18.2.14.tgz", "integrity": "sha512-vDyOh1lwjfVk9OqoroZAP8pf3xxKUvyl+TVR8nJxL4c5fOfUFkD7l94HaanqKSRwJcI2xiztuu92IVoHn8T33Q==", "license": "MIT", + "peer": true, "dependencies": { "tslib": "^2.3.0" }, @@ -518,6 +520,7 @@ "resolved": "https://registry.npmjs.org/@angular/common/-/common-18.2.14.tgz", "integrity": "sha512-ZPRswzaVRiqcfZoowuAM22Hr2/z10ajWOUoFDoQ9tWqz/fH/773kJv2F9VvePIekgNPCzaizqv9gF6tGNqaAwg==", "license": "MIT", + "peer": true, "dependencies": { "tslib": "^2.3.0" }, @@ -534,6 +537,7 @@ "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-18.2.14.tgz", "integrity": "sha512-Mpq3v/mztQzGAQAAFV+wAI1hlXxZ0m8eDBgaN2kD3Ue+r4S6bLm1Vlryw0iyUnt05PcFIdxPT6xkcphq5pl6lw==", "license": "MIT", + "peer": true, "dependencies": { "tslib": "^2.3.0" }, @@ -555,6 +559,7 @@ "integrity": "sha512-BmmjyrFSBSYkm0tBSqpu4cwnJX/b/XvhM36mj2k8jah3tNS5zLDDx5w6tyHmaPJa/1D95MlXx2h6u7K9D+Mhew==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/core": "7.25.2", "@jridgewell/sourcemap-codec": "^1.4.14", @@ -661,6 +666,7 @@ "resolved": "https://registry.npmjs.org/@angular/core/-/core-18.2.14.tgz", "integrity": "sha512-BIPrCs93ZZTY9ym7yfoTgAQ5rs706yoYeAdrgc8kh/bDbM9DawxKlgeKBx2FLt09Y0YQ1bFhKVp0cV4gDEaMxQ==", "license": "MIT", + "peer": true, "dependencies": { "tslib": "^2.3.0" }, @@ -677,6 +683,7 @@ "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-18.2.14.tgz", "integrity": "sha512-fZVwXctmBJa5VdopJae/T9MYKPXNd04+6j4k/6X819y+9fiyWLJt2QicSc5Rc+YD9mmhXag3xaljlrnotf9VGA==", "license": "MIT", + "peer": true, "dependencies": { "tslib": "^2.3.0" }, @@ -713,6 +720,7 @@ "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-18.2.14.tgz", "integrity": "sha512-W+JTxI25su3RiZVZT3Yrw6KNUCmOIy7OZIZ+612skPgYK2f2qil7VclnW1oCwG896h50cMJU/lnAfxZxefQgyQ==", "license": "MIT", + "peer": true, "dependencies": { "tslib": "^2.3.0" }, @@ -797,6 +805,7 @@ "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.2", @@ -3080,6 +3089,7 @@ "integrity": "sha512-b2BudQY/Si4Y2a0PdZZL6BeJtl8llgeZa7U2j47aaJSCeAl1e4UI7y8a9bSkO3o/ZbZrgT5muy/34JbsjfIWxA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@inquirer/checkbox": "^2.4.7", "@inquirer/confirm": "^3.1.22", @@ -4503,6 +4513,7 @@ "integrity": "sha512-r8uszLPpeIWbNKtvWRt/DbVi5zbqZyj1PTmhRMqBMvDnaz1QpmSKujUtJLrqGZeoM8v72MfYggDceY4K1itzWQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -4833,6 +4844,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4909,6 +4921,7 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -5359,6 +5372,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.3", "caniuse-lite": "^1.0.30001741", @@ -8396,7 +8410,8 @@ "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.2.0.tgz", "integrity": "sha512-tSAtdrvWybZkQmmaIoDgnvHG8ORUNw5kEVlO5CvrXj02Jjr9TZrmjFq7FUiOUzJiOP2wLGYT6PgrQgQF4R1xiw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/jest-worker": { "version": "27.5.1", @@ -8535,6 +8550,7 @@ "integrity": "sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@colors/colors": "1.5.0", "body-parser": "^1.19.0", @@ -8806,6 +8822,7 @@ "integrity": "sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "copy-anything": "^2.0.1", "parse-node-version": "^1.0.1", @@ -10773,6 +10790,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.0.1", @@ -11422,6 +11440,7 @@ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", "license": "Apache-2.0", + "peer": true, "dependencies": { "tslib": "^2.1.0" } @@ -11478,6 +11497,7 @@ "integrity": "sha512-ByXE1oLD79GVq9Ht1PeHWCPMPB8XHpBuz1r85oByKHjZY6qV6rWnQovQzXJXuQ/XyE1Oj3iPk3lo28uzaRA2/Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", @@ -12573,6 +12593,7 @@ "integrity": "sha512-PQ4DAriWzKj+qgehQ7LK5bQqCFNMmlhjR2PFFLuqGCpuCAauxemVBWwWOxo3UIwWQx8+Pr61Df++r76wDmkQBg==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", @@ -12712,7 +12733,8 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" + "license": "0BSD", + "peer": true }, "node_modules/tuf-js": { "version": "2.2.1", @@ -12769,6 +12791,7 @@ "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -13029,6 +13052,7 @@ "integrity": "sha512-j3lYzGC3P+B5Yfy/pfKNgVEg4+UtcIJcVRt2cDjIOmhLourAqPqf8P7acgxeiSgUB7E3p2P8/3gNIgDLpwzs4g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", @@ -13599,6 +13623,7 @@ "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.12.1", @@ -13676,6 +13701,7 @@ "integrity": "sha512-QcQ72gh8a+7JO63TAx/6XZf/CWhgMzu5m0QirvPfGvptOusAxG12w2+aua1Jkjr7hzaWDnJ2n6JFeexMHI+Zjg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/bonjour": "^3.5.13", "@types/connect-history-api-fallback": "^1.5.4", @@ -13828,6 +13854,7 @@ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -14162,7 +14189,8 @@ "version": "0.14.10", "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.14.10.tgz", "integrity": "sha512-YGAhaO7J5ywOXW6InXNlLmfU194F8lVgu7bRntUF3TiG8Y3nBK0x1UJJuHUP/e8IyihkjCYqhCScpSwnlaSRkQ==", - "license": "MIT" + "license": "MIT", + "peer": true } } } diff --git a/client/src/app/app.config.ts b/client/src/app/app.config.ts index 11ea685..b855b9b 100644 --- a/client/src/app/app.config.ts +++ b/client/src/app/app.config.ts @@ -4,7 +4,7 @@ import {provideRouter} from '@angular/router'; import {routes} from './app.routes'; import {provideHttpClient, withInterceptors} from '@angular/common/http'; import {provideAnimationsAsync} from '@angular/platform-browser/animations/async'; -import {authTokenInterceptor} from './interceptors/authToken/auth-token.interceptor'; +import {authTokenInterceptor} from './interceptors/auth-token.interceptor'; import {AuthService} from './services/auth/auth.service'; import {catchError, firstValueFrom, of} from 'rxjs'; diff --git a/client/src/app/app.routes.ts b/client/src/app/app.routes.ts index fb99875..61efc96 100644 --- a/client/src/app/app.routes.ts +++ b/client/src/app/app.routes.ts @@ -3,10 +3,10 @@ import {HomeComponent} from './pages/home/home.component'; import {RegisterComponent} from './pages/register/register.component'; import {LoginComponent} from './pages/login/login.component'; import {ProfileComponent} from './pages/profile/profile.component'; -import {guestOnlyCanActivate, guestOnlyCanMatch} from './guards/guest-only/guest-only.guard'; -import {authOnlyCanActivate, authOnlyCanMatch} from './guards/auth-only/auth-only.guard'; +import {guestOnlyCanActivate, guestOnlyCanMatch} from './guards/guest-only.guard'; +import {authOnlyCanActivate, authOnlyCanMatch} from './guards/auth-only.guard'; import {AdminComponent} from './pages/admin/admin.component'; -import {adminOnlyCanActivate, adminOnlyCanMatch} from './guards/admin-only/admin-only.guard'; +import {adminOnlyCanActivate, adminOnlyCanMatch} from './guards/admin-only.guard'; import {AddProductComponent} from './pages/add-product/add-product.component'; export const routes: Routes = [ diff --git a/client/src/app/components/admin-navbar/admin-navbar.component.css b/client/src/app/components/admin-navbar/admin-navbar.component.css new file mode 100644 index 0000000..e69de29 diff --git a/client/src/app/components/admin-navbar/admin-navbar.component.html b/client/src/app/components/admin-navbar/admin-navbar.component.html new file mode 100644 index 0000000..ec470ab --- /dev/null +++ b/client/src/app/components/admin-navbar/admin-navbar.component.html @@ -0,0 +1,9 @@ + + + + + + + + Catégories + diff --git a/client/src/app/components/admin-navbar/admin-navbar.component.ts b/client/src/app/components/admin-navbar/admin-navbar.component.ts new file mode 100644 index 0000000..818dea2 --- /dev/null +++ b/client/src/app/components/admin-navbar/admin-navbar.component.ts @@ -0,0 +1,28 @@ +import { Component } from '@angular/core'; +import {MatAnchor, MatButton} from "@angular/material/button"; +import {MatIcon} from '@angular/material/icon'; +import {RouterLink, RouterLinkActive} from '@angular/router'; +import {MatTab, MatTabGroup} from '@angular/material/tabs'; +import {BrandsListComponent} from '../brands-list/brands-list.component'; +import {PlatformsListComponent} from '../platforms-list/platforms-list.component'; + +@Component({ + selector: 'app-admin-navbar', + standalone: true, + imports: [ + MatButton, + MatIcon, + RouterLinkActive, + RouterLink, + MatAnchor, + MatTabGroup, + MatTab, + BrandsListComponent, + PlatformsListComponent + ], + templateUrl: './admin-navbar.component.html', + styleUrl: './admin-navbar.component.css' +}) +export class AdminNavbarComponent { + +} diff --git a/client/src/app/components/brand-dialog/brand-dialog.component.css b/client/src/app/components/brand-dialog/brand-dialog.component.css new file mode 100644 index 0000000..e69de29 diff --git a/client/src/app/components/brand-dialog/brand-dialog.component.html b/client/src/app/components/brand-dialog/brand-dialog.component.html new file mode 100644 index 0000000..ca584db --- /dev/null +++ b/client/src/app/components/brand-dialog/brand-dialog.component.html @@ -0,0 +1,13 @@ +

{{ brandExists ? 'Modifier la marque' : 'Nouvelle marque' }}

+ + + + Nom + + + + + + + + diff --git a/client/src/app/components/brand-dialog/brand-dialog.component.ts b/client/src/app/components/brand-dialog/brand-dialog.component.ts new file mode 100644 index 0000000..8d9ce7d --- /dev/null +++ b/client/src/app/components/brand-dialog/brand-dialog.component.ts @@ -0,0 +1,51 @@ +import { Component, Inject } from '@angular/core'; +import { + MatDialogRef, + MAT_DIALOG_DATA, + MatDialogTitle, + MatDialogContent, + MatDialogActions +} from '@angular/material/dialog'; +import { Brand } from '../../interfaces/brand'; +import {MatFormField, MatLabel} from '@angular/material/form-field'; +import {MatInput} from '@angular/material/input'; +import {FormsModule} from '@angular/forms'; +import {MatButton} from '@angular/material/button'; + +@Component({ + selector: 'app-brand-dialog', + standalone: true, + imports: [ + MatDialogTitle, + MatDialogContent, + MatFormField, + MatLabel, + MatInput, + FormsModule, + MatDialogActions, + MatButton + ], + templateUrl: './brand-dialog.component.html' +}) +export class BrandDialogComponent { + brand: Brand = { id: '', name: '' } + + constructor( + private readonly dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: { brand: Brand } + ) { + this.brand = { ...(data?.brand || { id: '', name: '' }) }; + } + + get brandExists(): boolean { + return !!this.data?.brand?.id; + } + + save() { + this.dialogRef.close(this.brand); + } + + cancel() { + this.dialogRef.close(); + } +} diff --git a/client/src/app/components/brands-list/brands-list.component.css b/client/src/app/components/brands-list/brands-list.component.css new file mode 100644 index 0000000..87a4b85 --- /dev/null +++ b/client/src/app/components/brands-list/brands-list.component.css @@ -0,0 +1,65 @@ +:host { + display: block; + box-sizing: border-box; + width: 100%; +} + +.container { + max-width: 900px; + margin: 0 auto; + width: 100%; +} + +.toolbar { + display: flex; + gap: 12px; + align-items: center; + justify-content: space-between; + margin-bottom: 12px; +} + +.filter { + max-width: 240px; + width: 100%; +} + +table { + width: 100%; + overflow: auto; +} + +td, th { + word-break: break-word; + white-space: normal; +} + +.actions-cell { + display: flex; + gap: 8px; + justify-content: flex-end; + min-width: 120px; +} + +button.mat-icon-button { + width: 40px; + height: 40px; +} + +.no-brands { + text-align: center; + margin-top: 16px; + color: rgba(0,0,0,0.6); + padding: 8px 12px; +} + +@media (max-width: 600px) { + .toolbar { + flex-direction: column; + align-items: stretch; + gap: 8px; + } + + .actions-cell { + min-width: 0; + } +} diff --git a/client/src/app/components/brands-list/brands-list.component.html b/client/src/app/components/brands-list/brands-list.component.html new file mode 100644 index 0000000..1ed48ec --- /dev/null +++ b/client/src/app/components/brands-list/brands-list.component.html @@ -0,0 +1,44 @@ +
+
+ + + + + +
+ + + + + + + + + + + + + + + + + +
Nom{{ brand.name }} + + +
+ + + + @if (!brands || brands.length === 0) { +
+ Aucune marque trouvée. +
+ } +
diff --git a/client/src/app/components/brands-list/brands-list.component.ts b/client/src/app/components/brands-list/brands-list.component.ts new file mode 100644 index 0000000..ee01363 --- /dev/null +++ b/client/src/app/components/brands-list/brands-list.component.ts @@ -0,0 +1,138 @@ +import { + Component, + Input, + Output, + EventEmitter, + ViewChild, + AfterViewInit, + OnChanges, + SimpleChanges, + OnInit, + inject +} from '@angular/core'; +import { + MatCell, MatCellDef, + MatColumnDef, + MatHeaderCell, + MatHeaderCellDef, MatHeaderRow, MatHeaderRowDef, MatRow, MatRowDef, + MatTable, + MatTableDataSource +} from '@angular/material/table'; +import {MatPaginator} from '@angular/material/paginator'; +import {MatSort} from '@angular/material/sort'; +import {Brand} from '../../interfaces/brand'; +import {MatButton, MatIconButton} from '@angular/material/button'; +import {MatIcon} from '@angular/material/icon'; +import {MatFormField} from '@angular/material/form-field'; +import {MatInput} from '@angular/material/input'; +import {BrandService} from '../../services/brand/brand.service'; +import {MatDialog} from '@angular/material/dialog'; +import { BrandDialogComponent } from '../brand-dialog/brand-dialog.component'; + +@Component({ + selector: 'app-brands-list', + templateUrl: './brands-list.component.html', + standalone: true, + imports: [ + MatButton, + MatIcon, + MatFormField, + MatInput, + MatTable, + MatColumnDef, + MatHeaderCell, + MatCell, + MatHeaderCellDef, + MatCellDef, + MatSort, + MatIconButton, + MatHeaderRow, + MatRow, + MatHeaderRowDef, + MatRowDef, + MatPaginator + ], + styleUrls: ['./brands-list.component.css'] +}) +export class BrandsListComponent implements OnInit, AfterViewInit, OnChanges { + + @Input() brands: Brand[] = []; + @Output() add = new EventEmitter(); + @Output() edit = new EventEmitter(); + @Output() delete = new EventEmitter(); + + displayedColumns: string[] = ['name', 'actions']; + dataSource = new MatTableDataSource([]); + + @ViewChild(MatPaginator) paginator!: MatPaginator; + @ViewChild(MatSort) sort!: MatSort; + + private readonly brandService: BrandService = inject(BrandService); + private readonly dialog = inject(MatDialog); + + ngOnInit(): void { + if (!this.brands || this.brands.length === 0) { + this.loadBrands(); + } else { + this.dataSource.data = this.brands; + } + } + + ngOnChanges(changes: SimpleChanges): void { + if (changes['brands']) { + this.dataSource.data = this.brands || []; + } + } + + ngAfterViewInit(): void { + this.dataSource.paginator = this.paginator; + this.dataSource.sort = this.sort; + } + + loadBrands() { + this.brandService.getBrands().subscribe({ + next: (brands:Brand[]) => { + this.brands = brands || [] + this.dataSource.data = this.brands; + }, + error: () => this.brands = [] + }); + } + + onAdd(): void { + const ref = this.dialog.open(BrandDialogComponent, { + data: { brand: { id: '', name: '' } }, + width: '420px' + }); + + ref.afterClosed().subscribe((result?: Brand) => { + if (result) { + this.add.emit(result); + this.brandService.addBrand(result).subscribe(() => this.loadBrands()); + } + }); + } + + onEdit(brand: Brand): void { + const ref = this.dialog.open(BrandDialogComponent, { + data: { brand: { ...brand } }, + width: '420px' + }); + + ref.afterClosed().subscribe((result?: Brand) => { + if (result) { + this.edit.emit(result); + this.brandService.updateBrand((brand as any).id, result).subscribe(() => this.loadBrands()); + } + }); + } + + onDelete(brand: Brand): void { + this.delete.emit(brand); + this.brandService.deleteBrand((brand as any).id).subscribe(() => this.loadBrands()); + } + + applyFilter(value: string): void { + this.dataSource.filter = (value || '').trim().toLowerCase(); + } +} diff --git a/client/src/app/components/platform-dialog/platform-dialog.component.css b/client/src/app/components/platform-dialog/platform-dialog.component.css new file mode 100644 index 0000000..e69de29 diff --git a/client/src/app/components/platform-dialog/platform-dialog.component.html b/client/src/app/components/platform-dialog/platform-dialog.component.html new file mode 100644 index 0000000..e236f44 --- /dev/null +++ b/client/src/app/components/platform-dialog/platform-dialog.component.html @@ -0,0 +1,21 @@ +

{{ platformExists ? 'Modifier la plateforme' : 'Nouvelle plateforme' }}

+ + + + Nom + + + + Marque + + @for (brand of brands; track brand.id) { + {{ brand.name }} + } + + + + + + + + diff --git a/client/src/app/components/platform-dialog/platform-dialog.component.ts b/client/src/app/components/platform-dialog/platform-dialog.component.ts new file mode 100644 index 0000000..2d93833 --- /dev/null +++ b/client/src/app/components/platform-dialog/platform-dialog.component.ts @@ -0,0 +1,74 @@ +import {Component, inject, Inject, OnInit} from '@angular/core'; +import {MatButton} from "@angular/material/button"; +import { + MAT_DIALOG_DATA, + MatDialogActions, + MatDialogContent, + MatDialogRef, + MatDialogTitle +} from "@angular/material/dialog"; +import {MatFormField, MatLabel} from "@angular/material/form-field"; +import {MatInput} from "@angular/material/input"; +import {FormsModule, ReactiveFormsModule} from "@angular/forms"; +import {Brand} from '../../interfaces/brand'; +import {Platform} from '../../interfaces/platform'; +import {MatOption} from '@angular/material/core'; +import {MatSelect} from '@angular/material/select'; +import {BrandService} from '../../services/brand/brand.service'; + +@Component({ + selector: 'app-platform-dialog', + standalone: true, + imports: [ + MatButton, + MatDialogActions, + MatDialogContent, + MatDialogTitle, + MatFormField, + MatInput, + MatLabel, + ReactiveFormsModule, + FormsModule, + MatOption, + MatSelect + ], + templateUrl: './platform-dialog.component.html', + styleUrl: './platform-dialog.component.css' +}) +export class PlatformDialogComponent implements OnInit { + + private readonly brandService: BrandService = inject(BrandService); + + platform: Platform = { id: '', name: '', brand: undefined }; + brands: Brand[] = []; + + constructor( + private readonly dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: { platform: Platform } + ) { + this.platform = { ...data.platform }; + } + + ngOnInit(): void { + this.loadBrands(); + } + + get platformExists(): boolean { + return !!this.data?.platform?.id; + } + + loadBrands() { + this.brandService.getBrands().subscribe({ + next: (brands:Brand[]) => this.brands = brands || [], + error: () => this.brands = [] + }); + } + + save() { + this.dialogRef.close(this.platform); + } + + cancel() { + this.dialogRef.close(); + } +} diff --git a/client/src/app/components/platforms-list/platforms-list.component.css b/client/src/app/components/platforms-list/platforms-list.component.css new file mode 100644 index 0000000..87a4b85 --- /dev/null +++ b/client/src/app/components/platforms-list/platforms-list.component.css @@ -0,0 +1,65 @@ +:host { + display: block; + box-sizing: border-box; + width: 100%; +} + +.container { + max-width: 900px; + margin: 0 auto; + width: 100%; +} + +.toolbar { + display: flex; + gap: 12px; + align-items: center; + justify-content: space-between; + margin-bottom: 12px; +} + +.filter { + max-width: 240px; + width: 100%; +} + +table { + width: 100%; + overflow: auto; +} + +td, th { + word-break: break-word; + white-space: normal; +} + +.actions-cell { + display: flex; + gap: 8px; + justify-content: flex-end; + min-width: 120px; +} + +button.mat-icon-button { + width: 40px; + height: 40px; +} + +.no-brands { + text-align: center; + margin-top: 16px; + color: rgba(0,0,0,0.6); + padding: 8px 12px; +} + +@media (max-width: 600px) { + .toolbar { + flex-direction: column; + align-items: stretch; + gap: 8px; + } + + .actions-cell { + min-width: 0; + } +} diff --git a/client/src/app/components/platforms-list/platforms-list.component.html b/client/src/app/components/platforms-list/platforms-list.component.html new file mode 100644 index 0000000..01dc89f --- /dev/null +++ b/client/src/app/components/platforms-list/platforms-list.component.html @@ -0,0 +1,50 @@ +
+
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + +
Nom{{ platform.name }}Marque{{ platform.brand.name }} + + +
+ + + + @if (!platforms || platforms.length === 0) { +
+ Aucune plateforme trouvée. +
+ } +
diff --git a/client/src/app/components/platforms-list/platforms-list.component.ts b/client/src/app/components/platforms-list/platforms-list.component.ts new file mode 100644 index 0000000..ea689fe --- /dev/null +++ b/client/src/app/components/platforms-list/platforms-list.component.ts @@ -0,0 +1,139 @@ +import { + Component, + Input, + Output, + EventEmitter, + ViewChild, + AfterViewInit, + OnChanges, + SimpleChanges, + OnInit, + inject +} from '@angular/core'; +import { + MatCell, MatCellDef, + MatColumnDef, + MatHeaderCell, + MatHeaderCellDef, MatHeaderRow, MatHeaderRowDef, MatRow, MatRowDef, + MatTable, + MatTableDataSource +} from '@angular/material/table'; +import {MatPaginator} from '@angular/material/paginator'; +import {MatSort} from '@angular/material/sort'; +import {Platform} from '../../interfaces/platform'; +import {MatButton, MatIconButton} from '@angular/material/button'; +import {MatIcon} from '@angular/material/icon'; +import {MatFormField} from '@angular/material/form-field'; +import {MatInput} from '@angular/material/input'; +import {PlatformService} from '../../services/platform/platform.service'; +import {MatDialog} from '@angular/material/dialog'; +import { PlatformDialogComponent } from '../platform-dialog/platform-dialog.component'; + +@Component({ + selector: 'app-platforms-list', + templateUrl: './platforms-list.component.html', + standalone: true, + imports: [ + MatButton, + MatIcon, + MatFormField, + MatInput, + MatTable, + MatColumnDef, + MatHeaderCell, + MatCell, + MatHeaderCellDef, + MatCellDef, + MatSort, + MatIconButton, + MatHeaderRow, + MatRow, + MatHeaderRowDef, + MatRowDef, + MatPaginator + ], + styleUrls: ['./platforms-list.component.css'] +}) +export class PlatformsListComponent implements OnInit, AfterViewInit, OnChanges { + + @Input() platforms: Platform[] = []; + @Output() add = new EventEmitter(); + @Output() edit = new EventEmitter(); + @Output() delete = new EventEmitter(); + + displayedColumns: string[] = ['name', 'brand', 'actions']; + dataSource = new MatTableDataSource([]); + + @ViewChild(MatPaginator) paginator!: MatPaginator; + @ViewChild(MatSort) sort!: MatSort; + + private readonly platformService: PlatformService = inject(PlatformService); + private readonly dialog = inject(MatDialog); + + ngOnInit(): void { + if (!this.platforms || this.platforms.length === 0) { + this.loadPlatforms(); + } else { + this.dataSource.data = this.platforms; + } + } + + ngOnChanges(changes: SimpleChanges): void { + if (changes['platforms']) { + this.dataSource.data = this.platforms || []; + } + } + + ngAfterViewInit(): void { + this.dataSource.paginator = this.paginator; + this.dataSource.sort = this.sort; + } + + loadPlatforms() { + this.platformService.getPlatforms().subscribe({ + next: (platforms:Platform[]) => { + this.platforms = platforms || [] + this.dataSource.data = this.platforms; + console.log("Fetched platforms:", this.platforms); + }, + error: () => this.platforms = [] + }); + } + + onAdd(): void { + const ref = this.dialog.open(PlatformDialogComponent, { + data: { platform: { id: '', name: '', brand: undefined } }, + width: '420px' + }); + + ref.afterClosed().subscribe((result?: Platform) => { + if (result) { + this.add.emit(result); + this.platformService.addPlatform(result).subscribe(() => this.loadPlatforms()); + } + }); + } + + onEdit(platform: Platform): void { + const ref = this.dialog.open(PlatformDialogComponent, { + data: { platform: { ...platform } }, + width: '420px' + }); + + ref.afterClosed().subscribe((result?: Platform) => { + if (result) { + this.edit.emit(result); + this.platformService.updatePlatform((platform as any).id, result).subscribe(() => this.loadPlatforms()); + } + }); + } + + onDelete(platform: Platform): void { + this.delete.emit(platform); + this.platformService.deletePlatform((platform as any).id).subscribe(() => this.loadPlatforms()); + } + + applyFilter(value: string): void { + this.dataSource.filter = (value || '').trim().toLowerCase(); + } +} diff --git a/client/src/app/guards/admin-only/admin-only.guard.ts b/client/src/app/guards/admin-only.guard.ts similarity index 94% rename from client/src/app/guards/admin-only/admin-only.guard.ts rename to client/src/app/guards/admin-only.guard.ts index cfee37b..498aa06 100644 --- a/client/src/app/guards/admin-only/admin-only.guard.ts +++ b/client/src/app/guards/admin-only.guard.ts @@ -1,6 +1,6 @@ import { inject } from '@angular/core'; import { CanActivateFn, CanMatchFn, Router, UrlTree, ActivatedRouteSnapshot, Route } from '@angular/router'; -import { AuthService } from '../../services/auth/auth.service'; +import { AuthService } from '../services/auth/auth.service'; function requireAdmin(url?: string): boolean | UrlTree { const authService: AuthService = inject(AuthService); diff --git a/client/src/app/guards/admin-only/admin-only.guard.spec.ts b/client/src/app/guards/admin-only/admin-only.guard.spec.ts deleted file mode 100644 index d0e6ca2..0000000 --- a/client/src/app/guards/admin-only/admin-only.guard.spec.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { TestBed } from '@angular/core/testing'; -import { CanActivateFn } from '@angular/router'; - -import { adminOnlyGuard } from './admin-only.guard'; - -describe('adminOnlyGuard', () => { - const executeGuard: CanActivateFn = (...guardParameters) => - TestBed.runInInjectionContext(() => adminOnlyGuard(...guardParameters)); - - beforeEach(() => { - TestBed.configureTestingModule({}); - }); - - it('should be created', () => { - expect(executeGuard).toBeTruthy(); - }); -}); diff --git a/client/src/app/guards/auth-only/auth-only.guard.ts b/client/src/app/guards/auth-only.guard.ts similarity index 92% rename from client/src/app/guards/auth-only/auth-only.guard.ts rename to client/src/app/guards/auth-only.guard.ts index 921dc6d..9e6473c 100644 --- a/client/src/app/guards/auth-only/auth-only.guard.ts +++ b/client/src/app/guards/auth-only.guard.ts @@ -1,6 +1,6 @@ import { inject } from '@angular/core'; import { CanActivateFn, CanMatchFn, Router, UrlTree, ActivatedRouteSnapshot, Route } from '@angular/router'; -import { AuthService } from '../../services/auth/auth.service'; +import { AuthService } from '../services/auth/auth.service'; function requireAuth(url?: string): boolean | UrlTree { const authService = inject(AuthService); diff --git a/client/src/app/guards/auth-only/auth-only.guard.spec.ts b/client/src/app/guards/auth-only/auth-only.guard.spec.ts deleted file mode 100644 index 05865b2..0000000 --- a/client/src/app/guards/auth-only/auth-only.guard.spec.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { TestBed } from '@angular/core/testing'; -import { CanMatchFn } from '@angular/router'; - -import { authOnlyGuard } from './auth-only.guard'; - -describe('authOnlyGuard', () => { - const executeGuard: CanMatchFn = (...guardParameters) => - TestBed.runInInjectionContext(() => authOnlyGuard(...guardParameters)); - - beforeEach(() => { - TestBed.configureTestingModule({}); - }); - - it('should be created', () => { - expect(executeGuard).toBeTruthy(); - }); -}); diff --git a/client/src/app/guards/guest-only/guest-only.guard.ts b/client/src/app/guards/guest-only.guard.ts similarity index 89% rename from client/src/app/guards/guest-only/guest-only.guard.ts rename to client/src/app/guards/guest-only.guard.ts index 9d23ec1..e37c6f0 100644 --- a/client/src/app/guards/guest-only/guest-only.guard.ts +++ b/client/src/app/guards/guest-only.guard.ts @@ -1,6 +1,6 @@ import { inject } from '@angular/core'; import { Router, UrlTree, CanActivateFn, CanMatchFn } from '@angular/router'; -import { AuthService } from '../../services/auth/auth.service'; +import { AuthService } from '../services/auth/auth.service'; function redirectIfLoggedIn(): boolean | UrlTree { const authService = inject(AuthService); diff --git a/client/src/app/guards/guest-only/guest-only.guard.spec.ts b/client/src/app/guards/guest-only/guest-only.guard.spec.ts deleted file mode 100644 index c26b600..0000000 --- a/client/src/app/guards/guest-only/guest-only.guard.spec.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { TestBed } from '@angular/core/testing'; -import { CanMatchFn } from '@angular/router'; - -import { guestOnlyGuard } from './guest-only.guard'; - -describe('guestOnlyGuard', () => { - const executeGuard: CanMatchFn = (...guardParameters) => - TestBed.runInInjectionContext(() => guestOnlyGuard(...guardParameters)); - - beforeEach(() => { - TestBed.configureTestingModule({}); - }); - - it('should be created', () => { - expect(executeGuard).toBeTruthy(); - }); -}); diff --git a/client/src/app/interceptors/authToken/auth-token.interceptor.ts b/client/src/app/interceptors/auth-token.interceptor.ts similarity index 94% rename from client/src/app/interceptors/authToken/auth-token.interceptor.ts rename to client/src/app/interceptors/auth-token.interceptor.ts index a2fe627..e219fc1 100644 --- a/client/src/app/interceptors/authToken/auth-token.interceptor.ts +++ b/client/src/app/interceptors/auth-token.interceptor.ts @@ -1,6 +1,6 @@ import {HttpErrorResponse, HttpInterceptorFn} from '@angular/common/http'; import {inject} from '@angular/core'; -import {AuthService} from '../../services/auth/auth.service'; +import {AuthService} from '../services/auth/auth.service'; import {catchError, switchMap, throwError} from 'rxjs'; let isRefreshing = false; @@ -19,7 +19,7 @@ export const authTokenInterceptor: HttpInterceptorFn = (req, next) => { catchError((error: any) => { const is401 = error instanceof HttpErrorResponse && error.status === 401; - // si 401 et pas déjà en refresh, tente un refresh puis rejoue la requête 1 fois + // si 401 et pas déjà en refresh, tente un refresh puis rejoue la requête une fois if (is401 && !isRefreshing) { isRefreshing = true; return inject(AuthService).refresh().pipe( diff --git a/client/src/app/interceptors/authToken/auth-token.interceptor.spec.ts b/client/src/app/interceptors/authToken/auth-token.interceptor.spec.ts deleted file mode 100644 index 26fa57c..0000000 --- a/client/src/app/interceptors/authToken/auth-token.interceptor.spec.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { TestBed } from '@angular/core/testing'; -import { HttpInterceptorFn } from '@angular/common/http'; - -import { authTokenInterceptor } from './auth-token.interceptor'; - -describe('authTokenInterceptor', () => { - const interceptor: HttpInterceptorFn = (req, next) => - TestBed.runInInjectionContext(() => authTokenInterceptor(req, next)); - - beforeEach(() => { - TestBed.configureTestingModule({}); - }); - - it('should be created', () => { - expect(interceptor).toBeTruthy(); - }); -}); diff --git a/client/src/app/interfaces/brand.ts b/client/src/app/interfaces/brand.ts new file mode 100644 index 0000000..0f6d120 --- /dev/null +++ b/client/src/app/interfaces/brand.ts @@ -0,0 +1,4 @@ +export interface Brand { + id: string | number; + name: string; +} diff --git a/client/src/app/interfaces/credentials/credentials.ts b/client/src/app/interfaces/credentials.ts similarity index 100% rename from client/src/app/interfaces/credentials/credentials.ts rename to client/src/app/interfaces/credentials.ts diff --git a/client/src/app/interfaces/credentials/credentials.spec.ts b/client/src/app/interfaces/credentials/credentials.spec.ts deleted file mode 100644 index 387c546..0000000 --- a/client/src/app/interfaces/credentials/credentials.spec.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Credentials } from './credentials'; - -describe('Credentials', () => { - it('should create an instance', () => { - expect(new Credentials()).toBeTruthy(); - }); -}); diff --git a/client/src/app/interfaces/platform.ts b/client/src/app/interfaces/platform.ts new file mode 100644 index 0000000..9155fcb --- /dev/null +++ b/client/src/app/interfaces/platform.ts @@ -0,0 +1,7 @@ +import {Brand} from './brand'; + +export interface Platform { + id: string | number; + name: string; + brand: Brand | undefined; +} diff --git a/client/src/app/interfaces/user.ts b/client/src/app/interfaces/user.ts new file mode 100644 index 0000000..0f9a309 --- /dev/null +++ b/client/src/app/interfaces/user.ts @@ -0,0 +1,7 @@ +export interface User { + firstName: string; + lastName: string; + username: string; + email: string ; + role: string; +} diff --git a/client/src/app/models/brand/brand.spec.ts b/client/src/app/models/brand/brand.spec.ts deleted file mode 100644 index 75cd13b..0000000 --- a/client/src/app/models/brand/brand.spec.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Brand } from './brand'; - -describe('Brand', () => { - it('should create an instance', () => { - expect(new Brand()).toBeTruthy(); - }); -}); diff --git a/client/src/app/models/brand/brand.ts b/client/src/app/models/brand/brand.ts deleted file mode 100644 index 8408f28..0000000 --- a/client/src/app/models/brand/brand.ts +++ /dev/null @@ -1,3 +0,0 @@ -export class Brand { - name: string = ''; -} diff --git a/client/src/app/models/user/user.model.spec.ts b/client/src/app/models/user/user.model.spec.ts deleted file mode 100644 index c5ec2e7..0000000 --- a/client/src/app/models/user/user.model.spec.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { UserModel } from './user.model'; - -describe('UserModel', () => { - it('should create an instance', () => { - expect(new UserModel()).toBeTruthy(); - }); -}); diff --git a/client/src/app/models/user/user.model.ts b/client/src/app/models/user/user.model.ts deleted file mode 100644 index 94188b4..0000000 --- a/client/src/app/models/user/user.model.ts +++ /dev/null @@ -1,7 +0,0 @@ -export class User { - firstName: string = ''; - lastName: string = ''; - username: string = ''; - email: string = ''; - role: string = ''; -} diff --git a/client/src/app/pages/add-product/add-product.component.html b/client/src/app/pages/add-product/add-product.component.html index 41ab698..84487e4 100644 --- a/client/src/app/pages/add-product/add-product.component.html +++ b/client/src/app/pages/add-product/add-product.component.html @@ -1,8 +1,7 @@
- Gestion des produits - Create a new account + Ajouter un produit @@ -61,7 +60,7 @@ Marque - @for (brand of brands; track brand.name) { + @for (brand of brands; track brand.id) { {{ brand.name }} } @@ -71,9 +70,9 @@ Plateforme - Option 1 - Option 2 - Option 3 + @for (platform of platforms; track platform.id) { + {{ platform.name }} + } diff --git a/client/src/app/pages/add-product/add-product.component.spec.ts b/client/src/app/pages/add-product/add-product.component.spec.ts deleted file mode 100644 index b8d7fed..0000000 --- a/client/src/app/pages/add-product/add-product.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { AddProductComponent } from './add-product.component'; - -describe('AddProductComponent', () => { - let component: AddProductComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [AddProductComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(AddProductComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/client/src/app/pages/add-product/add-product.component.ts b/client/src/app/pages/add-product/add-product.component.ts index acc6d1d..e9e4f9c 100644 --- a/client/src/app/pages/add-product/add-product.component.ts +++ b/client/src/app/pages/add-product/add-product.component.ts @@ -12,7 +12,6 @@ import { MatCardActions, MatCardContent, MatCardHeader, - MatCardSubtitle, MatCardTitle } from "@angular/material/card"; import {MatCheckbox} from "@angular/material/checkbox"; @@ -24,7 +23,9 @@ import {MatOption, MatSelect} from '@angular/material/select'; import {Router, RouterLink} from '@angular/router'; import {Subscription} from 'rxjs'; import {BrandService} from '../../services/brand/brand.service'; -import {Brand} from '../../models/brand/brand'; +import {Brand} from '../../interfaces/brand'; +import {PlatformService} from '../../services/platform/platform.service'; +import {Platform} from '../../interfaces/platform'; @Component({ selector: 'app-add-product', @@ -36,7 +37,6 @@ import {Brand} from '../../models/brand/brand'; MatCardActions, MatCardContent, MatCardHeader, - MatCardSubtitle, MatCardTitle, MatCheckbox, MatDivider, @@ -60,11 +60,13 @@ export class AddProductComponent implements OnInit, OnDestroy { isLoading = false; brands: Brand[] = []; + platforms: Platform[] = []; private readonly router: Router = inject(Router); - private addProductSubscription: Subscription | null = null; + private readonly addProductSubscription: Subscription | null = null; private readonly brandService: BrandService = inject(BrandService); + private readonly platformService = inject(PlatformService); constructor(private readonly formBuilder: FormBuilder) { this.addProductForm = this.formBuilder.group({ @@ -126,6 +128,18 @@ export class AddProductComponent implements OnInit, OnDestroy { console.log('Finished fetching brands:', this.brands); } }); + + this.platformService.getPlatforms().subscribe({ + next: (platforms) => { + this.platforms = platforms; + }, + error: (error) => { + console.error('Error fetching platforms:', error); + }, + complete: () => { + console.log('Finished fetching platforms:', this.platforms); + } + }); } ngOnDestroy(): void { diff --git a/client/src/app/pages/admin/admin.component.html b/client/src/app/pages/admin/admin.component.html index 49659db..3481ed4 100644 --- a/client/src/app/pages/admin/admin.component.html +++ b/client/src/app/pages/admin/admin.component.html @@ -1 +1 @@ -

admin works!

+ diff --git a/client/src/app/pages/admin/admin.component.spec.ts b/client/src/app/pages/admin/admin.component.spec.ts deleted file mode 100644 index 617b55b..0000000 --- a/client/src/app/pages/admin/admin.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { AdminComponent } from './admin.component'; - -describe('AdminComponent', () => { - let component: AdminComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [AdminComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(AdminComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/client/src/app/pages/admin/admin.component.ts b/client/src/app/pages/admin/admin.component.ts index 087b4f4..8d59fcd 100644 --- a/client/src/app/pages/admin/admin.component.ts +++ b/client/src/app/pages/admin/admin.component.ts @@ -1,12 +1,15 @@ import { Component } from '@angular/core'; +import {AdminNavbarComponent} from '../../components/admin-navbar/admin-navbar.component'; @Component({ selector: 'app-admin', - standalone: true, - imports: [], templateUrl: './admin.component.html', - styleUrl: './admin.component.css' + standalone: true, + imports: [ + AdminNavbarComponent + ], + styleUrls: ['./admin.component.scss'] }) -export class AdminComponent { +export class AdminComponent{ } diff --git a/client/src/app/pages/login/login.component.ts b/client/src/app/pages/login/login.component.ts index 586e8f5..847f8e2 100644 --- a/client/src/app/pages/login/login.component.ts +++ b/client/src/app/pages/login/login.component.ts @@ -4,8 +4,8 @@ import {FormBuilder, ReactiveFormsModule, Validators} from '@angular/forms'; import {AuthService} from '../../services/auth/auth.service'; import {Router} from '@angular/router'; import {Subscription} from 'rxjs'; -import {Credentials} from '../../interfaces/credentials/credentials'; -import {User} from '../../models/user/user.model'; +import {Credentials} from '../../interfaces/credentials'; +import {User} from '../../interfaces/user'; import {MatInput} from '@angular/material/input'; import {MatButton} from '@angular/material/button'; diff --git a/client/src/app/pages/profile/profile.component.ts b/client/src/app/pages/profile/profile.component.ts index 44b7d58..1529198 100644 --- a/client/src/app/pages/profile/profile.component.ts +++ b/client/src/app/pages/profile/profile.component.ts @@ -10,7 +10,7 @@ import { import {MatIcon} from '@angular/material/icon'; import {MatButton} from '@angular/material/button'; import {AuthService} from '../../services/auth/auth.service'; -import {User} from '../../models/user/user.model'; +import {User} from '../../interfaces/user'; import {Router} from '@angular/router'; @Component({ diff --git a/client/src/app/services/auth/auth.service.spec.ts b/client/src/app/services/auth/auth.service.spec.ts deleted file mode 100644 index f37fac4..0000000 --- a/client/src/app/services/auth/auth.service.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { AuthService } from './auth.service'; - -describe('LoginService', () => { - let service: AuthService; - - beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(AuthService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); diff --git a/client/src/app/services/auth/auth.service.ts b/client/src/app/services/auth/auth.service.ts index 8db180a..aedd398 100644 --- a/client/src/app/services/auth/auth.service.ts +++ b/client/src/app/services/auth/auth.service.ts @@ -1,8 +1,8 @@ import {inject, Injectable, signal} from '@angular/core'; import {catchError, map, of, switchMap, tap} from 'rxjs'; -import {Credentials} from '../../interfaces/credentials/credentials'; +import {Credentials} from '../../interfaces/credentials'; import {HttpClient} from '@angular/common/http'; -import {User} from '../../models/user/user.model'; +import {User} from '../../interfaces/user'; @Injectable({ providedIn: 'root' diff --git a/client/src/app/services/brand/brand.service.spec.ts b/client/src/app/services/brand/brand.service.spec.ts deleted file mode 100644 index 9b88b26..0000000 --- a/client/src/app/services/brand/brand.service.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { BrandService } from './brand.service'; - -describe('BrandService', () => { - let service: BrandService; - - beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(BrandService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); diff --git a/client/src/app/services/brand/brand.service.ts b/client/src/app/services/brand/brand.service.ts index 51f8096..2c43e9c 100644 --- a/client/src/app/services/brand/brand.service.ts +++ b/client/src/app/services/brand/brand.service.ts @@ -1,6 +1,6 @@ import {inject, Injectable} from '@angular/core'; import {HttpClient} from '@angular/common/http'; -import {Brand} from '../../models/brand/brand'; +import {Brand} from '../../interfaces/brand'; @Injectable({ providedIn: 'root' @@ -8,9 +8,24 @@ import {Brand} from '../../models/brand/brand'; export class BrandService { private readonly http = inject(HttpClient); - private readonly BASE_URL = 'http://localhost:3000/brands'; + private readonly BASE_URL = 'http://localhost:3000/api/brands'; getBrands() { return this.http.get(this.BASE_URL, {withCredentials: true}); } + + addBrand(brand: Brand) { + console.log("Adding brand:", brand); + return this.http.post(this.BASE_URL, brand, {withCredentials: true}); + } + + updateBrand(id: string, brand: Brand) { + console.log("Updating brand:", id, brand); + return this.http.put(`${this.BASE_URL}/${id}`, brand, {withCredentials: true}); + } + + deleteBrand(id: string) { + console.log("Deleting brand:", id); + return this.http.delete(`${this.BASE_URL}/${id}`, {withCredentials: true}); + } } diff --git a/client/src/app/services/platform/platform.service.ts b/client/src/app/services/platform/platform.service.ts new file mode 100644 index 0000000..b717eac --- /dev/null +++ b/client/src/app/services/platform/platform.service.ts @@ -0,0 +1,31 @@ +import {inject, Injectable} from '@angular/core'; +import {HttpClient} from '@angular/common/http'; +import {Platform} from '../../interfaces/platform'; + +@Injectable({ + providedIn: 'root' +}) +export class PlatformService { + + private readonly http = inject(HttpClient); + private readonly BASE_URL = 'http://localhost:3000/api/platforms'; + + getPlatforms() { + return this.http.get(this.BASE_URL, {withCredentials: true}); + } + + addPlatform(platform: Platform) { + console.log("Adding platform:", platform); + return this.http.post(this.BASE_URL, platform, {withCredentials: true}); + } + + updatePlatform(id: string, platform: Platform) { + console.log("Updating platform:", id, platform); + return this.http.put(`${this.BASE_URL}/${id}`, platform, {withCredentials: true}); + } + + deletePlatform(id: string) { + console.log("Deleting platform:", id); + return this.http.delete(`${this.BASE_URL}/${id}`, {withCredentials: true}); + } +} diff --git a/client/src/app/services/product/product.service.spec.ts b/client/src/app/services/product/product.service.spec.ts deleted file mode 100644 index d5c493e..0000000 --- a/client/src/app/services/product/product.service.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { ProductService } from './product.service'; - -describe('ProductService', () => { - let service: ProductService; - - beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(ProductService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -});