diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 6a9826c2..7ad5fadf 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -11,6 +11,7 @@ import { slideInAnimation } from './shared/animations/main'; import { FramaKeyboardShortcuts } from './shared/shortcuts/main'; import { ShortcutEventOutput, ShortcutInput } from 'ng-keyboard-shortcuts'; import { PollService } from './core/services/poll.service'; +import { Poll } from './core/models/poll.model'; @Component({ selector: 'app-root', @@ -64,7 +65,10 @@ export class AppComponent implements OnInit, OnDestroy, AfterViewInit { this.appTitle += ' [DEV]'; } - const loadedPoll = this.pollService._poll.getValue(); + let loadedPoll; + if (this.pollService.poll) { + loadedPoll = this.pollService.poll; + } this.titleService.setTitle(this.appTitle + ' - ' + loadedPoll.title); this.languageService.configureAndInitTranslations(); @@ -84,6 +88,7 @@ export class AppComponent implements OnInit, OnDestroy, AfterViewInit { } }); } + ngAfterViewInit(): void { console.log('this.shortcuts', this.shortcuts); this.shortcuts.push( diff --git a/src/app/core/services/poll.service.ts b/src/app/core/services/poll.service.ts index 97e8abb6..44cf869c 100644 --- a/src/app/core/services/poll.service.ts +++ b/src/app/core/services/poll.service.ts @@ -1,54 +1,145 @@ -import { Injectable } from '@angular/core'; -import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router'; -import { BehaviorSubject, Observable } from 'rxjs'; +import { Inject, Injectable } from '@angular/core'; +import { ActivatedRoute, ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router'; +import { BehaviorSubject, Observable, Subscription } from 'rxjs'; import { Answer } from '../enums/answer.enum'; import { Choice } from '../models/choice.model'; import { Poll } from '../models/poll.model'; -import { Owner } from '../models/owner.model'; import { ApiService } from './api.service'; import { ToastService } from './toast.service'; import { UserService } from './user.service'; import { UuidService } from './uuid.service'; import { HttpClient } from '@angular/common/http'; import { environment } from '../../../environments/environment'; -import { StorageService } from './storage.service'; -import { Title } from '@angular/platform-browser'; +import { FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms'; + +import { DOCUMENT } from '@angular/common'; +import { DateChoice, TimeSlices } from '../models/dateChoice.model'; import { DateUtilitiesService } from './date.utilities.service'; +import { Owner } from '../models/owner.model'; import { Stack } from '../models/stack.model'; -import { Vote } from '../models/vote.model'; -import { FormGroup } from '@angular/forms'; @Injectable({ providedIn: 'root', }) export class PollService implements Resolve { - _poll: BehaviorSubject = new BehaviorSubject(undefined); + private _poll: BehaviorSubject = new BehaviorSubject(undefined); public readonly poll: Observable = this._poll.asObservable(); - public pass_hash: string; - public calendar: Date[] = []; public form: FormGroup; public startDateInterval: string; public endDateInterval: string; - public intervalDays: any; + public intervalDays: number = 1; public intervalDaysDefault = 7; + public dateList: DateChoice[] = []; // sets of days as strings, config to set identical time for days in a special days poll + public timeList: TimeSlices[] = []; // ranges of time expressed as strings public previousRouteName: string = '/administration'; public nextRouteName: string = '/administration/step/2'; public step_current: number = 1; public step_max: number = 5; + public round: Function; + public pass_hash: string; + public urlPrefix: string = window.location.origin + '/participation/'; + public advancedDisplayEnabled = false; + public showDateInterval = false; + public allowSeveralHours = false; + public richTextMode = false; + public calendar: any; constructor( private http: HttpClient, private router: Router, private apiService: ApiService, - private storageService: StorageService, private userService: UserService, private uuidService: UuidService, - private dateUtils: DateUtilitiesService, - private titleService: Title, - private toastService: ToastService + private toastService: ToastService, + public DateUtilitiesService: DateUtilitiesService, + public route: ActivatedRoute, + @Inject(DOCUMENT) private document: any, + private fb: FormBuilder ) { - this._poll.next(new Poll(null, 'titre', 'custom-title')); + this.createFormGroup(); + if (environment.autofill) { + this.setDemoValues(); + } else { + this.calendar = [new Date()]; + } + } + + /** + * add example values to the form + */ + setDemoValues(): void { + this.addChoice('orange'); + this.addChoice('raisin'); + this.addChoice('abricot'); + + this.calendar = [ + this.DateUtilitiesService.addDaysToDate(1, new Date()), + this.DateUtilitiesService.addDaysToDate(2, new Date()), + this.DateUtilitiesService.addDaysToDate(3, new Date()), + ]; + this.form.patchValue({ + title: 'mon titre', + description: 'répondez SVP <3 ! *-* ', + custom_url: this.uuidService.getUUID(), + creatorPseudo: 'Chuck Norris', + creatorEmail: 'chucknorris@example.com', + isAboutDate: true, + whoModifiesAnswers: 'everybody', + whoCanChangeAnswers: 'everybody', + isProtectedByPassword: false, + isOwnerNotifiedByEmailOnNewVote: false, + isOwnerNotifiedByEmailOnNewComment: false, + isMaybeAnswerAvailable: false, + richTextMode: false, + areResultsPublic: true, + expiracyNumberOfDays: 60, + }); + this.automaticSlug(); + } + + public enrichVoteStackWithCurrentPollChoicesDefaultVotes(vote_stack: Stack) { + console.log('vote_stack', vote_stack); + this.toastService.display('TODO refill vote stack'); + // this.form.patchValue(vote_stack) + } + + /** + * set the poll slug from other data of the poll + */ + automaticSlug() { + this.form.patchValue({ custom_url: this.makeSlug(this.form) }); + } + + public createFormGroup() { + let form = this.fb.group({ + title: ['', [Validators.required, Validators.minLength(12)]], + creatorPseudo: ['', [Validators.required]], + created_at: [new Date(), [Validators.required]], + creatorEmail: ['', [Validators.required]], + custom_url: [this.uuidService.getUUID(), [Validators.required]], + description: ['', [Validators.required]], + choices: new FormArray([]), + whoModifiesAnswers: ['', [Validators.required]], + whoCanChangeAnswers: ['', [Validators.required]], + isAboutDate: [true, [Validators.required]], + startDateInterval: ['', [Validators.required]], + endDateInterval: ['', [Validators.required]], + isProtectedByPassword: [false, [Validators.required]], + isOwnerNotifiedByEmailOnNewVote: [false, [Validators.required]], + isOwnerNotifiedByEmailOnNewComment: [false, [Validators.required]], + isMaybeAnswerAvailable: [false, [Validators.required]], + areResultsPublic: [true, [Validators.required]], + richTextMode: [false, [Validators.required]], + expiracyNumberOfDays: [60, [Validators.required, Validators.min(0)]], + }); + this.form = form; + return form; + } + + public updateSlug(): void { + console.log('this.form.value', this.form.value); + this.form.patchValue({ custom_url: this.makeSlug(this.form) }); } /** @@ -57,127 +148,264 @@ export class PollService implements Resolve { * @param state */ public async resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise { - console.log('resolve route,state', route, state); const segments: string[] = state.url.split('/'); - const wantedcustom_url: string = segments.includes('poll') ? segments[segments.indexOf('poll') + 1] : ''; - + const wantedSlug: string = segments.includes('poll') ? segments[segments.indexOf('poll') + 1] : ''; + if (!wantedSlug && state.url.includes('administration')) { + // creation of new poll + const poll = new Poll(this.userService.getCurrentUser(), this.uuidService.getUUID(), ''); + this._poll.next(poll); + this.router.navigate(['poll/' + poll.custom_url + '/administration']); + } if ( !this._poll.getValue() || !this._poll.getValue().custom_url || - this._poll.getValue().custom_url !== wantedcustom_url + this._poll.getValue().custom_url !== wantedSlug ) { - 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); - } + await this.loadPollByCustomUrl(wantedSlug); } - const loadedPoll = this._poll.getValue(); - if (loadedPoll) { - this.storageService.vote_stack.poll_custom_url = loadedPoll.custom_url; - return loadedPoll; + if (this._poll.getValue()) { + return this._poll.getValue(); } else { this.router.navigate(['page-not-found']); return; } } - /** - * get all polls - */ - getAllAvailablePolls(): void { + getAllAvailablePolls() { const baseHref = environment.api.version.apiV1.baseHref; + console.log('getAllAvailablePolls baseHref', baseHref); const headers = ApiService.makeHeaders(); + console.log('getAllAvailablePolls headers', headers); try { this.http.get(`${baseHref}/poll`, headers).subscribe((res: Observable) => { console.log('getAllAvailablePolls res', res); }); } catch (e) { - console.error('getAllAvailablePolls e', e); + console.log('getAllAvailablePolls e', e); } } - public async loadPollBycustom_url(custom_url: string): Promise { - if (custom_url) { - const poll: Poll | undefined = await this.apiService.getPollByCustomUrl(custom_url); - - if (poll) { - this.updateCurrentPoll(poll); - this.titleService.setTitle(`☑️ ${poll.title} - ${environment.appTitle}`); - } else { - this.toastService.display(`sondage ${custom_url} non trouvé`); - this.router.navigate(['page-not-found']); - } - } else { - this.toastService.display(`sondage sans custom url : ${custom_url}`); + public async loadPollByCustomUrl(slug: string): Promise { + if (slug) { + const poll: Poll | undefined = await this.apiService.getPollByCustomUrl(slug); + console.log({ loadPollBySlugResponse: poll }); + this.updateCurrentPoll(poll); + } + } + public async loadPollByCustomUrlWithPasswordHash(slug: string, pass_hash: string): Promise { + if (slug) { + const poll: Poll | undefined = await this.apiService.getPollByCustomUrlWithHash(slug, pass_hash); + console.log({ loadPollBySlugResponse: poll }); + this.updateCurrentPoll(poll); } } - public async loadPollBycustom_urlWithPasswordHash(custom_url: string, hash: string): Promise { - if (custom_url) { - const poll: Poll | undefined = await this.apiService.getPollByCustomUrlWithHash(custom_url, hash); - - if (poll) { - this.updateCurrentPoll(poll); - this.titleService.setTitle(`☑️ ${poll.title} - ${environment.appTitle}`); - } else { - this.toastService.display(`sondage ${custom_url} non trouvé`); - this.router.navigate(['page-not-found']); - } - } else { - this.toastService.display(`sondage sans custom url : ${custom_url}`); - } - } - - /** - * update poll and parse its fields - * @param poll - */ public updateCurrentPoll(poll: Poll): void { - console.log('this.storageService.vote_stack.id', this.storageService.vote_stack.id); - - if (!this.storageService.vote_stack.id || this.storageService.vote_stack.poll_custom_url !== poll.custom_url) { - console.log('set base choices', poll.choices); - // set the choices only the first time the poll loads, or if we changed the poll - 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._poll.next(poll); } /** - * make a uniq custom_url for the current poll creation - * @param poll + * add all the dates between the start and end dates in the interval section */ - makecustom_url(poll: Poll): string { - let str = ''; - const creation_date = new Date(poll.creation_date); - str = - creation_date.getFullYear() + - '_' + - (creation_date.getMonth() + 1) + - '_' + - creation_date.getDate() + - '_' + - poll.owner.pseudo + - '_' + - poll.title; + addIntervalOfDates(): void { + const newIntervalArray = this.DateUtilitiesService.getDatesInRange( + this.DateUtilitiesService.parseInputDateToDateObject(new Date(this.startDateInterval)), + this.DateUtilitiesService.parseInputDateToDateObject(new Date(this.endDateInterval)), + 1 + ); - return this.convertTextToSlug(str) + '-' + this.uuidService.getUUID(); + const converted = []; + newIntervalArray.forEach((element) => { + converted.push({ + literal: element.literal, + date_object: element.date_object, + timeList: [], + }); + }); + this.dateList = [...new Set(converted)]; + // add only dates that are not already present with a Set of unique items + console.log('this.dateList', this.dateList); + this.showDateInterval = false; + + this.form.patchValue({ choices: this.dateList }); + + this.toastService.display(`les dates ont été ajoutées aux réponses possibles.`); } /** - * convert a text to a slug - * @param str + * handle keyboard shortcuts + * @param $event + * @param choice_number */ - public convertTextToSlug(str: string): string { - str = str.trim(); + keyOnChoice($event: KeyboardEvent, choice_number: number): void { + $event.preventDefault(); + + const lastChoice = this.choices.length - 1 === choice_number; + // reset field with Ctrl + D + // add a field with Ctrl + N + // go to previous choice with arrow up + // go to next choice with arrow down + + if ($event.key == 'ArrowUp' && choice_number > 0) { + this.focusOnChoice(choice_number - 1); + } + if ($event.key == 'ArrowDown') { + // add a field if we are on the last choice + if (lastChoice) { + this.addChoice(); + this.toastService.display('choix ajouté par raccourci "flèche bas"'); + } else { + this.focusOnChoice(choice_number + 1); + } + } + if ($event.ctrlKey && $event.key == 'Backspace') { + this.deleteChoiceField(choice_number); + this.toastService.display('choix supprimé par raccourci "Ctrl + retour"'); + this.focusOnChoice(Math.min(choice_number - 1, 0)); + } + if ($event.ctrlKey && $event.key == 'Enter') { + // go to other fields + const elem = this.document.querySelector('#creatorEmail'); + if (elem) { + elem.focus(); + } + } + } + + /** + * change time spans + */ + addTime() { + this.timeList.push({ + literal: '', + }); + } + + removeAllTimes() { + this.timeList = []; + } + + resetTimes() { + this.timeList = []; + } + + /** + * add a time period to a specific date choice, + * focus on the new input + * @param config + * @param id + */ + addTimeToDate(config: any, id: number) { + this.timeList.push({ + literal: '', + }); + const selector = '[ng-reflect-choice_label="dateTime_' + id + '_Choices_' + (this.timeList.length - 1) + '"]'; + const elem = this.document.querySelector(selector); + if (elem) { + elem.focus(); + } + } + + public createPoll(): void { + console.log('this.form', this.form); + const newpoll = this.newPollFromForm(this.form); + console.log('newpoll', newpoll); + this.apiService.createPoll(newpoll); + } + + /** + * default interval of dates proposed is from today to 7 days more + */ + setDefaultDatesForInterval(): void { + const dateCurrent = new Date(); + const dateJson = dateCurrent.toISOString(); + this.startDateInterval = dateJson.substring(0, 10); + this.endDateInterval = this.DateUtilitiesService.addDaysToDate(this.intervalDaysDefault, dateCurrent) + .toISOString() + .substring(0, 10); + this.form.patchValue({ + startDateInterval: this.startDateInterval, + endDateInterval: this.endDateInterval, + }); + this.countDays(); + } + + askInitFormDefault(): void { + this.initFormDefault(false); + this.toastService.display('formulaire réinitialisé'); + } + + countDays(): void { + this.intervalDays = this.DateUtilitiesService.countDays( + this.DateUtilitiesService.parseInputDateToDateObject(new Date(this.startDateInterval)), + this.DateUtilitiesService.parseInputDateToDateObject(new Date(this.endDateInterval)) + ); + } + + focusOnChoice(index): void { + const selector = '#choice_label_' + index; + const elem = this.document.querySelector(selector); + if (elem) { + elem.focus(); + } + } + + deleteChoiceField(index: number): void { + if (this.choices.length !== 1) { + this.choices.removeAt(index); + } + } + + initFormDefault(showDemoValues = true): void { + this.form = this.createFormGroup(); + this.setDefaultDatesForInterval(); + + if (showDemoValues) { + this.setDemoValues(); + } + } + + get choices(): FormArray { + return this.form.get('choices') as FormArray; + } + + reinitChoices(): void { + this.choices.setValue([]); + } + + addChoice(optionalLabel = ''): void { + const newControlGroup = this.fb.group({ + label: this.fb.control('', [Validators.required]), + imageUrl: ['', [Validators.required]], + }); + + if (optionalLabel) { + newControlGroup.patchValue({ + label: optionalLabel, + imageUrl: 'mon url', + }); + } + this.choices.push(newControlGroup); + + this.focusOnChoice(this.choices.length - 1); + } + + /** + * make a uniq slug for the current poll creation + * @param form + */ + makeSlug(form: FormGroup): string { + let str = ''; + str = + form.value.created_at.getFullYear() + + '_' + + (form.value.created_at.getMonth() + 1) + + '_' + + form.value.created_at.getDate() + + '_' + + form.value.creatorPseudo + + '_' + + form.value.title; str = str.replace(/^\s+|\s+$/g, ''); // trim str = str.toLowerCase(); @@ -192,10 +420,25 @@ export class PollService implements Resolve { .replace(/[^a-z0-9 -]/g, '') // remove invalid chars .replace(/\s+/g, '-') // collapse whitespace and replace by - .replace(/-+/g, '-'); // collapse dashes - return str; + + return str + '-' + this.uuidService.getUUID(); + } + + public async saveCurrentPoll(): Promise { + const pollUrl: Subscription = await this.apiService.createPoll(this._poll.getValue()); + // TODO: Maybe handle the url to update currentPoll according to backend response + if (pollUrl) { + this.toastService.display('Le sondage a été enregistré.'); + } else { + this.toastService.display('Le sondage n’a été correctement enregistré, veuillez ré-essayer.'); + } } public saveParticipation(choice: Choice, user: Owner, response: Answer): void { + const currentPoll = this._poll.getValue(); + currentPoll.choices.find((c) => c.name === choice.name)?.updateParticipation(user, response); + this.updateCurrentPoll(currentPoll); + this.apiService.createParticipation(currentPoll.custom_url, choice.name, user.pseudo, response); this.toastService.display('Votre participation au sondage a été enregistrée.'); } @@ -214,98 +457,95 @@ export class PollService implements Resolve { this.toastService.display('Les commentaires de ce sondage ont été supprimés.'); } - /** - * @description convert to API version 1 data transition object - * @param form - */ - newPollFromForm(form: any): Poll { - const newOwner = this.storageService.vote_stack.owner; - - const newpoll = new Poll(newOwner, form.value.custom_url, form.value.title); - - const pollKeys = Object.keys(newpoll); - const formFields = Object.keys(form.value); - newpoll.allowed_answers = ['yes']; - - for (const pk of pollKeys) { - if (formFields.indexOf(pk) !== -1) { - const field = form.value[pk]; - newpoll[pk] = field; - } else { - console.log('manque pollKey', pk); - } - } - - if (form.value.isMaybeAnswerAvailable) { - newpoll.allowed_answers.push('maybe'); - } - if (form.value.isNoAnswerAvailable) { - newpoll.allowed_answers.push('no'); - } - newpoll.description = form.value.description; - newpoll.has_several_hours = form.value.hasSeveralHours; - newpoll.hasSeveralHours = form.value.hasSeveralHours; - newpoll.max_count_of_answers = form.value.allowComments; - newpoll.maxCountOfAnswers = form.value.maxCountOfAnswers; - newpoll.password = form.value.password; - newpoll.kind = form.value.kind; - newpoll.allow_comments = form.value.allowComments; - // merge choices from storage - newpoll.choices = Object.assign([], this.storageService.choices); - newpoll.dateChoices = Object.assign([], this.storageService.dateChoices); - newpoll.timeSlices = Object.assign([], this.storageService.timeSlices); - return newpoll; - } - - public getAdministrationUrl(): string { - let url = ''; - if (this._poll && this._poll.getValue) { - const polltemp = this._poll.getValue(); - if (polltemp) { - url = `${environment.frontDomain}#/poll/admin/${polltemp.admin_key}`; - } - } - return url; - } - - public getParticipationUrl(): string { - let url = ''; - if (this._poll && this._poll.getValue) { - const polltemp = this._poll.getValue(); - if (polltemp) { - url = `${environment.api.baseHref}#/poll/${polltemp.custom_url}/consultation`; - } - } - // TODO handle pass access - 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)); - } + public buildAnswersByChoiceLabelByPseudo(poll: Poll): Map> { + const pseudos: Set = new Set(); + poll.choices.forEach((choice: Choice) => { + choice.participants.forEach((users: Set) => { + users.forEach((user: Owner) => { + pseudos.add(user.pseudo); + }); }); - } + }); + + const list = new Map>(); + pseudos.forEach((pseudo: string) => { + list.set( + pseudo, + new Map( + poll.choices.map((choice: Choice) => { + return [choice.name, undefined]; + }) + ) + ); + }); + + poll.choices.forEach((choice: Choice) => { + choice.participants.forEach((users: Set, answer: Answer) => { + users.forEach((user: Owner) => { + list.get(user.pseudo).set(choice.name, answer); + }); + }); + }); + + return list; } - /** - * 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; - } - }); + newPollFromForm(form: any): any { + const newpoll = new Poll( + this.userService.getCurrentUser(), + this.uuidService.getUUID(), + form.controls.title.value + ); + /** + * convert to API version 1 config poll + */ + const apiV1Poll = { + menuVisible: true, + expiracyDateDefaultInDays: newpoll.default_expiracy_days_from_now, + deletionDateAfterLastModification: newpoll.default_expiracy_days_from_now, + pollType: newpoll.kind ? 'date' : 'classic', // classic or dates + title: newpoll.title, + description: newpoll.description, + myName: newpoll.owner.pseudo, + myComment: '', + isAdmin: true, // when we create a poll, we are admin on it + myVoteStack: {}, + myTempVoteStack: 0, + myEmail: newpoll.owner.email, + myPolls: [], // list of retrieved polls from the backend api + /* + date specific poll, we have the choice to setup different hours (timeList) for all possible dates (dateList), or use the same hours for all dates + */ + allowSeveralHours: 'true', + // access + visibility: newpoll.areResultsPublic, // visible to one with the link: + voteChoices: newpoll.isMaybeAnswerAvailable ? 'yes, maybe, no' : 'yes', // possible answers to a vote choice: only "yes", "yes, maybe, no" + created_at: new Date(), + expirationDate: '', // expiracy date + voteStackId: null, // id of the vote stack to update + pollId: null, // id of the current poll when created. data given by the backend api + pollSlug: null, // id of the current poll when created. data given by the backend api + currentPoll: null, // current poll selected with createPoll or getPoll of ConfigService + passwordAccess: false, + password: newpoll.password, + customUrl: newpoll.custom_url, // custom slug in the url, must be unique + customUrlIsUnique: null, // given by the backend + urlSlugPublic: null, + urlPublic: null, + urlAdmin: null, + adminKey: '', // key to change config of the poll + owner_modifier_token: '', // key to change a vote stack + canModifyAnswers: newpoll.modification_policy, // bool for the frontend selector + whoModifiesAnswers: newpoll.modification_policy, // everybody, self, nobody (: just admin) + whoCanChangeAnswers: newpoll.modification_policy, // everybody, self, nobody (: just admin) + dateList: newpoll.dateChoices, // sets of days as strings, config to set identical time for days in a special days poll + timeList: newpoll.timeSlices, // ranges of time expressed as strings + + answers: newpoll.choices, + // modals + displayConfirmVoteModalAdmin: false, + }; + console.log('apiV1Poll', apiV1Poll); + return apiV1Poll; } } diff --git a/src/app/features/administration/form/base-config/base-config.component.ts b/src/app/features/administration/form/base-config/base-config.component.ts index 9a3ccf66..ac97d437 100644 --- a/src/app/features/administration/form/base-config/base-config.component.ts +++ b/src/app/features/administration/form/base-config/base-config.component.ts @@ -34,7 +34,7 @@ export class BaseConfigComponent { ) {} public updateSlug(): void { - const newValueFormatted = this.pollService.convertTextToSlug(this.form.value.title); + const newValueFormatted = this.pollService.makeSlug(this.pollService.form); console.log('newValueFormatted', newValueFormatted); this.form.patchValue({ custom_url: newValueFormatted }); } diff --git a/src/app/features/administration/form/date/interval/interval.component.ts b/src/app/features/administration/form/date/interval/interval.component.ts index dc5f1db5..3d2da36e 100644 --- a/src/app/features/administration/form/date/interval/interval.component.ts +++ b/src/app/features/administration/form/date/interval/interval.component.ts @@ -54,8 +54,8 @@ export class IntervalComponent implements OnInit { this.intervalDays = this.dateUtilities.countDays( this.startDateInterval, this.endDateInterval - // this.dateUtilities.parseInputDateToDateObject(this.startDateIntervalString), - // this.dateUtilities.parseInputDateToDateObject(this.endDateIntervalString) + // this.DateUtilitiesService.parseInputDateToDateObject(this.startDateIntervalString), + // this.DateUtilitiesService.parseInputDateToDateObject(this.endDateIntervalString) ); console.log('this.intervalDays ', this.intervalDays); } diff --git a/src/app/features/administration/form/form.component.ts b/src/app/features/administration/form/form.component.ts index a1455aed..7bb6a64f 100644 --- a/src/app/features/administration/form/form.component.ts +++ b/src/app/features/administration/form/form.component.ts @@ -1,231 +1,40 @@ -import { ChangeDetectorRef, Component, Inject, Input, OnInit, AfterViewInit } from '@angular/core'; +import { ChangeDetectorRef, Component, Inject, Input, OnInit } from '@angular/core'; import { Poll } from '../../../core/models/poll.model'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { UuidService } from '../../../core/services/uuid.service'; import { ApiService } from '../../../core/services/api.service'; import { ToastService } from '../../../core/services/toast.service'; import { PollService } from '../../../core/services/poll.service'; import { DOCUMENT } from '@angular/common'; -import { Router } from '@angular/router'; -import { environment } from '../../../../environments/environment'; -import { PollUtilitiesService } from '../../../core/services/poll.utilities.service'; -import { StorageService } from '../../../core/services/storage.service'; -import { DateUtilitiesService } from '../../../core/services/date.utilities.service'; -import { formatDate } from '@angular/common'; +import { ActivatedRoute, Router } from '@angular/router'; @Component({ selector: 'app-admin-form', templateUrl: './form.component.html', styleUrls: ['./form.component.scss'], }) -export class FormComponent implements OnInit, AfterViewInit { +export class FormComponent implements OnInit { @Input() public poll?: Poll; public form: FormGroup; - public displayDatePicker = false; - public advancedDisplayEnabled = false; - public show_debug_data = false; - public currentStep = 'base'; - public steps = ['base', 'choices', 'advanced']; - - public environment = environment; - constructor( private fb: FormBuilder, private cd: ChangeDetectorRef, - private pollUtilitiesService: PollUtilitiesService, + private uuidService: UuidService, private toastService: ToastService, private pollService: PollService, - private storageService: StorageService, - public apiService: ApiService, - public dateUtils: DateUtilitiesService, private router: Router, - private utilitiesService: PollUtilitiesService, + public route: ActivatedRoute, + private apiService: ApiService, @Inject(DOCUMENT) private document: any - ) {} + ) { + this.form = this.pollService.form; + } ngOnInit(): void { - this.initFormDefault(); - // this.goNextStep(); + this.pollService.askInitFormDefault(); } - ngAfterViewInit() { - // focus on first field of the creation form - const firstField = this.document.querySelector('#kind'); - if (firstField) { - console.log('focus on ', firstField); - firstField.focus(); - } else { - console.log('no first field of form'); - } - } - - initFormDefault(showDemoValues = environment.autofill): void { - const creationDate = new Date(); - - // choices of date are managed outside of this form - this.form = this.fb.group({ - title: ['', [Validators.required, Validators.minLength(5)]], - creatorPseudo: ['', [Validators.required]], - creatorEmail: ['', [Validators.required, Validators.email]], - custom_url: [this.pollUtilitiesService.makeUuid(), [Validators.required]], - description: ['', [Validators.required]], - kind: ['date', [Validators.required]], - areResultsPublic: [true, [Validators.required]], - whoCanChangeAnswers: ['everybody', [Validators.required]], - isProtectedByPassword: [false, [Validators.required]], - allowNewDateTime: [false, [Validators.required]], - isOwnerNotifiedByEmailOnNewVote: [false, [Validators.required]], - isOwnerNotifiedByEmailOnNewComment: [false, [Validators.required]], - isYesAnswerAvailable: [false, [Validators.required]], - isMaybeAnswerAvailable: [false, [Validators.required]], - isNoAnswerAvailable: [false, [Validators.required]], - isAboutDate: [true, [Validators.required]], - isZeroKnoledge: [false, [Validators.required]], - useVoterUniqueLink: [false, [Validators.required]], - expiresDaysDelay: [60, [Validators.required, Validators.min(1), Validators.max(365)]], - maxCountOfAnswers: [150, [Validators.required, Validators.min(1), Validators.max(5000)]], - allowComments: [true, [Validators.required]], - password: ['', []], - voterEmailList: ['', []], - natural_lang_interval: ['', []], - dateCreated: [creationDate, [Validators.required]], - hasSeveralHours: [false, [Validators.required]], - hasMaxCountOfAnswers: [true, [Validators.required, Validators.min(1)]], - startDateInterval: ['', [Validators.required]], - endDateInterval: ['', [Validators.required]], - }); - - // take back values from pollservice - // this.form.patchValue(this.pollService.poll); - this.setDefaultFormValues(); - - if (showDemoValues) { - this.setDemoValues(); - this.toastService.display('default values filled for demo'); - } - - if (environment.autoSendNewPoll) { - this.createPoll(); - } - } - - setDefaultFormValues(): void { - this.form.patchValue({ - creatorPseudo: 'Anne Onyme', - creatorEmail: 'anne_onyme@anonymous_email.com', - description: 'RSVP', - isAboutDate: true, - hasSeveralHours: false, - kind: 'date', - password: '', - whoCanChangeAnswers: 'everybody', - isProtectedByPassword: false, - isOwnerNotifiedByEmailOnNewVote: false, - isOwnerNotifiedByEmailOnNewComment: false, - isYesAnswerAvailable: true, - isMaybeAnswerAvailable: false, - isNoAnswerAvailable: false, - isZeroKnoledge: true, - areResultsPublic: true, - allowComments: true, - expiresDaysDelay: environment.expiresDaysDelay, - maxCountOfAnswers: environment.maxCountOfAnswers, - allowNewDateTime: false, - // startDateInterval: formatDate(new Date(), 'yyyy-MM-dd', 'fr_FR'), - endDateInterval: formatDate( - this.dateUtils.addDaysToDate(environment.interval_days_default, new Date()), - 'yyyy-MM-dd', - 'fr_FR' - ), - }); - console.log("this.form.controls['startDateInterval']", this.form.controls['startDateInterval']); - this.form.controls['startDateInterval'].setValue(formatDate(new Date(), 'yyyy-MM-dd', 'fr_FR')); - console.log("this.form.controls['startDateInterval']", this.form.controls['startDateInterval']); - this.automaticSlug(); - } - - /** - * add example values to the form, overrides defaults of PollConfiguration - */ - setDemoValues(): void { - const title = 'le titre de démo __ ' + new Date().getTime(); - - this.form.patchValue({ creatorPseudo: 'Chuck Norris', creatorEmail: 'chucknorris@example.com' }); - - this.form.patchValue({ - title: title, - custom_url: this.pollUtilitiesService.makeSlugFromString(title), - description: 'répondez SVP <3 ! *-*', - creatorPseudo: 'Chuck Norris', - creatorEmail: 'chucknorris@example.com', - }); - } - - askInitFormDefault(): void { - this.toastService.display('formulaire réinitialisé', 'info'); - } - - /** - * set the poll custom_url from other data of the poll - */ - automaticSlug(): void { - this.form.patchValue({ - custom_url: - this.pollService.convertTextToSlug(this.form.value.title) + - '_' + - this.utilitiesService.makeUuid().substr(0, 12), - }); - } - - goPreviousStep() { - alert('todo'); - } - - goNextStep() { - let indexCurrentStep = this.steps.indexOf(this.currentStep); - indexCurrentStep += 1; - this.currentStep = this.steps[indexCurrentStep]; - window.scrollTo(0, 0); - } - - public createPoll(): void { - const newpoll = this.pollService.newPollFromForm(this.form); - console.log('newpoll', newpoll); - const router = this.router; - - if (!environment.production) { - this.toastService.display('mode dev : envoi du form sans validation'); - this.apiService.createPoll(newpoll).then( - (resp: any) => { - this.pollService.updateCurrentPoll(resp.data.poll); - this.storageService.userPolls.push(resp.data.poll); - this.storageService.vote_stack.owner.polls.push(resp.data.poll); - this.toastService.display('sauvegarde du nouveau sondage réussie'); - router.navigate(['success']); - }, - (err) => { - this.toastService.display('erreur lors de la sauvegarde ' + err.message); - } - ); - } else { - if (this.form.valid) { - this.toastService.display("C'est parti!"); - this.apiService.createPoll(newpoll).then( - (resp: any) => { - this.pollService.updateCurrentPoll(resp.data.poll); - this.storageService.userPolls.push(resp.data.poll); - this.storageService.vote_stack.owner.polls.push(resp.data.poll); - this.toastService.display('sauvegarde du nouveau sondage réussie'); - router.navigate(['success']); - }, - (err) => { - this.toastService.display('erreur lors de la sauvegarde'); - } - ); - } else { - this.toastService.display('invalid form'); - } - } - } + goNextStep() {} } diff --git a/src/app/features/consultation/consultation.component.ts b/src/app/features/consultation/consultation.component.ts index a293e01a..e9ce1fb5 100644 --- a/src/app/features/consultation/consultation.component.ts +++ b/src/app/features/consultation/consultation.component.ts @@ -60,18 +60,18 @@ export class ConsultationComponent implements OnInit, OnDestroy { console.log('this.pass_hash ', this.pass_hash); if (this.pass_hash) { - this.pollService.loadPollBycustom_urlWithPasswordHash(this.pollSlug, this.pass_hash).then((resp) => { + this.pollService.loadPollByCustomUrlWithPasswordHash(this.pollSlug, this.pass_hash).then((resp) => { console.log('resp', resp); this.fetching = false; this.storageService.vote_stack.id = null; - this.storageService.setChoicesForVoteStack(this.pollService._poll.getValue().choices); + this.storageService.setChoicesForVoteStack(this.pollService.form.value.choices); }); } else { - this.pollService.loadPollBycustom_url(this.pollSlug).then((resp) => { + this.pollService.loadPollByCustomUrl(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); + this.storageService.setChoicesForVoteStack(this.pollService.form.value.choices); }); } }); @@ -131,9 +131,9 @@ export class ConsultationComponent implements OnInit, OnDestroy { this.storageService.mapVotes(voteStack.data); this.pollService.enrichVoteStackWithCurrentPollChoicesDefaultVotes(this.storageService.vote_stack); if (this.pass_hash) { - this.pollService.loadPollBycustom_urlWithPasswordHash(this.poll.custom_url, this.pass_hash); + this.pollService.loadPollByCustomUrlWithPasswordHash(this.poll.custom_url, this.pass_hash); } else { - this.pollService.loadPollBycustom_url(this.poll.custom_url); + this.pollService.loadPollByCustomUrl(this.poll.custom_url); } } else { this.toastService.display('erreur à l enregistrement'); diff --git a/src/app/features/consultation/poll-results-detailed/poll-results-detailed.component.ts b/src/app/features/consultation/poll-results-detailed/poll-results-detailed.component.ts index 6b780f13..b5383186 100644 --- a/src/app/features/consultation/poll-results-detailed/poll-results-detailed.component.ts +++ b/src/app/features/consultation/poll-results-detailed/poll-results-detailed.component.ts @@ -76,11 +76,11 @@ export class PollResultsDetailedComponent { storeVoteStackAndReloadPoll(voteStack: any) { if (voteStack.status == 200) { this.storageService.mapVotes(voteStack.data); - this.pollService.enrichVoteStackWithCurrentPollChoicesDefaultVotes(this.storageService.vote_stack); + // this.pollService.enrichVoteStackWithCurrentPollChoicesDefaultVotes(this.storageService.vote_stack); // if (this.pass_hash) { - // this.pollService.loadPollBycustom_urlWithPasswordHash(this.poll.custom_url, this.pass_hash); + // this.pollService.loadPollByCustomUrlWithPasswordHash(this.poll.custom_url, this.pass_hash); // } else { - this.pollService.loadPollBycustom_url(this.poll.custom_url); + this.pollService.loadPollByCustomUrl(this.poll.custom_url); // } } else { this.toastService.display('erreur à l enregistrement');