Compare commits

...

3 Commits

12 changed files with 148 additions and 75 deletions

View File

@ -6,7 +6,7 @@ import { environment } from '../environments/environment';
import { Theme } from './core/enums/theme.enum'; import { Theme } from './core/enums/theme.enum';
import { LanguageService } from './core/services/language.service'; import { LanguageService } from './core/services/language.service';
import { ThemeService } from './core/services/theme.service'; import { ThemeService } from './core/services/theme.service';
import { NavigationEnd, Router, RouterOutlet } from '@angular/router'; import { NavigationEnd, Route, Router, RouterOutlet } from '@angular/router';
import { slideInAnimation } from './shared/animations/main'; import { slideInAnimation } from './shared/animations/main';
import { FramaKeyboardShortcuts } from './shared/shortcuts/main'; import { FramaKeyboardShortcuts } from './shared/shortcuts/main';
import { ShortcutEventOutput, ShortcutInput } from 'ng-keyboard-shortcuts'; import { ShortcutEventOutput, ShortcutInput } from 'ng-keyboard-shortcuts';
@ -36,7 +36,19 @@ export class AppComponent implements OnInit, OnDestroy, AfterViewInit {
private languageService: LanguageService // private mockingService: MockingService private languageService: LanguageService // private mockingService: MockingService
) {} ) {}
printpath(parent: string, config: Route[]) {
for (let i = 0; i < config.length; i++) {
const route = config[i];
console.info(parent + '/' + route.path);
if (route.children) {
const currentPath = route.path ? parent + '/' + route.path : parent;
this.printpath(currentPath, route.children);
}
}
}
ngOnInit(): void { ngOnInit(): void {
this.printpath('', this.router.config);
this.router.events.subscribe((evt) => { this.router.events.subscribe((evt) => {
if (!(evt instanceof NavigationEnd)) { if (!(evt instanceof NavigationEnd)) {
return; return;

View File

@ -30,7 +30,6 @@ import { CipheringComponent } from './features/shared/components/ui/static-pages
import { ErrorsListComponent } from './features/shared/components/ui/form/errors-list/errors-list.component'; import { ErrorsListComponent } from './features/shared/components/ui/form/errors-list/errors-list.component';
import { KeyboardShortcutsModule } from 'ng-keyboard-shortcuts'; import { KeyboardShortcutsModule } from 'ng-keyboard-shortcuts';
import { AdministrationModule } from './features/administration/administration.module'; import { AdministrationModule } from './features/administration/administration.module';
import { ShortcutsHelpComponent } from './features/shared/components/ui/shortcuts-help/shortcuts-help.component';
registerLocaleData(localeEn, 'en-EN'); registerLocaleData(localeEn, 'en-EN');
registerLocaleData(localeFr, 'fr-FR'); registerLocaleData(localeFr, 'fr-FR');

View File

@ -61,13 +61,9 @@
voici des liens de démonstration issus des fixtures Doctrine de date-poll-api. voici des liens de démonstration issus des fixtures Doctrine de date-poll-api.
<div class="padded"> <div class="padded">
<div class=""> <div class="">
<a <a class="navbar-item" [routerLink]="['/poll/demo/consultation']" routerLinkActive="is-primary">
class="navbar-item"
[routerLink]="['/poll/le-titre-de-demo-oh-oh/consultation']"
routerLinkActive="is-primary"
>
<em> <em>
le-titre-de-demo-oh-oh demo
</em> </a </em> </a
><a ><a
class="navbar-item" class="navbar-item"
@ -90,9 +86,22 @@
</a> </a>
<a <a
class="navbar-item" class="navbar-item"
[routerLink]="['/poll/citron/consultation/1c01ed9c94fc640a1be864f197ff808c']" [routerLink]="[
'/admin/9S75b70ECXI5J5xDc058d3H40H9r2CHfO0Kj8T02EK2U8rY8fYTn-eS659j2Dhp794Oa6R1b9V70e3WGaE30iD9h45zwdm76C85SWB4LcUCrc7e0Ncc0'
]"
routerLinkActive="is-primary" routerLinkActive="is-primary"
> >
<i class="fa fa-gears"></i> administrer le sondage
<em>
aujourdhui-ou-demain
</em>
</a>
<a
class="navbar-item"
[routerLink]="['/poll/citron/consultation/secure/1c01ed9c94fc640a1be864f197ff808c']"
routerLinkActive="is-primary"
>
<i class="fa fa-key-modern"></i>
<em> <em>
citron citron
</em> </em>

View File

@ -4,6 +4,7 @@ import { Owner } from './owner.model';
export class Stack { export class Stack {
public id: number; public id: number;
public poll_custom_url: string; public poll_custom_url: string;
public pass_hash: string;
public pseudo = 'Choque Nourrice'; public pseudo = 'Choque Nourrice';
public comment = 'Le beau commentaire de Choque Nourrice'; public comment = 'Le beau commentaire de Choque Nourrice';
public owner: Owner = new Owner(); public owner: Owner = new Owner();

View File

@ -93,20 +93,15 @@ export class ApiService {
* @param vote_stack * @param vote_stack
*/ */
public sendNewVoteStackOfPoll(vote_stack: Stack): Promise<void> { public sendNewVoteStackOfPoll(vote_stack: Stack): Promise<void> {
// api_new_vote_stack POST ANY ANY /api/v1/poll/{id}/answer // const headers = ApiService.makeHeaders(vote_stack);
console.log('vote_stack', vote_stack);
console.log('this.baseHref', this.baseHref);
const headers = ApiService.makeHeaders(vote_stack);
console.log('headers', headers);
const url = `${this.baseHref}/vote-stack/`; const url = `${this.baseHref}/vote-stack/`;
const axiosconf = { // const axiosconf = {
url, // url,
method: 'POST', // method: 'POST',
body: vote_stack, // body: vote_stack,
headers, // headers,
}; // };
return this.axiosInstance.post(url, vote_stack); return this.axiosInstance.post(url, vote_stack);
} }

View File

@ -1,6 +1,6 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router'; import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router';
import { BehaviorSubject, Observable, Subscription } from 'rxjs'; import { BehaviorSubject, Observable } from 'rxjs';
import { Answer } from '../enums/answer.enum'; import { Answer } from '../enums/answer.enum';
import { Choice } from '../models/choice.model'; import { Choice } from '../models/choice.model';
@ -15,6 +15,8 @@ import { environment } from '../../../environments/environment';
import { StorageService } from './storage.service'; import { StorageService } from './storage.service';
import { Title } from '@angular/platform-browser'; import { Title } from '@angular/platform-browser';
import { DateUtilitiesService } from './date.utilities.service'; import { DateUtilitiesService } from './date.utilities.service';
import { Stack } from '../models/stack.model';
import { Vote } from '../models/vote.model';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
@ -22,6 +24,7 @@ import { DateUtilitiesService } from './date.utilities.service';
export class PollService implements Resolve<Poll> { export class PollService implements Resolve<Poll> {
_poll: BehaviorSubject<Poll | undefined> = new BehaviorSubject<Poll | undefined>(undefined); _poll: BehaviorSubject<Poll | undefined> = new BehaviorSubject<Poll | undefined>(undefined);
public readonly poll: Observable<Poll | undefined> = this._poll.asObservable(); public readonly poll: Observable<Poll | undefined> = this._poll.asObservable();
public pass_hash: string;
constructor( constructor(
private http: HttpClient, private http: HttpClient,
@ -52,10 +55,17 @@ export class PollService implements Resolve<Poll> {
!this._poll.getValue().custom_url || !this._poll.getValue().custom_url ||
this._poll.getValue().custom_url !== wantedcustom_url this._poll.getValue().custom_url !== wantedcustom_url
) { ) {
await this.loadPollBycustom_url(wantedcustom_url); if (this.pass_hash) {
this.storageService.vote_stack.pass_hash = this.pass_hash;
await this.loadPollBycustom_urlWithPasswordHash(wantedcustom_url, this.pass_hash);
} else {
await this.loadPollBycustom_url(wantedcustom_url);
}
} }
if (this._poll.getValue()) { const loadedPoll = this._poll.getValue();
return this._poll.getValue(); if (loadedPoll) {
this.storageService.vote_stack.poll_custom_url = loadedPoll.custom_url;
return loadedPoll;
} else { } else {
this.router.navigate(['page-not-found']); this.router.navigate(['page-not-found']);
return; return;
@ -116,13 +126,14 @@ export class PollService implements Resolve<Poll> {
public updateCurrentPoll(poll: Poll): void { public updateCurrentPoll(poll: Poll): void {
console.log('this.storageService.vote_stack.id', this.storageService.vote_stack.id); console.log('this.storageService.vote_stack.id', this.storageService.vote_stack.id);
if ( if (!this.storageService.vote_stack.id || this.storageService.vote_stack.poll_custom_url !== poll.custom_url) {
!this.storageService.vote_stack.id
// || this.storageService.vote_stack.poll_custom_url !== poll.custom_url
) {
console.log('set base choices', poll.choices); console.log('set base choices', poll.choices);
// set the choices only the first time the poll loads // set the choices only the first time the poll loads, or if we changed the poll
this.storageService.setChoicesForVoteStack(poll.choices); console.log(
'this.storageService.vote_stack.poll_custom_url',
this.storageService.vote_stack.poll_custom_url
);
// this.storageService.setChoicesForVoteStack(poll.choices);
} }
this.toastService.display('sondage bien mis à jour', 'success'); this.toastService.display('sondage bien mis à jour', 'success');
@ -150,7 +161,11 @@ export class PollService implements Resolve<Poll> {
return this.convertTextToSlug(str) + '-' + this.uuidService.getUUID(); return this.convertTextToSlug(str) + '-' + this.uuidService.getUUID();
} }
public convertTextToSlug(str: string) { /**
* convert a text to a slug
* @param str
*/
public convertTextToSlug(str: string): string {
str = str.trim(); str = str.trim();
str = str.replace(/^\s+|\s+$/g, ''); // trim str = str.replace(/^\s+|\s+$/g, ''); // trim
str = str.toLowerCase(); str = str.toLowerCase();
@ -253,4 +268,33 @@ export class PollService implements Resolve<Poll> {
// TODO handle pass access // TODO handle pass access
return url; return url;
} }
/**
* enrich vote stack with missing default votes
* @param vote_stack
*/
enrichVoteStackWithCurrentPollChoicesDefaultVotes(vote_stack: Stack) {
if (this._poll && this._poll.getValue) {
const polltemp = this._poll.getValue();
polltemp.choices.map((choice) => {
// for each vote, if it has the choice_id, do nothing, else, add a default vote
if (!this.findExistingVoteFromChoiceId(choice.id, vote_stack.votes)) {
vote_stack.votes.push(new Vote(choice.id));
}
});
}
}
/**
* find an existing vote in vote_stack from its choice_id
* @param choice_id
* @param votes
*/
findExistingVoteFromChoiceId(choice_id: number, votes: Vote[]) {
return votes.find((vote: Vote) => {
if (vote.choice_id === choice_id) {
return vote;
}
});
}
} }

View File

@ -65,7 +65,8 @@ export class StorageService {
* @param choices_list * @param choices_list
*/ */
setChoicesForVoteStack(choices_list: Choice[]) { setChoicesForVoteStack(choices_list: Choice[]) {
// text choices // change only if the poll custom_url changed or if there is no stack id for this poll
if (!this.vote_stack.id) { if (!this.vote_stack.id) {
this.vote_stack = new Stack(); this.vote_stack = new Stack();
@ -132,11 +133,10 @@ export class StorageService {
/** /**
* update vote stack from the backend * update vote stack from the backend
* @param resp * @param voteStack
*/ */
mapVotes(resp) { mapVotes(voteStack: Stack) {
console.log('mapVotes resp.data', resp.data); console.log('mapVotes voteStack', voteStack);
this.vote_stack.owner = resp.data.owner; this.vote_stack = voteStack;
this.vote_stack.id = resp.data.id;
} }
} }

View File

@ -2,14 +2,16 @@ import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router'; import { RouterModule, Routes } from '@angular/router';
import { ConsultationComponent } from './consultation.component'; import { ConsultationComponent } from './consultation.component';
import { WipTodoComponent } from '../../shared/components/ui/wip-todo/wip-todo.component';
const routes: Routes = [ const routes: Routes = [
{ path: 'secure/:pass_hash', component: ConsultationComponent },
{ {
path: '', path: '',
component: ConsultationComponent, component: ConsultationComponent,
children: [ children: [
{ path: '/', component: ConsultationComponent }, { path: 'simple', component: WipTodoComponent },
{ path: '/:pass_hash', component: ConsultationComponent }, { path: 'table', component: WipTodoComponent },
], ],
}, },
]; ];

View File

@ -1,4 +1,3 @@
<section class="loading_poll" *ngIf="fetching"></section>
<section class="poll_loaded padded" *ngIf="!fetching && poll"> <section class="poll_loaded padded" *ngIf="!fetching && poll">
<!-- messages--> <!-- messages-->
@ -7,6 +6,11 @@
⚰️ Ce sondage a expiré, il n'est plus possible d'y ajouter de votes ou de commentaires ⚰️ Ce sondage a expiré, il n'est plus possible d'y ajouter de votes ou de commentaires
</div> </div>
</div> </div>
<div class="message is-warning" *ngIf="poll && poll.admin_key">
<div class="message-body">
vous êtes admin de ce sondage
</div>
</div>
<div class="message is-info" *ngIf="poll.modification_policy == 'self'"> <div class="message is-info" *ngIf="poll.modification_policy == 'self'">
<div class="message-body"> <div class="message-body">
@ -14,6 +18,8 @@
</div> </div>
</div> </div>
<router-outlet></router-outlet>
<!-- actions--> <!-- actions-->
<!-- affichage des possibilités de réponse --> <!-- affichage des possibilités de réponse -->
@ -109,11 +115,12 @@
aucun vote pour le moment aucun vote pour le moment
</div> </div>
</div> </div>
<section class="loading_poll" *ngIf="fetching">
<i class="fa fa-spinner fa-4x"></i>
</section>
<button <button
class="btn btn-block submit-votestack" class="btn btn-block submit-votestack is-primary"
(click)="addVoteStack()" (click)="addVoteStack()"
[disabled]="!myTempVoteStack"
[ngClass]="{ 'btn--primary': myTempVoteStack }"
*ngIf="!storageService.vote_stack || !storageService.vote_stack.id" *ngIf="!storageService.vote_stack || !storageService.vote_stack.id"
> >
<i class="fa fa-paper-plane" aria-hidden="true"></i> Envoyer <i class="fa fa-paper-plane" aria-hidden="true"></i> Envoyer
@ -140,8 +147,6 @@
<button <button
class="btn btn-block submit-votestack" class="btn btn-block submit-votestack"
(click)="addVoteStack()" (click)="addVoteStack()"
[disabled]="!myTempVoteStack"
[ngClass]="{ 'btn--primary': myTempVoteStack }"
*ngIf="!storageService.vote_stack || !storageService.vote_stack.id" *ngIf="!storageService.vote_stack || !storageService.vote_stack.id"
> >
<i class="fa fa-paper-plane" aria-hidden="true"></i> Envoyer <i class="fa fa-paper-plane" aria-hidden="true"></i> Envoyer

View File

@ -0,0 +1,6 @@
.loading_poll {
position: fixed;
bottom: 5em;
left: 1em;
z-index: 10;
}

View File

@ -2,17 +2,12 @@ import { Component, OnDestroy, OnInit } from '@angular/core';
import { ActivatedRoute, ParamMap, Router } from '@angular/router'; import { ActivatedRoute, ParamMap, Router } from '@angular/router';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { Poll } from '../../core/models/poll.model'; import { Poll } from '../../core/models/poll.model';
import { ModalService } from '../../core/services/modal.service';
import { PollService } from '../../core/services/poll.service'; import { PollService } from '../../core/services/poll.service';
import { DateService } from '../../core/services/date.service'; import { DateService } from '../../core/services/date.service';
import { PollUtilitiesService } from '../../core/services/poll.utilities.service'; import { PollUtilitiesService } from '../../core/services/poll.utilities.service';
import { StorageService } from '../../core/services/storage.service'; import { StorageService } from '../../core/services/storage.service';
import { ApiService } from '../../core/services/api.service'; import { ApiService } from '../../core/services/api.service';
import { Stack } from '../../core/models/stack.model';
import { environment } from '../../../environments/environment';
import { ToastService } from '../../core/services/toast.service'; import { ToastService } from '../../core/services/toast.service';
import { AxiosResponse } from 'axios';
import { HttpResponse } from '@angular/common/http';
@Component({ @Component({
selector: 'app-consultation', selector: 'app-consultation',
@ -23,16 +18,11 @@ export class ConsultationComponent implements OnInit, OnDestroy {
public isCompactMode = true; public isCompactMode = true;
public poll: Poll; public poll: Poll;
public pollSlug: string; public pollSlug: string;
public passHash: string; public pass_hash: string;
public fetching = true; public fetching = true;
public isArchived: boolean; public isArchived: boolean;
public isAdmin: boolean;
public myVoteStack: any = {
id: '',
};
public myTempVoteStack: any = {
id: '',
};
private routeSubscription: Subscription; private routeSubscription: Subscription;
window: any; window: any;
@ -57,6 +47,7 @@ export class ConsultationComponent implements OnInit, OnDestroy {
if (newpoll) { if (newpoll) {
this.isArchived = new Date(newpoll.expiracy_date) < new Date(); this.isArchived = new Date(newpoll.expiracy_date) < new Date();
this.poll.is_archived = this.isArchived; this.poll.is_archived = this.isArchived;
this.isAdmin = this.poll.admin_key !== null;
this.poll.choices_grouped.map((elem) => (elem.subSetToYes = false)); this.poll.choices_grouped.map((elem) => (elem.subSetToYes = false));
} }
}); });
@ -64,16 +55,24 @@ export class ConsultationComponent implements OnInit, OnDestroy {
this._Activatedroute.paramMap.subscribe((params: ParamMap) => { this._Activatedroute.paramMap.subscribe((params: ParamMap) => {
console.log('params _Activatedroute', params); console.log('params _Activatedroute', params);
this.pollSlug = params.get('custom_url'); this.pollSlug = params.get('custom_url');
this.passHash = params.get('pass_hash'); this.pass_hash = params.get('pass_hash');
// if (this.passHash) { console.log('this.pass_hash ', this.pass_hash);
// this.pollService.loadPollBycustom_urlWithPasswordHash(this.pollSlug, this.passHash); if (this.pass_hash) {
// } else { this.pollService.loadPollBycustom_urlWithPasswordHash(this.pollSlug, this.pass_hash).then((resp) => {
this.pollService.loadPollBycustom_url(this.pollSlug).then((resp) => { console.log('resp', resp);
console.log('resp', resp); this.fetching = false;
this.fetching = false; this.storageService.vote_stack.id = null;
}); this.storageService.setChoicesForVoteStack(this.pollService._poll.getValue().choices);
// } });
} else {
this.pollService.loadPollBycustom_url(this.pollSlug).then((resp) => {
console.log('resp', resp);
this.fetching = false;
this.storageService.vote_stack.id = null;
this.storageService.setChoicesForVoteStack(this.pollService._poll.getValue().choices);
});
}
}); });
} }
@ -109,6 +108,7 @@ export class ConsultationComponent implements OnInit, OnDestroy {
*/ */
addVoteStack(): void { addVoteStack(): void {
this.storageService.vote_stack.poll_custom_url = this.poll.custom_url; this.storageService.vote_stack.poll_custom_url = this.poll.custom_url;
this.pollService.pass_hash = this.pass_hash;
this.toastService.display('envoi du vote ....'); this.toastService.display('envoi du vote ....');
this.api this.api
@ -127,8 +127,13 @@ export class ConsultationComponent implements OnInit, OnDestroy {
*/ */
storeVoteStackAndReloadPoll(voteStack: any) { storeVoteStackAndReloadPoll(voteStack: any) {
if (voteStack.status == 200) { if (voteStack.status == 200) {
this.storageService.mapVotes(voteStack); this.storageService.mapVotes(voteStack.data);
this.pollService.loadPollBycustom_url(this.poll.custom_url); this.pollService.enrichVoteStackWithCurrentPollChoicesDefaultVotes(this.storageService.vote_stack);
if (this.pass_hash) {
this.pollService.loadPollBycustom_urlWithPasswordHash(this.poll.custom_url, this.pass_hash);
} else {
this.pollService.loadPollBycustom_url(this.poll.custom_url);
}
} else { } else {
this.toastService.display('erreur à l enregistrement'); this.toastService.display('erreur à l enregistrement');
} }

View File

@ -23,7 +23,7 @@ export const routes: Routes = [
// resolve: { poll: PollService }, // resolve: { poll: PollService },
}, },
{ {
path: 'poll/:custom_url/administration', path: 'admin/:admin_key',
loadChildren: () => loadChildren: () =>
import('./features/administration/administration.module').then((m) => m.AdministrationModule), import('./features/administration/administration.module').then((m) => m.AdministrationModule),
// resolve: { poll: PollService }, // resolve: { poll: PollService },
@ -33,11 +33,6 @@ export const routes: Routes = [
loadChildren: () => import('./features/consultation/consultation.module').then((m) => m.ConsultationModule), loadChildren: () => import('./features/consultation/consultation.module').then((m) => m.ConsultationModule),
// resolve: { poll: PollService }, // resolve: { poll: PollService },
}, },
{
path: 'poll/:custom_url/participation',
loadChildren: () => import('./features/participation/participation.module').then((m) => m.ParticipationModule),
resolve: { poll: PollService },
},
{ {
path: 'success', path: 'success',
component: SuccessComponent, component: SuccessComponent,