Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • web-et-numerique/factory/pamn_plateforme-des-acteurs-de-la-mediation-numerique/pamn_client
1 result
Show changes
Commits on Source (12)
Showing
with 218 additions and 110 deletions
......@@ -2,6 +2,20 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
## [1.15.0](https://forge.grandlyon.com/web-et-numerique/pamn_plateforme-des-acteurs-de-la-mediation-numerique/pamn_client/compare/v1.14.0...v1.15.0) (2022-03-08)
### Features
* **data-consent:** add data sharing consent when creating and editing a structure and at log-in if no consent was ever registered for at least one of the user's structures ([4214766](https://forge.grandlyon.com/web-et-numerique/pamn_plateforme-des-acteurs-de-la-mediation-numerique/pamn_client/commit/42147665ecc287079acc03c169708b87daedcd2d))
### Bug Fixes
* **orientation-form:** set progression to 100 when clicking print ([e52fbf5](https://forge.grandlyon.com/web-et-numerique/pamn_plateforme-des-acteurs-de-la-mediation-numerique/pamn_client/commit/e52fbf5657b0a41d638f153bd6af708ec9f5cd60))
* **posts:** load more posts with tag ([a003d05](https://forge.grandlyon.com/web-et-numerique/pamn_plateforme-des-acteurs-de-la-mediation-numerique/pamn_client/commit/a003d055cf594db61cdf4ca773194bd913aa4159))
* typo and labels ([916739f](https://forge.grandlyon.com/web-et-numerique/pamn_plateforme-des-acteurs-de-la-mediation-numerique/pamn_client/commit/916739f91d9fb14334278a44a953a7bcdf1f575b))
## [1.14.0](https://forge.grandlyon.com/web-et-numerique/pamn_plateforme-des-acteurs-de-la-mediation-numerique/pamn_client/compare/v1.13.0...v1.14.0) (2022-02-21)
......
{
"name": "pamn",
"version": "1.14.0",
"version": "1.15.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
......
{
"name": "pamn",
"version": "1.14.0",
"version": "1.15.0",
"scripts": {
"ng": "ng",
"start": "ng serve --configuration=fr --proxy-config proxy.conf.json",
......
......@@ -63,6 +63,7 @@ export class ManageUsersComponent {
},
{
headerName: 'Actions',
editable: false,
minWidth: 150,
cellRenderer: 'deleteUserComponent',
cellRendererParams: {
......
......@@ -102,7 +102,6 @@ const routes: Routes = [
path: 'newsletter',
component: NewsletterSubscriptionComponent,
},
{
path: 'newsletter-unsubscribe',
component: NewsletterSubscriptionComponent,
......
......@@ -45,6 +45,7 @@ import { environment } from '../environments/environment';
import { StructureResolver } from './resolvers/structure.resolver';
import { RoleGuard } from './guards/role.guard';
import { UpdateService } from './services/update.service';
import { DataShareConsentComponent } from './shared/components/data-share-consent/data-share-consent.component';
@NgModule({
declarations: [
......@@ -72,6 +73,7 @@ import { UpdateService } from './services/update.service';
StructureDetailPrintComponent,
StructureListPrintComponent,
StructurePrintHeaderComponent,
DataShareConsentComponent,
OrientationComponent,
],
imports: [
......
......@@ -2,6 +2,12 @@
@import '../../assets/scss/layout';
@import '../../assets/scss/z-index';
::ng-deep .footer {
@media #{$tablet} {
display: none !important;
}
}
.left-pane {
width: 640px;
min-width: 640px;
......
......@@ -39,7 +39,7 @@
<!-- Opening Hours -->
<div fxLayout="row" class="w-100 mobile-column">
<div *ngIf="structure.hours.hasData()" fxFlex="50%">
<h3 class="subtitle">Horaires d’ouverture au public</h3>
<h3 class="subtitle">Horaires</h3>
<div fxLayout="column">
<div *ngFor="let day of structure.hours | keyvalue: keepOriginalOrder">
<div *ngIf="day.value.open">
......@@ -60,7 +60,7 @@
</div>
<!-- accessModality -->
<div *ngIf="structure.accessModality.length > 0" fxFlex="40%">
<h3 class="subtitle">Accès transports en commun</h3>
<h3 class="subtitle">Accès</h3>
<div fxLayout="column">
<div *ngFor="let tclStop of tclStopPoints">
<div fxLayout="row wrap" fxLayoutGap="5px">
......
......@@ -5,7 +5,6 @@ import { TclService } from '../../../../services/tcl.service';
import { TclStopPoint } from '../../../../models/tclStopPoint.model';
import { AuthService } from '../../../../services/auth.service';
import { AccessModality } from '../../../../structure-list/enum/access-modality.enum';
import { PublicCategorie } from '../../../../structure-list/enum/public.enum';
@Component({
selector: 'app-structure-detail-print',
templateUrl: './structure-detail-print.component.html',
......@@ -29,44 +28,12 @@ export class StructureDetailPrintComponent implements OnInit {
}
}
public keepOriginalOrder = (a, b) => a.key;
public userIsLoggedIn(): boolean {
return this.authService.isLoggedIn();
}
public getAccessLabel(accessModality: AccessModality): string {
switch (accessModality) {
case AccessModality.free:
return 'Accès libre';
case AccessModality.meeting:
return 'Sur rendez-vous';
case AccessModality.meetingOnly:
return 'Uniquement sur RDV';
case AccessModality.numeric:
return 'Téléphone / Visio';
default:
return null;
}
}
public getPublicLabel(tagetPublic: PublicCategorie): string {
switch (tagetPublic) {
case PublicCategorie.young:
return 'Jeunes (16 - 25 ans)';
case PublicCategorie.adult:
return 'Adultes';
case PublicCategorie.elderly:
return 'Séniors (+ de 65 ans)';
case PublicCategorie.all:
return 'Tout public';
case PublicCategorie.under16Years:
return 'Moins de 16 ans';
case PublicCategorie.women:
return 'Uniquement femmes';
default:
return null;
}
}
public getTclStopPoints(): void {
this.tclService.getTclStopPointBycoord(this.structure.getLon(), this.structure.getLat()).subscribe((res) => {
this.tclStopPoints = res;
......
......@@ -293,7 +293,7 @@
<!-- ADDRESS SEARCH -->
<div *ngIf="currentPage == pageTypeEnum.beneficiaryAddress" class="page">
<div class="title">
<h3>Autour de quelle adresse chercher une structure ?</h3>
<h3>Autour de quelle adresse cherchez-vous une structure ?</h3>
<p class="notRequired lg">facultatif</p>
</div>
<div class="form-group" fxLayout="column">
......
......@@ -85,7 +85,7 @@
<div class="summary" *ngFor="let page of pagesValidation; let index = index">
<div
class="itemSummary"
[ngClass]="{ last: index == 22 }"
[ngClass]="{ last: index == lastPage }"
fxLayout="row"
fxLayoutAlign="space-between center"
*ngIf="page.name && shouldDisplayPage(index)"
......@@ -408,7 +408,7 @@
>
<div *ngIf="currentPage == pageTypeEnum.structureNameAndAddress" class="page">
<div class="title">
<h3>Quelle structure voulez-vous réferencer ?</h3>
<h3>Quelle structure voulez-vous référencer ?</h3>
</div>
<p
class="missing-information"
......@@ -1285,49 +1285,88 @@
</div>
</div>
</form>
<div *ngIf="currentPage == pageTypeEnum.cgu" class="page">
<div class="section">
<div class="title">
<h3>
Acceptez-vous que les informations saisies soient enregistrées par la Métropole de Lyon<span
class="asterisk"
>*</span
<form>
<div *ngIf="currentPage == pageTypeEnum.cgu" class="page">
<div class="section" *ngIf="!isEditMode">
<div class="title">
<h3>
Acceptez-vous que les informations saisies soient enregistrées par la Métropole de Lyon<span
class="asterisk"
>*</span
>
?
</h3>
</div>
<app-checkbox-form
[isChecked]="userAcceptSavedDate"
[text]="'J\'accepte'"
(checkEvent)="acceptDataBeSaved($event)"
>
</app-checkbox-form>
</div>
<div class="section">
<div class="title">
<h3>
Acceptez-vous que les informations de votre structure soient mises à disposition sur la plateforme
data.grandlyon.com<span class="asterisk" *ngIf="!isEditMode">**</span
><span class="asterisk" *ngIf="isEditMode">*</span> ?
</h3>
<p class="notRequired" *ngIf="!isEditMode">facultatif</p>
</div>
<app-checkbox-form
*ngIf="!isEditMode"
[text]="'J\'accepte'"
(checkEvent)="acceptOpenData($event)"
></app-checkbox-form>
<div class="dataShareConsent">
<app-radio-form
*ngIf="isEditMode"
name="{{ getStructureControl('structureName').value }}"
horizontal="true"
[selectedOption]="getStructureControl('dataShareConsentDate').value === null ? false : true"
(selectedEvent)="onRadioBtnChange('dataShareConsentDate', $event)"
>
?
</h3>
</app-radio-form>
</div>
</div>
<app-checkbox-form
[isChecked]="userAcceptSavedDate"
[text]="'J\'accepte'"
(checkEvent)="acceptDataBeSaved($event)"
>
</app-checkbox-form>
</div>
<div *ngIf="!profile">
<div class="title">
<h3>Acceptez-vous de recevoir des mails d'informations de la part de Res'in ?</h3>
<div *ngIf="!profile">
<div class="title">
<h3>Souhaitez-vous vous abonner à la lettre d’information de Res'in ?</h3>
<p class="notRequired" *ngIf="!isEditMode">facultatif</p>
</div>
<app-checkbox-form
[isChecked]="userAcceptNewsletter"
[text]="'J\'accepte'"
(checkEvent)="acceptReceiveNewsletter($event)"
>
</app-checkbox-form>
</div>
<p *ngIf="!isEditMode" class="informationEndForm">
<span class="asterisk">*</span> Les informations recueillies sont enregistrées dans un fichier par la
Métropole de Lyon en vue de l'animation du réseau des acteurs de la médiation numérique. Elles sont conservées
pendant 24 mois et sont destinées aux seuls intervenants habilités de la Métropole de Lyon. Vos données
personnelles sont traitées dans ce cadre aux fins de recensement des actions de médiation numérique sur le
territoire de la métropole. Conformément à la loi 78-17 du 6 janvier 1978 modifiée relative à l'information,
aux fichiers et aux libertés, et au Règlement Général européen à la Protection des Données, vous avez la
possibilité d’exercer vos droits d’accès, de rectification, d’effacement, d’opposition, de limitation du
traitement et de révocation de votre consentement. Afin d'exercer vos droits, vous pouvez vous adresser : par
courrier postal à : Métropole de Lyon - Direction des Affaires Juridiques et de la Commande Publique - 20, rue
du Lac - BP 33569 - 69505 Lyon Cedex par courrier électronique en remplissant le formulaire dédié sur Toodego,
le site des services et démarches en ligne dans la Métropole de Lyon
</p>
<div class="page" *ngIf="currentPage == pageTypeEnum.cgu">
<p class="informationEndForm">
<span class="asterisk" *ngIf="!isEditMode">**</span><span class="asterisk" *ngIf="isEditMode">*</span> La
Métropole de Lyon, engagée pour la transparence de l’action publique et la valorisation de ses partenaires,
encourage l’ouverture des données. Les données de votre structure seront publiées sur la plateforme
<a href="https://data.grandlyon.com/" target="_blank">https://data.grandlyon.com/</a> sous la licence
ouverte (open data) et seront donc librement accessibles et réutilisables. Vous pourrez modifier votre choix
à tout moment, exercer vos droits d’accès et de modification, en le signifiant, par tout moyen à votre
convenance, auprès de vos interlocuteurs de la Métropole de Lyon.
</p>
</div>
<app-checkbox-form
[isChecked]="userAcceptNewsletter"
[text]="'J\'accepte'"
(checkEvent)="acceptReceiveNewsletter($event)"
>
</app-checkbox-form>
</div>
<p class="informationEndForm">
<span class="asterisk">*</span> Les informations recueillies sont enregistrées dans un fichier par la Métropole
de Lyon en vue de l'animation du réseau des acteurs de la médiation numérique. Elles sont conservées pendant 24
mois et sont destinées aux seuls intervenants habilités de la Métropole de Lyon. Vos données personnelles sont
traitées dans ce cadre aux fins de recensement des actions de médiation numérique sur le territoire de la
métropole. Conformément à la loi 78-17 du 6 janvier 1978 modifiée relative à l'information, aux fichiers et aux
libertés, et au Règlement Général européen à la Protection des Données, vous avez la possibilité d’exercer vos
droits d’accès, de rectification, d’effacement, d’opposition, de limitation du traitement et de révocation de
votre consentement. Afin d'exercer vos droits, vous pouvez vous adresser : par courrier postal à : Métropole de
Lyon - Direction des Affaires Juridiques et de la Commande Publique - 20, rue du Lac - BP 33569 - 69505 Lyon
Cedex par courrier électronique en remplissant le formulaire dédié sur Toodego, le site des services et
démarches en ligne dans la Métropole de Lyon
</p>
</div>
</form>
<div
*ngIf="currentPage == nbPagesForm && !profile"
class="page"
......@@ -1340,7 +1379,7 @@
</svg>
<h3>Un courriel vous a été envoyé afin de valider votre inscription</h3>
</div>
<div *ngIf="currentPage == nbPagesForm && profile" class="lastPage">
<div *ngIf="currentPage == nbPagesForm && profile && !isEditMode" class="lastPage">
<div class="lastPage">
<div class="title">
<h3>
......@@ -1390,7 +1429,7 @@
Ok
</button>
<button
*ngIf="currentPage == nbPagesForm && profile"
*ngIf="currentPage == nbPagesForm && profile && !isEditMode"
class="btn-primary unique"
routerLink="/acteurs"
[queryParams]="{ id: createdStructure._id }"
......
......@@ -189,6 +189,14 @@ h4 {
margin-top: 18px;
color: $grey-2;
@include cn-regular-14;
a {
color: $default-link-color;
text-decoration: underline;
font-weight: bold;
}
}
&.notRequired {
font-style: italic;
}
}
.textareaBlock {
......@@ -560,7 +568,11 @@ img {
.section {
padding-bottom: 2rem;
}
.dataShareConsent {
::ng-deep button p {
font-weight: normal !important;
}
}
.missing-information {
display: flex;
color: $orange-warning;
......
......@@ -83,6 +83,8 @@ export class FormComponent implements OnInit {
// Structure id for edit mode
public structureId: string;
// last page for edit form
public lastPage = this.pageTypeEnum.cgu;
constructor(
private structureService: StructureService,
......@@ -320,6 +322,7 @@ export class FormComponent implements OnInit {
Validators.min(0),
]),
freeWorkShop: new FormControl(structure.freeWorkShop, [Validators.required]),
dataShareConsentDate: new FormControl(structure.dataShareConsentDate),
});
}
......@@ -590,7 +593,14 @@ export class FormComponent implements OnInit {
valid: this.getStructureControl('lockdownActivity').valid,
name: 'Informations spécifiques à la période COVID',
};
this.pagesValidation[PageTypeEnum.cgu] = { valid: this.userAcceptSavedDate };
if (this.isEditMode) {
this.pagesValidation[PageTypeEnum.cgu] = {
valid: this.getStructureControl('dataShareConsentDate').valid,
name: 'Partage de données sur data.grandlyon.com',
};
} else {
this.pagesValidation[PageTypeEnum.cgu] = { valid: this.userAcceptSavedDate };
}
this.updatePageValid();
}
}
......@@ -898,6 +908,12 @@ export class FormComponent implements OnInit {
this.setValidationsForm();
}
public acceptOpenData(isAccepted: boolean): void {
let now = new Date().toString();
this.getStructureControl('dataShareConsentDate').setValue(now);
this.setValidationsForm();
}
public acceptReceiveNewsletter(isAccepted: boolean): void {
this.userAcceptNewsletter = isAccepted;
}
......
......@@ -57,6 +57,10 @@
</div>
<app-signup-modal *ngIf="displaySignUp" [openned]="isPopUpOpen" (closed)="closeSignUpModal($event)"></app-signup-modal>
<app-data-share-consent
*ngIf="isDisplayDataShare"
[dataConsentPendingStructures]="dataConsentPendingStructures"
></app-data-share-consent>
<ng-template #customTitle>
<img class="desktop-show logo-grand-lyon" width="108" height="37" src="/assets/logos/resin.svg" alt />
......
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
import { Structure } from '../models/structure.model';
import { ProfileService } from '../profile/services/profile.service';
import { AuthService } from '../services/auth.service';
......@@ -15,6 +16,9 @@ export class HeaderComponent implements OnInit {
public currentRoute = '';
public formRoute = '/create-structure';
public returnUrl = null;
public dataConsentPendingStructures: Structure[];
private displayDataShare = false;
private loadingDataShare = false;
constructor(
private authService: AuthService,
......@@ -53,6 +57,24 @@ export class HeaderComponent implements OnInit {
return this.authService.isLoggedIn();
}
public get isDisplayDataShare(): boolean {
if (this.displayDataShare) {
return this.displayDataShare;
} else {
if (this.isLoggedIn && !this.loadingDataShare) {
this.loadingDataShare = true;
this.profileService.getAllDataConsentPendingStructures().subscribe((dataConsentPendingStructures) => {
if (dataConsentPendingStructures.length) {
this.displayDataShare = true;
this.dataConsentPendingStructures = dataConsentPendingStructures;
return this.displayDataShare;
}
});
}
}
return false;
}
public closeSignInModal(): void {
this.isPopUpOpen = false;
this.displaySignUp = true;
......
......@@ -50,6 +50,7 @@ export class Structure {
public distance?: number;
public coord?: number[] = [];
public dataShareConsentDate?: string;
public accountVerified: boolean = false;
......
......@@ -22,9 +22,25 @@
}
.description {
div {
height: fit-content;
}
::ng-deep img {
height: 100%;
}
::ng-deep iframe {
width: 100% !important;
max-height: 400px;
height: 100vw;
}
::ng-deep .kg-embed-card {
max-height: 400px;
iframe {
width: 100% !important;
max-height: 100%;
min-height: 100px;
}
}
::ng-deep figure {
figcaption {
margin-top: 1%;
......
......@@ -56,11 +56,14 @@ export class PostListComponent implements OnInit {
...this.selectedLocationTagSlug,
...this.selectedPublicTagsSlug,
];
// Reset posts
this.resetPosts();
// Apply search
this.getPosts(this.filters);
this.getPosts(1, this.filters);
} else {
// Init default news list
this.allPosts = [];
this.filters = [];
this.postService.getPosts(1).subscribe((news) => {
this.fillArticles(news);
});
......@@ -84,7 +87,7 @@ export class PostListComponent implements OnInit {
this.allPosts = [...headLineTag, ..._.difference(this.allPosts, headLineTag)];
}
public getPosts(filters?: Tag[]): void {
public getPosts(page: number, filters?: Tag[]): void {
// Parse filter
let parsedFilters = null;
if (filters) {
......@@ -97,11 +100,8 @@ export class PostListComponent implements OnInit {
}
}
// Reset posts
this.resetPosts();
this.isLoading = true;
this.postService.getPosts(1, parsedFilters).subscribe((news) => {
this.postService.getPosts(page, parsedFilters).subscribe((news) => {
this.fillArticles(news);
});
}
......@@ -135,9 +135,13 @@ export class PostListComponent implements OnInit {
private loadMore(): void {
if (this.pagination && this.pagination.page < this.pagination.pages) {
this.isLoading = true;
this.postService.getPosts(this.pagination.next).subscribe((news) => {
this.fillArticles(news);
});
if (this.filters) {
this.getPosts(this.pagination.next, this.filters);
} else {
this.postService.getPosts(this.pagination.next).subscribe((news) => {
this.fillArticles(news);
});
}
}
}
......
......@@ -23,22 +23,21 @@ export class PostService {
}
public getPosts(page: number, tags?: string[]): Observable<PostWithMeta> {
if (!tags) {
return this.http
.get<PostWithMeta>(`${this.baseUrl}?page=${page}&include=tags,authors`)
.pipe(map((item: PostWithMeta) => new PostWithMeta(item)));
let tagsFilter = '';
if (tags) {
let tagsString = '';
// Transform tab filters to string filters
tags.forEach((tag, index) => {
tagsString += tag;
if (index != tags.length - 1) {
tagsString += '+tags:';
}
});
tagsFilter = `&filter=tags:${encodeURIComponent(tagsString)}`;
}
let tagsString = '';
// Transform tab filters to string filters
tags.forEach((tag, index) => {
tagsString += tag;
if (index != tags.length - 1) {
tagsString += '+tags:';
}
});
return this.http
.get<PostWithMeta>(`${this.baseUrl}?include=tags,authors&filter=tags:${encodeURIComponent(tagsString)}`)
.pipe(map((item: PostWithMeta) => new PostWithMeta(item)));
return this.http.get<PostWithMeta>(`${this.baseUrl}?page=${page}&include=tags,authors${tagsFilter}`);
}
public getTags(): Observable<TagWithMeta> {
......
......@@ -5,6 +5,8 @@ import { User } from '../../models/user.model';
import decode from 'jwt-decode';
import { UserRole } from '../../shared/enum/userRole.enum';
import { AuthService } from '../../services/auth.service';
import { Structure } from '../../models/structure.model';
import { map } from 'rxjs/operators';
@Injectable({
providedIn: 'root',
......@@ -81,4 +83,8 @@ export class ProfileService {
public isEmailAlreadyUsed(newMail: string): Observable<boolean> {
return this.http.post<boolean>(`${this.baseUrl}/verify-exist-user`, { newMail });
}
public getAllDataConsentPendingStructures(): Observable<Structure[]> {
return this.http.get<Structure[]>(`${this.baseUrl}/dataConsentValidation`);
}
}