diff --git a/angular.json b/angular.json index b232b7f9f..cb7ad6a57 100644 --- a/angular.json +++ b/angular.json @@ -30,8 +30,7 @@ "scripts": [ "node_modules/jquery/dist/jquery.js", "node_modules/bootstrap/dist/js/bootstrap.js", - "node_modules/bootstrap-notify/bootstrap-notify.js", - "node_modules/chartist/dist/chartist.js" + "node_modules/bootstrap-notify/bootstrap-notify.js" ] }, "configurations": { diff --git a/package.json b/package.json index c51c02de7..79f059e33 100644 --- a/package.json +++ b/package.json @@ -29,13 +29,14 @@ "@types/googlemaps": "3.43.3", "animate.css": "4.1.1", "arrive": "2.4.1", - "bootstrap": "3.3.7", + "bootstrap": "^3.3.7", "bootstrap-notify": "3.1.3", "chartist": "0.11.4", "googleapis": "66.0.0", "jquery": "3.5.1", "perfect-scrollbar": "1.5.0", "rxjs": "~7.5.0", + "sweetalert2": "^11.17.2", "tslib": "^2.3.0", "zone.js": "~0.11.4" }, @@ -43,22 +44,22 @@ "@angular-devkit/build-angular": "^14.2.3", "@angular/cli": "~14.2.3", "@angular/compiler-cli": "^14.2.0", + "@types/chartist": "0.11.0", "@types/jasmine": "~5.1.4", + "@types/jasminewd2": "~2.0.13", + "@types/jquery": "3.5.30", + "@types/node": "20.14.11", + "codelyzer": "6.0.2", + "cross-env": "^7.0.3", "jasmine-core": "~4.3.0", + "jasmine-spec-reporter": "~7.0.0", "karma": "~6.4.0", "karma-chrome-launcher": "~3.1.0", "karma-coverage": "~2.2.0", "karma-jasmine": "~5.1.0", "karma-jasmine-html-reporter": "~2.0.0", - "typescript": "~4.7.2", - "@types/jasminewd2": "~2.0.13", - "@types/chartist": "0.11.0", - "@types/jquery": "3.5.30", - "@types/node": "20.14.11", - "codelyzer": "6.0.2", - "jasmine-spec-reporter": "~7.0.0", "protractor": "7.0.0", "ts-node": "~10.7.0", - "cross-env": "^7.0.3" + "typescript": "~4.7.2" } } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 24da5d994..81bb856e5 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -3,6 +3,10 @@ import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { HttpClientModule } from '@angular/common/http'; import { RouterModule } from '@angular/router'; +import { ReactiveFormsModule } from '@angular/forms'; +import { HTTP_INTERCEPTORS } from '@angular/common/http'; +import { AuthInterceptor } from './interceptors/auth.interceptor'; + import { AppRoutingModule } from './app.routing'; import { NavbarModule } from './shared/navbar/navbar.module'; @@ -12,6 +16,12 @@ import { SidebarModule } from './sidebar/sidebar.module'; import { AppComponent } from './app.component'; import { AdminLayoutComponent } from './layouts/admin-layout/admin-layout.component'; +import { LoginComponent } from './login/login.component'; +import { UsersListComponent } from './users-list/users-list.component'; +import { FormateurListComponent } from './formateur-list/formateur-list.component'; +import { FormationListeComponent } from './formation-liste/formation-liste.component'; +import { ParticipantListComponent } from './participant-list/participant-list.component'; +import { SearchPaginationComponent } from './search-pagination/search-pagination.component'; @NgModule({ imports: [ @@ -22,13 +32,21 @@ import { AdminLayoutComponent } from './layouts/admin-layout/admin-layout.compon NavbarModule, FooterModule, SidebarModule, + ReactiveFormsModule, + AppRoutingModule ], declarations: [ AppComponent, - AdminLayoutComponent - ], - providers: [], + AdminLayoutComponent, + LoginComponent, + UsersListComponent, + FormateurListComponent, + FormationListeComponent, + ParticipantListComponent, + SearchPaginationComponent, +], + providers: [{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }], bootstrap: [AppComponent] }) export class AppModule { } diff --git a/src/app/app.routing.ts b/src/app/app.routing.ts index 993dc346d..be35698c7 100644 --- a/src/app/app.routing.ts +++ b/src/app/app.routing.ts @@ -3,24 +3,29 @@ import { CommonModule, } from '@angular/common'; import { BrowserModule } from '@angular/platform-browser'; import { Routes, RouterModule } from '@angular/router'; +import { LoginComponent } from './login/login.component'; import { AdminLayoutComponent } from './layouts/admin-layout/admin-layout.component'; const routes: Routes =[ + { + path: 'login', + component: LoginComponent, + }, { path: '', - redirectTo: 'dashboard', + redirectTo: 'login', pathMatch: 'full', }, { path: '', component: AdminLayoutComponent, - children: [ + children: [ { path: '', loadChildren: () => import('./layouts/admin-layout/admin-layout.module').then(x => x.AdminLayoutModule) }]}, { path: '**', - redirectTo: 'dashboard' + redirectTo: 'login' } ]; diff --git a/src/app/formateur-list/formateur-list.component.css b/src/app/formateur-list/formateur-list.component.css new file mode 100644 index 000000000..20575b25a --- /dev/null +++ b/src/app/formateur-list/formateur-list.component.css @@ -0,0 +1,56 @@ +/* Amélioration de l'apparence des boutons */ +.btn { + border-radius: 4px; + font-weight: 500; + padding: 0.375rem 0.75rem; + transition: all 0.2s ease-in-out; + margin-right: 8px; +} + +.btn-sm { + font-size: 1.3rem; + padding: 0.25rem 0.5rem; +} + +.btn-info { + box-shadow: 0 2px 4px rgba(23, 162, 184, 0.3); +} + +.btn-danger { + box-shadow: 0 2px 4px rgba(220, 53, 69, 0.3); +} + +.btn-primary { + box-shadow: 0 2px 4px rgba(13, 110, 253, 0.3); +} + +.btn:hover { + transform: translateY(-1px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); +} + +.btn i { + margin-right: 5px; +} + +/* Amélioration de l'espacement dans le tableau */ +.table td, .table th { + padding: 12px 15px; + vertical-align: middle; +} + +/* Amélioration pour le bouton Ajouter */ +.header .btn-primary { + padding: 0.5rem 1rem; + display: flex; + align-items: center; +} + +.header .btn-primary i { + margin-right: 8px; +} + +.modal-body { + max-height: 400px; /* Taille maximale du contenu */ + overflow-y: auto; /* Permet de faire défiler le contenu */ +} \ No newline at end of file diff --git a/src/app/formateur-list/formateur-list.component.html b/src/app/formateur-list/formateur-list.component.html new file mode 100644 index 000000000..f4a0bf1a3 --- /dev/null +++ b/src/app/formateur-list/formateur-list.component.html @@ -0,0 +1,139 @@ +
+
+
+
+
+
+
+
+

Liste des formateurs

+

Gérer les formateurs

+
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + +
{{ cell }}Actions
{{formateur.id}}{{formateur.nom}}{{formateur.prenom}}{{formateur.email}}{{formateur.tel}}{{formateur.type}}{{formateur.employeur.nomEmployeur}} +
+ + +
+
+
+
+
+
+
+
+ + + + + + + + diff --git a/src/app/formateur-list/formateur-list.component.spec.ts b/src/app/formateur-list/formateur-list.component.spec.ts new file mode 100644 index 000000000..6cd8d0edc --- /dev/null +++ b/src/app/formateur-list/formateur-list.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FormateurListComponent } from './formateur-list.component'; + +describe('FormateurListComponent', () => { + let component: FormateurListComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ FormateurListComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(FormateurListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/formateur-list/formateur-list.component.ts b/src/app/formateur-list/formateur-list.component.ts new file mode 100644 index 000000000..44b15365e --- /dev/null +++ b/src/app/formateur-list/formateur-list.component.ts @@ -0,0 +1,251 @@ +import { Component, OnInit , } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { FormateurListService } from 'app/services/formateur-list.service'; +import { EmployeurService } from 'app/services/employeur.service'; + +declare interface TableData { + headerRow: string[]; + dataRows: string[][]; +} + +@Component({ + selector: 'app-formateurs-list', + templateUrl: './formateur-list.component.html', + styleUrls: ['./formateur-list.component.css'] +}) +export class FormateurListComponent implements OnInit { + public tableData1: TableData; + public formateurs : any; + public formateurForm: FormGroup; + public isEditMode: boolean = false; + public selectedFormateurIndex: number = -1; + public selectedFormateur: string[] = null; + + // Variables pour contrôler l'affichage des modals + public showFormateurModal: boolean = false; + public showDeleteModal: boolean = false; + employeurs: any[] = []; + types: string[]=['interne','externe'] + constructor(private formBuilder: FormBuilder , private formateurService:FormateurListService, private employeurService:EmployeurService) { } + + ngOnInit() { + let data: string[][] = []; + this.loadEmployeurs(); + this.formateurService.getAllFormateurs().subscribe({ + next: (res) => { + console.log('Formateurs fetched:', res); + this.formateurs = res; + data = res.map((formateur: any) => [ + String(formateur.id), + String(formateur.nom), + String(formateur.prenom), + String(formateur.email), + String(formateur.tel), + String(formateur.type), + String(formateur.employeur ) + ]); + console.log("data = ", data); + }, + error: (err) => { + console.error('Error fetching formateurs:', err); + }, + complete: () => { + console.log('Formateur fetching completed.'); + } + }); + + this.tableData1 = { + headerRow: ['ID', 'Nom', 'Prénom', 'Email', 'Téléphone','Type', 'Employeur'], + dataRows: data + }; + + this.initForm(); + } + + loadEmployeurs(): void { + this.employeurService.getEmployeurs().subscribe( + (data) => { + this.employeurs = data; + }, + (error) => { + console.error('Erreur lors du chargement des employeurs', error); + } + ); + } + ngAfterViewInit(){ + this.loadFormateurs(); + } + + initForm() { + this.formateurForm = this.formBuilder.group({ + id: ['', Validators.required], + nom: ['', Validators.required], + prenom: ['', Validators.required], + email: ['', [Validators.required, Validators.email]], + tel: ['', Validators.required], + type: ['', Validators.required], + employeur: ['', Validators.required] + }); + } + + openAddModal() { + this.isEditMode = false; + this.selectedFormateurIndex = -1; + + // Generate a new ID + const nextId = (Math.max(...this.tableData1.dataRows.map(row => parseInt(row[0]))) + 1).toString(); + + this.formateurForm.reset(); + this.formateurForm.patchValue({ + id: nextId, + nom: '', + prenom: '', + email: '', + tel: '', + type: '', + employeur: '' + }); + + this.showFormateurModal = true; + } + + openEditModal(index: number) { + this.isEditMode = true; + this.selectedFormateurIndex = index; + const formateurData = this.formateurs[index]; + + this.formateurForm.patchValue({ + id: formateurData.id, + nom: formateurData.nom, + prenom: formateurData.prenom, + email: formateurData.email, + tel: formateurData.tel, + type: formateurData.type, + employeur: formateurData.employeur + }); + + this.showFormateurModal = true; + } + + saveFormateur() { + if (this.formateurForm.invalid) { + console.log("unvalid"); + } + + const formValues = this.formateurForm.value; + const employeurId=formValues.employeur; + console.log("el ID DYEL EMPLOYEUR",employeurId); + const formateurData :any = { + nom: formValues.nom, + prenom: formValues.prenom, + email: formValues.email, + tel: formValues.tel, + type: formValues.type + }; + const formateurData2 :any = { + nom: formValues.nom, + prenom: formValues.prenom, + email: formValues.email, + tel: formValues.tel, + type: formValues.type, + employeur: formValues.employeur + }; + if (this.isEditMode) { + // Update existing formateur + this.formateurService.updateFormateur(formValues.id, formateurData2).subscribe({ + next: (updatedFormateur) => { + console.log('Formateur updated:', updatedFormateur); + this.loadFormateurs(); // Refresh the formateur list + this.showFormateurModal = false; + }, + error: (err) => { + console.error('Error updating formateur:', err); + } + }); + } else { + // Add new formateur + this.formateurService.createFormateur(formateurData, employeurId).subscribe({ + next: (createdFormateur) => { + console.log('Formateur created:', createdFormateur); + this.loadFormateurs(); // Refresh the formateur list + this.showFormateurModal = false; + }, + error: (err) => { + console.error('Error creating formateur:', err); + } + }); + + } + + // Close modal + this.showFormateurModal = false; + this.formateurForm.reset(); + } + + // Add this method to refresh formateur data + loadFormateurs() { + this.formateurService.getAllFormateurs().subscribe({ + next: (res) => { + console.log('Formateurs fetched:', res); + this.formateurs = res; + this.tableData1.dataRows = res.map((formateur: any) => [ + String(formateur.id), + String(formateur.nom), + String(formateur.prenom), + String(formateur.email), + String(formateur.tel), + String(formateur.type), + String(formateur.employeur ) + ]); + }, + error: (err) => { + console.error('Error fetching formateurs:', err); + } + }); + } + + deleteFormateur(index: number) { + // 1. Vérifiez que l'index est valide + if (!this.tableData1?.dataRows || index < 0 || index >= this.tableData1.dataRows.length) { + console.error('Index invalide ou données non chargées'); + return; + } + + // 2. Récupérez l'élément en vérifiant son existence + this.selectedFormateurIndex = index; + this.selectedFormateur = this.tableData1.dataRows[index]; + + // 3. Vérification supplémentaire + if (!this.selectedFormateur) { + console.error('Aucun formateur trouvé à cet index'); + return; + } + + console.log("Formateur sélectionné :", this.selectedFormateur); + this.showDeleteModal = true; + } + confirmDelete() { + const formateurId = this.selectedFormateur[0]; // Assuming ID is the first element + console.log("elid fassakh",formateurId); + this.formateurService.deleteFormateur(formateurId).subscribe({ + next: () => { + this.loadFormateurs(); + // Close modal + this.showDeleteModal = false; + this.selectedFormateurIndex = -1; + this.selectedFormateur = null; + }, + error: (err) => { + console.error('Error deleting formateur:', err); + } +}); +} + + closeFormateurModal() { + this.showFormateurModal = false; + } + + closeDeleteModal() { + this.showDeleteModal = false; + } +} diff --git a/src/app/formation-liste/formation-liste.component.css b/src/app/formation-liste/formation-liste.component.css new file mode 100644 index 000000000..41dd2d1f7 --- /dev/null +++ b/src/app/formation-liste/formation-liste.component.css @@ -0,0 +1,126 @@ +/* Amélioration de l'apparence des boutons */ +.btn { + border-radius: 4px; + font-weight: 500; + padding: 0.375rem 0.75rem; + transition: all 0.2s ease-in-out; + margin-right: 8px; +} + +.btn-sm { + font-size: 0.875rem; + padding: 0.25rem 0.5rem; +} + +.btn-info { + box-shadow: 0 2px 4px rgba(23, 162, 184, 0.3); +} + +.btn-danger { + box-shadow: 0 2px 4px rgba(220, 53, 69, 0.3); +} + +.btn-primary { + box-shadow: 0 2px 4px rgba(13, 110, 253, 0.3); +} + +.btn-success { + box-shadow: 0 2px 4px rgba(40, 167, 69, 0.3); +} + +.btn-warning { + box-shadow: 0 2px 4px rgba(255, 193, 7, 0.3); + color: #212529; +} + +.btn:hover { + transform: translateY(-1px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); +} + +.btn i { + margin-right: 5px; +} + +/* Amélioration de l'espacement dans le tableau */ +.table td, .table th { + padding: 12px 15px; + vertical-align: middle; +} + +/* Amélioration pour le bouton Ajouter */ +.header .btn-primary { + padding: 0.5rem 1rem; + display: flex; + align-items: center; +} + +.header .btn-primary i { + margin-right: 8px; +} + +/* Style pour les modales */ +.modal-body { + max-height: 70vh; + overflow-y: auto; +} + +/* Style pour la liste des participants */ +.list-group-item { + transition: background-color 0.2s ease; +} + +.list-group-item.active { + background-color: #007bff; + border-color: #007bff; +} + +.list-group-item:hover { + background-color: #f8f9fa; +} + +.list-group-item.active:hover { + background-color: #0069d9; +} + +.badge { + transition: all 0.2s ease; +} + +/* Ajustements pour les modales de grande taille */ +.modal-lg { + max-width: 800px; +} + +/* Styles pour les cartes */ +.card { + border-radius: 8px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + margin-bottom: 20px; +} + +.card .header { + padding: 15px; + border-bottom: 1px solid rgba(0, 0, 0, 0.1); +} + +.card .content { + padding: 15px; +} + +/* Style pour l'entête du tableau */ +.table thead th { + background-color: #f8f9fa; + font-weight: 600; + border-top: none; +} + +/* Style pour les lignes alternées du tableau */ +.table-striped tbody tr:nth-of-type(odd) { + background-color: rgba(0, 0, 0, 0.02); +} + +/* Style pour le survol des lignes du tableau */ +.table-hover tbody tr:hover { + background-color: rgba(0, 123, 255, 0.05); +} \ No newline at end of file diff --git a/src/app/formation-liste/formation-liste.component.html b/src/app/formation-liste/formation-liste.component.html new file mode 100644 index 000000000..775afa3f9 --- /dev/null +++ b/src/app/formation-liste/formation-liste.component.html @@ -0,0 +1,266 @@ +
+
+
+
+
+
+
+
+

Liste des formations

+

Gérer les formations

+
+ +
+
+
+ + + + + + + + + + + + + +
{{ cell }}Actions
{{cell}} +
+ + + + +
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/app/formation-liste/formation-liste.component.spec.ts b/src/app/formation-liste/formation-liste.component.spec.ts new file mode 100644 index 000000000..47e2490f7 --- /dev/null +++ b/src/app/formation-liste/formation-liste.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FormationListeComponent } from './formation-liste.component'; + +describe('FormationListeComponent', () => { + let component: FormationListeComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ FormationListeComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(FormationListeComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/formation-liste/formation-liste.component.ts b/src/app/formation-liste/formation-liste.component.ts new file mode 100644 index 000000000..70af89bb7 --- /dev/null +++ b/src/app/formation-liste/formation-liste.component.ts @@ -0,0 +1,269 @@ +import { Component, OnInit } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; + +@Component({ + selector: 'app-formation-liste', + templateUrl: './formation-liste.component.html', + styleUrls: ['./formation-liste.component.css'] +}) +export class FormationListeComponent implements OnInit { + // Variables pour la table + tableData = { + headerRow: ['ID', 'Titre', 'Date Début', 'Date Fin', 'Durée (jours)', 'Domaine', 'Formateur', 'Budget (€)', 'Participants'], + dataRows: [ + ['F001', 'Angular Avancé', '10/05/2025', '15/05/2025', '5', 'Développement Web', 'Dupont Jean', '3500', '15'], + ['F002', 'React Fondamentaux', '01/06/2025', '05/06/2025', '5', 'Développement Web', 'Martin Sophie', '3000', '12'], + ['F003', 'Management d\'équipe', '15/06/2025', '17/06/2025', '3', 'Management', 'Dubois Pierre', '2500', '8'], + ['F004', 'Excel Avancé', '20/06/2025', '21/06/2025', '2', 'Bureautique', 'Leroy Marie', '1200', '20'] + ] + }; + + // Variables pour les modals + showFormationModal = false; + showDeleteModal = false; + showEmailModal = false; + showCertificatModal = false; + isEditMode = false; + selectedFormation: any = null; + selectedIndex: number = -1; + + // Listes pour les select + domaines = ['Développement Web', 'Management', 'Bureautique', 'Communication', 'Marketing Digital', 'Data Science']; + formateurs = ['Dupont Jean', 'Martin Sophie', 'Dubois Pierre', 'Leroy Marie', 'Garcia Thomas']; + participants = [ + { id: 'P001', nom: 'miladi', prenom: 'imen', email: 'miladiphg@gmail.com' }, + { id: 'P002', nom: 'Moreau', prenom: 'Thomas', email: 'thomas.moreau@example.com' }, + { id: 'P003', nom: 'Petit', prenom: 'Sophie', email: 'sophie.petit@example.com' }, + { id: 'P004', nom: 'Bernard', prenom: 'Luc', email: 'luc.bernard@example.com' }, + { id: 'P005', nom: 'Durant', prenom: 'Emma', email: 'emma.durant@example.com' } + ]; + + // Form Group + formationForm: FormGroup; + emailForm: FormGroup; + certificatForm: FormGroup; + + // Liste des participants sélectionnés pour la formation en cours d'édition + selectedParticipants: any[] = []; + + constructor(private fb: FormBuilder) { + this.formationForm = this.fb.group({ + id: ['', Validators.required], + titre: ['', Validators.required], + dateDebut: ['', Validators.required], + dateFin: ['', Validators.required], + duree: ['', [Validators.required, Validators.min(1)]], + domaine: ['', Validators.required], + formateur: ['', Validators.required], + budget: ['', [Validators.required, Validators.min(0)]], + participants: [[]] + }); + + this.emailForm = this.fb.group({ + objet: ['Convocation à la formation', Validators.required], + message: ['', Validators.required], + destinataires: [[], Validators.required] + }); + + this.certificatForm = this.fb.group({ + titre: ['Certificat de réussite', Validators.required], + dateGeneration: [new Date().toISOString().split('T')[0], Validators.required], + participants: [[], Validators.required] + }); + } + + ngOnInit() { + // Initialisation des données si nécessaire + } + + // Méthodes pour la gestion des formations + openAddModal() { + this.isEditMode = false; + this.formationForm.reset(); + this.generateNewId(); + this.selectedParticipants = []; + this.showFormationModal = true; + + } + + openEditModal(index: number) { + this.isEditMode = true; + this.selectedIndex = index; + const formation = this.tableData.dataRows[index]; + + // Simulation des participants pour l'édition + this.selectedParticipants = this.participants.slice(0, parseInt(formation[8])); + + this.formationForm.patchValue({ + id: formation[0], + titre: formation[1], + dateDebut: this.formatDateForInput(formation[2]), + dateFin: this.formatDateForInput(formation[3]), + duree: formation[4], + domaine: formation[5], + formateur: formation[6], + budget: formation[7], + participants: this.selectedParticipants.map(p => p.id) + }); + + this.showFormationModal = true; + } + + closeFormationModal() { + this.showFormationModal = false; + this.formationForm.reset(); + } + + saveFormation() { + const formValues = this.formationForm.value; + const formationData = [ + formValues.id, + formValues.titre, + this.formatDateForDisplay(formValues.dateDebut), + this.formatDateForDisplay(formValues.dateFin), + formValues.duree.toString(), + formValues.domaine, + formValues.formateur, + formValues.budget.toString(), + this.selectedParticipants.length.toString() + ]; + + if (this.isEditMode) { + this.tableData.dataRows[this.selectedIndex] = formationData; + } else { + this.tableData.dataRows.push(formationData); + } + + this.closeFormationModal(); + } + + deleteFormation(index: number) { + this.selectedIndex = index; + this.selectedFormation = this.tableData.dataRows[index]; + this.showDeleteModal = true; + } + + closeDeleteModal() { + this.showDeleteModal = false; + this.selectedFormation = null; + } + + confirmDelete() { + this.tableData.dataRows.splice(this.selectedIndex, 1); + this.closeDeleteModal(); + } + + // Méthodes pour l'envoi d'email + openEmailModal(index: number) { + this.selectedIndex = index; + this.selectedFormation = this.tableData.dataRows[index]; + + // Simulation des destinataires pour l'email + const nbParticipants = parseInt(this.selectedFormation[8]); + const destinataires = this.participants.slice(0, nbParticipants); + + const formationTitle = this.selectedFormation[1]; + const startDate = this.selectedFormation[2]; + + this.emailForm.patchValue({ + objet: `Convocation à la formation "${formationTitle}"`, + message: `Bonjour,\n\nVous êtes convoqué(e) à la formation "${formationTitle}" qui débutera le ${startDate}.\nVoici le lien google meet de la formation https://meet.google.com/landing\nCordialement,\nLe service formation`, + destinataires: destinataires.map(p => p.id) + }); + + this.showEmailModal = true; + } + + closeEmailModal() { + this.showEmailModal = false; + } + + sendEmail() { + // Simulation d'envoi d'email + console.log('Email envoyé !', this.emailForm.value); + alert('Les emails ont été envoyés avec succès !'); + this.closeEmailModal(); + } + + // Méthodes pour la génération de certificats + openCertificatModal(index: number) { + this.selectedIndex = index; + this.selectedFormation = this.tableData.dataRows[index]; + + // Simulation des participants pour les certificats + const nbParticipants = parseInt(this.selectedFormation[8]); + const participantsForCert = this.participants.slice(0, nbParticipants); + + this.certificatForm.patchValue({ + titre: `Certificat de réussite - ${this.selectedFormation[1]}`, + participants: participantsForCert.map(p => p.id) + }); + + this.showCertificatModal = true; + } + + closeCertificatModal() { + this.showCertificatModal = false; + } + + generateCertificats() { + // Simulation de génération de certificats + console.log('Certificats générés !', this.certificatForm.value); + alert('Les certificats ont été générés avec succès !'); + this.closeCertificatModal(); + } + + // Gestion des participants + toggleParticipant(participant: any) { + const index = this.selectedParticipants.findIndex(p => p.id === participant.id); + + if (index === -1) { + this.selectedParticipants.push(participant); + } else { + this.selectedParticipants.splice(index, 1); + } + + this.formationForm.patchValue({ + participants: this.selectedParticipants.map(p => p.id) + }); + } + + isParticipantSelected(participant: any): boolean { + return this.selectedParticipants.some(p => p.id === participant.id); + } + + // Utilitaires + generateNewId() { + // Génère un ID basé sur le nombre de formations existantes + const newNum = this.tableData.dataRows.length + 1; + const id = 'F' + newNum.toString().padStart(3, '0'); + this.formationForm.patchValue({ id }); + } + + formatDateForInput(dateStr: string): string { + // Convertit DD/MM/YYYY en YYYY-MM-DD pour les inputs de type date + const parts = dateStr.split('/'); + return `${parts[2]}-${parts[1]}-${parts[0]}`; + } + + formatDateForDisplay(dateStr: string): string { + // Convertit YYYY-MM-DD en DD/MM/YYYY pour l'affichage + const parts = dateStr.split('-'); + return `${parts[2]}/${parts[1]}/${parts[0]}`; + } + + calculateDuration() { + // Calcule automatiquement la durée entre les dates + const dateDebut = this.formationForm.get('dateDebut')?.value; + const dateFin = this.formationForm.get('dateFin')?.value; + + if (dateDebut && dateFin) { + const start = new Date(dateDebut); + const end = new Date(dateFin); + const diffTime = Math.abs(end.getTime() - start.getTime()); + const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)) + 1; // +1 pour inclure le jour de début + + this.formationForm.patchValue({ duree: diffDays }); + } + } +} \ No newline at end of file diff --git a/src/app/guards/auth.guard.spec.ts b/src/app/guards/auth.guard.spec.ts new file mode 100644 index 000000000..68889d22d --- /dev/null +++ b/src/app/guards/auth.guard.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { AuthGuard } from './auth.guard'; + +describe('AuthGuard', () => { + let guard: AuthGuard; + + beforeEach(() => { + TestBed.configureTestingModule({}); + guard = TestBed.inject(AuthGuard); + }); + + it('should be created', () => { + expect(guard).toBeTruthy(); + }); +}); diff --git a/src/app/guards/auth.guard.ts b/src/app/guards/auth.guard.ts new file mode 100644 index 000000000..a5fbc9200 --- /dev/null +++ b/src/app/guards/auth.guard.ts @@ -0,0 +1,18 @@ +import { Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot, UrlTree ,Router } from '@angular/router'; +import { Observable } from 'rxjs'; + +@Injectable({ + providedIn: 'root' +}) +export class AuthGuard implements CanActivate { + constructor(private router: Router) {} + canActivate(): boolean { + if (localStorage.getItem('token')) { + return true; + } + this.router.navigate(['/login']); + return false; + } + +} diff --git a/src/app/home/home.component.html b/src/app/home/home.component.html index 53fc2fd3a..a964960d2 100644 --- a/src/app/home/home.component.html +++ b/src/app/home/home.component.html @@ -1,10 +1,63 @@
+
+ +
+
+
+

Statistiques Générales

+

Nombre total par catégorie

+
+
+
+ +
+
+ +

{{participantsCount}}

+

Participants

+
+
+ +
+
+ +

{{formateursCount}}

+

Formateurs

+
+
+ +
+
+ +

{{formationsCount}}

+

Formations

+
+
+ +
+
+ +

{{utilisateursCount}}

+

Utilisateurs

+
+
+
+ +
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+

Top 3 formateurs

+

Les meilleurs formateurs du mois

+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
#NomPrénomEmailTéléphoneSpécialitéEmployeurNb formations
{{ i + 1 }}{{ formateur.nom }}{{ formateur.prenom }}{{ formateur.email }}{{ formateur.tel }}{{ formateur.specialite }}{{ formateur.employeur }}{{ formateur.nb }}
+
+
+
+
+
+
+ +
diff --git a/src/app/home/home.component.ts b/src/app/home/home.component.ts index 9aba51e04..f9ed327d2 100644 --- a/src/app/home/home.component.ts +++ b/src/app/home/home.component.ts @@ -9,6 +9,13 @@ import * as Chartist from 'chartist'; styleUrls: ['./home.component.css'] }) export class HomeComponent implements OnInit { + + // Nouveaux compteurs + public participantsCount: number = 0; + public formateursCount: number = 0; + public formationsCount: number = 0; + public utilisateursCount: number = 0; + public emailChartType: ChartType; public emailChartData: any; public emailChartLegendItems: LegendItem[]; @@ -25,7 +32,37 @@ export class HomeComponent implements OnInit { public activityChartResponsive: any[]; public activityChartLegendItems: LegendItem[]; constructor() { } - + formateurs = [ + { + nom: 'Miladi', + prenom: 'Imen', + email: 'imen.miladi@example.com', + tel: '+216 99 123 456', + specialite: 'Développement mobile', + employeur: 'ISI Ariana', + nb:'6' + }, + { + nom: 'Doe', + prenom: 'Jane', + email: 'jane.doe@example.com', + tel: '+216 20 654 321', + specialite: 'Intelligence Artificielle', + employeur: 'Université Centrale', + nb:'5' + }, + { + nom: 'Ali', + prenom: 'Ahmed', + email: 'ahmed.ali@example.com', + tel: '+216 25 789 456', + specialite: 'Mobile Development', + employeur: 'GoMyCode', + nb:'3' + }, + // Ajouter d'autres formateurs ici + ]; + ngOnInit() { this.emailChartType = ChartType.Pie; this.emailChartData = { @@ -77,36 +114,45 @@ export class HomeComponent implements OnInit { ]; this.activityChartType = ChartType.Bar; - this.activityChartData = { - labels: ['Jan', 'Feb', 'Mar', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], - series: [ - [542, 443, 320, 780, 553, 453, 326, 434, 568, 610, 756, 895], - [412, 243, 280, 580, 453, 353, 300, 364, 368, 410, 636, 695] - ] - }; - this.activityChartOptions = { - seriesBarDistance: 10, - axisX: { - showGrid: false - }, - height: '245px' - }; - this.activityChartResponsive = [ - ['screen and (max-width: 640px)', { - seriesBarDistance: 5, - axisX: { - labelInterpolationFnc: function (value) { - return value[0]; - } - } - }] - ]; - this.activityChartLegendItems = [ - { title: 'Tesla Model S', imageClass: 'fa fa-circle text-info' }, - { title: 'BMW 5 Series', imageClass: 'fa fa-circle text-danger' } - ]; - +this.activityChartData = { + labels: ['Jan', 'Feb', 'Mar', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + series: [ + [542, 443, 320, 780, 5530, 4530, 3260, 4340, 5680, 6100, 7560, 8950], + [412, 243, 280, 580, 4530, 3530, 3000, 3640, 3680, 4100, 6360, 6950], + [1000, 249, 3000, 580, 5000, 6000, 7000, 8000, 3000, 6000, 4500, 5700] + ] +}; +this.activityChartOptions = { + seriesBarDistance: 10, + axisX: { + showGrid: false + }, + axisY: { + type: Chartist.FixedScaleAxis, + low: 0, // 🔥 commence bien à zéro + high: 10000, + ticks: [0, 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000], // 🔥 on ajoute 0 ici + labelInterpolationFnc: function(value) { + return value; // si tu veux ajouter 'DT', fais return value + ' DT'; + } + }, + height: '245px' +}; +this.activityChartResponsive = [ + ['screen and (max-width: 640px)', { + seriesBarDistance: 5, + axisX: { + labelInterpolationFnc: function (value) { + return value[0]; + } } + }] +]; -} +this.activityChartLegendItems = [ + { title: 'Web', imageClass: 'fa fa-circle text-info' }, + { title: 'mobile', imageClass: 'fa fa-circle text-danger' }, + { title: 'AI', imageClass: 'fa fa-circle text-warning' } +]; + }} \ No newline at end of file diff --git a/src/app/interceptors/auth.interceptor.spec.ts b/src/app/interceptors/auth.interceptor.spec.ts new file mode 100644 index 000000000..7ab58dbd1 --- /dev/null +++ b/src/app/interceptors/auth.interceptor.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { AuthInterceptor } from './auth.interceptor'; + +describe('AuthInterceptor', () => { + beforeEach(() => TestBed.configureTestingModule({ + providers: [ + AuthInterceptor + ] + })); + + it('should be created', () => { + const interceptor: AuthInterceptor = TestBed.inject(AuthInterceptor); + expect(interceptor).toBeTruthy(); + }); +}); diff --git a/src/app/interceptors/auth.interceptor.ts b/src/app/interceptors/auth.interceptor.ts new file mode 100644 index 000000000..fe935125c --- /dev/null +++ b/src/app/interceptors/auth.interceptor.ts @@ -0,0 +1,26 @@ +import { Injectable } from '@angular/core'; +import { + HttpRequest, + HttpHandler, + HttpEvent, + HttpInterceptor +} from '@angular/common/http'; +import { Observable } from 'rxjs'; + +@Injectable() +export class AuthInterceptor implements HttpInterceptor { + + constructor() {} + + intercept(request: HttpRequest, next: HttpHandler): Observable> { + const token = localStorage.getItem('token'); + if (token) { + request = request.clone({ + setHeaders: { + Authorization: `Bearer ${token}` + } + }); + } + return next.handle(request); + } +} diff --git a/src/app/interfaces/login-request.interface.ts b/src/app/interfaces/login-request.interface.ts new file mode 100644 index 000000000..2b26d9f8c --- /dev/null +++ b/src/app/interfaces/login-request.interface.ts @@ -0,0 +1,4 @@ +export interface LoginRequest { + login: string; + motDePasse: string; + } \ No newline at end of file diff --git a/src/app/layouts/admin-layout/admin-layout.routing.ts b/src/app/layouts/admin-layout/admin-layout.routing.ts index e09417d82..09818768b 100644 --- a/src/app/layouts/admin-layout/admin-layout.routing.ts +++ b/src/app/layouts/admin-layout/admin-layout.routing.ts @@ -2,20 +2,33 @@ import { Routes } from '@angular/router'; import { HomeComponent } from '../../home/home.component'; import { UserComponent } from '../../user/user.component'; + import { TablesComponent } from '../../tables/tables.component'; import { TypographyComponent } from '../../typography/typography.component'; import { IconsComponent } from '../../icons/icons.component'; import { MapsComponent } from '../../maps/maps.component'; import { NotificationsComponent } from '../../notifications/notifications.component'; import { UpgradeComponent } from '../../upgrade/upgrade.component'; +import { UsersListComponent } from 'app/users-list/users-list.component'; +import { FormateurListComponent } from 'app/formateur-list/formateur-list.component'; +import { FormationListeComponent } from 'app/formation-liste/formation-liste.component'; +import { ParticipantListComponent } from 'app/participant-list/participant-list.component'; +import { AuthGuard } from 'app/guards/auth.guard'; export const AdminLayoutRoutes: Routes = [ - { path: 'dashboard', component: HomeComponent }, - { path: 'user', component: UserComponent }, - { path: 'table', component: TablesComponent }, - { path: 'typography', component: TypographyComponent }, - { path: 'icons', component: IconsComponent }, - { path: 'maps', component: MapsComponent }, - { path: 'notifications', component: NotificationsComponent }, - { path: 'upgrade', component: UpgradeComponent }, + { path: 'dashboard', component: HomeComponent ,canActivate: [AuthGuard]}, + { path: 'user', component: UserComponent ,canActivate: [AuthGuard]}, + + { path: 'user-list', component: UsersListComponent ,canActivate: [AuthGuard]}, + { path: 'participant-list', component: ParticipantListComponent ,canActivate: [AuthGuard]}, + { path: 'table', component: TablesComponent ,canActivate: [AuthGuard]}, + { path: 'typography', component: TypographyComponent ,canActivate: [AuthGuard]}, + { path: 'icons', component: IconsComponent ,canActivate: [AuthGuard]}, + { path: 'maps', component: MapsComponent ,canActivate: [AuthGuard]}, + { path: 'notifications', component: NotificationsComponent ,canActivate: [AuthGuard]}, + { path: 'upgrade', component: UpgradeComponent ,canActivate: [AuthGuard]}, + {path: 'formateur-list', component:FormateurListComponent ,canActivate: [AuthGuard]}, + {path: 'formation-liste', component:FormationListeComponent ,canActivate: [AuthGuard]}, + { path: 'participant-list', component: ParticipantListComponent ,canActivate: [AuthGuard]}, + ]; diff --git a/src/app/login/login.component.css b/src/app/login/login.component.css new file mode 100644 index 000000000..96cb2a90b --- /dev/null +++ b/src/app/login/login.component.css @@ -0,0 +1,106 @@ +/* login.component.css */ +.login-container { + height: 100vh; + width: 100%; + display: flex; + align-items: center; + justify-content: center; + background-image: url('assets/img/background_login.jpg'); + background-size: cover; + background-position: center; + } + + .login-card { + width: 400px; + padding: 40px; + border-radius: 10px; + background-color: rgba(255, 255, 255, 0.95); + box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2); + } + + .logo-container { + text-align: center; + margin-bottom: 30px; + } + + .logo { + width: 100px; + height: auto; + margin-bottom: 15px; + } + + h1 { + color: #d82c2c; + margin: 0; + font-size: 24px; + font-weight: 600; + } + + .login-form .form-group { + margin-bottom: 20px; + } + + .login-form label { + display: block; + margin-bottom: 8px; + font-weight: 500; + color: #333; + } + + .login-form input[type="text"], + .login-form input[type="password"] { + width: 100%; + padding: 12px; + border: 1px solid #ddd; + border-radius: 5px; + font-size: 16px; + transition: border-color 0.3s; + } + + .login-form input:focus { + border-color: #d82c2c; + outline: none; + box-shadow: 0 0 0 2px rgba(216, 44, 44, 0.2); + } + + .remember-me { + display: flex; + align-items: center; + margin-bottom: 20px; + } + + .remember-me input { + margin-right: 8px; + } + + .login-button { + width: 100%; + padding: 14px; + border: none; + border-radius: 5px; + background-color: #d82c2c; + color: white; + font-size: 16px; + font-weight: 600; + cursor: pointer; + transition: background-color 0.3s; + } + + .login-button:hover { + background-color: #b52222; + } + + .forgot-password { + margin-top: 15px; + text-align: center; + } + + .forgot-password a { + color: #d82c2c; + text-decoration: none; + font-size: 14px; + } + + .forgot-password a:hover { + text-decoration: underline; + } \ No newline at end of file diff --git a/src/app/login/login.component.html b/src/app/login/login.component.html new file mode 100644 index 000000000..ea6f0796e --- /dev/null +++ b/src/app/login/login.component.html @@ -0,0 +1,44 @@ + + \ No newline at end of file diff --git a/src/app/login/login.component.spec.ts b/src/app/login/login.component.spec.ts new file mode 100644 index 000000000..10eca249d --- /dev/null +++ b/src/app/login/login.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { LoginComponent } from './login.component'; + +describe('LoginComponent', () => { + let component: LoginComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ LoginComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(LoginComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/login/login.component.ts b/src/app/login/login.component.ts new file mode 100644 index 000000000..9d79d272d --- /dev/null +++ b/src/app/login/login.component.ts @@ -0,0 +1,53 @@ +// login.component.ts +import { Component } from '@angular/core'; +import { Router } from '@angular/router'; +import { AuthService } from 'app/services/auth.service'; +import Swal from 'sweetalert2'; +@Component({ + selector: 'app-login', + templateUrl: './login.component.html', + styleUrls: ['./login.component.css'] +}) +export class LoginComponent { + username: string = ''; + password: string = ''; + rememberMe: boolean = false; + + credentials = { + login: '', + motDePasse: '' + }; + + constructor(private authService: AuthService, private router: Router) {} + + onLogin() { + this.authService.login(this.credentials).subscribe({ + next: (response) => { + localStorage.setItem('token', response.token); + this.router.navigate(['/dashboard']); + }, + error: (err) => { + if (err.status === 500 || err.status === 0) { + // Erreur serveur ou backend non joignable + Swal.fire({ + icon: 'error', + title: 'Erreur serveur', + text: 'Veuillez réessayer plus tard.', + confirmButtonColor: '#d82c2c', + confirmButtonText:"réessayer" + }); + } else { + // Erreur d’identifiants (ex : exception Runtime côté backend) + Swal.fire({ + icon: 'warning', + title: 'Identifiants invalides', + text: "Nom d'utilisateur ou mot de passe incorrect.", + confirmButtonColor: '#d82c2c', + confirmButtonText:"réessayer", + + + }); + } + }, + }); + }} \ No newline at end of file diff --git a/src/app/participant-list/participant-list.component.css b/src/app/participant-list/participant-list.component.css new file mode 100644 index 000000000..35527918f --- /dev/null +++ b/src/app/participant-list/participant-list.component.css @@ -0,0 +1,56 @@ +/* Amélioration de l'apparence des boutons */ +.btn { + border-radius: 4px; + font-weight: 500; + padding: 0.375rem 0.75rem; + transition: all 0.2s ease-in-out; + margin-right: 8px; +} + +.btn-sm { + font-size: 1.3rem; + padding: 0.25rem 0.5rem; +} + +.btn-info { + box-shadow: 0 2px 4px rgba(23, 162, 184, 0.3); +} + +.btn-danger { + box-shadow: 0 2px 4px rgba(220, 53, 69, 0.3); +} + +.btn-primary { + box-shadow: 0 2px 4px rgba(13, 110, 253, 0.3); +} + +.btn:hover { + transform: translateY(-1px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); +} + +.btn i { + margin-right: 5px; +} + +/* Amélioration de l'espacement dans le tableau */ +.table td, .table th { + padding: 12px 15px; + vertical-align: middle; +} + +/* Amélioration pour le bouton Ajouter */ +.header .btn-primary { + padding: 0.5rem 1rem; + display: flex; + align-items: center; +} + +.header .btn-primary i { + margin-right: 8px; +} + +.modal-body { + max-height: 70vh; + overflow-y: auto; +} \ No newline at end of file diff --git a/src/app/participant-list/participant-list.component.html b/src/app/participant-list/participant-list.component.html new file mode 100644 index 000000000..2234dfff8 --- /dev/null +++ b/src/app/participant-list/participant-list.component.html @@ -0,0 +1,133 @@ +
+
+
+
+
+
+
+
+

Liste des participants

+

Gérer les participants

+
+ +
+
+
+ + + + + + + + + + + + + + + + + + + +
{{ cell }}Actions
{{ participant.id }}{{ participant.nom }}{{ participant.prenom }}{{ participant.structure?.libelle }}{{ participant.profile?.libelle }}{{ participant.email }}{{ participant.tel }} +
+ + +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/src/app/participant-list/participant-list.component.spec.ts b/src/app/participant-list/participant-list.component.spec.ts new file mode 100644 index 000000000..e8050d745 --- /dev/null +++ b/src/app/participant-list/participant-list.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ParticipantListComponent } from './participant-list.component'; + +describe('ParticipantListComponent', () => { + let component: ParticipantListComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ParticipantListComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ParticipantListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/participant-list/participant-list.component.ts b/src/app/participant-list/participant-list.component.ts new file mode 100644 index 000000000..0bd5ad838 --- /dev/null +++ b/src/app/participant-list/participant-list.component.ts @@ -0,0 +1,238 @@ +import { Component, OnInit } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { ParticipantListService } from 'app/services/participant-list.service'; + +declare interface TableData { + headerRow: string[]; + dataRows: string[][]; +} + +@Component({ + selector: 'app-participant-list', + templateUrl: './participant-list.component.html', + styleUrls: ['./participant-list.component.css'] +}) +export class ParticipantListComponent implements OnInit { + public tableData1: TableData; + public userForm: FormGroup; + public isEditMode: boolean = false; + public selectedUserIndex: number = -1; + public selectedUser: string[] = null; + public participants : any; + public showUserModal: boolean = false; + public showDeleteModal: boolean = false; + structures: any[] = [ + { id: 1, name: 'Direction centrale' }, + { id: 2, name: 'Direction régionale' }, + ]; + profiles: any[] = [ + { id: 3, name: 'gestionnaire' }, + { id: 2, name: 'informaticien (bac + 3)' }, + { id: 1, name: 'informaticien (bac + 5)' }, + { id: 4, name: 'juriste' }, + { id: 5, name: 'technicien supérieur' }, + ]; + + + constructor(private formBuilder: FormBuilder,private participantService: ParticipantListService) {} + + + ngOnInit() { + let data: string[][] = []; + this.participantService.getAllParticipant().subscribe({ + next: (res) => { + console.log('Users fetched:', res); + this.participants = res ; + data = res.map((user: any) => [ + String(user.id), + String(user.nom), + String(user.prenom), + String(user.structure?.libelle ), + String(user.profile?.libelle ), + String(user.email), + String(user.tel) + ]); + this.tableData1 = { + headerRow: ['ID', 'Nom', 'Prénom', 'Structure', 'Profil', 'Email', 'Téléphone'], + dataRows: data + }; + + console.log("data = ",data); + }, + error: (err) => { + console.error('Error fetching users:', err); + }, + complete: () => { + console.log('User fetching completed.'); + } + }); + + this.tableData1 = { + headerRow: ['ID', 'Nom', 'Prénom', 'Structure', 'Profil', 'Email', 'Téléphone'], + dataRows: data + }; + + + + this.initForm(); + } + + ngAfterViewInit(){ + this.loadParticipants(); + } + + initForm() { + this.userForm = this.formBuilder.group({ + id: ['', Validators.required], + nom: ['', Validators.required], + prenom: ['', Validators.required], + structure: ['', Validators.required], + profile: ['', Validators.required], + email: ['', [Validators.required, Validators.email]], + telephone: ['', [Validators.required, Validators.pattern('^[0-9]{8}$')]] + }); + } + + openAddModal() { + this.isEditMode = false; + this.selectedUserIndex = -1; + + const nextId = ( + Math.max(...this.tableData1.dataRows.map(row => parseInt(row[0]))) + 1 + ).toString(); + + this.userForm.reset(); + this.userForm.patchValue({ id: nextId }); + + this.showUserModal = true; + } + + openEditModal(index: number) { + this.isEditMode = true; + this.selectedUserIndex = index; + const userData = this.participants[index] + + this.userForm.patchValue({ + id: userData.id, + nom: userData.nom, + prenom: userData.prenom, + structure: userData.structure, + profile: userData.profile, + email: userData.email, + telephone: userData.telephone + }); + + this.showUserModal = true; + } + + saveUser() { + if (this.userForm.invalid) { + return; + } + + const formValues = this.userForm.value; + + // Récupérer les IDs de profile et structure sélectionnés + const profileId = formValues.profile; // ID du profil sélectionné + const structureId = formValues.structure; // ID de la structure sélectionnée + + if (this.isEditMode) { + const newParticipant = { + nom: formValues.nom, + prenom: formValues.prenom, + email: formValues.email, + tel: formValues.telephone, + profileId : formValues.profile, + structureId : formValues.structure + }; + // Update existing user + this.participantService.updateParticipant(formValues.id, newParticipant).subscribe({ + next: (updatedUser) => { + console.log('User updated:', updatedUser); + this.loadParticipants(); // Refresh the user list + this.showUserModal = false; + }, + error: (err) => { + console.error('Error updating user:', err); + } + }); + } else + // Envoie de la requête POST avec les IDs de profile et structure en paramètres + { + + // Créer l'objet participant sans les IDs de profile et structure + const newParticipant = { + nom: formValues.nom, + prenom: formValues.prenom, + email: formValues.email, + tel: formValues.telephone + }; + console.log(newParticipant) + // Récupérer les IDs de profile et structure sélectionnés + const profileId = formValues.profile; // ID du profil sélectionné + const structureId = formValues.structure; // ID de la structure sélectionnée + + + this.participantService.createParticipant(newParticipant, profileId, structureId) + .subscribe( + (response) => { + console.log('Participant créé avec succès', response); + this.closeUserModal(); + this.userForm.reset(); + this.initForm; + // Actualiser la liste des participants + this.loadParticipants(); + }, + (error) => { + console.error('Erreur lors de la création du participant', error); + } + ); + }} + loadParticipants(): void { + this.participantService.getAllParticipant().subscribe( + (data) => { + this.participants = data; // Mettre à jour le tableau des participants + }, + (error) => { + console.error('Erreur lors du chargement des participants:', error); + } + ); + } + + + deleteParticipant(index: number) { + this.selectedUserIndex = index; + this.selectedUser = this.tableData1.dataRows[index]; + this.showDeleteModal = true; + console.log("selectedParticipant!", this.selectedUser); + } + + confirmDelete() { + const participantId = this.selectedUser[0]; // ID du participant sélectionné + console.log('Deleting participant with ID:', participantId); + + // Effectuer la suppression + this.participantService.deleteParticipant(participantId).subscribe({ + next: () => { + // Mettre à jour la liste des participants après suppression + this.loadParticipants(); // Recharger les participants + console.log('teeeeeeeeeessssssssssssssssssst:'); + // Si le participant supprimé était dans la liste, réactualiser la sélection + this.showDeleteModal = false; // Fermer la modale de confirmation + this.selectedUserIndex = -1; + this.selectedUser = null; + }, + error: (err) => { + console.error('Error deleting user:', err); + } + }); + } + + closeUserModal() { + this.showUserModal = false; + } + + closeDeleteModal() { + this.showDeleteModal = false; + } +} diff --git a/src/app/search-pagination/search-pagination.component.css b/src/app/search-pagination/search-pagination.component.css new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/search-pagination/search-pagination.component.html b/src/app/search-pagination/search-pagination.component.html new file mode 100644 index 000000000..9da4dab7a --- /dev/null +++ b/src/app/search-pagination/search-pagination.component.html @@ -0,0 +1,20 @@ +
+ +
+ + + \ No newline at end of file diff --git a/src/app/search-pagination/search-pagination.component.spec.ts b/src/app/search-pagination/search-pagination.component.spec.ts new file mode 100644 index 000000000..7d1ded857 --- /dev/null +++ b/src/app/search-pagination/search-pagination.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SearchPaginationComponent } from './search-pagination.component'; + +describe('SearchPaginationComponent', () => { + let component: SearchPaginationComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ SearchPaginationComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(SearchPaginationComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/search-pagination/search-pagination.component.ts b/src/app/search-pagination/search-pagination.component.ts new file mode 100644 index 000000000..de64b1b5e --- /dev/null +++ b/src/app/search-pagination/search-pagination.component.ts @@ -0,0 +1,32 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; + +@Component({ + selector: 'app-search-pagination', + templateUrl: './search-pagination.component.html', + styleUrls: ['./search-pagination.component.css'] +}) +export class SearchPaginationComponent { + @Input() totalItems: number = 0; + @Input() pageSize: number = 5; + @Input() currentPage: number = 1; + @Output() pageChange = new EventEmitter(); + @Output() searchChange = new EventEmitter(); + + searchTerm: string = ''; + + onSearchChange() { + this.searchChange.emit(this.searchTerm); + } + + onPageChange(page: number) { + this.pageChange.emit(page); + } + + get totalPages(): number { + return Math.ceil(this.totalItems / this.pageSize); + } + + pages(): number[] { + return Array(this.totalPages).fill(0).map((x,i) => i+1); + } +} diff --git a/src/app/services/auth.service.ts b/src/app/services/auth.service.ts new file mode 100644 index 000000000..d10f2d7e3 --- /dev/null +++ b/src/app/services/auth.service.ts @@ -0,0 +1,32 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +@Injectable({ + providedIn: 'root' +}) +export class AuthService { + private apiUrl = 'http://localhost:8094/authenticate'; // URL du backend + + constructor(private http: HttpClient) {} + + login(credentials: { login: string, motDePasse: string }): Observable<{token: string}> { + return this.http.post<{token: string}>(this.apiUrl, credentials); + } + + saveToken(token: string) { + localStorage.setItem('jwt', token); + } + + getToken(): string | null { + return localStorage.getItem('jwt'); + } + + isLoggedIn(): boolean { + return !!this.getToken(); + } + + logout() { + localStorage.removeItem('jwt'); + } +} diff --git a/src/app/services/employeur.service.spec.ts b/src/app/services/employeur.service.spec.ts new file mode 100644 index 000000000..cc8431328 --- /dev/null +++ b/src/app/services/employeur.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { EmployeurService } from './employeur.service'; + +describe('EmployeurService', () => { + let service: EmployeurService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(EmployeurService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/services/employeur.service.ts b/src/app/services/employeur.service.ts new file mode 100644 index 000000000..7839eba62 --- /dev/null +++ b/src/app/services/employeur.service.ts @@ -0,0 +1,17 @@ +// employeur.service.ts +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +@Injectable({ + providedIn: 'root' +}) +export class EmployeurService { + private apiUrl = 'http://localhost:8094/employeurs'; // ou l'URL de votre API + + constructor(private http: HttpClient) { } + + getEmployeurs(): Observable { + return this.http.get(this.apiUrl); + } +} \ No newline at end of file diff --git a/src/app/services/formateur-list.service.spec.ts b/src/app/services/formateur-list.service.spec.ts new file mode 100644 index 000000000..a4d15b3d2 --- /dev/null +++ b/src/app/services/formateur-list.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { FormateurListService } from './formateur-list.service'; + +describe('FormateurListService', () => { + let service: FormateurListService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(FormateurListService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/services/formateur-list.service.ts b/src/app/services/formateur-list.service.ts new file mode 100644 index 000000000..8f111a5ee --- /dev/null +++ b/src/app/services/formateur-list.service.ts @@ -0,0 +1,27 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; + +@Injectable({ + providedIn: 'root' +}) +export class FormateurListService { + private apiUrl = 'http://localhost:8094/formateurs'; // URL du backend pour les formateurs + + constructor(private http: HttpClient) { } + + getAllFormateurs(): Observable { + return this.http.get(this.apiUrl); + } + + createFormateur(formateur: any,employeurId: Number): Observable { + return this.http.post(`${this.apiUrl}?employeurId=${employeurId}`, formateur); } + + updateFormateur(formateurId: number, formateur: any): Observable { + return this.http.put(`${this.apiUrl}/${formateurId}`, formateur); + } + + deleteFormateur(formateurId: any): Observable { + return this.http.delete(`${this.apiUrl}/${formateurId}`); + } +} diff --git a/src/app/services/participant-list.service.spec.ts b/src/app/services/participant-list.service.spec.ts new file mode 100644 index 000000000..f5ba56654 --- /dev/null +++ b/src/app/services/participant-list.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { ParticipantListService } from './participant-list.service'; + +describe('ParticipantListService', () => { + let service: ParticipantListService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(ParticipantListService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/services/participant-list.service.ts b/src/app/services/participant-list.service.ts new file mode 100644 index 000000000..b172f2260 --- /dev/null +++ b/src/app/services/participant-list.service.ts @@ -0,0 +1,53 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs/internal/Observable'; + +@Injectable({ + providedIn: 'root' +}) +export class ParticipantListService { + + private apiUrl = 'http://localhost:8094/participants'; + + constructor(private http: HttpClient) { } + + getAllParticipant() { + return this.http.get(this.apiUrl); + } + + deleteParticipant(participantId: any){ + return this.http.delete(`${this.apiUrl}/${participantId}`); + } + + createParticipant(participant: any, profileId: number, structureId: number){ + console.log(participant) + const body = { + ...participant + }; + return this.http.post(this.apiUrl, body, { + params: { + profileId: profileId.toString(), + structureId: structureId.toString() + } + }); + } + + updateParticipant(participantId: number, participant: any): Observable { + const participantToSend = { + nom: participant.nom, + prenom: participant.prenom, + email: participant.email, + tel: participant.tel, + profile: { id: participant.profileId }, + structure: { id: participant.structureId } + }; + + console.log('Participant envoyé :', participantToSend); + + return this.http.put(`${this.apiUrl}/${participantId}`, participantToSend); + } + + + + +} diff --git a/src/app/services/user-list.service.spec.ts b/src/app/services/user-list.service.spec.ts new file mode 100644 index 000000000..a65e883e1 --- /dev/null +++ b/src/app/services/user-list.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { UserListService } from './user-list.service'; + +describe('UserListService', () => { + let service: UserListService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(UserListService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/services/user-list.service.ts b/src/app/services/user-list.service.ts new file mode 100644 index 000000000..4caf6f151 --- /dev/null +++ b/src/app/services/user-list.service.ts @@ -0,0 +1,29 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; + +@Injectable({ + providedIn: 'root' +}) +export class UserListService { + private apiUrl = 'http://localhost:8094/utilisateurs'; // URL du backend + + constructor(private http: HttpClient) { } + + getAllUsers() { + return this.http.get(this.apiUrl); + } + + + createUser(user: any, roleId: any): Observable { + return this.http.post(`${this.apiUrl}?roleId=${roleId}`, user); + } + + updateUser(userId:any ,user: any, roleId: any): Observable { + return this.http.put(`${this.apiUrl}/${userId}?roleId=${roleId}`, user); + } + + deleteUser(userId: any): Observable { + return this.http.delete(`${this.apiUrl}/${userId}`); + } +} diff --git a/src/app/shared/footer/footer.component.html b/src/app/shared/footer/footer.component.html index 12fe1788b..c17954ad9 100644 --- a/src/app/shared/footer/footer.component.html +++ b/src/app/shared/footer/footer.component.html @@ -25,7 +25,7 @@
diff --git a/src/app/shared/navbar/navbar.component.html b/src/app/shared/navbar/navbar.component.html index 6a5aca258..f9630c9ef 100644 --- a/src/app/shared/navbar/navbar.component.html +++ b/src/app/shared/navbar/navbar.component.html @@ -67,7 +67,7 @@
  • - + Log out
  • diff --git a/src/app/shared/navbar/navbar.component.ts b/src/app/shared/navbar/navbar.component.ts index 068ed7ee8..4fa508b22 100644 --- a/src/app/shared/navbar/navbar.component.ts +++ b/src/app/shared/navbar/navbar.component.ts @@ -1,6 +1,7 @@ import { Component, OnInit, ElementRef } from '@angular/core'; import { ROUTES } from '../../sidebar/sidebar.component'; import {Location, LocationStrategy, PathLocationStrategy} from '@angular/common'; +import { Router } from '@angular/router'; @Component({ // moduleId: module.id, @@ -14,7 +15,7 @@ export class NavbarComponent implements OnInit{ private toggleButton: any; private sidebarVisible: boolean; - constructor(location: Location, private element: ElementRef) { + constructor(location: Location, private element: ElementRef , private router: Router) { this.location = location; this.sidebarVisible = false; } @@ -63,4 +64,9 @@ export class NavbarComponent implements OnInit{ } return 'Dashboard'; } + + logout() { + localStorage.removeItem('token'); // Supprime le token + this.router.navigate(['/login']); // Redirige vers la page de connexion + } } diff --git a/src/app/sidebar/sidebar.component.html b/src/app/sidebar/sidebar.component.html index 817b194c6..dfe3fd145 100644 --- a/src/app/sidebar/sidebar.component.html +++ b/src/app/sidebar/sidebar.component.html @@ -1,11 +1,11 @@