Skip to content
Snippets Groups Projects
Commit 80ce968d authored by Marlène SIMONDANT's avatar Marlène SIMONDANT
Browse files

feat : manage structures page

parent 5e77c069
No related branches found
No related tags found
4 merge requests!418V2.1.0,!400V2.0,!337feat/US226-manage-structures-page,!230V2.0
Showing
with 465 additions and 17 deletions
...@@ -12,6 +12,7 @@ export class Structure { ...@@ -12,6 +12,7 @@ export class Structure {
public numero: string = null; public numero: string = null;
public createdAt: string = null; public createdAt: string = null;
public updatedAt: string = null; public updatedAt: string = null;
public toBeDeletedAt: string = null;
public structureName: string = null; public structureName: string = null;
public structureType: string = null; public structureType: string = null;
public description: string = null; public description: string = null;
......
...@@ -4,6 +4,7 @@ import { RoleGuard } from '../guards/role.guard'; ...@@ -4,6 +4,7 @@ import { RoleGuard } from '../guards/role.guard';
import { StructureResolver } from '../resolvers/structure.resolver'; import { StructureResolver } from '../resolvers/structure.resolver';
import { RouteRole } from '../shared/enum/routeRole.enum'; import { RouteRole } from '../shared/enum/routeRole.enum';
import { StructureMembersManagementComponent } from './structure-members-management/structure-members-management.component'; import { StructureMembersManagementComponent } from './structure-members-management/structure-members-management.component';
import { StructuresManagementComponent } from './structures-management/structures-management.component';
import { ProfileComponent } from './profile.component'; import { ProfileComponent } from './profile.component';
import { StructureEditionSummaryComponent } from './structure-edition-summary/structure-edition-summary.component'; import { StructureEditionSummaryComponent } from './structure-edition-summary/structure-edition-summary.component';
import { AuthGuard } from '../guards/auth.guard'; import { AuthGuard } from '../guards/auth.guard';
...@@ -22,6 +23,10 @@ const routes: Routes = [ ...@@ -22,6 +23,10 @@ const routes: Routes = [
canActivate: [AuthGuard], canActivate: [AuthGuard],
component: EditComponent, component: EditComponent,
}, },
{
path: 'structures-management',
component: StructuresManagementComponent,
},
footerOutletRoute, footerOutletRoute,
{ {
path: '', path: '',
......
...@@ -71,24 +71,23 @@ ...@@ -71,24 +71,23 @@
<div fxLayout="row" fxLayoutAlign="start center" fxFill> <div fxLayout="row" fxLayoutAlign="start center" fxFill>
<h1>Structures</h1> <h1>Structures</h1>
<app-button <app-button
*ngIf="!isPublic" *ngIf="!isPublic && userProfile.structuresLink.length > 0"
class="hide-on-mobile" class="hide-on-mobile"
[type]="'button'" [type]="'button'"
[iconBtn]="'edit'" [iconBtn]="'edit'"
[text]="'Gérer mes structures'" [text]="'Gérer mes structures'"
[style]="buttonTypeEnum.SecondaryWide" [style]="buttonTypeEnum.SecondaryWide"
routerLink="./" routerLink="./structures-management"
[routerLinkActive]="'active'" [routerLinkActive]="'active'"
[disabled]="true"
></app-button> ></app-button>
<app-button <app-button
*ngIf="!isPublic && userProfile.structuresLink.length > 0"
class="hide-on-desktop" class="hide-on-desktop"
[type]="'button'" [type]="'button'"
[iconBtn]="'edit'" [iconBtn]="'edit'"
[style]="buttonTypeEnum.SecondaryOnlyIcon" [style]="buttonTypeEnum.SecondaryOnlyIcon"
routerLink="/" routerLink="./structures-management"
[routerLinkActive]="'active'" [routerLinkActive]="'active'"
[disabled]="true"
></app-button> ></app-button>
</div> </div>
<div fxLayoutGap="16px" fxLayout="column" fxFill> <div fxLayoutGap="16px" fxLayout="column" fxFill>
...@@ -102,6 +101,16 @@ ...@@ -102,6 +101,16 @@
</div> </div>
</ng-container> </ng-container>
</div> </div>
<div fxLayoutAlign="center center" fxFill>
<app-button
*ngIf="!isPublic && userProfile.structuresLink.length == 0"
[style]="buttonTypeEnum.SecondaryUltraWide"
[iconBtn]="'add'"
[text]="'Ajouter une structure'"
routerLink="/form/structure"
tabindex="0"
></app-button>
</div>
</div> </div>
</section> </section>
......
...@@ -7,14 +7,14 @@ import { StructureService } from '../services/structure.service'; ...@@ -7,14 +7,14 @@ import { StructureService } from '../services/structure.service';
import { ButtonType } from '../shared/components/button/buttonType.enum'; import { ButtonType } from '../shared/components/button/buttonType.enum';
import { ProfileService } from './services/profile.service'; import { ProfileService } from './services/profile.service';
import { Utils } from '../utils/utils'; import { Utils } from '../utils/utils';
import { catchError, map } from 'rxjs/operators'; import { catchError, map, tap } from 'rxjs/operators';
import { Observable } from 'rxjs'; import { forkJoin, Observable } from 'rxjs';
import { Location } from '@angular/common'; import { Location } from '@angular/common';
@Component({ @Component({
selector: 'app-profile', selector: 'app-profile',
templateUrl: './profile.component.html', templateUrl: './profile.component.html',
styleUrls: ['./profile.component.scss'] styleUrls: ['./profile.component.scss'],
}) })
export class ProfileComponent implements OnInit { export class ProfileComponent implements OnInit {
public userProfile: User; public userProfile: User;
...@@ -61,11 +61,20 @@ export class ProfileComponent implements OnInit { ...@@ -61,11 +61,20 @@ export class ProfileComponent implements OnInit {
} }
private getStructuresFromProfile() { private getStructuresFromProfile() {
const structures$: Observable<any>[] = [];
this.structures = []; this.structures = [];
this.userProfile.structuresLink.forEach((structureId) => { this.userProfile.structuresLink.forEach((structureId) => {
this.structureService.getStructureWithOwners(structureId, null).subscribe((s) => { structures$.push(
this.structures.push(s); this.structureService.getStructureWithOwners(structureId, null).pipe(
}); tap((structure) => {
this.structures.push(structure);
})
)
);
});
forkJoin(structures$).subscribe(() => {
this.structures.sort((a, b) => a.structure.structureName.localeCompare(b.structure.structureName));
}); });
} }
......
...@@ -9,6 +9,7 @@ import { StructureEditionSummaryComponent } from './structure-edition-summary/st ...@@ -9,6 +9,7 @@ import { StructureEditionSummaryComponent } from './structure-edition-summary/st
import { EditComponent } from './edit/edit.component'; import { EditComponent } from './edit/edit.component';
import { StructureMembersManagementComponent } from './structure-members-management/structure-members-management.component'; import { StructureMembersManagementComponent } from './structure-members-management/structure-members-management.component';
import { StructureAddMemberModalComponent } from './structure-add-member-modal/structure-add-member-modal.component'; import { StructureAddMemberModalComponent } from './structure-add-member-modal/structure-add-member-modal.component';
import { StructuresManagementComponent } from './structures-management/structures-management.component';
import { MissingInformationComponent } from './structure-edition-summary/missing-information/missing-information.component'; import { MissingInformationComponent } from './structure-edition-summary/missing-information/missing-information.component';
import { NoInformationComponent } from './structure-edition-summary/no-information/no-information.component'; import { NoInformationComponent } from './structure-edition-summary/no-information/no-information.component';
...@@ -23,6 +24,7 @@ import { NoInformationComponent } from './structure-edition-summary/no-informati ...@@ -23,6 +24,7 @@ import { NoInformationComponent } from './structure-edition-summary/no-informati
StructureAddMemberModalComponent, StructureAddMemberModalComponent,
StructureEditionSummaryComponent, StructureEditionSummaryComponent,
StructureMembersManagementComponent, StructureMembersManagementComponent,
StructuresManagementComponent,
], ],
imports: [CommonModule, ProfileRoutingModule, SharedModule], imports: [CommonModule, ProfileRoutingModule, SharedModule],
}) })
......
...@@ -78,17 +78,19 @@ ...@@ -78,17 +78,19 @@
[structure]="structureWithOwners" [structure]="structureWithOwners"
(closed)="closeAddMemberModal($event)" (closed)="closeAddMemberModal($event)"
></app-structure-add-member-modal> ></app-structure-add-member-modal>
<app-modal-confirmation <app-custom-modal
*ngIf="excludeModalOpenned" *ngIf="excludeModalOpenned"
[openned]="excludeModalOpenned" [openned]="excludeModalOpenned"
[content]="'Souhaitez-vous exclure ce membre\n(' + displayMemberName(memberToExclude) + ')&nbsp;?'" [content]="'Souhaitez-vous exclure ce membre\n(' + displayMemberName(memberToExclude) + ')&nbsp;?'"
[customConfirmationText]="'Oui'" [hideTitle]="true"
[customValidationButton]="'Oui'"
(closed)="excludeMember(memberToExclude, $event)" (closed)="excludeMember(memberToExclude, $event)"
></app-modal-confirmation> ></app-custom-modal>
<app-modal-confirmation <app-custom-modal
*ngIf="cancelAddTempUserModalOpenned" *ngIf="cancelAddTempUserModalOpenned"
[openned]="cancelAddTempUserModalOpenned" [openned]="cancelAddTempUserModalOpenned"
[content]="'Souhaitez-vous annuler la demande de rattachement de ce membre\n(' + tempUserToCancel.email + ')&nbsp;?'" [content]="'Souhaitez-vous annuler la demande de rattachement de ce membre\n(' + tempUserToCancel.email + ')&nbsp;?'"
[customConfirmationText]="'Oui'" [hideTitle]="true"
[customValidationButton]="'Oui'"
(closed)="cancelAddTempUser(tempUserToCancel, $event)" (closed)="cancelAddTempUser(tempUserToCancel, $event)"
></app-modal-confirmation> ></app-custom-modal>
<div class="content-container full-screen">
<div class="container">
<div class="header">
<div fxLayout="row wrap" fxLayoutAlign="space-between center" fxFill>
<div fxLayout="row" fxLayoutAlign="start center" class="headerBack">
<app-svg-icon [iconClass]="'backArrow'" [type]="'ico'" [icon]="'arrowBack'" (click)="goBack()"></app-svg-icon>
<h1>Gérer mes structures</h1>
</div>
<app-button
[style]="buttonTypeEnum.SecondaryWide"
[iconBtn]="'add'"
[text]="'Ajouter une structure'"
routerLink="/form/structure"
tabindex="0"
></app-button>
</div>
</div>
<div *ngIf="structures">
<div fxLayoutGap="16px" fxLayout="column" fxFill>
<ng-container *ngIf="structures">
<div *ngFor="let elt of structures" class="structureCard">
<div class="structureCardContent" fxLayout="row" fxLayoutAlign="space-between center" fxLayoutGap="16px">
<div class="structureDetails" fxLayout="column" fxLayoutAlign="space-between start">
<p class="structureName">{{ elt.structure.structureName }}</p>
<p class="structureLocation">{{ elt.structure.address.commune }}</p>
</div>
<div class="warningContainer">
<div *ngIf="isBeingDeleted(elt.structure)" class="deleteInProgress">
<app-svg-icon [iconClass]="'icon-26'" [type]="'form'" [icon]="'notValidate'"></app-svg-icon>
<span>Suppression en cours</span>
</div>
</div>
<div class="buttons" fxLayout="row" fxLayoutAlign="space-between center" fxLayoutGap="12px">
<app-button
[type]="'button'"
[text]="'Quitter la structure'"
[style]="buttonTypeEnum.SecondaryWide"
(click)="selectedStructure = elt.structure; leaveModalOpenned = true"
></app-button>
<app-button
*ngIf="!isBeingDeleted(elt.structure)"
class="deleteAction"
[type]="'button'"
[text]="'Supprimer la structure'"
[style]="buttonTypeEnum.SecondaryWide"
routerLink="./"
[routerLinkActive]="'active'"
(click)="selectedStructure = elt.structure; deleteModalOpenned = true"
></app-button>
<app-button
*ngIf="isBeingDeleted(elt.structure)"
class="deleteAction"
[type]="'button'"
[text]="'Annuler la suppression'"
[style]="buttonTypeEnum.SecondaryWide"
routerLink="./"
[routerLinkActive]="'active'"
(click)="selectedStructure = elt.structure; cancelDeleteModalOpenned = true"
></app-button>
</div>
</div>
</div>
</ng-container>
</div>
</div>
<app-custom-modal
*ngIf="leaveModalOpenned"
[openned]="leaveModalOpenned"
[content]="'Souhaitez-vous quitter cette structure&nbsp;?'"
[hideTitle]="true"
(closed)="leaveStructure(selectedStructure, $event)"
></app-custom-modal>
<app-custom-modal
*ngIf="deleteModalOpenned"
[openned]="deleteModalOpenned"
[content]="'Souhaitez-vous supprimer cette structure&nbsp;?'"
[hideTitle]="true"
(closed)="deleteStructure(selectedStructure, $event)"
></app-custom-modal>
<app-custom-modal
*ngIf="cancelDeleteModalOpenned"
[openned]="cancelDeleteModalOpenned"
[content]="'Souhaitez-vous annuler la suppression de cette structure&nbsp;?'"
[hideTitle]="true"
(closed)="cancelDelete(selectedStructure, $event)"
></app-custom-modal>
</div>
</div>
@import '../../../assets/scss/color';
@import '../../../assets/scss/typography';
@import '../../../assets/scss/breakpoint';
.container {
margin: 1rem auto;
max-width: 980px;
padding: 2rem;
background: $white;
.header {
margin-bottom: 2rem;
}
.headerBack {
cursor: pointer;
span {
color: $red;
}
}
h1 {
@include lato-regular-24;
color: $grey-1;
cursor: initial;
}
.structureCard {
padding: 5px 6px;
border-bottom: 1px solid $grey-8;
overflow: hidden;
.structureCardContent {
@media #{$phone} {
flex-wrap: wrap;
}
.structureDetails {
width: 45%;
margin-bottom: 8px;
@media #{$phone} {
width: initial;
}
p {
margin: 0 !important;
&.structureName {
@include lato-bold-16;
}
&.structureLocation {
@include lato-regular-13;
font-style: italic;
color: $grey-3;
}
}
}
.warningContainer {
width: 8rem;
.deleteInProgress {
display: flex;
color: $orange-warning;
padding-bottom: 5px;
span {
max-width: 6.875rem;
}
::ng-deep svg {
margin-right: 8px;
}
}
}
.buttons {
@media #{$tablet} {
max-width: 45%;
}
@media #{$phone} {
max-width: initial;
}
app-button.deleteAction > ::ng-deep button > div {
color: $red;
}
}
}
}
}
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { StructuresManagementComponent } from './structures-management.component';
describe('StructuresManagementComponent', () => {
let component: StructuresManagementComponent;
let fixture: ComponentFixture<StructuresManagementComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [StructuresManagementComponent],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(StructuresManagementComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, OnInit } from '@angular/core';
import { forkJoin, Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import { Structure } from '../../models/structure.model';
import { StructureWithOwners } from '../../models/structureWithOwners.model';
import { User } from '../../models/user.model';
import { NotificationService } from '../../services/notification.service';
import { StructureService } from '../../services/structure.service';
import { ButtonType } from '../../shared/components/button/buttonType.enum';
import { ProfileService } from '../services/profile.service';
@Component({
selector: 'app-structures-management',
templateUrl: './structures-management.component.html',
styleUrls: ['./structures-management.component.scss'],
})
export class StructuresManagementComponent implements OnInit {
public userProfile: User;
public structures: StructureWithOwners[] = [];
public selectedStructure: string;
public deleteInProgress: boolean;
public buttonTypeEnum = ButtonType;
public leaveModalOpenned: boolean = false;
public deleteModalOpenned: boolean = false;
public cancelDeleteModalOpenned: boolean = false;
constructor(
private profileService: ProfileService,
private structureService: StructureService,
private notificationService: NotificationService
) {}
ngOnInit(): void {
this.getUserAndStructures();
}
public goBack(): void {
history.back();
}
private getStructures(): void {
const structures$: Observable<any>[] = [];
this.structures = [];
this.userProfile.structuresLink.forEach((structureId) => {
structures$.push(
this.structureService.getStructureWithOwners(structureId, null).pipe(
tap((structure) => {
this.structures.push(structure);
})
)
);
});
forkJoin(structures$).subscribe(() => {
this.structures.sort((a, b) => a.structure.structureName.localeCompare(b.structure.structureName));
});
}
private getUserAndStructures(): void {
this.profileService.getProfile().then((profile: User) => {
this.userProfile = profile;
this.getStructures();
});
}
public leaveStructure(structure: Structure, shouldExclude: boolean): void {
this.leaveModalOpenned = false;
if (shouldExclude) {
this.structureService.removeOwnerFromStructure(this.userProfile._id, structure._id).subscribe(
() => {
this.structures = this.structures.filter((obj) => obj.structure._id !== structure._id);
this.notificationService.showSuccess(`Vous avez bien quitté ${structure.structureName}`, '');
},
() => {
this.notificationService.showError('Une erreur est survenue, veuillez réessayer.', '');
}
);
}
}
public deleteStructure(structure: Structure, shouldDelete: boolean): void {
this.deleteModalOpenned = false;
if (shouldDelete) {
this.structureService.delete(structure._id).subscribe((res) => {
this.getUserAndStructures();
});
}
}
public isBeingDeleted(structure: Structure): boolean {
return Boolean(structure.toBeDeletedAt);
}
public cancelDelete(structure: Structure, shouldCancel: boolean): void {
this.cancelDeleteModalOpenned = false;
if (shouldCancel) {
this.structureService.cancelDelete(structure._id).subscribe((res) => {
this.getStructures();
});
}
}
}
...@@ -64,6 +64,10 @@ export class StructureService { ...@@ -64,6 +64,10 @@ export class StructureService {
return this.http.delete<Structure>(`${this.baseUrl}/${id}`); return this.http.delete<Structure>(`${this.baseUrl}/${id}`);
} }
public cancelDelete(id: string): Observable<Structure> {
return this.http.post<Structure>(`${this.baseUrl}/${id}/cancelDelete`, null);
}
public removeTempUserFromStructure(idOwner: string, idStructure: string): Observable<any> { public removeTempUserFromStructure(idOwner: string, idStructure: string): Observable<any> {
return this.http.delete<any>(`${this.baseUrl}/${idStructure}/tempUser/${idOwner}`); return this.http.delete<any>(`${this.baseUrl}/${idStructure}/tempUser/${idOwner}`);
} }
......
<div *ngIf="openned" class="modalBackground">
<div class="modal">
<div class="contentModal" fxLayout="column" fxLayoutAlign="space-around center">
<div class="headerModal">
<svg aria-hidden="true" (click)="closeModal(false)">
<use [attr.xlink:href]="'assets/form/sprite.svg#close'"></use>
</svg>
<div class="contentText">
<h3 *ngIf="!hideTitle && customTitle">{{ customTitle }}</h3>
<h3 *ngIf="!hideTitle && !customTitle">ATTENTION</h3>
<p [ngClass]="{ mainText: hideTitle }">{{ content }}</p>
</div>
</div>
<div class="footerModal" fxLayout="row" fxLayoutAlign="space-around center" fxLayoutGap="8px">
<app-button
(action)="closeModal(false)"
[text]="customCancelButton ? customCancelButton : 'Annuler'"
[style]="buttonTypeEnum.modalSecondary"
></app-button>
<app-button
(action)="closeModal(true)"
[text]="customValidationButton ? customValidationButton : 'Valider'"
[style]="buttonTypeEnum.modalPrimary"
></app-button>
</div>
</div>
</div>
</div>
@import '../../../../assets/scss/color';
@import '../../../../assets/scss/typography';
@import '../../../../assets/scss/shapes';
@import '../../../../assets/scss/z-index';
.modalBackground {
.modal {
max-width: 390px;
.contentModal {
padding: 15px 5px 15px 45px;
.headerModal {
display: flex;
flex-direction: row-reverse;
svg {
cursor: pointer;
height: 40px;
width: 40px;
}
.contentText {
flex-direction: column;
max-width: 300px;
h3 {
@include lato-bold-18;
color: $red;
margin: 10px 0 25px;
text-align: center;
}
p {
text-align: center;
margin: 10px;
}
p.mainText {
@include lato-regular-18;
}
}
}
.footerModal {
padding-right: 40px;
gap: 8px;
app-button {
flex: 1;
}
}
}
}
}
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CustomModalComponent } from './custom-modal.component';
describe('CustomModalComponent', () => {
let component: CustomModalComponent;
let fixture: ComponentFixture<CustomModalComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [CustomModalComponent],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(CustomModalComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { ButtonType } from '../button/buttonType.enum';
@Component({
selector: 'app-custom-modal',
templateUrl: './custom-modal.component.html',
styleUrls: ['./custom-modal.component.scss'],
})
export class CustomModalComponent {
@Input() public openned: boolean;
@Input() public content: string;
@Input() public hideTitle?: boolean;
@Input() public customTitle?: string;
@Input() public customValidationButton?: string;
@Input() public customCancelButton?: string;
@Output() closed = new EventEmitter<boolean>();
public buttonTypeEnum = ButtonType;
public closeModal(value: boolean): void {
this.closed.emit(value);
}
}
...@@ -10,6 +10,7 @@ import { HourPickerComponent } from './hour-picker/hour-picker.component'; ...@@ -10,6 +10,7 @@ import { HourPickerComponent } from './hour-picker/hour-picker.component';
import { CopyPasteComponent } from './hour-picker/copy-paste/copy-paste.component'; import { CopyPasteComponent } from './hour-picker/copy-paste/copy-paste.component';
import { RadioFormComponent } from './radio-form/radio-form.component'; import { RadioFormComponent } from './radio-form/radio-form.component';
import { ModalConfirmationComponent } from './modal-confirmation/modal-confirmation.component'; import { ModalConfirmationComponent } from './modal-confirmation/modal-confirmation.component';
import { CustomModalComponent } from './custom-modal/custom-modal.component';
import { ModalJoinConfirmationComponent } from './modal-join-confirmation/modal-join-confirmation.component'; import { ModalJoinConfirmationComponent } from './modal-join-confirmation/modal-join-confirmation.component';
import { StructureOptionsModalComponent } from './structure-options-modal/structure-options-modal.component'; import { StructureOptionsModalComponent } from './structure-options-modal/structure-options-modal.component';
import { ModalOptionsComponent } from './modal-options/modal-options.component'; import { ModalOptionsComponent } from './modal-options/modal-options.component';
...@@ -32,6 +33,7 @@ export { ...@@ -32,6 +33,7 @@ export {
CopyPasteComponent, CopyPasteComponent,
RadioFormComponent, RadioFormComponent,
ModalConfirmationComponent, ModalConfirmationComponent,
CustomModalComponent,
StructureOptionsModalComponent, StructureOptionsModalComponent,
ModalOptionsComponent, ModalOptionsComponent,
TextInputModalComponent, TextInputModalComponent,
...@@ -53,6 +55,7 @@ export const SharedComponents = [ ...@@ -53,6 +55,7 @@ export const SharedComponents = [
CopyPasteComponent, CopyPasteComponent,
RadioFormComponent, RadioFormComponent,
ModalConfirmationComponent, ModalConfirmationComponent,
CustomModalComponent,
ModalJoinConfirmationComponent, ModalJoinConfirmationComponent,
StructureOptionsModalComponent, StructureOptionsModalComponent,
ModalOptionsComponent, ModalOptionsComponent,
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment