mirror of
https://framagit.org/framasoft/framadate/funky-framadate-front.git
synced 2023-08-25 13:53:14 +02:00
Merge branch 'feature/user_settings' into 'develop'
create services, add user settings button & modal, and other things See merge request framasoft/framadate/funky-framadate-front!37
This commit is contained in:
commit
789a1e7535
21
.eslintrc.js
21
.eslintrc.js
@ -1,19 +1,20 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
parser: '@typescript-eslint/parser',
|
parser: '@typescript-eslint/parser',
|
||||||
|
plugins: ['@typescript-eslint'],
|
||||||
|
extends: [
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:@typescript-eslint/eslint-recommended',
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'plugin:prettier/recommended',
|
||||||
|
'prettier/@typescript-eslint',
|
||||||
|
],
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
ecmaVersion: 2018,
|
ecmaVersion: 2018,
|
||||||
sourceType: 'module',
|
sourceType: 'module',
|
||||||
project: './tsconfig.json',
|
project: './tsconfig.json',
|
||||||
tsconfigRootDir: __dirname,
|
tsconfigRootDir: __dirname,
|
||||||
},
|
},
|
||||||
plugins: ['@typescript-eslint'],
|
rules: {
|
||||||
extends: [
|
'@typescript-eslint/unbound-method': ['error', { ignoreStatic: true }],
|
||||||
'eslint:recommended',
|
},
|
||||||
'plugin:@typescript-eslint/eslint-recommended',
|
|
||||||
'plugin:@typescript-eslint/recommended',
|
|
||||||
'plugin:@typescript-eslint/recommended-requiring-type-checking',
|
|
||||||
'prettier/@typescript-eslint',
|
|
||||||
'plugin:prettier/recommended',
|
|
||||||
],
|
|
||||||
rules: {},
|
|
||||||
};
|
};
|
||||||
|
@ -13,7 +13,7 @@ pages:
|
|||||||
stage: pages
|
stage: pages
|
||||||
script:
|
script:
|
||||||
- yarn install --pure-lockfile
|
- yarn install --pure-lockfile
|
||||||
- npx ng build --base-href=/framadate/funky-framadate-front/
|
- yarn build
|
||||||
- mv dist/framadate/ public/
|
- mv dist/framadate/ public/
|
||||||
artifacts:
|
artifacts:
|
||||||
paths:
|
paths:
|
||||||
|
10
README.md
10
README.md
@ -1,16 +1,17 @@
|
|||||||
## LIBRARIES USED
|
## LIBRARIES USED
|
||||||
|
|
||||||
| status | lib name | usage |
|
| status | lib name | usage |
|
||||||
| --------------- | -------------------------------------------------------------- | ---------------------------------------- |
|
| :-------------: | -------------------------------------------------------------- | --------------------------------------------------------- |
|
||||||
| | [axios](https://github.com/axios/axios) | http client |
|
| | [axios](https://github.com/axios/axios) | http client |
|
||||||
| | [bulma](https://bulma.io/) | CSS framework |
|
| | [bulma](https://bulma.io/) | CSS framework |
|
||||||
| | [chart.js](https://www.chartjs.org/) | Display graphs. (Comes with MomentJS) |
|
| | [chart.js](https://www.chartjs.org/) | PrimeNG solution for graphs. (Chart.js installs MomentJS) |
|
||||||
| | [compodoc](https://compodoc.app/) | Generate technic documentation |
|
| | [compodoc](https://compodoc.app/) | Generate technic documentation |
|
||||||
| | ESlint, Prettier, Lint-staged | Format & lint code |
|
| | ESlint, Prettier, Lint-staged | Format & lint code |
|
||||||
| | [fork-awesome](https://forkaweso.me) | Icons collection |
|
| | [fork-awesome](https://forkaweso.me) | Icons collection |
|
||||||
| | [fullcalendar](https://fullcalendar.io/docs/initialize-es6) | Manage & display calendars |
|
| | [fullcalendar](https://fullcalendar.io/docs/initialize-es6) | PrimeNG solution to manage & display calendars |
|
||||||
| | [husky](https://www.npmjs.com/package/husky) | Hook actions on commit |
|
| | [husky](https://www.npmjs.com/package/husky) | Hook actions on commit |
|
||||||
| | [jest](https://jestjs.io/) | test engine |
|
| | [jest](https://jestjs.io/) | test engine |
|
||||||
|
| | [json-server](https://www.npmjs.com/package/json-server) | local server for mocking data backend |
|
||||||
| removed | [locale-enum](https://www.npmjs.com/package/locale-enum) | enum of all locales |
|
| removed | [locale-enum](https://www.npmjs.com/package/locale-enum) | enum of all locales |
|
||||||
| | [momentJS](https://momentjs.com/) | manipulate dates. (chartJS’s dependency) |
|
| | [momentJS](https://momentjs.com/) | manipulate dates. (chartJS’s dependency) |
|
||||||
| to be installed | [ng2-charts](https://valor-software.com/ng2-charts/) | Manipulate graphs along with chart.js |
|
| to be installed | [ng2-charts](https://valor-software.com/ng2-charts/) | Manipulate graphs along with chart.js |
|
||||||
@ -19,9 +20,10 @@
|
|||||||
| | [ngx-webstorage](https://www.npmjs.com/package/ngx-webstorage) | handle localStorage & webStorage |
|
| | [ngx-webstorage](https://www.npmjs.com/package/ngx-webstorage) | handle localStorage & webStorage |
|
||||||
| | [primeNG](https://www.primefaces.org/primeng/) | UI components collection |
|
| | [primeNG](https://www.primefaces.org/primeng/) | UI components collection |
|
||||||
| | [quill](https://www.npmjs.com/package/quill) | powerful rich text editor. WYSIWYG. |
|
| | [quill](https://www.npmjs.com/package/quill) | powerful rich text editor. WYSIWYG. |
|
||||||
|
| to be installed | [short-uuid](https://www.npmjs.com/package/short-uuid) | generate uuid |
|
||||||
| removed | [storybook](https://storybook.js.org/) | StyleGuide UI |
|
| removed | [storybook](https://storybook.js.org/) | StyleGuide UI |
|
||||||
| | [ts-mockito](https://www.npmjs.com/package/ts-mockito) | Mocks for testing. |
|
| | [ts-mockito](https://www.npmjs.com/package/ts-mockito) | Mocks for testing. |
|
||||||
| | [uuid](https://www.npmjs.com/package/uuid) | handle client-side generation of uuids |
|
| to be removed | [uuid](https://www.npmjs.com/package/uuid) | generate uuid |
|
||||||
|
|
||||||
---
|
---
|
||||||
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 8.2.1.
|
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 8.2.1.
|
||||||
|
@ -25,9 +25,9 @@
|
|||||||
"assets": ["src/favicon.ico", "src/assets"],
|
"assets": ["src/favicon.ico", "src/assets"],
|
||||||
"styles": [
|
"styles": [
|
||||||
"node_modules/fork-awesome/css/fork-awesome.min.css",
|
"node_modules/fork-awesome/css/fork-awesome.min.css",
|
||||||
"node_modules/primeicons/primeicons.css",
|
|
||||||
"node_modules/primeng/resources/themes/nova-light/theme.css",
|
"node_modules/primeng/resources/themes/nova-light/theme.css",
|
||||||
"node_modules/primeng/resources/primeng.min.css",
|
"node_modules/primeng/resources/primeng.min.css",
|
||||||
|
"node_modules/bulma-switch/dist/css/bulma-switch.min.css",
|
||||||
"src/styles.scss"
|
"src/styles.scss"
|
||||||
],
|
],
|
||||||
"scripts": [
|
"scripts": [
|
||||||
|
234
mocks/db.json
Normal file
234
mocks/db.json
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
{
|
||||||
|
"owners": [
|
||||||
|
{ "id": 1, "email": "toto@gafam.com", "pseudo": "TOTO" },
|
||||||
|
{ "id": 2, "email": "titi@gafam.com", "pseudo": "TITI" }
|
||||||
|
],
|
||||||
|
"voters": [{ "pseudo": "TOTO", "polls": [] }],
|
||||||
|
"polls": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"slug": "picnic",
|
||||||
|
"configuration": {
|
||||||
|
"isAboutDate": true,
|
||||||
|
"isProtectedByPassword": false,
|
||||||
|
"isOwnerNotifiedByEmailOnNewVote": false,
|
||||||
|
"isOwnerNotifiedByEmailOnNewComment": false,
|
||||||
|
"isMaybeAnswerAvailable": true,
|
||||||
|
"areResultsPublic": true,
|
||||||
|
"dateCreated": "2020-05-17",
|
||||||
|
"expires": "2020-06-17"
|
||||||
|
},
|
||||||
|
"ownerId": 1,
|
||||||
|
"question": "Quelle date pour le picnic ?",
|
||||||
|
"description": "Gros badass picnic en plein air ! Come on !",
|
||||||
|
"answersByChoiceByParticipant": {
|
||||||
|
"TOTO": { "samedi": "YES", "dimanche": "NO" },
|
||||||
|
"TATA": { "samedi": "NO", "dimanche": null },
|
||||||
|
"TITI": { "samedi": "MAYBE", "dimanche": "NO" }
|
||||||
|
},
|
||||||
|
"answers": [
|
||||||
|
{
|
||||||
|
"pseudo": "TOTO",
|
||||||
|
"token": "TOTO-TOKEN",
|
||||||
|
"responsesByChoices": [
|
||||||
|
{ "choice": { "label": "samedi", "imagePath": null }, "responseType": "YES" },
|
||||||
|
{ "choice": { "label": "dimanche", "imagePath": null }, "responseType": "NO" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pseudo": "TATA",
|
||||||
|
"token": "TATA-TOKEN",
|
||||||
|
"responsesByChoices": [
|
||||||
|
{ "choice": { "label": "samedi", "imagePath": null }, "responseType": "NO" },
|
||||||
|
{ "choice": { "label": "dimanche", "imagePath": null }, "responseType": "NO" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pseudo": "TITI",
|
||||||
|
"token": "TITI-TOKEN",
|
||||||
|
"responsesByChoices": [
|
||||||
|
{ "choice": { "label": "samedi", "imagePath": null }, "responseType": "NO" },
|
||||||
|
{ "choice": { "label": "dimanche", "imagePath": null }, "responseType": "YES" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pseudo": "TETE",
|
||||||
|
"token": "TETE-TOKEN",
|
||||||
|
"responsesByChoices": [
|
||||||
|
{ "choice": { "label": "samedi", "imagePath": null }, "responseType": "YES" },
|
||||||
|
{ "choice": { "label": "dimanche", "imagePath": null }, "responseType": "NO" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"slug": "vacances",
|
||||||
|
"configuration": {
|
||||||
|
"isAboutDate": true,
|
||||||
|
"isProtectedByPassword": false,
|
||||||
|
"isOwnerNotifiedByEmailOnNewVote": false,
|
||||||
|
"isOwnerNotifiedByEmailOnNewComment": false,
|
||||||
|
"isMaybeAnswerAvailable": true,
|
||||||
|
"areResultsPublic": true,
|
||||||
|
"dateCreated": "2020-05-17",
|
||||||
|
"expires": "2020-06-17"
|
||||||
|
},
|
||||||
|
"ownerId": 2,
|
||||||
|
"question": "On fait quoi pendant les vacances ?",
|
||||||
|
"description": "Vacances en famille",
|
||||||
|
"answersByChoiceByParticipant": {
|
||||||
|
"TOTO": { "bateau": "YES", "montagne": "NO", "quad": "MAYBE" },
|
||||||
|
"TATA": { "bateau": "NO", "montagne": null, "quad": "YES" },
|
||||||
|
"TITI": { "bateau": "MAYBE", "montagne": "NO", "quad": null }
|
||||||
|
},
|
||||||
|
"answers": [
|
||||||
|
{
|
||||||
|
"pseudo": "TOTO",
|
||||||
|
"token": "TOTO-TOKEN",
|
||||||
|
"responsesByChoices": [
|
||||||
|
{ "choice": { "label": "bateau", "imagePath": null }, "responseType": "YES" },
|
||||||
|
{ "choice": { "label": "montagne", "imagePath": null }, "responseType": "NO" },
|
||||||
|
{ "choice": { "label": "quad", "imagePath": null }, "responseType": "NO" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pseudo": "TATA",
|
||||||
|
"token": "TATA-TOKEN",
|
||||||
|
"responsesByChoices": [
|
||||||
|
{ "choice": { "label": "bateau", "imagePath": null }, "responseType": "NO" },
|
||||||
|
{ "choice": { "label": "montagne", "imagePath": null }, "responseType": "NO" },
|
||||||
|
{ "choice": { "label": "quad", "imagePath": null }, "responseType": "NO" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pseudo": "TITI",
|
||||||
|
"token": "TITI-TOKEN",
|
||||||
|
"responsesByChoices": [
|
||||||
|
{ "choice": { "label": "bateau", "imagePath": null }, "responseType": "NO" },
|
||||||
|
{ "choice": { "label": "montagne", "imagePath": null }, "responseType": "YES" },
|
||||||
|
{ "choice": { "label": "quad", "imagePath": null }, "responseType": "NO" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pseudo": "TETE",
|
||||||
|
"token": "TETE-TOKEN",
|
||||||
|
"responsesByChoices": [
|
||||||
|
{ "choice": { "label": "bateau", "imagePath": null }, "responseType": "YES" },
|
||||||
|
{ "choice": { "label": "montagne", "imagePath": null }, "responseType": "NO" },
|
||||||
|
{ "choice": { "label": "quad", "imagePath": null }, "responseType": "NO" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"choices": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"pollId": 1,
|
||||||
|
"pollSlug": "picnic",
|
||||||
|
"label": "samedi",
|
||||||
|
"participants": [
|
||||||
|
["YES", ["TOTO", "TITI"]],
|
||||||
|
["NO", ["TETE"]],
|
||||||
|
["MAYBE", ["TATA"]]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"pollId": 1,
|
||||||
|
"pollSlug": "picnic",
|
||||||
|
"label": "dimanche",
|
||||||
|
"participants": [
|
||||||
|
["YES", ["TOTO", "TITI"]],
|
||||||
|
["NO", ["TETE"]],
|
||||||
|
["MAYBE", ["TATA"]]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"pollId": 2,
|
||||||
|
"pollSlug": "vacances",
|
||||||
|
"label": "bateau",
|
||||||
|
"participants": [
|
||||||
|
["YES", ["TOTO", "TITI"]],
|
||||||
|
["NO", ["TETE"]],
|
||||||
|
["MAYBE", ["TATA"]]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"pollId": 2,
|
||||||
|
"pollSlug": "vacances",
|
||||||
|
"label": "montagne",
|
||||||
|
"participants": [
|
||||||
|
["YES", ["TOTO", "TITI"]],
|
||||||
|
["NO", ["TETE"]],
|
||||||
|
["MAYBE", ["TATA"]]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"pollId": 2,
|
||||||
|
"pollSlug": "vacances",
|
||||||
|
"label": "quad",
|
||||||
|
"participants": [
|
||||||
|
["YES", ["TOTO", "TITI"]],
|
||||||
|
["NO", ["TETE"]],
|
||||||
|
["MAYBE", ["TATA"]]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"answers": [
|
||||||
|
{ "choiceId": 1, "pseudo": "TOTO", "response": "YES", "token": "TOTO-TOKEN" },
|
||||||
|
{ "choiceId": 1, "pseudo": "TATA", "response": "NO", "token": "TATA-TOKEN" },
|
||||||
|
{ "choiceId": 1, "pseudo": "TITI", "response": "MAYBE", "token": "TITI-TOKEN" },
|
||||||
|
{ "choiceId": 2, "pseudo": "TOTO", "response": "YES", "token": "TOTO-TOKEN" },
|
||||||
|
{ "choiceId": 2, "pseudo": "TATA", "response": "NO", "token": "TATA-TOKEN" },
|
||||||
|
{ "choiceId": 2, "pseudo": "TITI", "response": "MAYBE", "token": "TITI-TOKEN" },
|
||||||
|
{ "choiceId": 2, "pseudo": "EVA", "response": null, "token": "EVA-TOKEN" },
|
||||||
|
{ "choiceId": 3, "pseudo": "TOTO", "response": "YES", "token": "TOTO-TOKEN" },
|
||||||
|
{ "choiceId": 3, "pseudo": "TATA", "response": "NO", "token": "TATA-TOKEN" },
|
||||||
|
{ "choiceId": 3, "pseudo": "TITI", "response": "MAYBE", "token": "TITI-TOKEN" },
|
||||||
|
{ "choiceId": 4, "pseudo": "TOTO", "response": "YES", "token": "TOTO-TOKEN" },
|
||||||
|
{ "choiceId": 4, "pseudo": "TATA", "response": "NO", "token": "TATA-TOKEN" },
|
||||||
|
{ "choiceId": 4, "pseudo": "TITI", "response": "MAYBE", "token": "TITI-TOKEN" },
|
||||||
|
{ "choiceId": 5, "pseudo": "TOTO", "response": "YES", "token": "TOTO-TOKEN" },
|
||||||
|
{ "choiceId": 5, "pseudo": "TATA", "response": "NO", "token": "TATA-TOKEN" },
|
||||||
|
{ "choiceId": 5, "pseudo": "TITI", "response": "MAYBE", "token": "TITI-TOKEN" }
|
||||||
|
],
|
||||||
|
"comments": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"pollId": 1,
|
||||||
|
"pollSlug": "picnic",
|
||||||
|
"content": "Les picnics, c’est trop bien, j’adore!",
|
||||||
|
"author": "TATA",
|
||||||
|
"dateCreated": 1589111111111
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"pollId": 1,
|
||||||
|
"pollSlug": "picnic",
|
||||||
|
"content": "Oué, grave!",
|
||||||
|
"author": "TETE",
|
||||||
|
"dateCreated": 1589222222222
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"pollId": 2,
|
||||||
|
"pollSlug": "vacances",
|
||||||
|
"content": "Désolé je pourrai pas être là, mais je penserai bien à vous. Mamie",
|
||||||
|
"author": "MAMIE",
|
||||||
|
"dateCreated": 1589333333333
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"pollId": 2,
|
||||||
|
"pollSlug": "vacances",
|
||||||
|
"content": "Arf, trop dommage.",
|
||||||
|
"author": "Tom",
|
||||||
|
"dateCreated": 1589444444444
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
8
mocks/routes.json
Normal file
8
mocks/routes.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"/api/v1/*": "/$1",
|
||||||
|
"/owners/:email/": "/owners?email=:email",
|
||||||
|
"/choices": "/choices?_embed=answers",
|
||||||
|
"/polls/:slug": "/polls?slug=:slug&_expand=owner&_embed=choices&_embed=comments",
|
||||||
|
"/polls/:slug/choices": "/choices?pollSlug=:slug&_embed=answers",
|
||||||
|
"/polls/:slug/comments": "/comments?pollSlug=:slug"
|
||||||
|
}
|
39
package.json
39
package.json
@ -12,12 +12,15 @@
|
|||||||
"test": "jest",
|
"test": "jest",
|
||||||
"test:watch": "jest --watch",
|
"test:watch": "jest --watch",
|
||||||
"test:ci": "jest --runInBand",
|
"test:ci": "jest --runInBand",
|
||||||
"lint": "ng lint",
|
"lint": "prettier --write \"src/**/*.{js,jsx,ts,tsx,md,html,css,scss}\"",
|
||||||
"e2e": "ng e2e",
|
"e2e": "ng e2e",
|
||||||
"format:check": "prettier --list-different \"src/{app,environments,assets}/**/*{.ts,.js,.json,.css,.scss}\"",
|
"format:check": "prettier --list-different \"src/{app,environments,assets}/**/*{.ts,.js,.json,.css,.scss}\"",
|
||||||
"format:all": "prettier --write \"src/**/*.{js,jsx,ts,tsx,md,html,css,scss}\"",
|
"format:all": "prettier --write \"src/**/*.{js,jsx,ts,tsx,md,html,css,scss}\"",
|
||||||
"trans": "ng xi18n --output-path=src/locale --i18n-locale=fr",
|
"trans": "ng xi18n --output-path=src/locale --i18n-locale=fr",
|
||||||
"compodoc": "compodoc -p tsconfig.app.json"
|
"compodoc": "compodoc -p tsconfig.app.json",
|
||||||
|
"mock:server": "json-server --port 8000 --watch ./mocks/db.json --routes ./mocks/routes.json",
|
||||||
|
"start:proxy": "ng serve --proxy-config proxy.conf.json",
|
||||||
|
"start:proxymock": "concurrently --kill-others \"yarn mock:server\" \"yarn start:proxy\""
|
||||||
},
|
},
|
||||||
"private": false,
|
"private": false,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -36,19 +39,19 @@
|
|||||||
"@ngx-translate/http-loader": "^4.0.0",
|
"@ngx-translate/http-loader": "^4.0.0",
|
||||||
"angular-date-value-accessor": "^1.0.2",
|
"angular-date-value-accessor": "^1.0.2",
|
||||||
"axios": "^0.19.2",
|
"axios": "^0.19.2",
|
||||||
"bulma": "^0.8.2",
|
"bulma": "^0.9.0",
|
||||||
|
"bulma-switch": "^2.0.0",
|
||||||
"chart.js": "^2.9.3",
|
"chart.js": "^2.9.3",
|
||||||
"fork-awesome": "^1.1.7",
|
"fork-awesome": "^1.1.7",
|
||||||
"ng2-charts": "^2.3.0",
|
"ng2-charts": "^2.3.0",
|
||||||
"ngx-clipboard": "^13.0.0",
|
"ngx-clipboard": "^13.0.0",
|
||||||
"ngx-markdown": "^9.0.0",
|
"ngx-markdown": "^9.0.0",
|
||||||
"ngx-webstorage": "^5.0.0",
|
"ngx-webstorage": "^5.0.0",
|
||||||
"primeicons": "^2.0.0",
|
|
||||||
"primeng": "^9.0.6",
|
"primeng": "^9.0.6",
|
||||||
"quill": "^1.3.7",
|
"quill": "^1.3.7",
|
||||||
"rxjs": "^6.5.5",
|
"rxjs": "^6.5.5",
|
||||||
"rxjs-compat": "^6.5.5",
|
"rxjs-compat": "^6.5.5",
|
||||||
"tslib": "^1.11.1",
|
"tslib": "<2.0.0",
|
||||||
"uuid": "^8.0.0",
|
"uuid": "^8.0.0",
|
||||||
"zone.js": "^0.10.3"
|
"zone.js": "^0.10.3"
|
||||||
},
|
},
|
||||||
@ -63,22 +66,24 @@
|
|||||||
"@babel/preset-typescript": "^7.9.0",
|
"@babel/preset-typescript": "^7.9.0",
|
||||||
"@compodoc/compodoc": "^1.1.11",
|
"@compodoc/compodoc": "^1.1.11",
|
||||||
"@types/jest": "^25.2.1",
|
"@types/jest": "^25.2.1",
|
||||||
"@types/node": "^13.13.2",
|
"@types/node": "^14.0.1",
|
||||||
"@types/uuid": "^7.0.2",
|
"@types/uuid": "^8.0.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^2.27.0",
|
"@typescript-eslint/eslint-plugin": "^3.0.0",
|
||||||
"@typescript-eslint/parser": "^2.27.0",
|
"@typescript-eslint/parser": "^3.0.0",
|
||||||
"babel-jest": "^25.4.0",
|
"babel-jest": "^26.0.0",
|
||||||
"eslint": "^6.8.0",
|
"concurrently": "^5.2.0",
|
||||||
|
"eslint": "^7.0.0",
|
||||||
"eslint-config-prettier": "^6.11.0",
|
"eslint-config-prettier": "^6.11.0",
|
||||||
"eslint-plugin-prettier": "^3.1.3",
|
"eslint-plugin-prettier": "^3.1.3",
|
||||||
"husky": "^4.2.5",
|
"husky": "^4.2.5",
|
||||||
"jest": "^25.5.1",
|
"jest": "^26.0.0",
|
||||||
"jest-environment-jsdom-sixteen": "^1.0.3",
|
"jest-environment-jsdom-sixteen": "^1.0.3",
|
||||||
"jest-preset-angular": "^8.1.3",
|
"jest-preset-angular": "^8.1.3",
|
||||||
|
"json-server": "^0.16.1",
|
||||||
"lint-staged": "^10.1.7",
|
"lint-staged": "^10.1.7",
|
||||||
"prettier": "^2.0.5",
|
"prettier": "^2.0.5",
|
||||||
"protractor": "~5.4.3",
|
"protractor": "~7.0.0",
|
||||||
"ts-jest": "^25.4.0",
|
"ts-jest": "^26.0.0",
|
||||||
"ts-mockito": "^2.5.0",
|
"ts-mockito": "^2.5.0",
|
||||||
"ts-node": "^8.10.1",
|
"ts-node": "^8.10.1",
|
||||||
"typescript": "~3.8.3"
|
"typescript": "~3.8.3"
|
||||||
@ -89,11 +94,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"src/{app,environments,assets}/**/*.{js,jsx,ts,tsx,md,html,css,scss}": [
|
"src/**/*.{js,jsx,ts,tsx,md,html,css,scss}": [
|
||||||
"prettier --write",
|
"prettier --write",
|
||||||
"git add"
|
"git add"
|
||||||
],
|
],
|
||||||
"*.js": "eslint --cache --fix"
|
"*.js": [
|
||||||
|
"prettier --write"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
"preset": "jest-preset-angular",
|
"preset": "jest-preset-angular",
|
||||||
|
11
proxy.conf.json
Normal file
11
proxy.conf.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"/api/v1/*": {
|
||||||
|
"target": "http://localhost:8000",
|
||||||
|
"secure": false,
|
||||||
|
"pathRewrite": {
|
||||||
|
"^/api/v1": ""
|
||||||
|
},
|
||||||
|
"changeOrigin": false,
|
||||||
|
"logLevel": "debug"
|
||||||
|
}
|
||||||
|
}
|
@ -2,17 +2,19 @@ import { NgModule } from '@angular/core';
|
|||||||
import { RouterModule, Routes } from '@angular/router';
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
|
||||||
import { HomeComponent } from './core/components/home/home.component';
|
import { HomeComponent } from './core/components/home/home.component';
|
||||||
import { LoginComponent } from './core/components/login/login.component';
|
|
||||||
import { PageNotFoundComponent } from './shared/components/page-not-found/page-not-found.component';
|
import { PageNotFoundComponent } from './shared/components/page-not-found/page-not-found.component';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{ path: '', component: HomeComponent },
|
{ path: '', component: HomeComponent },
|
||||||
{ path: 'login', component: LoginComponent },
|
|
||||||
{
|
{
|
||||||
path: 'administration',
|
path: 'administration',
|
||||||
loadChildren: () =>
|
loadChildren: () =>
|
||||||
import('./features/administration/administration.module').then((m) => m.AdministrationModule),
|
import('./features/administration/administration.module').then((m) => m.AdministrationModule),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'consultation',
|
||||||
|
loadChildren: () => import('./features/consultation/consultation.module').then((m) => m.ConsultationModule),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'participation',
|
path: 'participation',
|
||||||
loadChildren: () => import('./features/participation/participation.module').then((m) => m.ParticipationModule),
|
loadChildren: () => import('./features/participation/participation.module').then((m) => m.ParticipationModule),
|
||||||
|
@ -5,6 +5,8 @@ import { Subscription } from 'rxjs';
|
|||||||
import { environment } from '../environments/environment';
|
import { environment } from '../environments/environment';
|
||||||
import { Theme } from './core/enums/theme.enum';
|
import { Theme } from './core/enums/theme.enum';
|
||||||
import { UserRole } from './core/enums/user-role.enum';
|
import { UserRole } from './core/enums/user-role.enum';
|
||||||
|
import { User } from './core/models/user.model';
|
||||||
|
import { LanguageService } from './core/services/language.service';
|
||||||
import { MockingService } from './core/services/mocking.service';
|
import { MockingService } from './core/services/mocking.service';
|
||||||
import { ThemeService } from './core/services/theme.service';
|
import { ThemeService } from './core/services/theme.service';
|
||||||
|
|
||||||
@ -22,15 +24,18 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||||||
constructor(
|
constructor(
|
||||||
private titleService: Title,
|
private titleService: Title,
|
||||||
private themeService: ThemeService,
|
private themeService: ThemeService,
|
||||||
|
private languageService: LanguageService,
|
||||||
private mockingService: MockingService
|
private mockingService: MockingService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
if (!environment.production) {
|
if (!environment.production) {
|
||||||
this.appTitle += ' [DEV]';
|
this.appTitle += ' [DEV]';
|
||||||
this.mockingService.loadUser(UserRole.REGISTERED);
|
// TODO: to be removed
|
||||||
|
this.mockingService.loadUser(new User('TOTO', 'toto@gafam.com', UserRole.REGISTERED));
|
||||||
}
|
}
|
||||||
this.titleService.setTitle(this.appTitle);
|
this.titleService.setTitle(this.appTitle);
|
||||||
|
this.languageService.configureAndInitTranslations();
|
||||||
this.themeSubscription = this.themeService.theme.subscribe((theme: Theme) => {
|
this.themeSubscription = this.themeService.theme.subscribe((theme: Theme) => {
|
||||||
switch (theme) {
|
switch (theme) {
|
||||||
case Theme.DARK:
|
case Theme.DARK:
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { CommonModule } from '@angular/common';
|
import { APP_BASE_HREF, CommonModule, registerLocaleData } from '@angular/common';
|
||||||
import { HttpClient, HttpClientModule } from '@angular/common/http';
|
import { HttpClient, HttpClientModule } from '@angular/common/http';
|
||||||
|
import localeEn from '@angular/common/locales/en';
|
||||||
|
import localeFr from '@angular/common/locales/fr';
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
import { BrowserModule, Title } from '@angular/platform-browser';
|
import { BrowserModule, Title } from '@angular/platform-browser';
|
||||||
@ -21,8 +23,9 @@ import { AppRoutingModule } from './app-routing.module';
|
|||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
import { CoreModule } from './core/core.module';
|
import { CoreModule } from './core/core.module';
|
||||||
import { SharedModule } from './shared/shared.module';
|
import { SharedModule } from './shared/shared.module';
|
||||||
import { ParticipationModule } from './features/participation/participation.module';
|
|
||||||
import { AdministrationModule } from './features/administration/administration.module';
|
registerLocaleData(localeEn, 'en-EN');
|
||||||
|
registerLocaleData(localeFr, 'fr-FR');
|
||||||
|
|
||||||
export class MyMissingTranslationHandler implements MissingTranslationHandler {
|
export class MyMissingTranslationHandler implements MissingTranslationHandler {
|
||||||
public handle(params: MissingTranslationHandlerParams): string {
|
public handle(params: MissingTranslationHandlerParams): string {
|
||||||
@ -38,7 +41,6 @@ export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader {
|
|||||||
declarations: [AppComponent],
|
declarations: [AppComponent],
|
||||||
imports: [
|
imports: [
|
||||||
AppRoutingModule,
|
AppRoutingModule,
|
||||||
AdministrationModule,
|
|
||||||
BrowserAnimationsModule,
|
BrowserAnimationsModule,
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
ClipboardModule,
|
ClipboardModule,
|
||||||
@ -61,9 +63,8 @@ export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader {
|
|||||||
},
|
},
|
||||||
useDefaultLang: false,
|
useDefaultLang: false,
|
||||||
}),
|
}),
|
||||||
ParticipationModule,
|
|
||||||
],
|
],
|
||||||
providers: [Title, TranslateService],
|
providers: [{ provide: APP_BASE_HREF, useValue: environment.baseHref }, Title, TranslateService],
|
||||||
bootstrap: [AppComponent],
|
bootstrap: [AppComponent],
|
||||||
})
|
})
|
||||||
export class AppModule {}
|
export class AppModule {}
|
||||||
|
@ -37,35 +37,43 @@
|
|||||||
<div class="navbar-item has-dropdown is-hoverable">
|
<div class="navbar-item has-dropdown is-hoverable">
|
||||||
<a class="navbar-link"> Modules </a>
|
<a class="navbar-link"> Modules </a>
|
||||||
<div class="navbar-dropdown">
|
<div class="navbar-dropdown">
|
||||||
|
<a class="navbar-item" routerLink="oldstuff" routerLinkActive="is-active">
|
||||||
|
Old stuff
|
||||||
|
</a>
|
||||||
<a class="navbar-item" routerLink="administration" routerLinkActive="is-active">
|
<a class="navbar-item" routerLink="administration" routerLinkActive="is-active">
|
||||||
Administration
|
Administration
|
||||||
</a>
|
</a>
|
||||||
|
<a class="navbar-item" routerLink="consultation" routerLinkActive="is-active">
|
||||||
|
Consultation
|
||||||
|
</a>
|
||||||
<a class="navbar-item" routerLink="participation" routerLinkActive="is-active">
|
<a class="navbar-item" routerLink="participation" routerLinkActive="is-active">
|
||||||
Participation
|
Participation
|
||||||
</a>
|
</a>
|
||||||
<a class="navbar-item" routerLink="oldstuff" routerLinkActive="is-active">
|
</div>
|
||||||
Old stuff
|
</div>
|
||||||
|
<div class="navbar-item has-dropdown is-hoverable">
|
||||||
|
<a class="navbar-link"> Tous les sondages </a>
|
||||||
|
<div class="navbar-dropdown">
|
||||||
|
<a
|
||||||
|
class="navbar-item"
|
||||||
|
*ngFor="let slug of slugsAvailables"
|
||||||
|
routerLink="{{ '/consultation/poll/' + slug }}"
|
||||||
|
routerLinkActive="is-active"
|
||||||
|
>
|
||||||
|
« {{ slug }} »
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="navbar-item">
|
|
||||||
<app-theme-selector></app-theme-selector>
|
|
||||||
</div>
|
|
||||||
<div class="navbar-item">
|
|
||||||
<app-language-selector></app-language-selector>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="navbar-end">
|
<div class="navbar-end">
|
||||||
<div class="navbar-item">
|
<div class="navbar-item" #container>
|
||||||
<div class="buttons">
|
<div class="buttons has-addons is-centered clickable" (click)="openDialog()">
|
||||||
<a class="button is-primary">
|
<button class="button is-static"><i class="fa fa-user-circle" aria-hidden="true"></i></button>
|
||||||
<strong>Sign up</strong>
|
<button class="button is-static" *ngIf="_user | async">
|
||||||
</a>
|
{{ (_user | async)?.pseudo || 'anonyme' }}
|
||||||
<a class="button is-light">
|
</button>
|
||||||
Log in
|
<button class="button is-static"><i class="fa fa-cogs" aria-hidden="true"></i></button>
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,14 +1,38 @@
|
|||||||
import { Component, EventEmitter, Output, Input } from '@angular/core';
|
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
import { User } from '../../models/user.model';
|
||||||
|
import { ApiService } from '../../services/api.service';
|
||||||
|
import { ModalService } from '../../services/modal.service';
|
||||||
|
import { UserService } from '../../services/user.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-header',
|
selector: 'app-header',
|
||||||
templateUrl: './header.component.html',
|
templateUrl: './header.component.html',
|
||||||
styleUrls: ['./header.component.scss'],
|
styleUrls: ['./header.component.scss'],
|
||||||
})
|
})
|
||||||
export class HeaderComponent {
|
export class HeaderComponent implements OnInit {
|
||||||
@Input() isSidebarOpened: boolean;
|
@Input() isSidebarOpened: boolean;
|
||||||
@Output() toggleSidebarEE = new EventEmitter<boolean>();
|
@Output() toggleSidebarEE = new EventEmitter<boolean>();
|
||||||
|
|
||||||
|
public _user: Observable<User> = this.userService.user;
|
||||||
|
|
||||||
|
public slugsAvailables: string[] = [];
|
||||||
|
|
||||||
|
constructor(private userService: UserService, private modalService: ModalService, private apiService: ApiService) {}
|
||||||
|
|
||||||
|
public ngOnInit(): void {
|
||||||
|
this.getSlugs();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getSlugs(): Promise<void> {
|
||||||
|
this.slugsAvailables = await this.apiService.getAllPollsSlugs();
|
||||||
|
}
|
||||||
|
|
||||||
|
public openDialog(): void {
|
||||||
|
this.modalService.openSettingsComponent();
|
||||||
|
}
|
||||||
|
|
||||||
public toggleSidebarOpening(): void {
|
public toggleSidebarOpening(): void {
|
||||||
this.isSidebarOpened = !this.isSidebarOpened;
|
this.isSidebarOpened = !this.isSidebarOpened;
|
||||||
this.toggleSidebarEE.emit(this.isSidebarOpened);
|
this.toggleSidebarEE.emit(this.isSidebarOpened);
|
||||||
|
@ -1 +0,0 @@
|
|||||||
<p>login works!</p>
|
|
@ -1,12 +0,0 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-login',
|
|
||||||
templateUrl: './login.component.html',
|
|
||||||
styleUrls: ['./login.component.scss'],
|
|
||||||
})
|
|
||||||
export class LoginComponent implements OnInit {
|
|
||||||
constructor() {}
|
|
||||||
|
|
||||||
ngOnInit(): void {}
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
<div class="control has-icons-left">
|
|
||||||
<select class="select is-small" [(ngModel)]="currentLang">
|
|
||||||
<ng-container *ngFor="let language of languagesAvailable">
|
|
||||||
<option value="{{ language }}">{{ 'LANGUAGES.' + language | translate }}</option>
|
|
||||||
</ng-container>
|
|
||||||
</select>
|
|
||||||
<div class="icon is-left">
|
|
||||||
<i class="fa fa-globe"></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,38 +0,0 @@
|
|||||||
import { Component, DoCheck, OnInit } from '@angular/core';
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
|
||||||
|
|
||||||
import { Language } from '../../../enums/language.enum';
|
|
||||||
import { StorageService } from '../../../services/storage.service';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-language-selector',
|
|
||||||
templateUrl: './language-selector.component.html',
|
|
||||||
styleUrls: ['./language-selector.component.scss'],
|
|
||||||
})
|
|
||||||
export class LanguageSelectorComponent implements OnInit, DoCheck {
|
|
||||||
public currentLang: Language;
|
|
||||||
public languagesAvailable: string[] = Object.values(Language);
|
|
||||||
|
|
||||||
constructor(private translate: TranslateService, private storageService: StorageService) {}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
const currentBrowserLanguage: Language = this.translate.getBrowserLang().toUpperCase() as Language;
|
|
||||||
if (this.storageService.language && Object.keys(Language).includes(this.storageService.language)) {
|
|
||||||
this.currentLang = this.storageService.language;
|
|
||||||
} else if (Object.keys(Language).includes(currentBrowserLanguage)) {
|
|
||||||
this.currentLang = currentBrowserLanguage;
|
|
||||||
} else {
|
|
||||||
this.currentLang = Language.EN;
|
|
||||||
}
|
|
||||||
this.updateLanguage();
|
|
||||||
}
|
|
||||||
|
|
||||||
ngDoCheck(): void {
|
|
||||||
this.updateLanguage();
|
|
||||||
}
|
|
||||||
|
|
||||||
public updateLanguage(): void {
|
|
||||||
this.translate.use(this.currentLang.toString().toUpperCase());
|
|
||||||
this.storageService.language = this.currentLang;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
<div class="buttons has-addons is-centered">
|
|
||||||
<button class="button is-small is-static">Theme</button>
|
|
||||||
<button
|
|
||||||
class="button is-small"
|
|
||||||
[ngClass]="{ 'is-active': (currentTheme | async) === themeEnum.LIGHT }"
|
|
||||||
(click)="selectTheme('LIGHT')"
|
|
||||||
>
|
|
||||||
<i class="fa fa-sun-o"></i>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="button is-small"
|
|
||||||
[ngClass]="{ 'is-active': (currentTheme | async) === themeEnum.DARK }"
|
|
||||||
(click)="selectTheme('DARK')"
|
|
||||||
>
|
|
||||||
<i class="fa fa-moon"></i>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="button is-small"
|
|
||||||
[ngClass]="{ 'is-active': (currentTheme | async) === themeEnum.RED }"
|
|
||||||
(click)="selectTheme('RED')"
|
|
||||||
>
|
|
||||||
<i class="fa fa-adjust"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
@ -1,11 +1,7 @@
|
|||||||
<nav>
|
<nav>
|
||||||
<a class="button" routerLink="administration" routerLinkActive="active"> AdministrationModule</a>
|
<a class="button" routerLink="oldstuff/home" routerLinkActive="active">
|
||||||
<a class="button" routerLink="participation/poll/SuperCustomSlug" routerLinkActive="active">
|
<i class="fa fa-home" aria-hidden="true"></i> Accueil
|
||||||
Participate to poll/SuperCustomSlug
|
|
||||||
</a>
|
</a>
|
||||||
<hr />
|
|
||||||
|
|
||||||
<a class="button" routerLink="oldstuff/home" routerLinkActive="active"> <i class="fa fa-home"></i> Accueil </a>
|
|
||||||
<a class="button" routerLink="oldstuff/step/creation" routerLinkActive="active"> Création </a>
|
<a class="button" routerLink="oldstuff/step/creation" routerLinkActive="active"> Création </a>
|
||||||
<a class="button" routerLink="oldstuff/step/date" routerLinkActive="active"> Les Dates </a>
|
<a class="button" routerLink="oldstuff/step/date" routerLinkActive="active"> Les Dates </a>
|
||||||
<a class="button" routerLink="oldstuff/step/answers" routerLinkActive="active"> Réponses </a>
|
<a class="button" routerLink="oldstuff/step/answers" routerLinkActive="active"> Réponses </a>
|
||||||
|
@ -1,18 +1,12 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
|
||||||
import { MockingService } from '../../../services/mocking.service';
|
|
||||||
import { Poll } from '../../../models/poll.model';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-navigation',
|
selector: 'app-navigation',
|
||||||
templateUrl: './navigation.component.html',
|
templateUrl: './navigation.component.html',
|
||||||
styleUrls: ['./navigation.component.scss'],
|
styleUrls: ['./navigation.component.scss'],
|
||||||
})
|
})
|
||||||
export class NavigationComponent implements OnInit {
|
export class NavigationComponent implements OnInit {
|
||||||
public pollsDatabase: Poll[] = [];
|
constructor() {}
|
||||||
constructor(private mockingService: MockingService) {}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {}
|
||||||
this.pollsDatabase = this.mockingService.pollsDatabase;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -7,26 +7,14 @@ import { TranslateModule } from '@ngx-translate/core';
|
|||||||
import { FooterComponent } from './components/footer/footer.component';
|
import { FooterComponent } from './components/footer/footer.component';
|
||||||
import { HeaderComponent } from './components/header/header.component';
|
import { HeaderComponent } from './components/header/header.component';
|
||||||
import { HomeComponent } from './components/home/home.component';
|
import { HomeComponent } from './components/home/home.component';
|
||||||
import { LoginComponent } from './components/login/login.component';
|
|
||||||
import { LogoComponent } from './components/logo/logo.component';
|
import { LogoComponent } from './components/logo/logo.component';
|
||||||
import { LanguageSelectorComponent } from './components/selectors/language-selector/language-selector.component';
|
|
||||||
import { ThemeSelectorComponent } from './components/selectors/theme-selector/theme-selector.component';
|
|
||||||
import { NavigationComponent } from './components/sibebar/navigation/navigation.component';
|
import { NavigationComponent } from './components/sibebar/navigation/navigation.component';
|
||||||
import { throwIfAlreadyLoaded } from './guards/module-import.guard';
|
import { throwIfAlreadyLoaded } from './guards/module-import.guard';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [FooterComponent, HeaderComponent, HomeComponent, LogoComponent, NavigationComponent],
|
||||||
FooterComponent,
|
|
||||||
HeaderComponent,
|
|
||||||
HomeComponent,
|
|
||||||
LanguageSelectorComponent,
|
|
||||||
LoginComponent,
|
|
||||||
LogoComponent,
|
|
||||||
NavigationComponent,
|
|
||||||
ThemeSelectorComponent,
|
|
||||||
],
|
|
||||||
imports: [CommonModule, FormsModule, RouterModule, TranslateModule],
|
imports: [CommonModule, FormsModule, RouterModule, TranslateModule],
|
||||||
exports: [HeaderComponent, FooterComponent, NavigationComponent, LoginComponent, LogoComponent],
|
exports: [HeaderComponent, FooterComponent, NavigationComponent, LogoComponent],
|
||||||
})
|
})
|
||||||
export class CoreModule {
|
export class CoreModule {
|
||||||
constructor(@Optional() @SkipSelf() parentModule: CoreModule) {
|
constructor(@Optional() @SkipSelf() parentModule: CoreModule) {
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
export enum AnswerGranularity {
|
|
||||||
BASIC = 'BASIC',
|
|
||||||
COMPLEX = 'COMPLEX',
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
export enum AnswerType {
|
export enum ResponseType {
|
||||||
YES = 'YES',
|
YES = 'YES',
|
||||||
NO = 'NO',
|
NO = 'NO',
|
||||||
MAYBE = 'MAYBE',
|
MAYBE = 'MAYBE',
|
@ -1,5 +1,5 @@
|
|||||||
export enum UserRole {
|
export enum UserRole {
|
||||||
ANONYMOUS = 'ANONYMOUS',
|
ANONYMOUS = 'ANONYMOUS',
|
||||||
REGISTERED = 'ADMIN',
|
REGISTERED = 'REGISTERED',
|
||||||
ADMIN = 'ADMIN',
|
ADMIN = 'ADMIN',
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
export enum WorkflowStep {
|
export enum WorkflowStep {
|
||||||
DESCRIPTION = 'DESCRIPTION',
|
DESCRIPTION = 'DESCRIPTION',
|
||||||
OPTIONS = 'OPTIONS',
|
CHOICES = 'CHOICES',
|
||||||
CONFIGURATION = 'CONFIGURATION',
|
CONFIGURATION = 'CONFIGURATION',
|
||||||
}
|
}
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
export interface DateOption {
|
|
||||||
timeList: any;
|
|
||||||
literal: string;
|
|
||||||
date_object?: object;
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
import { AnswerType } from '../enums/answer-type.enum';
|
|
||||||
import { PollOption } from './poll-options.model';
|
|
||||||
|
|
||||||
export class Answer {
|
|
||||||
constructor(public pollOption: PollOption, public type: AnswerType, public userPseudo: string) {}
|
|
||||||
}
|
|
38
src/app/core/models/choice.model.ts
Normal file
38
src/app/core/models/choice.model.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { ResponseType } from '../enums/response-type.enum';
|
||||||
|
|
||||||
|
export class Choice {
|
||||||
|
constructor(
|
||||||
|
public label: string,
|
||||||
|
public imageUrl?: string,
|
||||||
|
public participants: Map<ResponseType, Set<string>> = new Map<ResponseType, Set<string>>([
|
||||||
|
[ResponseType.YES, new Set<string>()],
|
||||||
|
[ResponseType.NO, new Set<string>()],
|
||||||
|
[ResponseType.MAYBE, new Set<string>()],
|
||||||
|
]),
|
||||||
|
public counts: Map<ResponseType, number> = new Map<ResponseType, number>([
|
||||||
|
[ResponseType.YES, 0],
|
||||||
|
[ResponseType.NO, 0],
|
||||||
|
[ResponseType.MAYBE, 0],
|
||||||
|
])
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public updateParticipation(pseudo: string, responseType: ResponseType): void {
|
||||||
|
this.removeParticipant(pseudo);
|
||||||
|
this.participants.get(responseType).add(pseudo);
|
||||||
|
this.updateCounts();
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeParticipant(pseudo: string): void {
|
||||||
|
for (const responseType of Object.values(ResponseType)) {
|
||||||
|
if (this.participants.get(responseType).has(pseudo)) {
|
||||||
|
this.participants.get(responseType).delete(pseudo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public updateCounts(): void {
|
||||||
|
for (const responseType of Object.values(ResponseType)) {
|
||||||
|
this.counts.set(responseType, this.participants.get(responseType).size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
src/app/core/models/comment.model.ts
Normal file
13
src/app/core/models/comment.model.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
export class Comment {
|
||||||
|
constructor(public author: string, public content: string, public dateCreated: Date) {}
|
||||||
|
|
||||||
|
public static sortChronologically(a: Comment, b: Comment): number {
|
||||||
|
if (a.dateCreated < b.dateCreated) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (a.dateCreated > b.dateCreated) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
24
src/app/core/models/configuration.model.ts
Normal file
24
src/app/core/models/configuration.model.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { environment } from '../../../environments/environment';
|
||||||
|
import { DateUtilsService } from '../utils/date-utils.service';
|
||||||
|
|
||||||
|
export class Configuration {
|
||||||
|
constructor(
|
||||||
|
public isAboutDate: boolean = false,
|
||||||
|
public isProtectedByPassword: boolean = false,
|
||||||
|
public isOwnerNotifiedByEmail: { onNewVote: boolean; onNewComment: boolean } = {
|
||||||
|
onNewVote: false,
|
||||||
|
onNewComment: false,
|
||||||
|
},
|
||||||
|
public isMaybeAnswerAvailable: boolean = false,
|
||||||
|
public areResultsPublic: boolean = false,
|
||||||
|
public dateCreated: Date = new Date(Date.now()),
|
||||||
|
public expires: Date = DateUtilsService.addDaysToDate(
|
||||||
|
environment.poll.defaultConfig.expiracyInDays,
|
||||||
|
new Date(Date.now())
|
||||||
|
)
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public static isArchived(configuration: Configuration): boolean {
|
||||||
|
return DateUtilsService.isDateInPast(configuration.expires);
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +0,0 @@
|
|||||||
import { AnswerGranularity } from '../enums/answer-granularity.enum';
|
|
||||||
|
|
||||||
export class PollConfig {
|
|
||||||
constructor(
|
|
||||||
public allowSeveralHours = true,
|
|
||||||
public isVisibleToAnyoneWithTheLink: boolean = true,
|
|
||||||
public answerType: AnswerGranularity = AnswerGranularity.BASIC,
|
|
||||||
public creationDate: Date = new Date(),
|
|
||||||
public expirationDate?: Date,
|
|
||||||
public canVotersModifyTheirAnswers = true,
|
|
||||||
public isProtectedByPassword: boolean = false
|
|
||||||
) {}
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
import * as moment from 'moment';
|
|
||||||
|
|
||||||
export class PollOption {
|
|
||||||
constructor(public label: string, public url?: string, public subOptions?: PollOption[]) {}
|
|
||||||
|
|
||||||
public isDatePoll(): boolean {
|
|
||||||
return moment(this.label).isValid();
|
|
||||||
}
|
|
||||||
}
|
|
6
src/app/core/models/poll-user-answers.model.ts
Normal file
6
src/app/core/models/poll-user-answers.model.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { ResponseType } from '../enums/response-type.enum';
|
||||||
|
import { Choice } from './choice.model';
|
||||||
|
|
||||||
|
export class PollUserAnswers {
|
||||||
|
constructor(public pseudo: string, public token: string, public responsesByChoices: Map<Choice, ResponseType>) {}
|
||||||
|
}
|
@ -1,23 +1,89 @@
|
|||||||
import { Answer } from './answer.model';
|
|
||||||
import { PollConfig } from './poll-config.model';
|
|
||||||
import { PollOption } from './poll-options.model';
|
|
||||||
import { User } from './user.model';
|
|
||||||
import { environment } from 'src/environments/environment';
|
import { environment } from 'src/environments/environment';
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
|
import { ResponseType } from '../enums/response-type.enum';
|
||||||
|
import { PollUserAnswers } from './poll-user-answers.model';
|
||||||
|
import { Choice } from './choice.model';
|
||||||
|
import { Comment } from './comment.model';
|
||||||
|
import { Configuration } from './configuration.model';
|
||||||
|
import { User } from './user.model';
|
||||||
|
|
||||||
export class Poll {
|
export class Poll {
|
||||||
constructor(
|
constructor(
|
||||||
public isDateType: boolean,
|
public owner: User,
|
||||||
public title: string,
|
public question: string,
|
||||||
public description: string,
|
public description?: string,
|
||||||
public slug: string,
|
public slug: string = uuidv4(),
|
||||||
public id: string,
|
public configuration: Configuration = new Configuration(),
|
||||||
public owner?: User,
|
public comments: Comment[] = [],
|
||||||
public config?: PollConfig,
|
public choices: Choice[] = [],
|
||||||
public options: PollOption[] = [],
|
public answersByChoiceByParticipant: Map<string, Map<string, ResponseType>> = new Map<
|
||||||
public answers: Answer[] = []
|
string,
|
||||||
|
Map<string, ResponseType>
|
||||||
|
>(),
|
||||||
|
public answers: PollUserAnswers[] = []
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public getUrl(): string {
|
public getAdministrationUrl(): string {
|
||||||
return `${environment.api.baseHref}/${this.slug}`;
|
return `${environment.api.baseHref}/administration/polls/${this.slug}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getParticipationUrl(): string {
|
||||||
|
return `${environment.api.baseHref}/participation/polls/${this.slug}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static adaptFromLocalJsonServer(
|
||||||
|
item: Pick<
|
||||||
|
Poll,
|
||||||
|
| 'owner'
|
||||||
|
| 'question'
|
||||||
|
| 'description'
|
||||||
|
| 'slug'
|
||||||
|
| 'configuration'
|
||||||
|
| 'comments'
|
||||||
|
| 'choices'
|
||||||
|
| 'answersByChoiceByParticipant'
|
||||||
|
| 'answers'
|
||||||
|
>
|
||||||
|
): Poll {
|
||||||
|
const poll = new Poll(
|
||||||
|
new User(item.owner.pseudo, item.owner.email, undefined),
|
||||||
|
item.question,
|
||||||
|
item.description,
|
||||||
|
item.slug,
|
||||||
|
item.configuration,
|
||||||
|
item.comments
|
||||||
|
.map(
|
||||||
|
(c: Pick<Comment, 'author' | 'content' | 'dateCreated'>) =>
|
||||||
|
new Comment(c.author, c.content, new Date(c.dateCreated))
|
||||||
|
)
|
||||||
|
.sort(Comment.sortChronologically),
|
||||||
|
item.choices.map((c: Pick<Choice, 'label' | 'imageUrl' | 'participants' | 'imageUrl'>) => {
|
||||||
|
const choice = new Choice(c.label, c.imageUrl, new Map(c.participants));
|
||||||
|
choice.participants.forEach((value, key) => {
|
||||||
|
choice.participants.set(key, new Set(value));
|
||||||
|
});
|
||||||
|
choice.updateCounts();
|
||||||
|
return choice;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// handle answersByChoiceByParticipant
|
||||||
|
for (const [pseudo, answersByChoice] of Object.entries(item.answersByChoiceByParticipant)) {
|
||||||
|
if (!poll.answersByChoiceByParticipant.has(pseudo)) {
|
||||||
|
poll.answersByChoiceByParticipant.set(pseudo, new Map<string, ResponseType>());
|
||||||
|
}
|
||||||
|
for (const [choiceLabel, answer] of Object.entries(answersByChoice)) {
|
||||||
|
poll.answersByChoiceByParticipant.get(pseudo).set(choiceLabel, answer as ResponseType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle answers
|
||||||
|
poll.answers = item.answers.map(
|
||||||
|
(pollUserAnswers: Pick<PollUserAnswers, 'pseudo' | 'token' | 'responsesByChoices'>) =>
|
||||||
|
new PollUserAnswers(pollUserAnswers.pseudo, pollUserAnswers.token, pollUserAnswers.responsesByChoices)
|
||||||
|
);
|
||||||
|
|
||||||
|
return poll;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
3
src/app/core/models/question.model.ts
Normal file
3
src/app/core/models/question.model.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export class Question {
|
||||||
|
constructor(public label: string, public description: string) {}
|
||||||
|
}
|
@ -1,12 +1,6 @@
|
|||||||
import { Poll } from './poll.model';
|
|
||||||
import { UserRole } from '../enums/user-role.enum';
|
import { UserRole } from '../enums/user-role.enum';
|
||||||
|
import { Poll } from './poll.model';
|
||||||
|
|
||||||
export class User {
|
export class User {
|
||||||
constructor(
|
constructor(public pseudo: string, public email: string, public role?: UserRole, public polls?: Poll[]) {}
|
||||||
public role: UserRole = UserRole.ANONYMOUS,
|
|
||||||
public isOwner: boolean = false,
|
|
||||||
public pseudo?: string,
|
|
||||||
public email?: string,
|
|
||||||
public polls?: Poll[]
|
|
||||||
) {}
|
|
||||||
}
|
}
|
||||||
|
@ -2,55 +2,60 @@ import { Injectable } from '@angular/core';
|
|||||||
import axios, { AxiosInstance, AxiosResponse } from 'axios';
|
import axios, { AxiosInstance, AxiosResponse } from 'axios';
|
||||||
import { environment } from 'src/environments/environment';
|
import { environment } from 'src/environments/environment';
|
||||||
|
|
||||||
|
import { ResponseType } from '../enums/response-type.enum';
|
||||||
import { Poll } from '../models/poll.model';
|
import { Poll } from '../models/poll.model';
|
||||||
import { User } from '../models/user.model';
|
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class ApiService {
|
export class ApiService {
|
||||||
private axiosInstance: AxiosInstance;
|
private axiosInstance: AxiosInstance;
|
||||||
|
|
||||||
private readonly pollsEndpoint = environment.api.endpoints.polls.name;
|
private readonly pollsEndpoint = environment.api.endpoints.polls.name;
|
||||||
|
private readonly answersEndpoint = environment.api.endpoints.polls.answers.name;
|
||||||
private readonly commentsEndpoint = environment.api.endpoints.polls.comments.name;
|
private readonly commentsEndpoint = environment.api.endpoints.polls.comments.name;
|
||||||
private readonly votesEndpoint = environment.api.endpoints.polls.votes.name;
|
|
||||||
private readonly slugsEndpoint = environment.api.endpoints.polls.slugs.name;
|
private readonly slugsEndpoint = environment.api.endpoints.polls.slugs.name;
|
||||||
private readonly votesStacksEndpoint = environment.api.endpoints.voteStack.name;
|
|
||||||
private readonly usersEndpoint = environment.api.endpoints.users.name;
|
private readonly usersEndpoint = environment.api.endpoints.users.name;
|
||||||
private readonly usersPollsEndpoint = environment.api.endpoints.users.polls.name;
|
private readonly usersPollsEndpoint = environment.api.endpoints.users.polls.name;
|
||||||
private readonly usersPollsSendEmailEndpoint = environment.api.endpoints.users.polls.sendEmail.name;
|
private readonly usersPollsSendEmailEndpoint = environment.api.endpoints.users.polls.sendEmail.name;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.axiosInstance = axios.create({ baseURL: environment.api.baseHref });
|
this.axiosInstance = axios.create({ baseURL: environment.api.baseHref });
|
||||||
|
this.axiosInstance.defaults.timeout = 2500;
|
||||||
|
this.axiosInstance.defaults.headers.post['Content-Type'] = 'application/json';
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////
|
//////////////////////
|
||||||
// CREATE //
|
// CREATE OR UPDATE //
|
||||||
////////////
|
//////////////////////
|
||||||
public async savePoll(poll: Poll): Promise<void> {
|
public async createPoll(poll: Poll): Promise<string> {
|
||||||
try {
|
try {
|
||||||
await this.axiosInstance.post(`${this.pollsEndpoint}`, { params: { config: poll.config } });
|
return await this.axiosInstance.post(`${this.pollsEndpoint}`, poll);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.handleError(error);
|
this.handleError(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async saveVote(poll: Poll): Promise<void> {
|
public async createParticipation(
|
||||||
|
pollId: string,
|
||||||
|
choiceLabel: string,
|
||||||
|
pseudo: string,
|
||||||
|
response: ResponseType
|
||||||
|
): Promise<string> {
|
||||||
try {
|
try {
|
||||||
// TODO: add the votestack in the params
|
return await this.axiosInstance.post(`${this.pollsEndpoint}/${pollId}${this.answersEndpoint}`, {
|
||||||
await this.axiosInstance.post(`${this.pollsEndpoint}/${poll.id}${this.votesEndpoint}`, {
|
choiceLabel,
|
||||||
params: { voteStack: {} },
|
pseudo,
|
||||||
|
response,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.handleError(error);
|
this.handleError(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async saveComment(poll: Poll, comment: string): Promise<void> {
|
public async createComment(slug: string, comment: string): Promise<string> {
|
||||||
try {
|
try {
|
||||||
// TODO: add the comment in the params
|
return await this.axiosInstance.post(`${this.pollsEndpoint}/${slug}${this.commentsEndpoint}`, comment);
|
||||||
await this.axiosInstance.post(`${this.pollsEndpoint}/${poll.id}${this.commentsEndpoint}`, {
|
|
||||||
params: { comment },
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.handleError(error);
|
this.handleError(error);
|
||||||
}
|
}
|
||||||
@ -59,7 +64,48 @@ export class ApiService {
|
|||||||
//////////
|
//////////
|
||||||
// READ //
|
// READ //
|
||||||
//////////
|
//////////
|
||||||
public async isSlugAvailable(slug: string): Promise<boolean> {
|
public async getAllPollsSlugs(): Promise<string[]> {
|
||||||
|
// TODO: used for facilities in DEV, should be removed in production
|
||||||
|
try {
|
||||||
|
const response: AxiosResponse<Poll[]> = await this.axiosInstance.get<Poll[]>(`${this.pollsEndpoint}`);
|
||||||
|
return response?.data.map((poll: Poll) => poll.slug);
|
||||||
|
} catch (error) {
|
||||||
|
this.handleError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getPollBySlug(slug: string): Promise<Poll | undefined> {
|
||||||
|
// TODO: identifier should be decided according to backend : Id || Slug ?
|
||||||
|
try {
|
||||||
|
// TODO: this interceptor should be avoided if backends returns the good object
|
||||||
|
const adapterInterceptor: number = this.axiosInstance.interceptors.response.use(
|
||||||
|
(response): AxiosResponse => {
|
||||||
|
if (response.data['poll']) {
|
||||||
|
// response from cipherbliss backend, actually used by oldstuffModule
|
||||||
|
response.data = response.data['poll'];
|
||||||
|
} else if (response.data[0] && response.data[0]['slug'] && response.data[0]['question']) {
|
||||||
|
// response from local json-server
|
||||||
|
response.data = Poll.adaptFromLocalJsonServer(response.data[0]);
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
console.log('fetch API : asking for poll with slug=' + slug);
|
||||||
|
const response: AxiosResponse<Poll> = await this.axiosInstance.get<Poll>(`${this.pollsEndpoint}/${slug}`);
|
||||||
|
|
||||||
|
axios.interceptors.request.eject(adapterInterceptor);
|
||||||
|
|
||||||
|
return response?.data;
|
||||||
|
} catch (error) {
|
||||||
|
if (error.response?.status === 404) {
|
||||||
|
return undefined;
|
||||||
|
} else {
|
||||||
|
this.handleError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getSlug(slug: string): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
// TODO: scenario should be : if we can get this slug, it exists. if not, it doesn't. It's just a GET.
|
// TODO: scenario should be : if we can get this slug, it exists. if not, it doesn't. It's just a GET.
|
||||||
const response: AxiosResponse = await this.axiosInstance.get(
|
const response: AxiosResponse = await this.axiosInstance.get(
|
||||||
@ -77,23 +123,24 @@ export class ApiService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async sendEmailToUserOfItsPollsList(user: User): Promise<void> {
|
public async sendEmailToUserOfItsPollsList(email: string): Promise<void> {
|
||||||
// If user is not authenticated: the list of polls is send to user's email by the backend.
|
// If user is not authenticated: the list of polls is send to user's email by the backend.
|
||||||
try {
|
try {
|
||||||
await this.axiosInstance.get<Poll[]>(
|
await this.axiosInstance.get<Poll[]>(
|
||||||
`${this.usersEndpoint}/${user.email}${this.usersPollsEndpoint}${this.usersPollsSendEmailEndpoint}`
|
`${this.usersEndpoint}/${email}${this.usersPollsEndpoint}${this.usersPollsSendEmailEndpoint}`
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.handleError(error);
|
this.handleError(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getPollsByUserEmail(user: User): Promise<Poll[]> {
|
public async getPollsUrlsByUserEmail(email: string): Promise<Poll[]> {
|
||||||
// If user is authenticated : retrieve polls & display directly in frontend.
|
// If user is authenticated : retrieve polls & display directly in frontend.
|
||||||
// TODO: Backend should handle this case. Actually the endpoint doesn't exist in backend.
|
// TODO: Backend should handle this case. Actually the endpoint doesn't exist in backend.
|
||||||
|
// Here, only the list of slugs is usefull. Maybe just handle the list of slugs.
|
||||||
try {
|
try {
|
||||||
const response: AxiosResponse<Poll[]> = await this.axiosInstance.get<Poll[]>(
|
const response: AxiosResponse<Poll[]> = await this.axiosInstance.get<Poll[]>(
|
||||||
`${this.usersEndpoint}/${user.email}${this.usersPollsEndpoint}`
|
`${this.usersEndpoint}/${email}${this.usersPollsEndpoint}`
|
||||||
);
|
);
|
||||||
return response?.data;
|
return response?.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -101,41 +148,20 @@ export class ApiService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getPollByIdentifier(identifier: string): Promise<Poll | undefined> {
|
|
||||||
// TODO: identifier should be decided according to backend : Id || Slug ?
|
|
||||||
try {
|
|
||||||
const response: AxiosResponse<Poll> = await this.axiosInstance.get<Poll>(
|
|
||||||
`${this.pollsEndpoint}/${identifier}`
|
|
||||||
);
|
|
||||||
return response?.data;
|
|
||||||
} catch (error) {
|
|
||||||
if (error.response?.status === 404) {
|
|
||||||
return undefined;
|
|
||||||
} else {
|
|
||||||
this.handleError(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
////////////
|
////////////
|
||||||
// UPDATE //
|
// UPDATE //
|
||||||
////////////
|
////////////
|
||||||
public async updatePoll(poll: Poll): Promise<void> {
|
public async updateAnswer(
|
||||||
|
slug: string,
|
||||||
|
choiceLabel: string,
|
||||||
|
pseudo: string,
|
||||||
|
response: ResponseType
|
||||||
|
): Promise<string> {
|
||||||
try {
|
try {
|
||||||
// TODO: implement the params when entities are finalized.
|
return await this.axiosInstance.patch(`${this.pollsEndpoint}/${slug}${this.answersEndpoint}`, {
|
||||||
await this.axiosInstance.put(`${this.pollsEndpoint}/${poll.id}`, {
|
choiceLabel,
|
||||||
params: { voteStack: {}, token: '' },
|
pseudo,
|
||||||
});
|
response,
|
||||||
} catch (error) {
|
|
||||||
this.handleError(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async updateVote(voteStack: any): Promise<void> {
|
|
||||||
try {
|
|
||||||
// TODO: implement the params when entities are finalized.
|
|
||||||
await this.axiosInstance.patch(`${this.votesStacksEndpoint}/${voteStack.id}`, {
|
|
||||||
params: { voteStack: {}, token: '' },
|
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.handleError(error);
|
this.handleError(error);
|
||||||
@ -145,28 +171,32 @@ export class ApiService {
|
|||||||
////////////
|
////////////
|
||||||
// DELETE //
|
// DELETE //
|
||||||
////////////
|
////////////
|
||||||
|
public async deletePoll(slug: string): Promise<boolean> {
|
||||||
public async deletePoll(poll: Poll): Promise<void> {
|
|
||||||
try {
|
try {
|
||||||
await this.axiosInstance.delete(`${this.pollsEndpoint}${poll.id}`, {});
|
const response: AxiosResponse = await this.axiosInstance.delete(`${this.pollsEndpoint}/${slug}`);
|
||||||
|
return response?.status === 204;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.handleError(error);
|
this.handleError(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async deletePollVotes(poll: Poll): Promise<void> {
|
public async deletePollAnswers(slug: string): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
// TODO: update endpoint in Backend
|
const response: AxiosResponse = await this.axiosInstance.delete(
|
||||||
await this.axiosInstance.delete(`${this.pollsEndpoint}${poll.id}${this.votesEndpoint}`);
|
`${this.pollsEndpoint}/${slug}${this.answersEndpoint}`
|
||||||
|
);
|
||||||
|
return response?.status === 204;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.handleError(error);
|
this.handleError(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async deletePollComments(poll: Poll): Promise<void> {
|
public async deletePollComments(slug: string): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
// TODO: modify endpoint in Backend
|
const response: AxiosResponse = await this.axiosInstance.delete(
|
||||||
await this.axiosInstance.delete(`${this.pollsEndpoint}${poll.id}${this.commentsEndpoint}`);
|
`${this.pollsEndpoint}/${slug}${this.commentsEndpoint}`
|
||||||
|
);
|
||||||
|
return response?.status === 204;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.handleError(error);
|
this.handleError(error);
|
||||||
}
|
}
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
|
|
||||||
import { Poll } from '../models/poll.model';
|
|
||||||
import { ApiService } from './api.service';
|
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: 'root',
|
|
||||||
})
|
|
||||||
export class CommentService {
|
|
||||||
constructor(private apiService: ApiService) {}
|
|
||||||
|
|
||||||
public saveComment(poll: Poll, comment: string): void {
|
|
||||||
this.apiService.saveComment(poll, comment);
|
|
||||||
}
|
|
||||||
}
|
|
16
src/app/core/services/language.service.spec.ts
Normal file
16
src/app/core/services/language.service.spec.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { LanguageService } from './language.service';
|
||||||
|
|
||||||
|
describe('LanguageService', () => {
|
||||||
|
let service: LanguageService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({});
|
||||||
|
service = TestBed.inject(LanguageService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
66
src/app/core/services/language.service.ts
Normal file
66
src/app/core/services/language.service.ts
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { TranslateService, LangChangeEvent } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
import { Language } from '../enums/language.enum';
|
||||||
|
import { StorageService } from './storage.service';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class LanguageService {
|
||||||
|
constructor(private translate: TranslateService, private storageService: StorageService) {}
|
||||||
|
|
||||||
|
public getLangage(): Language {
|
||||||
|
return this.translate.currentLang as Language;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setLanguage(language: Language): void {
|
||||||
|
this.translate.use(language.toString().toUpperCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
public getAvailableLanguages(): string[] {
|
||||||
|
return this.translate.getLangs();
|
||||||
|
}
|
||||||
|
|
||||||
|
public configureAndInitTranslations(): void {
|
||||||
|
// always save in storage the currentLang used
|
||||||
|
this.translate.onLangChange.subscribe((event: LangChangeEvent) => {
|
||||||
|
this.storageService.language = event.lang as Language;
|
||||||
|
});
|
||||||
|
|
||||||
|
// set all languages available
|
||||||
|
this.translate.addLangs(Object.keys(Language));
|
||||||
|
|
||||||
|
// set language
|
||||||
|
this.setLanguageOnInit();
|
||||||
|
}
|
||||||
|
|
||||||
|
private setLanguageOnInit(): void {
|
||||||
|
// set language from storage
|
||||||
|
if (!this.translate.currentLang) {
|
||||||
|
this.setLanguageFromStorage();
|
||||||
|
}
|
||||||
|
|
||||||
|
// or set language from browser
|
||||||
|
if (!this.translate.currentLang) {
|
||||||
|
this.setLanguageFromBrowser();
|
||||||
|
}
|
||||||
|
|
||||||
|
// set default language
|
||||||
|
if (!this.translate.currentLang) {
|
||||||
|
this.setLanguage(Language.EN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private setLanguageFromStorage(): void {
|
||||||
|
if (this.storageService.language && this.translate.getLangs().includes(this.storageService.language)) {
|
||||||
|
this.setLanguage(this.storageService.language);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private setLanguageFromBrowser(): void {
|
||||||
|
const currentBrowserLanguage: Language = this.translate.getBrowserLang().toUpperCase() as Language;
|
||||||
|
if (this.translate.getLangs().includes(currentBrowserLanguage)) {
|
||||||
|
this.setLanguage(currentBrowserLanguage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,13 @@
|
|||||||
import { TestBed } from '@angular/core/testing';
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { CommentService } from './comment.service';
|
import { LoaderService } from './loader.service';
|
||||||
|
|
||||||
describe('CommentService', () => {
|
describe('LoaderService', () => {
|
||||||
let service: CommentService;
|
let service: LoaderService;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({});
|
TestBed.configureTestingModule({});
|
||||||
service = TestBed.inject(CommentService);
|
service = TestBed.inject(LoaderService);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be created', () => {
|
it('should be created', () => {
|
14
src/app/core/services/loader.service.ts
Normal file
14
src/app/core/services/loader.service.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { BehaviorSubject, Observable } from 'rxjs';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class LoaderService {
|
||||||
|
private _loadingStatus: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
|
||||||
|
public readonly isLoading: Observable<boolean> = this._loadingStatus.asObservable();
|
||||||
|
|
||||||
|
public setStatus(status: boolean): void {
|
||||||
|
this._loadingStatus.next(status);
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,8 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
import { UserRole } from '../enums/user-role.enum';
|
import { Choice } from '../models/choice.model';
|
||||||
import { Poll } from '../models/poll.model';
|
import { Poll } from '../models/poll.model';
|
||||||
|
import { Question } from '../models/question.model';
|
||||||
import { User } from '../models/user.model';
|
import { User } from '../models/user.model';
|
||||||
import { PollService } from './poll.service';
|
import { PollService } from './poll.service';
|
||||||
import { UserService } from './user.service';
|
import { UserService } from './user.service';
|
||||||
@ -10,28 +11,29 @@ import { UserService } from './user.service';
|
|||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class MockingService {
|
export class MockingService {
|
||||||
public pollsDatabase: Poll[];
|
private user: User;
|
||||||
|
public pollsDatabase: Poll[] = [];
|
||||||
|
|
||||||
private user: User = new User(UserRole.ANONYMOUS, false, 'toto', 'toto@gafam.com', []);
|
private poll1: Poll = new Poll(this.user, 'Quand le picnic ?', 'Pour faire la teuf');
|
||||||
private poll1: Poll = new Poll(false, 'mon super sondage', 'super description 1', 'super_slug_1', 'id1');
|
private poll2: Poll = new Poll(this.user, 'On fait quoi à la soirée ?', 'Balancez vos idées');
|
||||||
private poll2: Poll = new Poll(false, 'mon autre sondage', 'super description 2', 'super_slug_2', 'id2');
|
|
||||||
|
|
||||||
constructor(private userService: UserService, private pollService: PollService) {
|
constructor(private userService: UserService, private pollService: PollService) {
|
||||||
|
this.poll1.choices = [new Choice('mardi prochain'), new Choice('mercredi')];
|
||||||
|
this.poll2.choices = [new Choice('jeux'), new Choice('danser'), new Choice('discuter en picolant')];
|
||||||
|
|
||||||
this.pollsDatabase = [this.poll1, this.poll2];
|
this.pollsDatabase = [this.poll1, this.poll2];
|
||||||
this.user.polls = this.pollsDatabase;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public loadUser(role: UserRole): void {
|
public loadUser(user: User): void {
|
||||||
this.user.role = role;
|
this.user = user;
|
||||||
|
this.user.polls = this.pollsDatabase;
|
||||||
console.info('MOCKING user', { user: this.user });
|
console.info('MOCKING user', { user: this.user });
|
||||||
this.userService.updateUser(this.user);
|
this.userService.updateUser(this.user);
|
||||||
}
|
}
|
||||||
|
|
||||||
public loadPoll(slug: string): void {
|
public loadPoll(slug: string): void {
|
||||||
const poll: Poll | undefined = this.pollsDatabase.find((poll: Poll) => poll.slug === slug);
|
this.poll1.slug = slug;
|
||||||
if (poll) {
|
console.info('MOCKING poll', { poll: this.poll1 });
|
||||||
console.info('MOCKING poll', { poll });
|
this.pollService.updateCurrentPoll(this.poll1);
|
||||||
this.pollService.updateCurrentPoll(poll);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
16
src/app/core/services/modal.service.spec.ts
Normal file
16
src/app/core/services/modal.service.spec.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ModalService } from './modal.service';
|
||||||
|
|
||||||
|
describe('ModalService', () => {
|
||||||
|
let service: ModalService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({});
|
||||||
|
service = TestBed.inject(ModalService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
26
src/app/core/services/modal.service.ts
Normal file
26
src/app/core/services/modal.service.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { DialogService } from 'primeng';
|
||||||
|
|
||||||
|
import { ChoiceDetailsComponent } from '../../shared/components/choice-details/choice-details.component';
|
||||||
|
import { SettingsComponent } from '../../shared/components/settings/settings.component';
|
||||||
|
import { Choice } from '../models/choice.model';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class ModalService {
|
||||||
|
constructor(public dialogService: DialogService) {}
|
||||||
|
|
||||||
|
public openSettingsComponent(): void {
|
||||||
|
this.dialogService.open(SettingsComponent, { header: 'Paramètres', dismissableMask: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
public openChoiceDetailsComponent(choice: Choice): void {
|
||||||
|
this.dialogService.open(ChoiceDetailsComponent, {
|
||||||
|
header: 'Détails des votes',
|
||||||
|
dismissableMask: true,
|
||||||
|
data: choice,
|
||||||
|
width: '70%',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,10 @@ import { Injectable } from '@angular/core';
|
|||||||
import { BehaviorSubject, Observable } from 'rxjs';
|
import { BehaviorSubject, Observable } from 'rxjs';
|
||||||
|
|
||||||
import { MessageSeverity } from '../enums/message-severity.enum';
|
import { MessageSeverity } from '../enums/message-severity.enum';
|
||||||
|
import { ResponseType } from '../enums/response-type.enum';
|
||||||
|
import { Choice } from '../models/choice.model';
|
||||||
import { Poll } from '../models/poll.model';
|
import { Poll } from '../models/poll.model';
|
||||||
|
import { User } from '../models/user.model';
|
||||||
import { ApiService } from './api.service';
|
import { ApiService } from './api.service';
|
||||||
import { MessageDisplayerService } from './message-displayer.service';
|
import { MessageDisplayerService } from './message-displayer.service';
|
||||||
|
|
||||||
@ -13,44 +16,59 @@ export class PollService {
|
|||||||
private _poll: BehaviorSubject<Poll | undefined> = new BehaviorSubject<Poll | undefined>(undefined);
|
private _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();
|
||||||
|
|
||||||
constructor(private apiService: ApiService, private messageService: MessageDisplayerService) {}
|
constructor(private apiService: ApiService, private messageDisplayerService: MessageDisplayerService) {}
|
||||||
|
|
||||||
public updateCurrentPoll(poll: Poll): void {
|
public updateCurrentPoll(poll: Poll): void {
|
||||||
this._poll.next(poll);
|
this._poll.next(poll);
|
||||||
}
|
}
|
||||||
|
|
||||||
// SAVE
|
public async getPollBySlug(slug: string): Promise<void> {
|
||||||
public async savePoll(poll: Poll): Promise<void> {
|
const poll: Poll | undefined = await this.apiService.getPollBySlug(slug);
|
||||||
await this.apiService.savePoll(poll);
|
this.updateCurrentPoll(poll);
|
||||||
this.messageService.display(MessageSeverity.SUCCESS, 'Le sondage a été créé.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async saveVote(poll: Poll): Promise<void> {
|
public async saveCurrentPoll(): Promise<void> {
|
||||||
await this.apiService.saveVote(poll);
|
const pollUrl: string = await this.apiService.createPoll(this._poll.getValue());
|
||||||
this.messageService.display(MessageSeverity.SUCCESS, 'Votre participation au sondage a été enregistrée.');
|
// TODO: Maybe handle the url to update currentPoll according to backend response
|
||||||
|
if (pollUrl) {
|
||||||
|
this.messageDisplayerService.display(MessageSeverity.SUCCESS, 'Le sondage a été enregistré.');
|
||||||
|
} else {
|
||||||
|
this.messageDisplayerService.display(
|
||||||
|
MessageSeverity.ERROR,
|
||||||
|
'Le sondage n’a été correctement enregistré, veuillez ré-essayer.'
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async saveComment(poll: Poll, comment: string): Promise<void> {
|
public saveParticipation(choice: Choice, user: User, response: ResponseType): void {
|
||||||
await this.apiService.saveComment(poll, comment);
|
const currentPoll = this._poll.getValue();
|
||||||
this.messageService.display(MessageSeverity.SUCCESS, 'Votre commentaire a été enregistré.');
|
currentPoll.choices.find((c) => c.label === choice.label)?.updateParticipation(user.pseudo, response);
|
||||||
|
this.updateCurrentPoll(currentPoll);
|
||||||
|
this.apiService.createParticipation(currentPoll.slug, choice.label, user.pseudo, response);
|
||||||
|
this.messageDisplayerService.display(
|
||||||
|
MessageSeverity.SUCCESS,
|
||||||
|
'Votre participation au sondage a été enregistrée.'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET
|
public async deleteAllAnswers(): Promise<void> {
|
||||||
public async getPollByIdentifier(slug: string): Promise<void> {
|
await this.apiService.deletePollAnswers(this._poll.getValue().slug);
|
||||||
this.updateCurrentPoll(await this.apiService.getPollByIdentifier(slug));
|
this.messageDisplayerService.display(
|
||||||
}
|
|
||||||
|
|
||||||
// DELETE
|
|
||||||
public async deletePollVotes(poll: Poll): Promise<void> {
|
|
||||||
await this.apiService.deletePollVotes(poll);
|
|
||||||
this.messageService.display(
|
|
||||||
MessageSeverity.SUCCESS,
|
MessageSeverity.SUCCESS,
|
||||||
'Les participations des votants à ce sondage ont été supprimées.'
|
'Les participations des votants à ce sondage ont été supprimées.'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async deletePollComments(poll: Poll): Promise<void> {
|
public async addComment(comment: string): Promise<void> {
|
||||||
await this.apiService.deletePollComments(poll);
|
await this.apiService.createComment(this._poll.getValue().slug, comment);
|
||||||
this.messageService.display(MessageSeverity.SUCCESS, 'Les commentaires de ce sondage ont été supprimés.');
|
this.messageDisplayerService.display(MessageSeverity.SUCCESS, 'Votre commentaire a été enregistré.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public async deleteComments(): Promise<void> {
|
||||||
|
await this.apiService.deletePollComments(this._poll.getValue().slug);
|
||||||
|
this.messageDisplayerService.display(
|
||||||
|
MessageSeverity.SUCCESS,
|
||||||
|
'Les commentaires de ce sondage ont été supprimés.'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,4 +16,7 @@ export class StorageService {
|
|||||||
|
|
||||||
@LocalStorage()
|
@LocalStorage()
|
||||||
public userPollsIds: string[];
|
public userPollsIds: string[];
|
||||||
|
|
||||||
|
@LocalStorage()
|
||||||
|
public pseudo: string;
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { TestBed } from '@angular/core/testing';
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { VoteService } from './vote.service';
|
import { UrlService } from './url.service';
|
||||||
|
|
||||||
describe('VoteService', () => {
|
describe('UrlService', () => {
|
||||||
let service: VoteService;
|
let service: UrlService;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({});
|
TestBed.configureTestingModule({});
|
||||||
service = TestBed.inject(VoteService);
|
service = TestBed.inject(UrlService);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be created', () => {
|
it('should be created', () => {
|
26
src/app/core/services/url.service.ts
Normal file
26
src/app/core/services/url.service.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
|
||||||
|
import { LoaderService } from './loader.service';
|
||||||
|
import { PollService } from './poll.service';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class UrlService {
|
||||||
|
constructor(
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private pollService: PollService,
|
||||||
|
private loaderService: LoaderService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public async loadPollFromUrl(): Promise<void> {
|
||||||
|
// TODO: behavior improvement needed.
|
||||||
|
// check if pollService currentPoll’s slug match the url : if yes, don’t fetch again.
|
||||||
|
|
||||||
|
this.loaderService.setStatus(true);
|
||||||
|
const wantedSlug: string = this.route.snapshot.firstChild.firstChild.url[1].path;
|
||||||
|
await this.pollService.getPollBySlug(wantedSlug);
|
||||||
|
this.loaderService.setStatus(false);
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,8 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
import { DialogService } from 'primeng';
|
||||||
import { BehaviorSubject, Observable } from 'rxjs';
|
import { BehaviorSubject, Observable } from 'rxjs';
|
||||||
|
|
||||||
|
import { UserRole } from '../enums/user-role.enum';
|
||||||
import { User } from '../models/user.model';
|
import { User } from '../models/user.model';
|
||||||
import { ApiService } from './api.service';
|
import { ApiService } from './api.service';
|
||||||
|
|
||||||
@ -8,25 +10,28 @@ import { ApiService } from './api.service';
|
|||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class UserService {
|
export class UserService {
|
||||||
public anonymous: User = new User();
|
public anonymous: User = new User('', '', UserRole.ANONYMOUS);
|
||||||
|
|
||||||
private _user: BehaviorSubject<User> = new BehaviorSubject<User>(this.anonymous);
|
private _user: BehaviorSubject<User> = new BehaviorSubject<User>(this.anonymous);
|
||||||
public readonly user: Observable<User> = this._user.asObservable();
|
public readonly user: Observable<User> = this._user.asObservable();
|
||||||
|
|
||||||
constructor(private apiService: ApiService) {}
|
constructor(private apiService: ApiService, public dialogService: DialogService) {}
|
||||||
|
|
||||||
public updateUser(user: User): void {
|
public updateUser(user: User): void {
|
||||||
this._user.next(user);
|
this._user.next(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET
|
public isCurrentUserIdentifiable(): boolean {
|
||||||
|
return this._user.getValue().pseudo ? true : false;
|
||||||
|
}
|
||||||
|
|
||||||
public async getUserPolls(): Promise<void> {
|
public async getUserPolls(): Promise<void> {
|
||||||
const currentUser: User = this._user.getValue();
|
const currentUser: User = this._user.getValue();
|
||||||
currentUser.polls = await this.apiService.getPollsByUserEmail(currentUser);
|
currentUser.polls = await this.apiService.getPollsUrlsByUserEmail(currentUser.email);
|
||||||
this.updateUser(currentUser);
|
this.updateUser(currentUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST
|
|
||||||
public async sendEmailToUserTheListOfItsPolls(): Promise<void> {
|
public async sendEmailToUserTheListOfItsPolls(): Promise<void> {
|
||||||
await this.apiService.sendEmailToUserOfItsPollsList(this._user.getValue());
|
await this.apiService.sendEmailToUserOfItsPollsList(this._user.getValue().email);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { ApiService } from './api.service';
|
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: 'root',
|
|
||||||
})
|
|
||||||
export class VoteService {
|
|
||||||
constructor(private apiService: ApiService) {}
|
|
||||||
|
|
||||||
public saveVote(vote: any): void {
|
|
||||||
this.apiService.saveVote(vote);
|
|
||||||
}
|
|
||||||
|
|
||||||
public updateVote(vote: any): void {
|
|
||||||
this.apiService.updateVote(vote);
|
|
||||||
}
|
|
||||||
}
|
|
@ -7,7 +7,7 @@ import { WorkflowStep } from '../enums/workflow-step.enum';
|
|||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class WorkflowService {
|
export class WorkflowService {
|
||||||
private steps = [WorkflowStep.DESCRIPTION, WorkflowStep.OPTIONS, WorkflowStep.CONFIGURATION];
|
private steps = [WorkflowStep.DESCRIPTION, WorkflowStep.CHOICES, WorkflowStep.CONFIGURATION];
|
||||||
|
|
||||||
private _currentStep: BehaviorSubject<WorkflowStep> = new BehaviorSubject<WorkflowStep>(WorkflowStep[0]);
|
private _currentStep: BehaviorSubject<WorkflowStep> = new BehaviorSubject<WorkflowStep>(WorkflowStep[0]);
|
||||||
public readonly currentStep: Observable<WorkflowStep> = this._currentStep.asObservable();
|
public readonly currentStep: Observable<WorkflowStep> = this._currentStep.asObservable();
|
||||||
|
@ -13,38 +13,15 @@ export class DateUtilsService {
|
|||||||
return moment(dateLeft).diff(moment(dateRight));
|
return moment(dateLeft).diff(moment(dateRight));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static formatDate(date): string {
|
public static isDateInFuture(date: Date): boolean {
|
||||||
|
return this.diffInDays(date, new Date()) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static isDateInPast(date: Date): boolean {
|
||||||
|
return this.diffInDays(date, new Date()) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static formatDate(date: Date): string {
|
||||||
return moment(date).format('yyyy-MM-dd');
|
return moment(date).format('yyyy-MM-dd');
|
||||||
}
|
}
|
||||||
|
|
||||||
public static orderDates(): Date[] {
|
|
||||||
// TODO: to implement
|
|
||||||
const datesOrdered: Date[] = [];
|
|
||||||
return datesOrdered;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static getDatesInRange(d1: Date, d2: Date, interval: number): Date[] {
|
|
||||||
// TODO: refacto this
|
|
||||||
d1 = new Date(d1);
|
|
||||||
d2 = new Date(d2);
|
|
||||||
const dates = [];
|
|
||||||
while (+d1 < +d2) {
|
|
||||||
dates.push({
|
|
||||||
literal: this.formateDate(d1),
|
|
||||||
date_object: d1,
|
|
||||||
});
|
|
||||||
d1.setDate(d1.getDate() + interval);
|
|
||||||
}
|
|
||||||
return [...dates];
|
|
||||||
}
|
|
||||||
|
|
||||||
public static getDoubleDigits(str: string): string {
|
|
||||||
// TODO: ça sert à quoi ça ?
|
|
||||||
// Parce que ajouter 2 caractère à une string et ensuite slicer à partir du 2ème caractère, euh…
|
|
||||||
return ('00' + str).slice(-2);
|
|
||||||
}
|
|
||||||
|
|
||||||
private isInChronologicalOrder(date1: Date, date2: Date): boolean {
|
|
||||||
return date1 < date2;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -18,8 +18,8 @@ export class EditDescriptionComponent implements OnInit {
|
|||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.pollForm = this.fb.group({
|
this.pollForm = this.fb.group({
|
||||||
type: [this.poll ? this.poll.isDateType : false, [Validators.required]],
|
type: [this.poll ? this.poll.configuration.isAboutDate : false, [Validators.required]],
|
||||||
title: [this.poll ? this.poll.title : '', [Validators.required]],
|
title: [this.poll ? this.poll.question : '', [Validators.required]],
|
||||||
description: [this.poll ? this.poll.description : ''],
|
description: [this.poll ? this.poll.description : ''],
|
||||||
slug: [this.poll ? this.poll.slug : this.generateRandomSlug(), [Validators.required]],
|
slug: [this.poll ? this.poll.slug : this.generateRandomSlug(), [Validators.required]],
|
||||||
address: this.fb.group({
|
address: this.fb.group({
|
||||||
|
@ -18,8 +18,8 @@ export class PollEditComponent implements OnInit {
|
|||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.pollForm = this.fb.group({
|
this.pollForm = this.fb.group({
|
||||||
type: [this.poll ? this.poll.isDateType : false, [Validators.required]],
|
type: [this.poll ? this.poll.configuration.isAboutDate : false, [Validators.required]],
|
||||||
title: [this.poll ? this.poll.title : '', [Validators.required]],
|
title: [this.poll ? this.poll.question : '', [Validators.required]],
|
||||||
description: [this.poll ? this.poll.description : ''],
|
description: [this.poll ? this.poll.description : ''],
|
||||||
slug: [this.poll ? this.poll.slug : this.generateRandomSlug(), [Validators.required]],
|
slug: [this.poll ? this.poll.slug : this.generateRandomSlug(), [Validators.required]],
|
||||||
address: this.fb.group({
|
address: this.fb.group({
|
||||||
|
@ -11,10 +11,10 @@
|
|||||||
<thead></thead>
|
<thead></thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr *ngFor="let poll of (_user | async)?.polls">
|
<tr *ngFor="let poll of (_user | async)?.polls">
|
||||||
<th>{{ poll.title }}</th>
|
<th>{{ poll.question }}</th>
|
||||||
<td>
|
<td>
|
||||||
<a routerLink="{{ '/administration/edit/description/' + poll.slug }}">
|
<a routerLink="{{ '/administration/edit/description/' + poll.slug }}">
|
||||||
{{ poll.getUrl() }}
|
{{ poll.slug }}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -26,7 +26,7 @@
|
|||||||
<ng-container *ngIf="['ANONYMOUS'].includes((_user | async)?.role)">
|
<ng-container *ngIf="['ANONYMOUS'].includes((_user | async)?.role)">
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<a class="button is-primary" role="button" routerLink="/login">
|
<a class="button is-primary" role="button" routerLink="/">
|
||||||
J’ai un compte, je me connecte
|
J’ai un compte, je me connecte
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -10,14 +10,13 @@ import { UserService } from '../../../core/services/user.service';
|
|||||||
styleUrls: ['./profile.component.scss'],
|
styleUrls: ['./profile.component.scss'],
|
||||||
})
|
})
|
||||||
export class ProfileComponent implements OnInit {
|
export class ProfileComponent implements OnInit {
|
||||||
public _user: Observable<User>;
|
public _user: Observable<User> = this.userService.user;
|
||||||
public isModalOpened = false;
|
public isModalOpened = false;
|
||||||
|
|
||||||
constructor(private userService: UserService) {}
|
constructor(private userService: UserService) {}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {}
|
||||||
this._user = this.userService.user;
|
|
||||||
}
|
|
||||||
public toggleModal(): void {
|
public toggleModal(): void {
|
||||||
this.isModalOpened = !this.isModalOpened;
|
this.isModalOpened = !this.isModalOpened;
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ export class StepperComponent implements OnInit {
|
|||||||
};
|
};
|
||||||
public itemOptions: MenuItem = {
|
public itemOptions: MenuItem = {
|
||||||
id: '2',
|
id: '2',
|
||||||
label: WorkflowStep.OPTIONS,
|
label: WorkflowStep.CHOICES,
|
||||||
title: 'Je renseigne les différentes options sur lesquelles les gens vont donner leur avis',
|
title: 'Je renseigne les différentes options sur lesquelles les gens vont donner leur avis',
|
||||||
routerLink: './options',
|
routerLink: './options',
|
||||||
command: () => {},
|
command: () => {},
|
||||||
|
17
src/app/features/consultation/consultation-routing.module.ts
Normal file
17
src/app/features/consultation/consultation-routing.module.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
|
||||||
|
import { ConsultationComponent } from './consultation.component';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{ path: '', redirectTo: 'poll', pathMatch: 'full' },
|
||||||
|
{ path: 'poll', component: ConsultationComponent },
|
||||||
|
{ path: ':slug', redirectTo: 'poll/:slug', pathMatch: 'full' },
|
||||||
|
{ path: 'poll/:slug', component: ConsultationComponent },
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [RouterModule.forChild(routes)],
|
||||||
|
exports: [RouterModule],
|
||||||
|
})
|
||||||
|
export class ConsultationRoutingModule {}
|
62
src/app/features/consultation/consultation.component.html
Normal file
62
src/app/features/consultation/consultation.component.html
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
<div class="columns">
|
||||||
|
<div class="column has-text-centered">
|
||||||
|
<h1>Consultation</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="_isLoading | async" class="columns p-justify-center">
|
||||||
|
<div class="column has-text-centered">
|
||||||
|
<p-progressSpinner></p-progressSpinner>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ng-container *ngIf="!(_isLoading | async)">
|
||||||
|
<ng-container *ngIf="!(_poll | async)">
|
||||||
|
<app-page-not-found [message]="'PAGE_NOT_FOUND.POLL'"></app-page-not-found>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container *ngIf="_poll | async as poll">
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column">
|
||||||
|
<div class="card">
|
||||||
|
<header class="card-header">
|
||||||
|
<p class="card-header-title">{{ poll.question }}</p>
|
||||||
|
<p class="card-header-icon">author : {{ poll.owner.pseudo }}</p>
|
||||||
|
</header>
|
||||||
|
<div class="card-content">
|
||||||
|
<div class="content">
|
||||||
|
<p>{{ poll.description }}</p>
|
||||||
|
<div class="buttons has-addons is-small is-right">
|
||||||
|
<button class="button" [class.is-active]="isCompactMode" (click)="isCompactMode = true">
|
||||||
|
Compact
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="button"
|
||||||
|
[class.is-active]="!isCompactMode"
|
||||||
|
(click)="isCompactMode = false"
|
||||||
|
>
|
||||||
|
Detailed
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<app-poll-results-compact *ngIf="isCompactMode" [poll]="poll"></app-poll-results-compact>
|
||||||
|
<app-poll-results-detailed *ngIf="!isCompactMode" [poll]="poll"></app-poll-results-detailed>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<footer class="card-footer" *ngIf="!isArchived(poll)">
|
||||||
|
<a routerLink="{{ '../../../participation/poll/' + poll.slug }}" class="card-footer-item">
|
||||||
|
Participer
|
||||||
|
</a>
|
||||||
|
<a routerLink="{{ '../../../administration/poll/' + poll.slug }}" class="card-footer-item">
|
||||||
|
Administrer
|
||||||
|
</a>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column">
|
||||||
|
<app-comments></app-comments>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
24
src/app/features/consultation/consultation.component.spec.ts
Normal file
24
src/app/features/consultation/consultation.component.spec.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ConsultationComponent } from './consultation.component';
|
||||||
|
|
||||||
|
describe('ConsultationComponent', () => {
|
||||||
|
let component: ConsultationComponent;
|
||||||
|
let fixture: ComponentFixture<ConsultationComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ConsultationComponent],
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ConsultationComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
40
src/app/features/consultation/consultation.component.ts
Normal file
40
src/app/features/consultation/consultation.component.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
import { Configuration } from '../../core/models/configuration.model';
|
||||||
|
import { Poll } from '../../core/models/poll.model';
|
||||||
|
import { LoaderService } from '../../core/services/loader.service';
|
||||||
|
import { ModalService } from '../../core/services/modal.service';
|
||||||
|
import { PollService } from '../../core/services/poll.service';
|
||||||
|
import { UrlService } from '../../core/services/url.service';
|
||||||
|
import { UserService } from '../../core/services/user.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-consultation',
|
||||||
|
templateUrl: './consultation.component.html',
|
||||||
|
styleUrls: ['./consultation.component.scss'],
|
||||||
|
})
|
||||||
|
export class ConsultationComponent implements OnInit {
|
||||||
|
public _isLoading: Observable<boolean> = this.loaderService.isLoading;
|
||||||
|
public _poll: Observable<Poll> = this.pollService.poll;
|
||||||
|
public isCompactMode = true;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private urlService: UrlService,
|
||||||
|
private loaderService: LoaderService,
|
||||||
|
private pollService: PollService,
|
||||||
|
private userService: UserService,
|
||||||
|
private modalService: ModalService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
if (!this.userService.isCurrentUserIdentifiable()) {
|
||||||
|
this.modalService.openSettingsComponent();
|
||||||
|
}
|
||||||
|
this.urlService.loadPollFromUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
public isArchived(poll: Poll): boolean {
|
||||||
|
return Configuration.isArchived(poll.configuration);
|
||||||
|
}
|
||||||
|
}
|
15
src/app/features/consultation/consultation.module.ts
Normal file
15
src/app/features/consultation/consultation.module.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
import { SharedModule } from '../../shared/shared.module';
|
||||||
|
import { ConsultationRoutingModule } from './consultation-routing.module';
|
||||||
|
import { ConsultationComponent } from './consultation.component';
|
||||||
|
import { PollResultsCompactComponent } from './poll-results-compact/poll-results-compact.component';
|
||||||
|
import { PollResultsDetailedComponent } from './poll-results-detailed/poll-results-detailed.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [ConsultationComponent, PollResultsCompactComponent, PollResultsDetailedComponent],
|
||||||
|
imports: [CommonModule, ConsultationRoutingModule, SharedModule, TranslateModule.forChild({ extend: true })],
|
||||||
|
})
|
||||||
|
export class ConsultationModule {}
|
@ -0,0 +1,19 @@
|
|||||||
|
<div class="box" *ngFor="let choice of poll.choices">
|
||||||
|
<div class="columns is-vcentered is-mobile">
|
||||||
|
<div class="column">
|
||||||
|
<label class="label">{{ choice.label }}</label>
|
||||||
|
</div>
|
||||||
|
<div class="column is-narrow">
|
||||||
|
<div class="buttons has-addons is-right" (click)="openModal(choice)">
|
||||||
|
<button class="button is-white">
|
||||||
|
<img class="image is-24x24" src="../../../assets/img/icon_voter_YES.svg" />
|
||||||
|
{{ choice.counts.get(responseTypeEnum.YES) }}
|
||||||
|
</button>
|
||||||
|
<button class="button is-white" *ngIf="poll.configuration.isMaybeAnswerAvailable">
|
||||||
|
<img class="image is-24x24" src="../../../assets/img/icon_voter_MAYBE.svg" />
|
||||||
|
{{ choice.counts.get(responseTypeEnum.MAYBE) }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,24 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { PollResultsCompactComponent } from './poll-results-compact.component';
|
||||||
|
|
||||||
|
describe('PollResultsCompactComponent', () => {
|
||||||
|
let component: PollResultsCompactComponent;
|
||||||
|
let fixture: ComponentFixture<PollResultsCompactComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [PollResultsCompactComponent],
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(PollResultsCompactComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,28 @@
|
|||||||
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
|
|
||||||
|
import { ResponseType } from '../../../core/enums/response-type.enum';
|
||||||
|
import { Choice } from '../../../core/models/choice.model';
|
||||||
|
import { Poll } from '../../../core/models/poll.model';
|
||||||
|
import { ModalService } from '../../../core/services/modal.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-poll-results-compact',
|
||||||
|
templateUrl: './poll-results-compact.component.html',
|
||||||
|
styleUrls: ['./poll-results-compact.component.scss'],
|
||||||
|
})
|
||||||
|
export class PollResultsCompactComponent implements OnInit {
|
||||||
|
@Input() public poll: Poll;
|
||||||
|
public isModalOpened = false;
|
||||||
|
public choiceInModal: Choice;
|
||||||
|
public responseTypeEnum = ResponseType;
|
||||||
|
|
||||||
|
constructor(private modalService: ModalService) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {}
|
||||||
|
|
||||||
|
public openModal(choice: Choice): void {
|
||||||
|
this.modalService.openChoiceDetailsComponent(choice);
|
||||||
|
this.choiceInModal = choice;
|
||||||
|
this.isModalOpened = true;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th *ngFor="let choice of poll.choices">{{ choice.label }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<ng-container *ngFor="let item of poll.answersByChoiceByParticipant | keyvalue">
|
||||||
|
<tr>
|
||||||
|
<td>{{ item.key }}</td>
|
||||||
|
<td *ngFor="let subItem of item.value | keyvalue">{{ subItem.value }}</td>
|
||||||
|
</tr>
|
||||||
|
</ng-container>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<hr />
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th *ngFor="let choice of poll.choices">{{ choice.label }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<ng-container *ngFor="let answer of poll.answers">
|
||||||
|
<tr>
|
||||||
|
<td>{{ answer.pseudo }}</td>
|
||||||
|
<td *ngFor="let response of answer.responsesByChoices | keyvalue">
|
||||||
|
{{ response.value }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</ng-container>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
@ -0,0 +1,24 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { PollResultsDetailedComponent } from './poll-results-detailed.component';
|
||||||
|
|
||||||
|
describe('PollResultsDetailedComponent', () => {
|
||||||
|
let component: PollResultsDetailedComponent;
|
||||||
|
let fixture: ComponentFixture<PollResultsDetailedComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [PollResultsDetailedComponent],
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(PollResultsDetailedComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,16 @@
|
|||||||
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
|
|
||||||
|
import { Poll } from '../../../core/models/poll.model';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-poll-results-detailed',
|
||||||
|
templateUrl: './poll-results-detailed.component.html',
|
||||||
|
styleUrls: ['./poll-results-detailed.component.scss'],
|
||||||
|
})
|
||||||
|
export class PollResultsDetailedComponent implements OnInit {
|
||||||
|
@Input() public poll: Poll;
|
||||||
|
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
ngOnInit(): void {}
|
||||||
|
}
|
@ -32,8 +32,8 @@ const routes: Routes = [
|
|||||||
{ path: 'step/resume', component: ResumeComponent },
|
{ path: 'step/resume', component: ResumeComponent },
|
||||||
{ path: 'step/end', component: EndConfirmationComponent },
|
{ path: 'step/end', component: EndConfirmationComponent },
|
||||||
{ path: 'graphic/:poll', component: PollGraphicComponent },
|
{ path: 'graphic/:poll', component: PollGraphicComponent },
|
||||||
{ path: 'vote/poll/id/:poll', component: PollDisplayComponent },
|
{ path: 'vote/poll/id/:id', component: PollDisplayComponent },
|
||||||
{ path: 'vote/poll/slug/:pollSlug', component: PollDisplayComponent },
|
{ path: 'vote/poll/slug/:slug', component: PollDisplayComponent },
|
||||||
{ path: 'votingchoice', component: VotingChoiceComponent },
|
{ path: 'votingchoice', component: VotingChoiceComponent },
|
||||||
{ path: 'voting', component: VotingComponent },
|
{ path: 'voting', component: VotingComponent },
|
||||||
];
|
];
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
<ol>
|
<ol>
|
||||||
<li #answers *ngFor="let answer of config.answers; index as i; trackBy: trackFunction" class="answer-item">
|
<li #answers *ngFor="let answer of config.answers; index as i; trackBy: trackFunction" class="answer-item">
|
||||||
<button class="btn btn--default" title="ajouter une image" (click)="showModalForPictureOfAnswer(answer)">
|
<button class="btn btn--default" title="ajouter une image" (click)="showModalForPictureOfAnswer(answer)">
|
||||||
<i class="fa fa-image"></i>
|
<i class="fa fa-image" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
<label for="answer_{{ answer.id }}_url" (click)="showModalForPictureOfAnswer(answer)">
|
<label for="answer_{{ answer.id }}_url" (click)="showModalForPictureOfAnswer(answer)">
|
||||||
<img class="img-thumbnail" src="{{ answer.url }}" alt="image {{ answer.url }}" />
|
<img class="img-thumbnail" src="{{ answer.url }}" alt="image {{ answer.url }}" />
|
||||||
@ -24,7 +24,7 @@
|
|||||||
<label for="answer_{{ answer.id }}_url">
|
<label for="answer_{{ answer.id }}_url">
|
||||||
Choisissez une URL pour illustrer le choix de réponse
|
Choisissez une URL pour illustrer le choix de réponse
|
||||||
</label>
|
</label>
|
||||||
<i class="fa fa-image"></i>
|
<i class="fa fa-image" aria-hidden="true"></i>
|
||||||
<br />
|
<br />
|
||||||
<input
|
<input
|
||||||
class="input is-block"
|
class="input is-block"
|
||||||
@ -56,7 +56,7 @@
|
|||||||
[ngClass]="{ 'btn--primary': allAnswersAreValid }"
|
[ngClass]="{ 'btn--primary': allAnswersAreValid }"
|
||||||
i18n
|
i18n
|
||||||
>
|
>
|
||||||
<i class="fa fa-plus"></i>
|
<i class="fa fa-plus" aria-hidden="true"></i>
|
||||||
Ajouter une proposition
|
Ajouter une proposition
|
||||||
</button>
|
</button>
|
||||||
<br />
|
<br />
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
</h1>
|
</h1>
|
||||||
<form (ngSubmit)="findMyPollsByEmail(config.myEmail)">
|
<form (ngSubmit)="findMyPollsByEmail(config.myEmail)">
|
||||||
<label class="description" for="sendemail" i18n>
|
<label class="description" for="sendemail" i18n>
|
||||||
<i class="fa fa-envelope"></i>
|
<i class="fa fa-envelope" aria-hidden="true"></i>
|
||||||
{{ 'config.find_helper' | translate }} :
|
{{ 'config.find_helper' | translate }} :
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@ -51,7 +51,7 @@
|
|||||||
<ul class="poll-list" *ngFor="let poll of config.myPolls; index as i; trackBy: trackFunction">
|
<ul class="poll-list" *ngFor="let poll of config.myPolls; index as i; trackBy: trackFunction">
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ poll.url }}">
|
<a href="{{ poll.url }}">
|
||||||
{{ poll.title }}
|
{{ poll.question }}
|
||||||
<sub>
|
<sub>
|
||||||
{{ poll.description }}
|
{{ poll.description }}
|
||||||
</sub>
|
</sub>
|
||||||
@ -64,7 +64,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<div class="loading" *ngIf="config.loading">
|
<div class="loading" *ngIf="config.loading">
|
||||||
<i class="fa fa-refresh fa-spin"></i>
|
<i class="fa fa-refresh fa-spin" aria-hidden="true"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<h1 class="title is-1"><i class="fa fa-calendar"></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>
|
||||||
<label for="multi_hours">
|
<label for="multi_hours">
|
||||||
@ -16,7 +16,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button (click)="addDate()" class="btn btn--primary" id="add_date_button">
|
<button (click)="addDate()" class="btn btn--primary" id="add_date_button">
|
||||||
<i class="fa fa-plus"></i>
|
<i class="fa fa-plus" aria-hidden="true"></i>
|
||||||
{{ 'dates.add' | translate }}
|
{{ 'dates.add' | translate }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@ -25,12 +25,12 @@
|
|||||||
class="btn btn--primary"
|
class="btn btn--primary"
|
||||||
id="toggle_interval_button"
|
id="toggle_interval_button"
|
||||||
>
|
>
|
||||||
<i class="fa fa-clock-o"></i>
|
<i class="fa fa-clock-o" aria-hidden="true"></i>
|
||||||
{{ 'dates.add_interval' | translate }}
|
{{ 'dates.add_interval' | translate }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button (click)="emptyAll()" class="btn btn--warning" id="empty_button">
|
<button (click)="emptyAll()" class="btn btn--warning" id="empty_button">
|
||||||
<i class="fa fa-trash"></i>
|
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||||
{{ 'dates.empty' | translate }}
|
{{ 'dates.empty' | translate }}
|
||||||
</button>
|
</button>
|
||||||
<section *ngIf="showDateInterval" class="date-interval">
|
<section *ngIf="showDateInterval" class="date-interval">
|
||||||
@ -45,7 +45,7 @@
|
|||||||
<br />
|
<br />
|
||||||
</p>
|
</p>
|
||||||
<button (click)="addIntervalOfDates()" class="btn btn-block btn--primary">
|
<button (click)="addIntervalOfDates()" class="btn btn-block btn--primary">
|
||||||
<i class="fa fa-plus"></i>
|
<i class="fa fa-plus" aria-hidden="true"></i>
|
||||||
{{ 'dates.interval_button' | translate }}
|
{{ 'dates.interval_button' | translate }}
|
||||||
{{ intervalDays }}
|
{{ intervalDays }}
|
||||||
{{ 'dates.interval_button_dates' | translate }}
|
{{ 'dates.interval_button_dates' | translate }}
|
||||||
@ -71,7 +71,7 @@
|
|||||||
class="btn btn--primary"
|
class="btn btn--primary"
|
||||||
id="add_time_button"
|
id="add_time_button"
|
||||||
>
|
>
|
||||||
<i class="fa fa-plus"></i>
|
<i class="fa fa-plus" aria-hidden="true"></i>
|
||||||
{{ 'dates.add_time' | translate }}
|
{{ 'dates.add_time' | translate }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@ -80,7 +80,7 @@
|
|||||||
class="btn btn--warning"
|
class="btn btn--warning"
|
||||||
id="remove_time_button"
|
id="remove_time_button"
|
||||||
>
|
>
|
||||||
<i class="fa fa-trash"></i>
|
<i class="fa fa-trash" aria-hidden="true"></i>
|
||||||
Aucune plage horaire
|
Aucune plage horaire
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@ -89,7 +89,7 @@
|
|||||||
class="btn btn--warning"
|
class="btn btn--warning"
|
||||||
id="reset_time_button"
|
id="reset_time_button"
|
||||||
>
|
>
|
||||||
<i class="fa fa-refresh"></i>
|
<i class="fa fa-refresh" aria-hidden="true"></i>
|
||||||
réinitialiser
|
réinitialiser
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -97,7 +97,7 @@
|
|||||||
<div *ngIf="'false' === config.allowSeveralHours" class="identical-dates">
|
<div *ngIf="'false' === config.allowSeveralHours" class="identical-dates">
|
||||||
<div *ngFor="let time of config.timeList; index as id" class="time-choice">
|
<div *ngFor="let time of config.timeList; index as id" class="time-choice">
|
||||||
<label for="timeChoices_{{ id }}">
|
<label for="timeChoices_{{ id }}">
|
||||||
<i class="fa fa-clock-o"></i>
|
<i class="fa fa-clock-o" aria-hidden="true"></i>
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
[(ngModel)]="time.literal"
|
[(ngModel)]="time.literal"
|
||||||
@ -106,7 +106,7 @@
|
|||||||
id="timeChoices_{{ id }}"
|
id="timeChoices_{{ id }}"
|
||||||
/>
|
/>
|
||||||
<button (click)="time.timeList.splice(id, 1)" class="btn btn-warning">
|
<button (click)="time.timeList.splice(id, 1)" class="btn btn-warning">
|
||||||
<i class="fa fa-times"></i>
|
<i class="fa fa-times" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -130,7 +130,7 @@
|
|||||||
type="date"
|
type="date"
|
||||||
/>
|
/>
|
||||||
<button (click)="config.dateList.splice(id, 1)" class="btn btn-warning">
|
<button (click)="config.dateList.splice(id, 1)" class="btn btn-warning">
|
||||||
<i class="fa fa-times"></i>
|
<i class="fa fa-times" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
(click)="addTimeToDate(choice, id)"
|
(click)="addTimeToDate(choice, id)"
|
||||||
@ -148,7 +148,7 @@
|
|||||||
type="text"
|
type="text"
|
||||||
/>
|
/>
|
||||||
<button (click)="choice.timeList.splice(idTime, 1)" class="btn btn-warning">
|
<button (click)="choice.timeList.splice(idTime, 1)" class="btn btn-warning">
|
||||||
<i class="fa fa-times"></i>
|
<i class="fa fa-times" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -47,7 +47,7 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<button class="btn btn--primary" (click)="sendToEmail()">
|
<button class="btn btn--primary" (click)="sendToEmail()">
|
||||||
<i class="fa fa-paper-plane"></i>
|
<i class="fa fa-paper-plane" aria-hidden="true"></i>
|
||||||
Envoyer les liens du sondage
|
Envoyer les liens du sondage
|
||||||
</button>
|
</button>
|
||||||
<a href="{{ config.urlPublic }}">
|
<a href="{{ config.urlPublic }}">
|
||||||
|
@ -79,11 +79,11 @@
|
|||||||
|
|
||||||
<button routerLink="../answers" class="btn btn--primary btn--full" *ngIf="config.pollType == 'classic'" i18n>
|
<button routerLink="../answers" class="btn btn--primary btn--full" *ngIf="config.pollType == 'classic'" i18n>
|
||||||
Continuer
|
Continuer
|
||||||
<i class="fa fa-file-text"></i>
|
<i class="fa fa-file-text" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
<button routerLink="../date" class="btn btn--primary btn--full" *ngIf="config.pollType == 'dates'" i18n>
|
<button routerLink="../date" class="btn btn--primary btn--full" *ngIf="config.pollType == 'dates'" i18n>
|
||||||
Continuer
|
Continuer
|
||||||
<i class="fa fa-calendar-check-o"></i>
|
<i class="fa fa-calendar-check-o" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
<a routerLink="../creation" class="prev" i18n>
|
<a routerLink="../creation" class="prev" i18n>
|
||||||
Retour
|
Retour
|
||||||
|
@ -3,12 +3,12 @@
|
|||||||
launch admin action execStuff !
|
launch admin action execStuff !
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn--primary" (click)="config.exportJson()" *ngIf="config.isAdmin">
|
<button class="btn btn--primary" (click)="config.exportJson()" *ngIf="config.isAdmin">
|
||||||
<i class="fa fa-file-archive-o"></i>
|
<i class="fa fa-file-archive-o" aria-hidden="true"></i>
|
||||||
export CSV
|
export CSV
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="loading" *ngIf="config.loading">
|
<div class="loading" *ngIf="config.loading">
|
||||||
<i class="fa fa-refresh fa-spin"></i>
|
<i class="fa fa-refresh fa-spin" aria-hidden="true"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="loaded-poll" *ngIf="!config.loading && config.currentPoll">
|
<div class="loaded-poll" *ngIf="!config.loading && config.currentPoll">
|
||||||
<div id="choices">
|
<div id="choices">
|
||||||
@ -29,7 +29,7 @@
|
|||||||
<h3 class="margin-top-x8">
|
<h3 class="margin-top-x8">
|
||||||
Partager le sondage
|
Partager le sondage
|
||||||
|
|
||||||
<i class="fa fa-share"></i>
|
<i class="fa fa-share" aria-hidden="true"></i>
|
||||||
</h3>
|
</h3>
|
||||||
<p class="nobold text-14" for="copyLink">
|
<p class="nobold text-14" for="copyLink">
|
||||||
Pour partager le sondage, vous pouvez diffuser ce lien :
|
Pour partager le sondage, vous pouvez diffuser ce lien :
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { BaseComponent } from '../../example/base-page/base.component';
|
import { BaseComponent } from '../../example/base-page/base.component';
|
||||||
import { ConfigService } from '../../../services/config.service';
|
import { ConfigService } from '../../../services/config.service';
|
||||||
import { mockComments } from '../../../../../mocks/mock-comments';
|
import { mockComments } from '../../../mocks/mock-comments';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { environment } from '../../../../../../environments/environment';
|
import { environment } from '../../../../../../environments/environment';
|
||||||
import { mockPoll3 } from '../../../../../mocks/mock-poll3';
|
import { mockPoll3 } from '../../../mocks/mock-poll3';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-poll-display',
|
selector: 'app-poll-display',
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { Component, Inject, OnInit } from '@angular/core';
|
import { Component, Inject, OnInit } from '@angular/core';
|
||||||
import { Chart } from 'chart.js';
|
import { Chart } from 'chart.js';
|
||||||
import { DOCUMENT } from '@angular/common';
|
import { DOCUMENT } from '@angular/common';
|
||||||
import { mockGraphConfig } from '../../../../../mocks/mock-graph';
|
import { mockGraphConfig } from '../../../mocks/mock-graph';
|
||||||
import { ConfigService } from '../../../services/config.service';
|
import { ConfigService } from '../../../services/config.service';
|
||||||
import { mockPoll3 } from '../../../../../mocks/mock-poll3';
|
import { mockPoll3 } from '../../../mocks/mock-poll3';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-poll-graphic',
|
selector: 'app-poll-graphic',
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
<section class="type-date" *ngIf="config.pollType !== 'classic'">
|
<section class="type-date" *ngIf="config.pollType !== 'classic'">
|
||||||
<i class="fa fa-clock-o"></i>
|
<i class="fa fa-clock-o" aria-hidden="true"></i>
|
||||||
<span class="well" *ngIf="'true' === config.allowSeveralHours">
|
<span class="well" *ngIf="'true' === config.allowSeveralHours">
|
||||||
{{ 'dates.multiple.different' | translate }}
|
{{ 'dates.multiple.different' | translate }}
|
||||||
</span>
|
</span>
|
||||||
@ -32,7 +32,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<div *ngFor="let choice of config.dateList; index as id" class="date-choice">
|
<div *ngFor="let choice of config.dateList; index as id" class="date-choice">
|
||||||
<div class="only-one-slice" *ngIf="!choice.timeList.length">
|
<div class="only-one-slice" *ngIf="!choice.timeList.length">
|
||||||
<i class="fa fa-square-o"></i>
|
<i class="fa fa-square-o" aria-hidden="true"></i>
|
||||||
</div>
|
</div>
|
||||||
{{ choice.literal }}
|
{{ choice.literal }}
|
||||||
|
|
||||||
@ -40,14 +40,14 @@
|
|||||||
<div *ngIf="'true' === config.allowSeveralHours" class="several-times">
|
<div *ngIf="'true' === config.allowSeveralHours" class="several-times">
|
||||||
<div *ngFor="let time of choice.timeList; index as idTime" class="time-choice">
|
<div *ngFor="let time of choice.timeList; index as idTime" class="time-choice">
|
||||||
{{ idTime }})
|
{{ idTime }})
|
||||||
<i class="fa fa-square-o"></i>
|
<i class="fa fa-square-o" aria-hidden="true"></i>
|
||||||
{{ time.literal }}
|
{{ time.literal }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- CASE all dates having the same slices of the day-->
|
<!-- CASE all dates having the same slices of the day-->
|
||||||
<div *ngIf="'false' === config.allowSeveralHours" class="same-times">
|
<div *ngIf="'false' === config.allowSeveralHours" class="same-times">
|
||||||
<div *ngFor="let time of config.timeList" class="time-choice">
|
<div *ngFor="let time of config.timeList" class="time-choice">
|
||||||
<i class="fa fa-square-o"></i>
|
<i class="fa fa-square-o" aria-hidden="true"></i>
|
||||||
{{ time.literal }}
|
{{ time.literal }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -142,7 +142,7 @@
|
|||||||
[disabled]="!config.password"
|
[disabled]="!config.password"
|
||||||
class="btn btn--default"
|
class="btn btn--default"
|
||||||
>
|
>
|
||||||
<i class="fa fa-eye"></i>
|
<i class="fa fa-eye" aria-hidden="true"></i>
|
||||||
{{ 'visibility.see_pass' | translate }}
|
{{ 'visibility.see_pass' | translate }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -151,10 +151,10 @@
|
|||||||
<button (click)="submitCreationAndGoToEnd()" class="btn btn--primary btn--full" i18n="@@confirm">
|
<button (click)="submitCreationAndGoToEnd()" class="btn btn--primary btn--full" i18n="@@confirm">
|
||||||
{{ 'visibility.validate_btn' | translate }}
|
{{ 'visibility.validate_btn' | translate }}
|
||||||
<ng-container *ngIf="!config.loading">
|
<ng-container *ngIf="!config.loading">
|
||||||
<i class="fa fa-paper-plane"></i>
|
<i class="fa fa-paper-plane" aria-hidden="true"></i>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<span class="loading" *ngIf="config.loading">
|
<span class="loading" *ngIf="config.loading">
|
||||||
<i class="fa fa-refresh fa-spin fa-fw"></i>
|
<i class="fa fa-refresh fa-spin fa-fw" aria-hidden="true"></i>
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
@ -17,13 +17,13 @@
|
|||||||
[ngClass]="{ 'btn--primary': config.myTempVoteStack }"
|
[ngClass]="{ 'btn--primary': config.myTempVoteStack }"
|
||||||
*ngIf="!config.myVoteStack || !config.myVoteStack.id"
|
*ngIf="!config.myVoteStack || !config.myVoteStack.id"
|
||||||
>
|
>
|
||||||
<i class="fa fa-paper-plane"></i> Envoyer
|
<i class="fa fa-paper-plane" aria-hidden="true"></i> Envoyer
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="btn btn--primary btn-block submit-votestack update"
|
class="btn btn--primary btn-block submit-votestack update"
|
||||||
(click)="config.updateVote(config.myVoteStack)"
|
(click)="config.updateVote(config.myVoteStack)"
|
||||||
*ngIf="config.myVoteStack && config.myVoteStack.id"
|
*ngIf="config.myVoteStack && config.myVoteStack.id"
|
||||||
>
|
>
|
||||||
<i class="fa fa-edit"></i> Mettre à jour
|
<i class="fa fa-edit" aria-hidden="true"></i> Mettre à jour
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
<section class="name">
|
<section class="name">
|
||||||
<label for="name">
|
<label for="name">
|
||||||
<i class="fa fa-user"></i>
|
<i class="fa fa-user" aria-hidden="true"></i>
|
||||||
Votre nom :</label
|
Votre nom :</label
|
||||||
>
|
>
|
||||||
<input type="text" name="name" id="name" [(ngModel)]="config.myName" />
|
<input type="text" name="name" id="name" [(ngModel)]="config.myName" />
|
||||||
<input type="text" name="name" id="email" [(ngModel)]="config.myEmail" />
|
<input type="text" name="name" id="email" [(ngModel)]="config.myEmail" />
|
||||||
<i class="fa fa-envelope"></i>
|
<i class="fa fa-envelope" aria-hidden="true"></i>
|
||||||
</section>
|
</section>
|
||||||
<div class="comments" id="comments">
|
<div class="comments" id="comments">
|
||||||
<h2 class="margin-top-x7">Laisser un commentaire</h2>
|
<h2 class="margin-top-x7">Laisser un commentaire</h2>
|
||||||
@ -13,7 +13,7 @@
|
|||||||
<input type="text" class="margin-btm-x3" name="crname" [(ngModel)]="config.myName" id="crname" />
|
<input type="text" class="margin-btm-x3" name="crname" [(ngModel)]="config.myName" id="crname" />
|
||||||
<input type="text" name="cremail" id="email_comment" [(ngModel)]="config.myEmail" />
|
<input type="text" name="cremail" id="email_comment" [(ngModel)]="config.myEmail" />
|
||||||
<label for="email_comment">
|
<label for="email_comment">
|
||||||
<i class="fa fa-envelope"></i>
|
<i class="fa fa-envelope" aria-hidden="true"></i>
|
||||||
</label>
|
</label>
|
||||||
<div>
|
<div>
|
||||||
<label for="comment">Votre commentaire :</label>
|
<label for="comment">Votre commentaire :</label>
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
class="btn btn--primary manage"
|
class="btn btn--primary manage"
|
||||||
(click)="choice.simpleAnswer = !choice.simpleAnswer"
|
(click)="choice.simpleAnswer = !choice.simpleAnswer"
|
||||||
>
|
>
|
||||||
<i class="fa fa-cogs"></i>
|
<i class="fa fa-cogs" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="choicebox__subject">
|
<div class="choicebox__subject">
|
||||||
@ -84,16 +84,21 @@
|
|||||||
>
|
>
|
||||||
<div class="choicebox__vote">
|
<div class="choicebox__vote">
|
||||||
{{ poll.choices_count.counts[choice.id].yes.count }}
|
{{ poll.choices_count.counts[choice.id].yes.count }}
|
||||||
<img width="20px" height="21px" src="../../../assets/img/votant-sur.svg" alt="" />
|
<img width="20px" height="21px" src="../../../assets/img/icon_voter_YES.svg" alt="" />
|
||||||
</div>
|
</div>
|
||||||
<div class="choicebox__vote">
|
<div class="choicebox__vote">
|
||||||
{{ poll.choices_count.counts[choice.id].maybe.count }}
|
{{ poll.choices_count.counts[choice.id].maybe.count }}
|
||||||
<img width="22px" height="24px" src="../../../assets/img/votant-pas-sur.svg" alt="" />
|
<img width="22px" height="24px" src="../../../assets/img/icon_voter_MAYBE.svg" alt="" />
|
||||||
</div>
|
</div>
|
||||||
<div class="choicebox__tooltip" id="choicebox-tooltip">
|
<div class="choicebox__tooltip" id="choicebox-tooltip">
|
||||||
<div class="choicebox__tooltiplist">
|
<div class="choicebox__tooltiplist">
|
||||||
<div class="choicebox__tooltipttl">
|
<div class="choicebox__tooltipttl">
|
||||||
<img width="20px" height="21px" src="../../../assets/img/votant-sur.svg" alt="" />
|
<img
|
||||||
|
width="20px"
|
||||||
|
height="21px"
|
||||||
|
src="../../../assets/img/icon_voter_YES.svg"
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
{{ poll.choices_count.counts[choice.id].yes.count }} "Oui"
|
{{ poll.choices_count.counts[choice.id].yes.count }} "Oui"
|
||||||
</div>
|
</div>
|
||||||
<!-- liste des gens qui ont répondu oui-->
|
<!-- liste des gens qui ont répondu oui-->
|
||||||
@ -108,7 +113,7 @@
|
|||||||
<img
|
<img
|
||||||
width="22px"
|
width="22px"
|
||||||
height="24px"
|
height="24px"
|
||||||
src="../../../assets/img/votant-pas-sur.svg"
|
src="../../../assets/img/icon_voter_MAYBE.svg"
|
||||||
alt=""
|
alt=""
|
||||||
/>
|
/>
|
||||||
{{ poll.choices_count.counts[choice.id].maybe.count }} "Peut-être"
|
{{ poll.choices_count.counts[choice.id].maybe.count }} "Peut-être"
|
||||||
@ -121,7 +126,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="choicebox__tooltiplist" *ngIf="!simpleAnswer">
|
<div class="choicebox__tooltiplist" *ngIf="!simpleAnswer">
|
||||||
<div class="choicebox__tooltipttl">
|
<div class="choicebox__tooltipttl">
|
||||||
<i class="fa fa-times"></i>
|
<i class="fa fa-times" aria-hidden="true"></i>
|
||||||
{{ poll.choices_count.counts[choice.id].no.count }} "Non"
|
{{ poll.choices_count.counts[choice.id].no.count }} "Non"
|
||||||
</div>
|
</div>
|
||||||
<ul>
|
<ul>
|
||||||
|
@ -7,8 +7,8 @@ import { ConfirmationService, MessageService } from 'primeng';
|
|||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { ConfigService } from '../../../services/config.service';
|
import { ConfigService } from '../../../services/config.service';
|
||||||
import { VotingChoiceComponent } from './voting-choice.component';
|
import { VotingChoiceComponent } from './voting-choice.component';
|
||||||
import { mockChoice } from '../../../../../mocks/choice';
|
import { mockChoice } from '../../../mocks/choice';
|
||||||
import { mockPoll3 } from '../../../../../mocks/mock-poll3';
|
import { mockPoll3 } from '../../../mocks/mock-poll3';
|
||||||
|
|
||||||
const routerSpy = jest.fn({ navigateByUrl: jest.fn() });
|
const routerSpy = jest.fn({ navigateByUrl: jest.fn() });
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Component, Input, OnInit } from '@angular/core';
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
import { mockComments } from '../../../../../mocks/mock-comments';
|
import { mockComments } from '../../../mocks/mock-comments';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-voting-comment',
|
selector: 'app-voting-comment',
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<a href="title">
|
<a href="title">
|
||||||
{{ config.currentPoll.poll.title }}
|
{{ config.currentPoll.poll.question }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
@ -19,7 +19,7 @@
|
|||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="comments">
|
<a href="comments">
|
||||||
<i class="fa fa-comments"></i>
|
<i class="fa fa-comments" aria-hidden="true"></i>
|
||||||
<span *ngIf="config.currentPoll && config.currentPoll.comments" class="comments-count">
|
<span *ngIf="config.currentPoll && config.currentPoll.comments" class="comments-count">
|
||||||
{{ config.currentPoll.comments.length }}
|
{{ config.currentPoll.comments.length }}
|
||||||
</span>
|
</span>
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user