Commit 99a1c6c0 authored by Augustin LECONTE's avatar Augustin LECONTE
Browse files

Merge branch 'feat/US43-user-management' into 'dev'

feat/US43-user-management

See merge request !186
parents 89bebcaf 461fed50
Pipeline #20263 passed with stages
in 3 minutes and 51 seconds
......@@ -3412,6 +3412,31 @@
"integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=",
"dev": true
},
"ag-grid-angular": {
"version": "26.2.0",
"resolved": "https://registry.npmjs.org/ag-grid-angular/-/ag-grid-angular-26.2.0.tgz",
"integrity": "sha512-IJYNniJkQXQhEMdsZ50MFMY80K3PQGsh4Jh1Nu7G1Det5Pq2QNPgZ/FwNucsxYDPn32VICVVlUEQTwtEl63FZQ==",
"requires": {
"tslib": "^1.10.0"
},
"dependencies": {
"tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}
}
},
"ag-grid-community": {
"version": "26.2.1",
"resolved": "https://registry.npmjs.org/ag-grid-community/-/ag-grid-community-26.2.1.tgz",
"integrity": "sha512-aChSGNdPkBda4BhOUUEAmAkRlIG7rFU8UTXx3NPStavrCOHKLDRV90djIKuiXfM6ONBqKmeqw2as0yuLnSN8dw=="
},
"ag-grid-enterprise": {
"version": "26.2.1",
"resolved": "https://registry.npmjs.org/ag-grid-enterprise/-/ag-grid-enterprise-26.2.1.tgz",
"integrity": "sha512-DSf+PPRHv9JXhWTg7KSEm9+ixYAEPuo3LKBUzowUCQWvygGiWBrZXEB67MnjW+Z9zULeFhU4Z6OuHbnQUDhCag=="
},
"agent-base": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz",
......
......@@ -2,14 +2,30 @@ import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { PanelComponent } from './components/panel/panel.component';
import { ClaimStructureComponent } from './components/claim-structure/claim-structure.component';
import { DeleteUserComponent } from './components/delete-user/delete-user.component';
import { ManageUsersComponent } from './components/manage-users/manage-users.component';
import { SharedModule } from '../shared/shared.module';
import { NewsletterUsersComponent } from './components/newsletter-users/newsletter-users.component';
import { AdminStructuresListComponent } from './components/structures-list/admin-structures-list.component';
import { AdminRoutingModule } from './admin-routing.module';
import { AgGridModule } from 'ag-grid-angular';
import { DeleteUserComponent } from './components/manage-users/delete-user/delete-user.component';
import { AdministredStructuresComponent } from './components/manage-users/administred-structures/administred-structures.component';
@NgModule({
declarations: [PanelComponent, ClaimStructureComponent, DeleteUserComponent, NewsletterUsersComponent, AdminStructuresListComponent],
imports: [CommonModule, AdminRoutingModule, SharedModule],
declarations: [
PanelComponent,
ClaimStructureComponent,
NewsletterUsersComponent,
AdminStructuresListComponent,
ManageUsersComponent,
DeleteUserComponent,
AdministredStructuresComponent,
],
imports: [
CommonModule,
AdminRoutingModule,
SharedModule,
AgGridModule.withComponents([DeleteUserComponent, AdministredStructuresComponent]),
],
})
export class AdminModule {}
<div fxLayout="column" fxLayoutGap="5px" fxLayoutAlign="center center" class="userBlock">
<h4>Suppression d'utilisateurs</h4>
<div fxLayout="row">
<input #searchstring (keyup)="(0)" />
<button (click)="searchUsers(searchstring.value)">Rechercher</button>
</div>
<div class="userList">
<tr *ngFor="let user of users">
<td>{{ user.email }}</td>
<td>
<button (click)="toggleDeleteModal(user)">Supprimer</button>
</td>
</tr>
<app-modal-confirmation
*ngIf="userToDelete"
[openned]="deleteModalOpenned"
[content]="'Voulez-vous vraiment supprimer cet utilisateur&nbsp;? (' + userToDelete.email + ')'"
(closed)="deleteUser(userToDelete, $event)"
></app-modal-confirmation>
<div *ngIf="users && users.length == 0">Aucun résultat</div>
</div>
</div>
import { Component } from '@angular/core';
import { User } from '../../../models/user.model';
import { ProfileService } from '../../../profile/services/profile.service';
import { AdminService } from '../../services/admin.service';
@Component({
selector: 'app-admin-delete-user',
templateUrl: './delete-user.component.html',
})
export class DeleteUserComponent {
public users: User[];
public deleteModalOpenned = false;
public userToDelete: User = null;
constructor(private adminService: AdminService, private profileService: ProfileService) {}
public deleteUser(user: User, shouldDelete: boolean): void {
this.toggleDeleteModal(user);
if (shouldDelete) {
this.adminService.deleteUser(user._id).subscribe((data) => {
this.users = this.users.filter((obj) => obj.email !== data.email);
});
}
}
public toggleDeleteModal(userToDelete: User): void {
this.userToDelete = userToDelete;
this.deleteModalOpenned = !this.deleteModalOpenned;
}
public searchUsers(searchString: string): void {
this.adminService.searchUsers(searchString).subscribe((users) => {
this.profileService.getProfile().then((profile) => {
this.users = users.filter((obj) => obj.email != profile.email);
});
});
}
}
<div id="structure-list" *ngIf="structures.data.structures && structures.data.structures.length != 0">
<p *ngFor="let structure of structures.data.structures">
<a href="/acteurs?id={{ structure._id }}" target="_blank">{{ structure.structureName }}</a>
</p>
</div>
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AdministredStructuresComponent } from './administred-structures.component';
describe('AdministredStructuresComponent', () => {
let component: AdministredStructuresComponent;
let fixture: ComponentFixture<AdministredStructuresComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ AdministredStructuresComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(AdministredStructuresComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component } from '@angular/core';
@Component({
selector: 'app-administred-structures',
templateUrl: './administred-structures.component.html',
styleUrls: ['./administred-structures.component.scss'],
})
export class AdministredStructuresComponent {
public structures: any;
public agInit(params: any): void {
this.structures = params;
}
}
<button type="button" (click)="onClick()">{{ label }}</button>
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DeleteUserComponent } from './delete-user.component';
describe('DeleteUserComponent', () => {
let component: DeleteUserComponent;
let fixture: ComponentFixture<DeleteUserComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ DeleteUserComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(DeleteUserComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component } from '@angular/core';
@Component({
selector: 'app-delete-user',
templateUrl: './delete-user.component.html',
})
export class DeleteUserComponent {
public params: any;
public label: string;
public agInit(params: any): void {
this.params = params;
this.label = this.params.label || null;
}
public onClick(): void {
if (this.params.onClick instanceof Function) {
this.params.onClick(this.params);
}
}
}
<div fxLayout="column" fxLayoutGap="5px" fxLayoutAlign="center center" class="userBlock">
<h3>
Gestion des utilisateurs
<span *ngIf="unVerifiedUsers && unAttachedUsers && attachedUsers">
({{ unVerifiedUsers.length + unAttachedUsers.length + attachedUsers.length }})
</span>
</h3>
</div>
<div class="tables-list">
<div>
<h3 class="title" *ngIf="unVerifiedUsers">Utilisateurs non vérifiés ({{ unVerifiedUsers.length }})</h3>
<ag-grid-angular
class="ag-theme-alpine user-table"
[rowData]="unVerifiedUsers"
[columnDefs]="columnDefs"
rowSelection="multiple"
domLayout="autoHeight"
[getRowHeight]="getRowHeight"
[defaultColDef]="unAttachedColDef"
[frameworkComponents]="frameworkComponents"
>
</ag-grid-angular>
</div>
<div>
<h3 class="title" *ngIf="unAttachedUsers">Utilisateurs non rattachés ({{ unAttachedUsers.length }})</h3>
<ag-grid-angular
class="ag-theme-alpine user-table"
[rowData]="unAttachedUsers"
[columnDefs]="columnDefs"
rowSelection="multiple"
domLayout="autoHeight"
[getRowHeight]="getRowHeight"
[defaultColDef]="unAttachedColDef"
[frameworkComponents]="frameworkComponents"
>
</ag-grid-angular>
</div>
<div>
<h3 class="title" *ngIf="attachedUsers">Utilisateurs rattachés ({{ attachedUsers.length }})</h3>
<ag-grid-angular
class="ag-theme-alpine user-table"
[rowData]="attachedUsers"
[columnDefs]="columnDefs"
rowSelection="multiple"
domLayout="autoHeight"
[getRowHeight]="getRowHeight"
[defaultColDef]="defaultColDef"
[frameworkComponents]="frameworkComponents"
>
</ag-grid-angular>
</div>
</div>
<app-modal-confirmation
*ngIf="userToDelete"
[openned]="deleteModalOpenned"
[content]="'Voulez-vous vraiment supprimer cet utilisateur&nbsp;? (' + userToDelete.email + ')'"
(closed)="deleteUser(userToDelete, $event)"
></app-modal-confirmation>
@import '../../../../assets/scss/color';
.user-table {
width: 100%;
height: 100%;
text-overflow: clip;
}
.tables-list {
height: 100%;
width: 80%;
display: flex;
flex-direction: column;
margin-left: 10%;
margin-bottom: 2%;
}
app-delete-user {
text-align: center;
}
......@@ -2,11 +2,11 @@ import { HttpClientModule } from '@angular/common/http';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { of } from 'rxjs';
import { DeleteUserComponent } from './delete-user.component';
import { ManageUsersComponent } from './manage-users.component';
describe('DeleteUserComponent', () => {
let component: DeleteUserComponent;
let fixture: ComponentFixture<DeleteUserComponent>;
describe('ManageUsersComponent', () => {
let component: ManageUsersComponent;
let fixture: ComponentFixture<ManageUsersComponent>;
let USERS;
let service;
......@@ -18,17 +18,17 @@ describe('DeleteUserComponent', () => {
];
await TestBed.configureTestingModule({
imports: [HttpClientModule],
declarations: [DeleteUserComponent],
declarations: [ManageUsersComponent],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(DeleteUserComponent);
fixture = TestBed.createComponent(ManageUsersComponent);
component = fixture.componentInstance;
fixture.detectChanges();
service = jasmine.createSpyObj(['searchUsers', 'deleteUser']);
component = new DeleteUserComponent(service);
service = jasmine.createSpyObj(['searchUsers', 'manageUsers']);
component = new ManageUsersComponent(service, null);
});
it('should create', () => {
......@@ -61,7 +61,7 @@ describe('DeleteUserComponent', () => {
)
)
);
component.deleteUser(component.users[0]);
component.deleteUser(component.users[0], true);
expect(component.users.length).toBe(2);
});
});
import { Component } from '@angular/core';
import { User } from '../../../models/user.model';
import { AdminService } from '../../services/admin.service';
import { DeleteUserComponent } from './delete-user/delete-user.component';
import { AdministredStructuresComponent } from './administred-structures/administred-structures.component';
@Component({
selector: 'app-admin-manage-users',
templateUrl: './manage-users.component.html',
styleUrls: ['./manage-users.component.scss'],
})
export class ManageUsersComponent {
public attachedUsers: User[] = [];
public unAttachedUsers: User[] = [];
public unVerifiedUsers: User[] = [];
public deleteModalOpenned = false;
public userToDelete: User = null;
public columnDefs;
public frameworkComponents;
public defaultColDef = {
editable: true,
sortable: true,
flex: 1,
minWidth: 100,
filter: true,
resizable: true,
};
public unAttachedColDef = {
...this.defaultColDef,
cellStyle: {
color: '#da6c2e',
},
};
constructor(private adminService: AdminService) {
this.columnDefs = [
{
headerName: 'Nom',
field: 'surname',
},
{
headerName: 'Prénom',
field: 'name',
},
{
minWidth: 150,
headerName: 'Mail',
field: 'email',
},
{
minWidth: 150,
headerName: 'Téléphone',
field: 'phone',
},
{
headerName: 'Structures administrées',
cellRenderer: 'administredStructuresComponent',
cellRendererParams: {
structures: 'structures',
},
minWidth: 350,
},
{
headerName: 'Actions',
minWidth: 150,
cellRenderer: 'deleteUserComponent',
cellRendererParams: {
onClick: this.onDeleteButtonClick.bind(this),
label: 'Supprimer',
},
cellStyle: { 'text-align': 'center' },
},
];
this.frameworkComponents = {
deleteUserComponent: DeleteUserComponent,
administredStructuresComponent: AdministredStructuresComponent,
};
this.findAttachedUsers();
this.findUnAttachedUsers();
this.findUnVerifiedUsers();
}
public onDeleteButtonClick(arg): void {
this.deleteUser(arg.data, false);
}
public deleteUser(user: User, shouldDelete: boolean): void {
this.toggleDeleteModal(user);
if (shouldDelete) {
this.adminService.deleteUser(user._id).subscribe((data) => {
this.unAttachedUsers = this.unAttachedUsers.filter((obj) => obj._id !== data._id);
this.unVerifiedUsers = this.unVerifiedUsers.filter((obj) => obj._id !== data._id);
this.attachedUsers = this.attachedUsers.filter((obj) => obj._id !== data._id);
});
}
}
public toggleDeleteModal(userToDelete: User): void {
this.userToDelete = userToDelete;
this.deleteModalOpenned = !this.deleteModalOpenned;
}
public findAttachedUsers(): void {
this.adminService.getAttachedUsers().subscribe((users) => {
this.attachedUsers = users;
this.attachedUsers.map((user) => {
user._id = user['id'];
});
});
}
public findUnAttachedUsers(): void {
this.adminService.getUnAttachedUsers().subscribe((users) => {
this.unAttachedUsers = users;
});
}
public findUnVerifiedUsers(): void {
this.adminService.getUnVerifiedUsers().subscribe((users) => {
this.unVerifiedUsers = users;
this.unVerifiedUsers.map((user) => {
user._id = user['id'];
});
});
}
public getRowHeight(params): number {
return params.data.structures ? (params.data.structures.length != 0 ? params.data.structures.length * 40 : 40) : 40;
}
}
......@@ -3,7 +3,7 @@
<div fxLayout="row" fxLayoutGap="20px" fxLayoutAlign="center center">
<button (click)="changeActiveFeature(features.pendingStructures)">Revendication structure</button>
<button (click)="changeActiveFeature(features.structuresList)">Liste structures</button>
<button (click)="changeActiveFeature(features.deleteUsers)">Suppression d'utilisateurs</button>
<button (click)="changeActiveFeature(features.deleteUsers)">Gestion des utilisateurs</button>
<button (click)="changeActiveFeature(features.newsletterUsers)">Newsletter</button>
<a target="_blank" class="custom-link" rel="noopener noreferrer" [href]="ghostLink">Ghost</a>
</div>
......@@ -11,7 +11,7 @@
<app-admin-structures-list></app-admin-structures-list>
</div>
<div *ngIf="selectedFeature === features.deleteUsers">
<app-admin-delete-user></app-admin-delete-user>
<app-admin-manage-users></app-admin-manage-users>
</div>
<div *ngIf="selectedFeature === features.pendingStructures">
<app-admin-claim-structure></app-admin-claim-structure>
......
......@@ -29,6 +29,18 @@ export class AdminService {
return this.http.get<User[]>(`api/admin/searchUsers`);
}
public getAttachedUsers(): Observable<User[]> {
return this.http.get<User[]>(`api/admin/getAttachedUsers`);
}
public getUnAttachedUsers(): Observable<User[]> {
return this.http.get<User[]>(`api/admin/getUnAttachedUsers`);
}
public getUnVerifiedUsers(): Observable<User[]> {
return this.http.get<User[]>(`api/admin/getUnVerifiedUsers`);
}
public searchUsers(searchString: string): Observable<User[]> {
return this.http.post<User[]>(`${this.baseUrl}/searchUsers`, { searchString });
}
......
......@@ -69,7 +69,15 @@ import { RoleGuard } from './guards/role.guard';
StructureListPrintComponent,
StructurePrintHeaderComponent,
],
imports: [BrowserModule, HttpClientModule, AppRoutingModule, SharedModule, MapModule, BrowserAnimationsModule, ToastrModule.forRoot()],
imports: [
BrowserModule,
HttpClientModule,
AppRoutingModule,
SharedModule,
MapModule,
BrowserAnimationsModule,
ToastrModule.forRoot(),
],
providers: [
{ provide: LOCALE_ID, useValue: 'fr' },
{ provide: HTTP_INTERCEPTORS, useClass: CustomHttpInterceptor, multi: true },
......
......@@ -48,6 +48,8 @@
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/leaflet.locatecontrol@0.72.0/dist/L.Control.Locate.min.css"
/>
<link rel="stylesheet" href="https://unpkg.com/ag-grid-community/dist/styles/ag-grid.css" />
<link rel="stylesheet" href="https://unpkg.com/ag-grid-community/dist/styles/ag-theme-alpine.css" />