diff --git a/client/package.json b/client/package.json
index bf1a994..12bc953 100644
--- a/client/package.json
+++ b/client/package.json
@@ -3,7 +3,7 @@
"version": "0.0.0",
"scripts": {
"ng": "ng",
- "start": "ng serve",
+ "start": "ng serve --proxy-config proxy.conf.json",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test"
@@ -37,4 +37,4 @@
"karma-jasmine-html-reporter": "~2.1.0",
"typescript": "~5.5.2"
}
-}
\ No newline at end of file
+}
diff --git a/client/src/app/admin-presta/admin-presta.module.ts b/client/src/app/admin-presta/admin-presta.module.ts
deleted file mode 100644
index 61d2b82..0000000
--- a/client/src/app/admin-presta/admin-presta.module.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import {NgModule} from '@angular/core';
-import {CommonModule} from '@angular/common';
-import {ReactiveFormsModule, FormsModule} from '@angular/forms';
-
-// Angular Material (tous utilisés dans ces composants)
-import {MatTabsModule} from '@angular/material/tabs';
-import {MatTableModule} from '@angular/material/table';
-import {MatFormFieldModule} from '@angular/material/form-field';
-import {MatInputModule} from '@angular/material/input';
-import {MatButtonModule} from '@angular/material/button';
-import {MatIconModule} from '@angular/material/icon';
-
-// Composants de cette feature
-import {PsCrudTabsComponent} from './ps-crud-tabs/ps-crud-tabs.component';
-import {CategoriesCrudComponent} from './categories-crud/categories-crud.component';
-import {ManufacturersCrudComponent} from './manufacturers-crud/manufacturers-crud.component';
-import {SuppliersCrudComponent} from './suppliers-crud/suppliers-crud.component';
-
-@NgModule({
- declarations: [
-
- ],
- imports: [
- CommonModule,
- ReactiveFormsModule, FormsModule,
- MatTabsModule, MatTableModule, MatFormFieldModule, MatInputModule,
- MatButtonModule, MatIconModule, PsCrudTabsComponent, CategoriesCrudComponent, ManufacturersCrudComponent, SuppliersCrudComponent
- ],
- exports: [PsCrudTabsComponent] // pour pouvoir l’utiliser ailleurs
-})
-export class AdminPrestaModule {
-}
diff --git a/client/src/app/admin-presta/categories-crud/categories-crud.component.css b/client/src/app/admin-presta/categories-crud/categories-crud.component.css
deleted file mode 100644
index a6fe2c5..0000000
--- a/client/src/app/admin-presta/categories-crud/categories-crud.component.css
+++ /dev/null
@@ -1,18 +0,0 @@
-.crud {
- display: grid;
- gap: 16px
-}
-
-.row {
- display: flex;
- gap: 12px;
- align-items: flex-end
-}
-
-.grow {
- flex: 1
-}
-
-table {
- width: 100%
-}
diff --git a/client/src/app/admin-presta/categories-crud/categories-crud.component.html b/client/src/app/admin-presta/categories-crud/categories-crud.component.html
deleted file mode 100644
index 542d81f..0000000
--- a/client/src/app/admin-presta/categories-crud/categories-crud.component.html
+++ /dev/null
@@ -1,36 +0,0 @@
-
-
-
-
-
- | ID |
- {{ el.id }} |
-
-
-
- Nom |
- {{ el.name }} |
-
-
-
- Actions |
-
-
-
- |
-
-
-
-
-
-
diff --git a/client/src/app/admin-presta/categories-crud/categories-crud.component.ts b/client/src/app/admin-presta/categories-crud/categories-crud.component.ts
deleted file mode 100644
index e783492..0000000
--- a/client/src/app/admin-presta/categories-crud/categories-crud.component.ts
+++ /dev/null
@@ -1,99 +0,0 @@
-import {Component, inject, OnInit} from '@angular/core';
-import {FormBuilder, ReactiveFormsModule, Validators} from '@angular/forms';
-import {PrestaService, PsItem} from '../../services/presta.serivce';
-import {map} from 'rxjs';
-import {MatIcon} from '@angular/material/icon';
-import {MatButton, MatIconButton} from '@angular/material/button';
-import {
- MatCell,
- MatCellDef,
- MatColumnDef,
- MatHeaderCell, MatHeaderCellDef,
- MatHeaderRow,
- MatHeaderRowDef,
- MatRow,
- MatRowDef,
- MatTable
-} from '@angular/material/table';
-import {MatFormField, MatLabel} from '@angular/material/form-field';
-import {MatInput} from '@angular/material/input';
-import {NgIf} from '@angular/common';
-
-@Component({
- standalone : true,
- selector: 'app-categories-crud',
- templateUrl: './categories-crud.component.html',
- styleUrls: ['./categories-crud.component.css'],
- imports: [
- MatIcon,
- MatIconButton,
- MatHeaderRow,
- MatRow,
- MatRowDef,
- MatHeaderRowDef,
- ReactiveFormsModule,
- MatFormField,
- MatLabel,
- MatInput,
- MatButton,
- MatTable,
- MatColumnDef,
- MatHeaderCell,
- NgIf,
- MatHeaderCellDef,
- MatCellDef,
- MatCell
- ]
-})
-export class CategoriesCrudComponent implements OnInit {
- private readonly fb = inject(FormBuilder);
- private readonly ps = inject(PrestaService);
-
- items: PsItem[] = [];
- cols = ['id', 'name', 'actions'];
- form = this.fb.group({name: ['', Validators.required]});
- editId: number | null = null;
-
- ngOnInit() {
- this.reload();
- }
-
- reload() {
- this.ps.list('categories').subscribe(data => this.items = data);
- }
-
- startEdit(el: PsItem) {
- this.editId = el.id;
- this.form.patchValue({name: el.name});
- }
-
- cancelEdit() {
- this.editId = null;
- this.form.reset({name: ''});
- }
-
- onSubmit() {
- const name = this.form.value.name!.trim();
- if (!name) return;
-
- const op$ = this.editId
- ? this.ps.update('categories', this.editId, name).pipe(map(() => undefined))
- : this.ps.create('categories', name).pipe(map(() => undefined));
-
- op$.subscribe({
- next: () => {
- this.cancelEdit();
- this.reload();
- },
- error: (e: unknown) => alert('Erreur: ' + (e instanceof Error ? e.message : String(e)))
- });
- }
-
- remove(el: PsItem) {
- if (!confirm(`Supprimer la catégorie "${el.name}" (#${el.id}) ?`)) return;
- this.ps.delete('categories', el.id).subscribe({
- next: () => this.reload(),
- error: e => alert('Erreur: ' + (e?.message || e))
- });
- }
-}
diff --git a/client/src/app/admin-presta/manufacturers-crud/manufacturers-crud.component.css b/client/src/app/admin-presta/manufacturers-crud/manufacturers-crud.component.css
deleted file mode 100644
index a6fe2c5..0000000
--- a/client/src/app/admin-presta/manufacturers-crud/manufacturers-crud.component.css
+++ /dev/null
@@ -1,18 +0,0 @@
-.crud {
- display: grid;
- gap: 16px
-}
-
-.row {
- display: flex;
- gap: 12px;
- align-items: flex-end
-}
-
-.grow {
- flex: 1
-}
-
-table {
- width: 100%
-}
diff --git a/client/src/app/admin-presta/manufacturers-crud/manufacturers-crud.component.html b/client/src/app/admin-presta/manufacturers-crud/manufacturers-crud.component.html
deleted file mode 100644
index 1b4beff..0000000
--- a/client/src/app/admin-presta/manufacturers-crud/manufacturers-crud.component.html
+++ /dev/null
@@ -1,40 +0,0 @@
-
-
-
-
-
- | ID |
- {{ el.id }} |
-
-
-
- Nom |
- {{ el.name }} |
-
-
-
- Actions |
-
-
-
- |
-
-
-
-
-
-
diff --git a/client/src/app/admin-presta/manufacturers-crud/manufacturers-crud.component.ts b/client/src/app/admin-presta/manufacturers-crud/manufacturers-crud.component.ts
deleted file mode 100644
index 658c661..0000000
--- a/client/src/app/admin-presta/manufacturers-crud/manufacturers-crud.component.ts
+++ /dev/null
@@ -1,99 +0,0 @@
-import {Component, inject, OnInit} from '@angular/core';
-import {FormBuilder, ReactiveFormsModule, Validators} from '@angular/forms';
-import {PrestaService, PsItem} from '../../services/presta.serivce';
-import {map} from 'rxjs';
-import {MatFormField, MatLabel} from '@angular/material/form-field';
-import {MatIcon} from '@angular/material/icon';
-import {MatButton, MatIconButton} from '@angular/material/button';
-import {
- MatCell,
- MatCellDef,
- MatColumnDef,
- MatHeaderCell, MatHeaderCellDef,
- MatHeaderRow,
- MatHeaderRowDef,
- MatRow,
- MatRowDef,
- MatTable
-} from '@angular/material/table';
-import {MatInput} from '@angular/material/input';
-import {NgIf} from '@angular/common';
-
-@Component({
- standalone: true,
- selector: 'app-manufacturers-crud',
- templateUrl: './manufacturers-crud.component.html',
- imports: [
- MatIcon,
- MatIconButton,
- MatHeaderRow,
- MatRow,
- MatRowDef,
- MatHeaderRowDef,
- ReactiveFormsModule,
- MatFormField,
- MatLabel,
- MatInput,
- MatButton,
- MatTable,
- MatColumnDef,
- MatHeaderCell,
- NgIf,
- MatHeaderCellDef,
- MatCellDef,
- MatCell
- ],
- styleUrls: ['./manufacturers-crud.component.css']
-})
-export class ManufacturersCrudComponent implements OnInit {
- private readonly fb = inject(FormBuilder);
- private readonly ps = inject(PrestaService);
-
- items: PsItem[] = [];
- cols = ['id', 'name', 'actions'];
- form = this.fb.group({name: ['', Validators.required]});
- editId: number | null = null;
-
- ngOnInit() {
- this.reload();
- }
-
- reload() {
- this.ps.list('manufacturers').subscribe(d => this.items = d);
- }
-
- startEdit(el: PsItem) {
- this.editId = el.id;
- this.form.patchValue({name: el.name});
- }
-
- cancelEdit() {
- this.editId = null;
- this.form.reset({name: ''});
- }
-
- onSubmit() {
- const name = this.form.value.name!.trim();
- if (!name) return;
-
- const op$ = this.editId
- ? this.ps.update('manufacturers', this.editId, name).pipe(map(() => undefined))
- : this.ps.create('manufacturers', name).pipe(map(() => undefined));
-
- op$.subscribe({
- next: () => {
- this.cancelEdit();
- this.reload();
- },
- error: (e: unknown) => alert('Erreur: ' + (e instanceof Error ? e.message : String(e)))
- });
- }
-
- remove(el: PsItem) {
- if (!confirm(`Supprimer la marque "${el.name}" (#${el.id}) ?`)) return;
- this.ps.delete('manufacturers', el.id).subscribe({
- next: () => this.reload(),
- error: e => alert('Erreur: ' + (e?.message || e))
- });
- }
-}
diff --git a/client/src/app/admin-presta/ps-crud-tabs/ps-crud-tabs.component.html b/client/src/app/admin-presta/ps-crud-tabs/ps-crud-tabs.component.html
deleted file mode 100644
index 017ddcf..0000000
--- a/client/src/app/admin-presta/ps-crud-tabs/ps-crud-tabs.component.html
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
PrestaShop — CRUD simplifié
-
-
-
-
-
-
diff --git a/client/src/app/admin-presta/ps-crud-tabs/ps-crud-tabs.component.ts b/client/src/app/admin-presta/ps-crud-tabs/ps-crud-tabs.component.ts
deleted file mode 100644
index a80b0fd..0000000
--- a/client/src/app/admin-presta/ps-crud-tabs/ps-crud-tabs.component.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { Component } from '@angular/core';
-import {MatTab, MatTabGroup} from '@angular/material/tabs';
-import {CategoriesCrudComponent} from '../categories-crud/categories-crud.component';
-import {ManufacturersCrudComponent} from '../manufacturers-crud/manufacturers-crud.component';
-import {SuppliersCrudComponent} from '../suppliers-crud/suppliers-crud.component';
-
-@Component({
- standalone: true,
- selector: 'app-ps-crud-tabs',
- templateUrl: './ps-crud-tabs.component.html',
- imports: [
- MatTabGroup,
- MatTab,
- CategoriesCrudComponent,
- ManufacturersCrudComponent,
- SuppliersCrudComponent
- ],
- styleUrls: ['./ps-crud-tabs.component.css']
-})
-export class PsCrudTabsComponent {}
diff --git a/client/src/app/admin-presta/suppliers-crud/suppliers-crud.component.css b/client/src/app/admin-presta/suppliers-crud/suppliers-crud.component.css
deleted file mode 100644
index a6fe2c5..0000000
--- a/client/src/app/admin-presta/suppliers-crud/suppliers-crud.component.css
+++ /dev/null
@@ -1,18 +0,0 @@
-.crud {
- display: grid;
- gap: 16px
-}
-
-.row {
- display: flex;
- gap: 12px;
- align-items: flex-end
-}
-
-.grow {
- flex: 1
-}
-
-table {
- width: 100%
-}
diff --git a/client/src/app/admin-presta/suppliers-crud/suppliers-crud.component.html b/client/src/app/admin-presta/suppliers-crud/suppliers-crud.component.html
deleted file mode 100644
index e6118f6..0000000
--- a/client/src/app/admin-presta/suppliers-crud/suppliers-crud.component.html
+++ /dev/null
@@ -1,40 +0,0 @@
-
-
-
-
-
- | ID |
- {{ el.id }} |
-
-
-
- Nom |
- {{ el.name }} |
-
-
-
- Actions |
-
-
-
- |
-
-
-
-
-
-
diff --git a/client/src/app/admin-presta/suppliers-crud/suppliers-crud.component.ts b/client/src/app/admin-presta/suppliers-crud/suppliers-crud.component.ts
deleted file mode 100644
index f9364a1..0000000
--- a/client/src/app/admin-presta/suppliers-crud/suppliers-crud.component.ts
+++ /dev/null
@@ -1,99 +0,0 @@
-import {Component, inject, OnInit} from '@angular/core';
-import {FormBuilder, ReactiveFormsModule, Validators} from '@angular/forms';
-import {PrestaService, PsItem} from '../../services/presta.serivce';
-import {map} from 'rxjs';
-import {MatIcon} from '@angular/material/icon';
-import {MatButton, MatIconButton} from '@angular/material/button';
-import {
- MatCell,
- MatCellDef,
- MatColumnDef,
- MatHeaderCell, MatHeaderCellDef,
- MatHeaderRow,
- MatHeaderRowDef,
- MatRow,
- MatRowDef,
- MatTable
-} from '@angular/material/table';
-import {MatFormField, MatLabel} from '@angular/material/form-field';
-import {MatInput} from '@angular/material/input';
-import {NgIf} from '@angular/common';
-
-@Component({
- standalone: true,
- selector: 'app-suppliers-crud',
- templateUrl: './suppliers-crud.component.html',
- imports: [
- MatIcon,
- MatIconButton,
- MatHeaderRow,
- MatRow,
- MatRowDef,
- MatHeaderRowDef,
- ReactiveFormsModule,
- MatFormField,
- MatLabel,
- MatInput,
- MatButton,
- MatTable,
- MatColumnDef,
- MatHeaderCell,
- NgIf,
- MatHeaderCellDef,
- MatCellDef,
- MatCell
- ],
- styleUrls: ['./suppliers-crud.component.css']
-})
-export class SuppliersCrudComponent implements OnInit {
- private readonly fb = inject(FormBuilder);
- private readonly ps = inject(PrestaService);
-
- items: PsItem[] = [];
- cols = ['id', 'name', 'actions'];
- form = this.fb.group({name: ['', Validators.required]});
- editId: number | null = null;
-
- ngOnInit() {
- this.reload();
- }
-
- reload() {
- this.ps.list('suppliers').subscribe(d => this.items = d);
- }
-
- startEdit(el: PsItem) {
- this.editId = el.id;
- this.form.patchValue({name: el.name});
- }
-
- cancelEdit() {
- this.editId = null;
- this.form.reset({name: ''});
- }
-
- onSubmit() {
- const name = this.form.value.name!.trim();
- if (!name) return;
-
- const op$ = this.editId
- ? this.ps.update('suppliers', this.editId, name).pipe(map(() => undefined))
- : this.ps.create('suppliers', name).pipe(map(() => undefined));
-
- op$.subscribe({
- next: () => {
- this.cancelEdit();
- this.reload();
- },
- error: (e: unknown) => alert('Erreur: ' + (e instanceof Error ? e.message : String(e)))
- });
- }
-
- remove(el: PsItem) {
- if (!confirm(`Supprimer le fournisseur "${el.name}" (#${el.id}) ?`)) return;
- this.ps.delete('suppliers', el.id).subscribe({
- next: () => this.reload(),
- error: e => alert('Erreur: ' + (e?.message || e))
- });
- }
-}
diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts
index 03410b1..4da18e2 100644
--- a/client/src/app/app.component.ts
+++ b/client/src/app/app.component.ts
@@ -1,6 +1,6 @@
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
-import {MainNavbarComponent} from './components/navbar/main-navbar/main-navbar.component';
+import {MainNavbarComponent} from './components/main-navbar/main-navbar.component';
@Component({
selector: 'app-root',
diff --git a/client/src/app/app.config.ts b/client/src/app/app.config.ts
index b855b9b..ffdf2b5 100644
--- a/client/src/app/app.config.ts
+++ b/client/src/app/app.config.ts
@@ -5,7 +5,7 @@ import {routes} from './app.routes';
import {provideHttpClient, withInterceptors} from '@angular/common/http';
import {provideAnimationsAsync} from '@angular/platform-browser/animations/async';
import {authTokenInterceptor} from './interceptors/auth-token.interceptor';
-import {AuthService} from './services/auth/auth.service';
+import {AuthService} from './services/auth.service';
import {catchError, firstValueFrom, of} from 'rxjs';
export const appConfig: ApplicationConfig = {
diff --git a/client/src/app/app.routes.ts b/client/src/app/app.routes.ts
index 58db7e1..dd6b6a7 100644
--- a/client/src/app/app.routes.ts
+++ b/client/src/app/app.routes.ts
@@ -1,15 +1,13 @@
import {Routes} from '@angular/router';
import {HomeComponent} from './pages/home/home.component';
-import {RegisterComponent} from './pages/register/register.component';
-import {LoginComponent} from './pages/login/login.component';
+import {RegisterComponent} from './pages/auth/register/register.component';
+import {LoginComponent} from './pages/auth/login/login.component';
import {ProfileComponent} from './pages/profile/profile.component';
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.guard';
+import {authOnlyCanMatch} from './guards/auth-only.guard';
+import {PsAdminComponent} from './pages/admin/ps-admin/ps-admin.component';
import {ProductsComponent} from './pages/products/products.component';
-import {AddProductComponent} from './pages/add-product/add-product.component';
-import {PsCrudTabsComponent} from './admin-presta/ps-crud-tabs/ps-crud-tabs.component';
export const routes: Routes = [
{
@@ -44,29 +42,17 @@ export const routes: Routes = [
canMatch: [authOnlyCanMatch],
canActivate: [authOnlyCanMatch]
},
- {
- path: 'admin',
- component: AdminComponent,
- canMatch: [adminOnlyCanMatch],
- canActivate: [adminOnlyCanActivate]
- },
{
path: 'products',
component: ProductsComponent,
canMatch: [authOnlyCanMatch],
- canActivate: [authOnlyCanActivate],
+ canActivate: [authOnlyCanMatch]
},
{
- path: 'products/add',
- component: AddProductComponent,
- canMatch: [authOnlyCanMatch],
- canActivate: [authOnlyCanActivate],
- },
- {
- path: 'prestashop',
- component: PsCrudTabsComponent,
- canMatch: [authOnlyCanMatch],
- canActivate: [authOnlyCanActivate],
+ path: 'admin',
+ component: PsAdminComponent,
+ canMatch: [adminOnlyCanMatch],
+ canActivate: [adminOnlyCanActivate]
},
{
path: '**',
diff --git a/client/src/app/components/dialog/confirm-dialog/confirm-dialog.component.css b/client/src/app/components/dialog/confirm-dialog/confirm-dialog.component.css
deleted file mode 100644
index e69de29..0000000
diff --git a/client/src/app/components/dialog/confirm-dialog/confirm-dialog.component.html b/client/src/app/components/dialog/confirm-dialog/confirm-dialog.component.html
deleted file mode 100644
index 76403eb..0000000
--- a/client/src/app/components/dialog/confirm-dialog/confirm-dialog.component.html
+++ /dev/null
@@ -1,8 +0,0 @@
-{{ data.title ?? 'Confirmation' }}
-
- {{ data.message ?? 'Êtes-vous sûr·e ?' }}
-
-
-
-
-
diff --git a/client/src/app/components/dialog/confirm-dialog/confirm-dialog.component.ts b/client/src/app/components/dialog/confirm-dialog/confirm-dialog.component.ts
deleted file mode 100644
index a65c0d1..0000000
--- a/client/src/app/components/dialog/confirm-dialog/confirm-dialog.component.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { Component, Inject } from '@angular/core';
-import { CommonModule } from '@angular/common';
-import { MatDialogModule, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
-import { MatButtonModule } from '@angular/material/button';
-
-@Component({
- selector: 'app-confirm-dialog',
- standalone: true,
- imports: [CommonModule, MatDialogModule, MatButtonModule],
- templateUrl: './confirm-dialog.component.html',
-})
-export class ConfirmDialogComponent {
- constructor(
- private readonly dialogRef: MatDialogRef,
- @Inject(MAT_DIALOG_DATA) public data: { title?: string; message?: string }
- ) {}
-
- close(result: boolean) {
- this.dialogRef.close(result);
- }
-}
diff --git a/client/src/app/components/dialog/generic-dialog/generic-dialog.component.css b/client/src/app/components/dialog/generic-dialog/generic-dialog.component.css
deleted file mode 100644
index e69de29..0000000
diff --git a/client/src/app/components/dialog/generic-dialog/generic-dialog.component.html b/client/src/app/components/dialog/generic-dialog/generic-dialog.component.html
deleted file mode 100644
index 7f39b11..0000000
--- a/client/src/app/components/dialog/generic-dialog/generic-dialog.component.html
+++ /dev/null
@@ -1,48 +0,0 @@
-
diff --git a/client/src/app/components/dialog/generic-dialog/generic-dialog.component.ts b/client/src/app/components/dialog/generic-dialog/generic-dialog.component.ts
deleted file mode 100644
index 9f9ccef..0000000
--- a/client/src/app/components/dialog/generic-dialog/generic-dialog.component.ts
+++ /dev/null
@@ -1,178 +0,0 @@
-import {Component, Inject, OnInit, OnDestroy} from '@angular/core';
-import {CommonModule} from '@angular/common';
-import {FormBuilder, FormGroup, ReactiveFormsModule} from '@angular/forms';
-import {MatDialogModule, MatDialogRef, MAT_DIALOG_DATA} from '@angular/material/dialog';
-import {MatFormFieldModule} from '@angular/material/form-field';
-import {MatInputModule} from '@angular/material/input';
-import {MatSelectModule} from '@angular/material/select';
-import {MatCheckboxModule} from '@angular/material/checkbox';
-import {MatButtonModule} from '@angular/material/button';
-import {Subscription} from 'rxjs';
-
-@Component({
- selector: 'app-generic-dialog',
- standalone: true,
- imports: [
- CommonModule,
- ReactiveFormsModule,
- MatDialogModule,
- MatFormFieldModule,
- MatInputModule,
- MatSelectModule,
- MatCheckboxModule,
- MatButtonModule
- ],
- templateUrl: './generic-dialog.component.html',
-})
-export class GenericDialogComponent implements OnInit, OnDestroy {
- form!: FormGroup;
- fields: any[] = [];
- compareFns = new Map boolean>();
- optionsCache = new Map();
- private readonly subscriptions: Subscription[] = [];
-
- constructor(
- private readonly fb: FormBuilder,
- private readonly dialogRef: MatDialogRef,
- @Inject(MAT_DIALOG_DATA) public data: { title?: string; fields?: any[]; model?: any }
- ) {
- this.fields = data?.fields ?? [];
- }
-
- ngOnInit(): void {
- const model = this.data?.model ?? {};
- const controls: { [key: string]: any[] } = {};
-
- for (const f of this.fields) {
- if (f.options && Array.isArray(f.options)) {
- this.optionsCache.set(f.key, f.options);
- }
-
- if (f.options$ && typeof f.options$.subscribe === 'function') {
- try {
- const sub = (f.options$ as any).subscribe((opts: any[]) => {
- this.optionsCache.set(f.key, opts || []);
- console.log(`[GenericDialog] options for "${f.key}":`, opts);
- }, (err: any) => {
- console.warn(`[GenericDialog] error loading options for "${f.key}":`, err);
- });
- this.subscriptions.push(sub);
- } catch (err) {
- console.warn(`[GenericDialog] cannot subscribe to options$ for "${f.key}":`, err);
- }
- }
-
- let value = model?.[f.key];
-
- if (f.type === 'checkbox') {
- value = !!value;
- } else if (f.type === 'select') {
- if (value && typeof value === 'object' && f.valueKey) {
- value = value[f.valueKey] ?? value;
- }
-
- if (value === null || value === undefined) {
- const idKey = `${f.key}Id`;
- if (model[idKey] !== undefined) {
- value = model[idKey];
- }
- }
-
- if ((value === null || value === undefined) && f.key === 'brand') {
- const platBrand = model?.platform?.brand;
- if (platBrand) {
- value = f.valueKey ? platBrand[f.valueKey] ?? platBrand : platBrand;
- }
- }
-
- if ((value === null || value === undefined) && f.key === 'condition') {
- const cond = model?.condition;
- if (cond) {
- value = f.valueKey ? cond[f.valueKey] ?? cond : cond;
- }
- }
- } else {
- value = value ?? f.default ?? null;
- }
-
- console.log(`[GenericDialog] field "${f.key}" computed initial value:`, value, 'valueKey:', f.valueKey);
-
- controls[f.key] = [value ?? (f.default ?? null)];
-
- const valueKey = f.valueKey;
- this.compareFns.set(f.key, (a: any, b: any) => {
- if (a === null || a === undefined || b === null || b === undefined) {
- return a === b;
- }
- if (valueKey) {
- const aval = (typeof a === 'object') ? (a[valueKey] ?? a) : a;
- const bval = (typeof b === 'object') ? (b[valueKey] ?? b) : b;
- return String(aval) === String(bval);
- }
- return a === b;
- });
- }
-
- this.form = this.fb.group(controls);
- console.log('[GenericDialog] form initial value:', this.form.value);
-
- for (const f of this.fields) {
- if (f.type === 'select') {
- const ctrl = this.form.get(f.key);
- if (ctrl) {
- const sub = ctrl.valueChanges.subscribe((v) => {
- console.log(`[GenericDialog] form control "${f.key}" valueChanges ->`, v);
- });
- this.subscriptions.push(sub);
- }
- }
- }
- }
-
- save(): void {
- if (this.form.invalid) {
- this.form.markAllAsTouched();
- return;
- }
-
- const raw = this.form.value;
- const payload: any = {...raw};
-
- for (const f of this.fields) {
- if (f.type === 'select') {
- const val = raw[f.key];
- if (f.valueKey) {
- const opts = this.optionsCache.get(f.key) ?? [];
- const found = opts.find((o: any) => String(o[f.valueKey]) === String(val));
- if (found) {
- payload[f.key] = found;
- } else if (val === null || val === undefined) {
- payload[f.key] = null;
- } else {
- payload[f.key] = {[f.valueKey]: val};
- }
- } else {
- const opts = this.optionsCache.get(f.key) ?? [];
- const found = opts.find((o: any) => o === val || JSON.stringify(o) === JSON.stringify(val));
- payload[f.key] = found ?? val;
- }
- }
- }
-
- this.dialogRef.close(payload);
- }
-
- close(): void {
- this.dialogRef.close(null);
- }
-
- ngOnDestroy(): void {
- for (let subscription of this.subscriptions) {
- subscription.unsubscribe();
- }
- }
-
- getCompareFn(field: any) {
- return this.compareFns.get(field?.key) ?? ((a: any, b: any) => a === b);
- }
-}
diff --git a/client/src/app/components/list/brand-list/brand-list.component.css b/client/src/app/components/list/brand-list/brand-list.component.css
deleted file mode 100644
index e69de29..0000000
diff --git a/client/src/app/components/list/brand-list/brand-list.component.html b/client/src/app/components/list/brand-list/brand-list.component.html
deleted file mode 100644
index 1dd9dd7..0000000
--- a/client/src/app/components/list/brand-list/brand-list.component.html
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
diff --git a/client/src/app/components/list/brand-list/brand-list.component.ts b/client/src/app/components/list/brand-list/brand-list.component.ts
deleted file mode 100644
index 98e6acc..0000000
--- a/client/src/app/components/list/brand-list/brand-list.component.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import {
- Component, inject
-} from '@angular/core';
-import {BrandService} from '../../../services/app/brand.service';
-import {GenericListComponent} from '../generic-list/generic-list.component';
-
-@Component({
- selector: 'app-brand-list',
- templateUrl: './brand-list.component.html',
- standalone: true,
- imports: [
- GenericListComponent
- ],
- styleUrls: ['./brand-list.component.css']
-})
-export class BrandListComponent {
-
- brandService: BrandService = inject(BrandService)
-
- fields = [
- {key: 'name', label: 'Nom', sortable: true}
- ];
-}
diff --git a/client/src/app/components/list/category-list/category-list.component.html b/client/src/app/components/list/category-list/category-list.component.html
deleted file mode 100644
index 69a9f5e..0000000
--- a/client/src/app/components/list/category-list/category-list.component.html
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
diff --git a/client/src/app/components/list/category-list/category-list.component.ts b/client/src/app/components/list/category-list/category-list.component.ts
deleted file mode 100644
index 09387e5..0000000
--- a/client/src/app/components/list/category-list/category-list.component.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import {
- Component, inject
-} from '@angular/core';
-import {GenericListComponent} from '../generic-list/generic-list.component';
-import {CategoryService} from '../../../services/app/category.service';
-
-@Component({
- selector: 'app-category-list',
- templateUrl: './category-list.component.html',
- standalone: true,
- imports: [
- GenericListComponent
- ],
- styleUrls: ['./category.component.css']
-})
-export class CategoryListComponent {
-
- categoryService: CategoryService = inject(CategoryService)
-
- fields = [
- {key: 'name', label: 'Nom', sortable: true}
- ];
-}
diff --git a/client/src/app/components/list/category-list/category.component.css b/client/src/app/components/list/category-list/category.component.css
deleted file mode 100644
index 87a4b85..0000000
--- a/client/src/app/components/list/category-list/category.component.css
+++ /dev/null
@@ -1,65 +0,0 @@
-: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/list/generic-list/generic-list.component.css b/client/src/app/components/list/generic-list/generic-list.component.css
deleted file mode 100644
index 2324320..0000000
--- a/client/src/app/components/list/generic-list/generic-list.component.css
+++ /dev/null
@@ -1,171 +0,0 @@
-/* ===== Container centré ===== */
-.generic-list {
- display: flex;
- flex-direction: column;
- gap: 1rem;
- padding: 1rem clamp(1rem, 3vw, 3rem);
- max-width: 1200px;
- margin: 0 auto;
-}
-
-/* ===== Header ===== */
-.gl-header {
- display: flex;
- align-items: flex-end;
- justify-content: space-between;
- gap: 1rem;
- flex-wrap: wrap;
- border-bottom: 1px solid rgba(0, 0, 0, .08);
- padding-bottom: .75rem;
-}
-
-.gl-title {
- margin: 0;
- font-size: clamp(1.1rem, 1.3rem + 0.3vw, 1.6rem);
- font-weight: 600;
-}
-
-/* ===== Cartes (filtre, tableau, pagination) partagent le même style ===== */
-.gl-block {
- border: 1px solid rgba(0, 0, 0, .08);
- border-radius: 12px;
- background: var(--gl-surface, #fff);
- box-shadow: 0 1px 2px rgba(0, 0, 0, .04),
- 0 2px 8px rgba(0, 0, 0, .06);
-}
-
-/* ===== Barre de filtre ===== */
-.gl-filter-bar {
- padding: .75rem;
-}
-
-.gl-filter {
- display: block;
- width: 100%;
- max-width: none;
-}
-
-/* ===== Tableau ===== */
-.gl-table-wrapper {
- overflow: auto;
- -webkit-overflow-scrolling: touch;
- padding: 0.25rem 0.5rem;
-}
-
-.gl-table {
- width: 100%;
- border-collapse: separate;
- border-spacing: 0;
- min-width: 600px;
-}
-
-/* Header sticky */
-.gl-table th[mat-header-cell] {
- position: sticky;
- top: 0;
- z-index: 2;
- background: inherit;
- box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .08);
-}
-
-/* Cellules */
-.gl-table th[mat-header-cell],
-.gl-table td[mat-cell] {
- padding: 14px 18px;
- vertical-align: middle;
- white-space: nowrap;
- text-overflow: ellipsis;
- overflow: hidden;
-}
-
-/* Zebra + hover */
-.gl-table tr.mat-mdc-row:nth-child(odd) td[mat-cell] {
- background: rgba(0, 0, 0, .015);
-}
-
-.gl-table tr.mat-mdc-row:hover td[mat-cell] {
- background: rgba(0, 0, 0, .035);
-}
-
-/* Actions */
-.actions-cell {
- display: flex;
- align-items: center;
- justify-content: center;
- gap: .4rem;
-}
-
-.actions-cell .mat-mdc-icon-button {
- width: 40px;
- height: 40px;
-}
-
-/* ===== Pagination ===== */
-.gl-paginator-wrap {
- padding: .25rem .5rem;
-}
-
-.gl-paginator {
- margin-top: .25rem;
- padding-top: .5rem;
- border-top: 1px solid rgba(0, 0, 0, .08);
- display: flex;
- justify-content: flex-end;
-}
-
-/* ===== Responsive ===== */
-@media (max-width: 799px) {
- .generic-list {
- padding: 0.75rem 1rem;
- }
-
- .gl-header {
- flex-direction: column;
- align-items: stretch;
- gap: 0.75rem;
- }
-
- .gl-table {
- min-width: 0;
- }
-
- .gl-table th[mat-header-cell],
- .gl-table td[mat-cell] {
- white-space: normal;
- padding: 10px 12px;
- }
-
- .actions-cell {
- justify-content: flex-start;
- }
-}
-
-/* ===== Dark mode ===== */
-@media (prefers-color-scheme: dark) {
- .gl-block {
- background: #1b1b1b;
- border-color: rgba(255, 255, 255, .08);
- box-shadow: 0 1px 2px rgba(0, 0, 0, .6),
- 0 2px 8px rgba(0, 0, 0, .45);
- }
-
- .gl-header {
- border-bottom-color: rgba(255, 255, 255, .08);
- }
-
- .gl-table th[mat-header-cell] {
- box-shadow: inset 0 -1px 0 rgba(255, 255, 255, .08);
- }
-
- .gl-table tr.mat-mdc-row:nth-child(odd) td[mat-cell] {
- background: rgba(255, 255, 255, .025);
- }
-
- .gl-table tr.mat-mdc-row:hover td[mat-cell] {
- background: rgba(255, 255, 255, .06);
- }
-
- .gl-paginator {
- border-top-color: rgba(255, 255, 255, .08);
- }
-}
diff --git a/client/src/app/components/list/generic-list/generic-list.component.html b/client/src/app/components/list/generic-list/generic-list.component.html
deleted file mode 100644
index b490c2e..0000000
--- a/client/src/app/components/list/generic-list/generic-list.component.html
+++ /dev/null
@@ -1,86 +0,0 @@
-
-
-
-
-
- Rechercher
-
-
-
-
-
-
- @for (col of (fields ?? []); track $index) {
-
- |
- {{ col.label }}
- |
-
-
- {{ displayValue(element, col) }}
- |
-
- }
-
-
- Actions |
-
-
-
- |
-
-
-
-
-
-
-
-
-
-
-
diff --git a/client/src/app/components/list/generic-list/generic-list.component.ts b/client/src/app/components/list/generic-list/generic-list.component.ts
deleted file mode 100644
index 495332a..0000000
--- a/client/src/app/components/list/generic-list/generic-list.component.ts
+++ /dev/null
@@ -1,219 +0,0 @@
-import {Component, Input, Output, EventEmitter, ViewChild, AfterViewInit, OnInit} from '@angular/core';
-import {MatTableDataSource, MatTableModule} from '@angular/material/table';
-import {MatPaginator, MatPaginatorModule} from '@angular/material/paginator';
-import {MatSort, MatSortModule} from '@angular/material/sort';
-import {MatDialog, MatDialogModule} from '@angular/material/dialog';
-import {MatButtonModule} from '@angular/material/button';
-import {CommonModule} from '@angular/common';
-import {CrudService} from '../../../services/crud.service';
-import {GenericDialogComponent} from '../../dialog/generic-dialog/generic-dialog.component';
-import {MatFormField, MatInput, MatLabel} from '@angular/material/input';
-import {MatIcon} from '@angular/material/icon';
-import {ConfirmDialogComponent} from '../../dialog/confirm-dialog/confirm-dialog.component';
-import {MatChip} from '@angular/material/chips';
-
-type Field = {
- key: string;
- label: string;
- sortable?: boolean;
- displayKey?: string;
- displayFn?: (value: any, element?: any) => string;
- sortKey?: string | ((item: any) => any);
- sortFn?: (a: any, b: any) => number;
-};
-
-@Component({
- selector: 'app-generic-list',
- standalone: true,
- imports: [CommonModule, MatTableModule, MatPaginatorModule, MatSortModule, MatDialogModule, MatButtonModule, MatInput, MatLabel, MatFormField, MatIcon, MatChip],
- templateUrl: './generic-list.component.html',
- styleUrl: './generic-list.component.css'
-})
-export class GenericListComponent implements OnInit, AfterViewInit {
- @Input() service!: CrudService;
- @Input() fields?: Field[];
- @Input() title = '';
- @Input() addTitle?: string;
- @Input() editTitle?: string;
- @Input() deleteTitle?: string;
- @Input() idKey = 'id';
- @Input() dialogComponent: any = GenericDialogComponent;
- @Output() add = new EventEmitter();
- @Output() edit = new EventEmitter();
- @Output() delete = new EventEmitter();
-
- dataSource = new MatTableDataSource([]);
- displayedColumns: string[] = [];
-
- @ViewChild(MatPaginator) paginator!: MatPaginator;
- @ViewChild(MatSort) sort!: MatSort;
-
- constructor(private readonly dialog: MatDialog) {
- }
-
- ngOnInit() {
- this.fields = this.fields ?? [];
- this.displayedColumns = this.fields.map(f => f.key).concat(['actions']);
- this.load();
- }
-
- ngAfterViewInit() {
-
- this.dataSource.sortingDataAccessor = (data: any, sortHeaderId: string) => {
- const field = this.fields!.find(f => f.key === sortHeaderId);
- if (!field) {
- const raw = getByPath(data, sortHeaderId) ?? data?.[sortHeaderId];
- return raw == null ? '' : String(raw);
- }
-
- if (field.sortKey) {
- if (typeof field.sortKey === 'function') {
- const v = field.sortKey(data);
- return v == null ? '' : String(v);
- }
- const v = getByPath(data, field.sortKey as string);
- return v == null ? '' : String(v);
- }
-
- const val = getByPath(data, field.key);
- if (val == null) return '';
- if (typeof val === 'object') {
- if (field.displayKey && val[field.displayKey] != null) return String(val[field.displayKey]);
- const candidates = ['name', 'title', 'label', 'id'];
- for (const k of candidates) {
- if (val[k] != null) return String(val[k]);
- }
- try {
- return JSON.stringify(val);
- } catch {
- return String(val);
- }
- }
- return String(val);
- };
-
- this.dataSource.sort = this.sort;
- this.dataSource.paginator = this.paginator;
-
- const originalSortData = this.dataSource.sortData;
- this.dataSource.sortData = (data: T[], sort: MatSort) => {
- if (!sort || !sort.active || sort.direction === '') return originalSortData.call(this.dataSource, data, sort);
- const field = this.fields!.find(f => f.key === sort.active);
- if (field?.sortFn) {
- const dir = sort.direction === 'asc' ? 1 : -1;
- return [...data].sort((a, b) => dir * field.sortFn!(a, b));
- }
- return originalSortData.call(this.dataSource, data, sort);
- };
- }
-
- load() {
- this.service.getAll().subscribe(items => {
- console.debug('Loaded items from service:', items);
- this.dataSource.data = (items as any[]).map(item => {
- const normalizedId = getByPath(item, this.idKey) ?? item?.[this.idKey] ?? item?.id ?? item?._id ?? item?.platformId ?? null;
- return {...item, [this.idKey]: normalizedId};
- }) as T[];
- });
- }
-
- applyFilter(value: string) {
- this.dataSource.filter = (value || '').trim().toLowerCase();
- }
-
- openDialog(item: any | null) {
- const originalId = item ? (getByPath(item, this.idKey) ?? item?.[this.idKey] ?? item?.id ?? item?._id) : null;
-
- const dialogTitle = item
- ? (this.editTitle ?? 'Modifier')
- : (this.addTitle ?? 'Ajouter');
-
- const dialogRef = this.dialog.open(this.dialogComponent, {
- width: '420px',
- data: {
- item: item ? {...item} : {},
- fields: this.fields,
- title: dialogTitle,
- originalId
- }
- });
-
- dialogRef.afterClosed().subscribe((result: any) => {
- if (!result) return;
-
- if (item) {
- const idToUpdate = originalId ?? getByPath(result, this.idKey) ?? result?.[this.idKey] ?? result?.id ?? result?._id;
- if (idToUpdate == null) {
- console.error('Cannot update: id is null/undefined for item', {item, result});
- return;
- }
- this.service.update(idToUpdate, result).subscribe(() => {
- this.edit.emit(result);
- this.load();
- });
- } else {
- this.service.add(result).subscribe(() => {
- this.add.emit(result);
- this.load();
- });
- }
- });
- }
-
- remove(item: any) {
- const id = getByPath(item, this.idKey) ?? item[this.idKey] ?? item?.id ?? item?._id;
- if (id == null) {
- console.error('Cannot delete: id is null/undefined for item', item);
- return;
- }
-
- const dialogRef = this.dialog.open(ConfirmDialogComponent, {
- width: '380px',
- data: {
- title: this.deleteTitle ?? 'Confirmer la suppression',
- message: 'Voulez-vous vraiment supprimer cet élément ? Cette action est irréversible.'
- }
- });
-
- dialogRef.afterClosed().subscribe((confirmed: boolean) => {
- if (!confirmed) return;
- this.service.delete(id).subscribe(() => {
- this.delete.emit(item);
- this.load();
- });
- });
- }
-
- displayValue(element: any, field: Field): string {
- const val = getByPath(element, field.key);
- if (field.displayFn) {
- try {
- return String(field.displayFn(val, element) ?? '');
- } catch {
- return '';
- }
- }
- if (val == null) return '';
- if (typeof val === 'object') {
- if (field.displayKey && val[field.displayKey] != null) return String(val[field.displayKey]);
- const candidates = ['name', 'title', 'label', 'id'];
- for (const k of candidates) {
- if (val[k] != null) return String(val[k]);
- }
- try {
- return JSON.stringify(val);
- } catch {
- return String(val);
- }
- }
- return String(val);
- }
-
- protected readonly HTMLInputElement = HTMLInputElement;
-}
-
-function getByPath(obj: any, path: string | undefined): any {
- if (!obj || !path) return undefined;
- if (typeof path !== 'string') return undefined;
- return path.split('.').reduce((acc, key) => (acc == null ? undefined : acc[key]), obj);
-}
diff --git a/client/src/app/components/list/platform-list/platform-list.component.css b/client/src/app/components/list/platform-list/platform-list.component.css
deleted file mode 100644
index e69de29..0000000
diff --git a/client/src/app/components/list/platform-list/platform-list.component.html b/client/src/app/components/list/platform-list/platform-list.component.html
deleted file mode 100644
index 7d45967..0000000
--- a/client/src/app/components/list/platform-list/platform-list.component.html
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
diff --git a/client/src/app/components/list/platform-list/platform-list.component.ts b/client/src/app/components/list/platform-list/platform-list.component.ts
deleted file mode 100644
index 1af718d..0000000
--- a/client/src/app/components/list/platform-list/platform-list.component.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import {
- Component,
- inject
-} from '@angular/core';
-import {PlatformService} from '../../../services/app/platform.service';
-import {GenericListComponent} from '../generic-list/generic-list.component';
-import {BrandService} from '../../../services/app/brand.service';
-
-@Component({
- selector: 'app-platform-list',
- templateUrl: './platform-list.component.html',
- standalone: true,
- imports: [
- GenericListComponent
- ],
- styleUrls: ['./platform-list.component.css']
-})
-export class PlatformListComponent {
-
- platformService: PlatformService = inject(PlatformService)
- brandService: BrandService = inject(BrandService);
-
- fields = [
- {key: 'name', label: 'Nom', sortable: true},
- {
- key: 'brand',
- label: 'Marque',
- type: 'select',
- options$: this.brandService.getAll(),
- displayKey: 'name',
- sortable: true,
- sortKey: 'brand.name'
- }
- ];
-}
diff --git a/client/src/app/components/navbar/main-navbar/main-navbar.component.css b/client/src/app/components/main-navbar/main-navbar.component.css
similarity index 100%
rename from client/src/app/components/navbar/main-navbar/main-navbar.component.css
rename to client/src/app/components/main-navbar/main-navbar.component.css
diff --git a/client/src/app/components/navbar/main-navbar/main-navbar.component.html b/client/src/app/components/main-navbar/main-navbar.component.html
similarity index 87%
rename from client/src/app/components/navbar/main-navbar/main-navbar.component.html
rename to client/src/app/components/main-navbar/main-navbar.component.html
index 3744d3f..bf5757c 100644
--- a/client/src/app/components/navbar/main-navbar/main-navbar.component.html
+++ b/client/src/app/components/main-navbar/main-navbar.component.html
@@ -10,10 +10,6 @@
@if (authService.hasRole('Administrator')) {
-