forked from tykayn/funky-framadate-front
start form
This commit is contained in:
parent
6059533c36
commit
8cac3d1862
@ -96,7 +96,7 @@ L'export d'un sondage et des résultats d'un sondage est possible au format CSV.
|
|||||||
# Nouveautés secondaires
|
# Nouveautés secondaires
|
||||||
|
|
||||||
* Choix de réponses possibles. Proposer de ne répondre que « oui » ou rien, ou aller dans la nuance en proposant « oui », « peut-être », « non », « ? ». *# Redondance ou le choix de réponses possibles de la première phrase concerne un autre choix ?*
|
* Choix de réponses possibles. Proposer de ne répondre que « oui » ou rien, ou aller dans la nuance en proposant « oui », « peut-être », « non », « ? ». *# Redondance ou le choix de réponses possibles de la première phrase concerne un autre choix ?*
|
||||||
* Insertion d'images dans le sondage de type texte, avec des URL uniquement. Une seule image par question possible ou rien.
|
* Insertion d'images dans le sondage de type texte, avec des URL uniquement. Une seule image par title possible ou rien.
|
||||||
* Thème sombre.
|
* Thème sombre.
|
||||||
* Boutons pour copier dans le presse-papier les liens publics et privés / admin des sondages.
|
* Boutons pour copier dans le presse-papier les liens publics et privés / admin des sondages.
|
||||||
* Limiter le nombre de participants maximum
|
* Limiter le nombre de participants maximum
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
"expires": "2020-12-31"
|
"expires": "2020-12-31"
|
||||||
},
|
},
|
||||||
"ownerId": 1,
|
"ownerId": 1,
|
||||||
"question": "Quelle date pour le picnic ?",
|
"title": "Quelle date pour le picnic ?",
|
||||||
"description": "Gros badass picnic en plein air ! Come on !"
|
"description": "Gros badass picnic en plein air ! Come on !"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -37,7 +37,7 @@
|
|||||||
"expires": "2020-11-30"
|
"expires": "2020-11-30"
|
||||||
},
|
},
|
||||||
"ownerId": 2,
|
"ownerId": 2,
|
||||||
"question": "On fait quoi pendant les vacances ?",
|
"title": "On fait quoi pendant les vacances ?",
|
||||||
"description": "Vacances en famille"
|
"description": "Vacances en famille"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { environment } from '../../../environments/environment';
|
import { environment } from '../../../environments/environment';
|
||||||
import { DateService } from '../services/date.service';
|
import { DateService } from '../services/date.service';
|
||||||
|
|
||||||
export class Configuration {
|
export class PollConfiguration {
|
||||||
constructor(
|
constructor(
|
||||||
public isAboutDate: boolean = false,
|
public isAboutDate: boolean = false,
|
||||||
public isProtectedByPassword: boolean = false,
|
public isProtectedByPassword: boolean = false,
|
||||||
@ -16,7 +16,7 @@ export class Configuration {
|
|||||||
)
|
)
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public static isArchived(configuration: Configuration): boolean {
|
public static isArchived(configuration: PollConfiguration): boolean {
|
||||||
return configuration.expires ? DateService.isDateInPast(configuration.expires) : undefined;
|
return configuration.expires ? DateService.isDateInPast(configuration.expires) : undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,16 +2,16 @@ import { environment } from 'src/environments/environment';
|
|||||||
|
|
||||||
import { Choice } from './choice.model';
|
import { Choice } from './choice.model';
|
||||||
import { Comment } from './comment.model';
|
import { Comment } from './comment.model';
|
||||||
import { Configuration } from './configuration.model';
|
import { PollConfiguration } from './configuration.model';
|
||||||
import { User } from './user.model';
|
import { User } from './user.model';
|
||||||
|
|
||||||
export class Poll {
|
export class Poll {
|
||||||
constructor(
|
constructor(
|
||||||
public owner: User,
|
public owner: User,
|
||||||
public slug: string,
|
public slug: string,
|
||||||
public question: string,
|
public title: string,
|
||||||
public description?: string,
|
public description?: string,
|
||||||
public configuration: Configuration = new Configuration(),
|
public configuration: PollConfiguration = new PollConfiguration(),
|
||||||
public comments: Comment[] = [],
|
public comments: Comment[] = [],
|
||||||
public choices: Choice[] = []
|
public choices: Choice[] = []
|
||||||
) {}
|
) {}
|
||||||
@ -25,12 +25,12 @@ export class Poll {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static adaptFromLocalJsonServer(
|
public static adaptFromLocalJsonServer(
|
||||||
item: Pick<Poll, 'owner' | 'question' | 'description' | 'slug' | 'configuration' | 'comments' | 'choices'>
|
item: Pick<Poll, 'owner' | 'title' | 'description' | 'slug' | 'configuration' | 'comments' | 'choices'>
|
||||||
): Poll {
|
): Poll {
|
||||||
const poll = new Poll(
|
const poll = new Poll(
|
||||||
new User(item.owner.pseudo, item.owner.email, undefined),
|
new User(item.owner.pseudo, item.owner.email, undefined),
|
||||||
item.slug,
|
item.slug,
|
||||||
item.question,
|
item.title,
|
||||||
item.description,
|
item.description,
|
||||||
item.configuration,
|
item.configuration,
|
||||||
item.comments
|
item.comments
|
||||||
|
@ -6,8 +6,7 @@
|
|||||||
|
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<app-stepper [poll]="poll"></app-stepper>
|
<app-admin-form [poll]="poll"></app-admin-form>
|
||||||
<h1>Administration de sondage</h1>
|
|
||||||
<h1 class="title is-1"><i class="fa fa-calendar" aria-hidden="true"></i> {{ 'dates.title' | translate }}</h1>
|
<h1 class="title is-1"><i class="fa fa-calendar" aria-hidden="true"></i> {{ 'dates.title' | translate }}</h1>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -8,9 +8,10 @@ import { AdministrationRoutingModule } from './administration-routing.module';
|
|||||||
import { AdministrationComponent } from './administration.component';
|
import { AdministrationComponent } from './administration.component';
|
||||||
import { StepperComponent } from './stepper/stepper.component';
|
import { StepperComponent } from './stepper/stepper.component';
|
||||||
import { NamingComponent } from './naming/naming.component';
|
import { NamingComponent } from './naming/naming.component';
|
||||||
|
import { FormComponent } from './form/form.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [AdministrationComponent, StepperComponent, NamingComponent],
|
declarations: [AdministrationComponent, StepperComponent, NamingComponent, FormComponent],
|
||||||
imports: [
|
imports: [
|
||||||
AdministrationRoutingModule,
|
AdministrationRoutingModule,
|
||||||
CommonModule,
|
CommonModule,
|
||||||
|
139
src/app/features/administration/form/form.component.html
Normal file
139
src/app/features/administration/form/form.component.html
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
<div class="admin-form">
|
||||||
|
<h1 i18n>
|
||||||
|
{{ 'creation.title' | translate }}
|
||||||
|
</h1>
|
||||||
|
<span class="pre-selector" i18n>
|
||||||
|
{{ 'creation.want' | translate }}
|
||||||
|
</span>
|
||||||
|
<button class="btn btn--warning">
|
||||||
|
Reset all
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="simple">
|
||||||
|
<form [formGroup]="pollFormGroup">
|
||||||
|
<label for="title">Titre</label>
|
||||||
|
<input
|
||||||
|
#title
|
||||||
|
matInput
|
||||||
|
placeholder="title posée, sujet"
|
||||||
|
formControlName="title"
|
||||||
|
id="title"
|
||||||
|
autofocus="autofocus"
|
||||||
|
(change)="updateSlug()"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
mat-button
|
||||||
|
*ngIf="title.value"
|
||||||
|
matSuffix
|
||||||
|
mat-icon-button
|
||||||
|
aria-label="Clear"
|
||||||
|
(click)="title.value = ''"
|
||||||
|
>
|
||||||
|
<i class="fa fa-close"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<label for="descr">Description</label>
|
||||||
|
<textarea
|
||||||
|
#description
|
||||||
|
matInput
|
||||||
|
id="descr"
|
||||||
|
placeholder="Description"
|
||||||
|
formControlName="description"
|
||||||
|
required
|
||||||
|
></textarea>
|
||||||
|
<button
|
||||||
|
mat-button
|
||||||
|
*ngIf="description.value"
|
||||||
|
matSuffix
|
||||||
|
mat-icon-button
|
||||||
|
aria-label="Clear"
|
||||||
|
(click)="description.value = ''"
|
||||||
|
>
|
||||||
|
<i class="fa fa-close"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<label for="slug"
|
||||||
|
>Url pour les participants
|
||||||
|
|
||||||
|
<button
|
||||||
|
mat-button
|
||||||
|
*ngIf="slug.value"
|
||||||
|
matSuffix
|
||||||
|
mat-icon-button
|
||||||
|
aria-label="Clear"
|
||||||
|
(click)="slug.value = ''"
|
||||||
|
>
|
||||||
|
<i class="fa fa-close"></i>
|
||||||
|
</button>
|
||||||
|
</label>
|
||||||
|
<br />
|
||||||
|
<span
|
||||||
|
>{{ urlPrefix }}
|
||||||
|
<strong>
|
||||||
|
{{ slug.value }}
|
||||||
|
</strong>
|
||||||
|
</span>
|
||||||
|
<input #slug matInput id="slug" placeholder="Url" formControlName="slug" required />
|
||||||
|
<br />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="complete" *ngIf="longFormVersionEnabled">
|
||||||
|
<form [formGroup]="configurationFormGroup">
|
||||||
|
<h2>Version complète du formulaire</h2>
|
||||||
|
<mat-form-field appearance="outline" class="is-flex">
|
||||||
|
<mat-label>Nombre de jours avant expiration</mat-label>
|
||||||
|
<input
|
||||||
|
#expiracy
|
||||||
|
matInput
|
||||||
|
type="number"
|
||||||
|
placeholder="Nombre de jours avant expiration"
|
||||||
|
formControlName="expiracyNumberOfDays"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
mat-button
|
||||||
|
*ngIf="expiracy.value"
|
||||||
|
matSuffix
|
||||||
|
mat-icon-button
|
||||||
|
aria-label="Clear"
|
||||||
|
(click)="expiracy.value = ''"
|
||||||
|
>
|
||||||
|
<i class="fa fa-close"></i>
|
||||||
|
</button>
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-checkbox class="is-flex" formControlName="areResultsPublic">
|
||||||
|
Les participants pourront consulter les résultats
|
||||||
|
</mat-checkbox>
|
||||||
|
<mat-checkbox class="is-flex" formControlName="isAboutDate">
|
||||||
|
Les choix possibles concerneront des dates
|
||||||
|
</mat-checkbox>
|
||||||
|
<mat-checkbox class="is-flex" formControlName="isProtectedByPassword">
|
||||||
|
Le sondage sera protégé par un mot de passe
|
||||||
|
</mat-checkbox>
|
||||||
|
<mat-checkbox class="is-flex" formControlName="isOwnerNotifiedByEmailOnNewVote">
|
||||||
|
Vous recevrez un mail à chaque nouvelle participation
|
||||||
|
</mat-checkbox>
|
||||||
|
<mat-checkbox class="is-flex" formControlName="isOwnerNotifiedByEmailOnNewComment">
|
||||||
|
Vous recevrez un mail à chaque nouveau commentaire
|
||||||
|
</mat-checkbox>
|
||||||
|
<mat-checkbox class="is-flex" formControlName="isMaybeAnswerAvailable">
|
||||||
|
La réponse « peut-être » sera disponible
|
||||||
|
</mat-checkbox>
|
||||||
|
|
||||||
|
<button
|
||||||
|
mat-button
|
||||||
|
(click)="createPoll()"
|
||||||
|
[disabled]="!pollFormGroup.valid || !configurationFormGroup.valid"
|
||||||
|
>
|
||||||
|
Enregistrer le sondage
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<pre class="debug padded warning">
|
||||||
|
poll :
|
||||||
|
{{ poll | json }}
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
</div>
|
7
src/app/features/administration/form/form.component.scss
Normal file
7
src/app/features/administration/form/form.component.scss
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
:host {
|
||||||
|
input,
|
||||||
|
textarea {
|
||||||
|
padding: 0.5em;
|
||||||
|
border: solid #eee;
|
||||||
|
}
|
||||||
|
}
|
24
src/app/features/administration/form/form.component.spec.ts
Normal file
24
src/app/features/administration/form/form.component.spec.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { FormComponent } from './form.component';
|
||||||
|
|
||||||
|
describe('FormComponent', () => {
|
||||||
|
let component: FormComponent;
|
||||||
|
let fixture: ComponentFixture<FormComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [FormComponent],
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(FormComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
69
src/app/features/administration/form/form.component.ts
Normal file
69
src/app/features/administration/form/form.component.ts
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
|
import { Poll } from '../../../core/models/poll.model';
|
||||||
|
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
|
import { UuidService } from '../../../core/services/uuid.service';
|
||||||
|
import { DateService } from '../../../core/services/date.service';
|
||||||
|
import { ApiService } from '../../../core/services/api.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-admin-form',
|
||||||
|
templateUrl: './form.component.html',
|
||||||
|
styleUrls: ['./form.component.scss'],
|
||||||
|
})
|
||||||
|
export class FormComponent implements OnInit {
|
||||||
|
@Input()
|
||||||
|
public poll?: Poll;
|
||||||
|
public pollFormGroup: FormGroup;
|
||||||
|
public configurationFormGroup: FormGroup;
|
||||||
|
public longFormVersionEnabled = true;
|
||||||
|
|
||||||
|
public urlPrefix: string = window.location.origin + '/participation/';
|
||||||
|
|
||||||
|
constructor(private fb: FormBuilder, private uuidService: UuidService, private apiService: ApiService) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.pollFormGroup = this.fb.group({
|
||||||
|
title: [this.poll ? this.poll.title : '', [Validators.required]],
|
||||||
|
slug: [this.poll ? this.poll.slug : this.uuidService.getUUID(), [Validators.required]],
|
||||||
|
description: [this.poll ? this.poll.description : ''],
|
||||||
|
});
|
||||||
|
|
||||||
|
this.configurationFormGroup = this.fb.group({
|
||||||
|
isAboutDate: [this.poll ? this.poll.configuration.isAboutDate : false, [Validators.required]],
|
||||||
|
isProtectedByPassword: [
|
||||||
|
this.poll ? this.poll.configuration.isProtectedByPassword : false,
|
||||||
|
[Validators.required],
|
||||||
|
],
|
||||||
|
isOwnerNotifiedByEmailOnNewVote: [
|
||||||
|
this.poll ? this.poll.configuration.isOwnerNotifiedByEmailOnNewVote : false,
|
||||||
|
[Validators.required],
|
||||||
|
],
|
||||||
|
isOwnerNotifiedByEmailOnNewComment: [
|
||||||
|
this.poll ? this.poll.configuration.isOwnerNotifiedByEmailOnNewComment : false,
|
||||||
|
[Validators.required],
|
||||||
|
],
|
||||||
|
isMaybeAnswerAvailable: [
|
||||||
|
this.poll ? this.poll.configuration.isMaybeAnswerAvailable : false,
|
||||||
|
[Validators.required],
|
||||||
|
],
|
||||||
|
areResultsPublic: [this.poll ? this.poll.configuration.areResultsPublic : true, [Validators.required]],
|
||||||
|
expiracyNumberOfDays: [
|
||||||
|
this.poll ? DateService.diffInDays(new Date(), this.poll.configuration.expires) : 60,
|
||||||
|
[Validators.required],
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public createPoll(): void {
|
||||||
|
if (this.pollFormGroup.valid && this.configurationFormGroup.valid) {
|
||||||
|
console.log('Le sondage est correctement rempli, prêt à enregistrer.');
|
||||||
|
// TODO : save the poll
|
||||||
|
this.apiService.createPoll(this.poll);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public updateSlug() {
|
||||||
|
let newValueFormatted = 'TODO';
|
||||||
|
}
|
||||||
|
// this.pollFormGroup.setValue({'slug' : newValueFormatted);
|
||||||
|
// }
|
||||||
|
}
|
@ -4,7 +4,7 @@
|
|||||||
<ng-template matStepLabel>Informations du sondage</ng-template>
|
<ng-template matStepLabel>Informations du sondage</ng-template>
|
||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline">
|
||||||
<mat-label>Titre</mat-label>
|
<mat-label>Titre</mat-label>
|
||||||
<input #question matInput placeholder="Question posée, sujet" formControlName="question" required />
|
<input #title matInput placeholder="Question posée, sujet" formControlName="title" required />
|
||||||
<button
|
<button
|
||||||
mat-button
|
mat-button
|
||||||
*ngIf="question.value"
|
*ngIf="question.value"
|
||||||
@ -60,7 +60,7 @@
|
|||||||
|
|
||||||
<mat-step [stepControl]="configurationFormGroup">
|
<mat-step [stepControl]="configurationFormGroup">
|
||||||
<form [formGroup]="configurationFormGroup">
|
<form [formGroup]="configurationFormGroup">
|
||||||
<ng-template matStepLabel>Configuration du sondage</ng-template>
|
<ng-template matStepLabel>PollConfiguration du sondage</ng-template>
|
||||||
<mat-form-field appearance="outline" class="is-flex">
|
<mat-form-field appearance="outline" class="is-flex">
|
||||||
<mat-label>Nombre de jours avant expiration</mat-label>
|
<mat-label>Nombre de jours avant expiration</mat-label>
|
||||||
<input
|
<input
|
||||||
|
@ -24,12 +24,13 @@ export class StepperComponent implements OnInit {
|
|||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.pollFormGroup = this.fb.group({
|
this.pollFormGroup = this.fb.group({
|
||||||
question: [this.poll ? this.poll.question : '', [Validators.required]],
|
question: [this.poll ? this.poll.title : '', [Validators.required]],
|
||||||
slug: [this.poll ? this.poll.slug : this.uuidService.getUUID(), [Validators.required]],
|
slug: [this.poll ? this.poll.slug : this.uuidService.getUUID(), [Validators.required]],
|
||||||
description: [this.poll ? this.poll.description : ''],
|
description: [this.poll ? this.poll.description : ''],
|
||||||
});
|
});
|
||||||
|
|
||||||
this.configurationFormGroup = this.fb.group({
|
this.configurationFormGroup = this.fb.group({
|
||||||
|
title: [this.poll ? this.poll.configuration : false, [Validators.required]],
|
||||||
isAboutDate: [this.poll ? this.poll.configuration.isAboutDate : false, [Validators.required]],
|
isAboutDate: [this.poll ? this.poll.configuration.isAboutDate : false, [Validators.required]],
|
||||||
isProtectedByPassword: [
|
isProtectedByPassword: [
|
||||||
this.poll ? this.poll.configuration.isProtectedByPassword : false,
|
this.poll ? this.poll.configuration.isProtectedByPassword : false,
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
<div class="column">
|
<div class="column">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<header class="card-header">
|
<header class="card-header">
|
||||||
<p class="card-header-title">{{ poll.question }}</p>
|
<p class="card-header-title">{{ poll.title }}</p>
|
||||||
<p class="card-header-icon">author : {{ poll.owner?.pseudo }}</p>
|
<p class="card-header-icon">author : {{ poll.owner?.pseudo }}</p>
|
||||||
</header>
|
</header>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
|
@ -2,7 +2,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core';
|
|||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
|
|
||||||
import { Configuration } from '../../core/models/configuration.model';
|
import { PollConfiguration } from '../../core/models/configuration.model';
|
||||||
import { Poll } from '../../core/models/poll.model';
|
import { Poll } from '../../core/models/poll.model';
|
||||||
import { ModalService } from '../../core/services/modal.service';
|
import { ModalService } from '../../core/services/modal.service';
|
||||||
import { UserService } from '../../core/services/user.service';
|
import { UserService } from '../../core/services/user.service';
|
||||||
@ -33,7 +33,7 @@ export class ConsultationComponent implements OnInit, OnDestroy {
|
|||||||
this.routeSubscription = this.activatedRoute.data.subscribe((data: { poll: Poll }) => {
|
this.routeSubscription = this.activatedRoute.data.subscribe((data: { poll: Poll }) => {
|
||||||
if (data.poll) {
|
if (data.poll) {
|
||||||
this.poll = data.poll;
|
this.poll = data.poll;
|
||||||
this.isArchived = Configuration.isArchived(data.poll.configuration);
|
this.isArchived = PollConfiguration.isArchived(data.poll.configuration);
|
||||||
} else {
|
} else {
|
||||||
this.router.navigate(['/page-not-found']);
|
this.router.navigate(['/page-not-found']);
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
<thead></thead>
|
<thead></thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr *ngFor="let poll of (_user | async)?.polls">
|
<tr *ngFor="let poll of (_user | async)?.polls">
|
||||||
<th>{{ poll.question }}</th>
|
<th>{{ poll.title }}</th>
|
||||||
<td>
|
<td>
|
||||||
<a routerLink="{{ '../../poll/' + poll.slug + '/consultation' }}">
|
<a routerLink="{{ '../../poll/' + poll.slug + '/consultation' }}">
|
||||||
{{ poll.slug }}
|
{{ poll.slug }}
|
||||||
|
Loading…
Reference in New Issue
Block a user