feat: add frontend

This commit is contained in:
2022-10-07 16:15:53 +02:00
parent abfaf19c47
commit 97059d4c6e
72 changed files with 47026 additions and 0 deletions

View File

@@ -0,0 +1,42 @@
<template>
<div class="header">
<nuxt-link to="/">
<div class="secondary-logo">
<img src="/logo/logo_apes.svg" height="50px" alt="Logo APES"/>
</div>
<div class="main-logo">
<img src="/logo/main_logo.svg" width="245px" alt="Boussole PLUSS"/>
</div>
</nuxt-link>
</div>
</template>
<style lang="scss" scoped>
@import "assets/css/spacing";
@import "assets/css/color";
.header {
width: 100%;
height: 115px;
margin-bottom: $xx_medium;
background-repeat: no-repeat;
background-image: url("/decoration/background.svg");
background-position: bottom center, 50%;
background-size: cover;
.secondary-logo img {
padding: 12px 0 0 12px;
}
.main-logo {
position: absolute;
margin-left: auto;
margin-right: auto;
top: 40px;
left: 0;
right: 0;
width: 225px;
}
}
</style>

View File

@@ -0,0 +1,29 @@
<template>
<div>
<Header/>
<div class="content">
<section>
<h1>La démarche</h1>
<p>
Les crises successives que nous subissons révèlent encore plus la fragilité d'une économie productive
prioritairement mondialisée. S'il est impératif que les producteurs locaux reprennent la main sur certaines
filières, il faut aussi, pour favoriser la transition écologique et sociale, que la production locale soit
avant tout utile, solidaire et soutenable.
</p>
</section>
<section>
<h1>La boussole comme auto-évaluation</h1>
<p>
Nous proposons dix points de repères pour permettre aux différents écosystèmes de production locale sur un
territoire d'agir conjointement et collectivement dans ce sens et faire en sorte que les acteurs locaux
puissent innover, expérimenter et renforcer la production locale quantitativement et qualitativement.
</p>
</section>
<div class="button-container">
<nuxt-link class="button orange" to="/login">Démarrer</nuxt-link>
</div>
</div>
</div>
</template>

View File

@@ -0,0 +1,79 @@
<template>
<div>
<span class="bold">Légende</span>
<ul>
<!-- <li v-for="axe in axes" :key="axe.identifier" :class="'axe-'+axe.identifier">{{ axe.shortTitle }}</li>-->
<li class="axe-1">Pouvoir d'agir</li>
<li class="axe-2">Multi-secteur</li>
<li class="axe-3">Local global</li>
<li class="axe-4">Utilité (sociale et écologique)</li>
<li class="axe-5">Communs</li>
<li class="axe-6">Démocratie</li>
<li class="axe-7">Coopération</li>
<li class="axe-8">Finances</li>
<li class="axe-9">Moyens de production</li>
<li class="axe-10">Travail</li>
</ul>
</div>
</template>
<!--<script lang="ts">-->
<!--import { Component, Vue} from "nuxt-property-decorator";-->
<!--import {axeStore} from "~/utils/store-accessor";-->
<!--import {Axe} from "~/repositories/models/axe.model";-->
<!--@Component-->
<!--export default class Legend extends Vue {-->
<!-- async created() {-->
<!-- await axeStore.getAxes();-->
<!-- }-->
<!-- get axes(): Axe[] {-->
<!-- return axeStore.axes;-->
<!-- }-->
<!--}-->
<!--</script>-->
<style lang="scss" scoped>
@import "assets/css/color";
@import "assets/css/spacing";
ul {
list-style: none;
margin: 0;
}
ul li::before {
content: "";
border: 0 solid;
border-radius: 50%;
display: inline-block;
width: 16px;
height: 16px;
margin-right: $xxx_small;
}
ul li+li {
border-top: 1px solid #E8E8E8;
}
ul li {
line-height: 16px;
padding: $xxx_small 0;
}
.axe-1::before { color: #CA8AE8; background: #CA8AE8 }
.axe-2::before { color: #22B9A6; background: #22B9A6}
.axe-3::before { color: #E7E145; background: #E7E145}
.axe-4::before { color: #F39345; background: #F39345}
.axe-5::before { color: #9FCC8B; background: #9FCC8B}
.axe-6::before { color: #FDA6C5; background: #FDA6C5}
.axe-7::before { color: #7E91F1; background: #7E91F1}
.axe-8::before { color: #F37665; background: #F37665}
.axe-9::before { color: #E9D280; background: #E9D280}
.axe-10::before { color: #7BD1F5; background: #7BD1F5}
</style>

View File

@@ -0,0 +1,29 @@
<template>
<div class="lds-dual-ring"></div>
</template>
<style lang="scss" scoped>
@import "assets/css/spacing";
@import "assets/css/color";
.lds-dual-ring:after {
content: " ";
display: block;
width: 64px;
height: 64px;
margin: 8px;
border-radius: 50%;
border: 6px solid;
border-color: #8BCDCD transparent #8BCDCD transparent;
animation: lds-dual-ring 1.2s linear infinite;
}
@keyframes lds-dual-ring {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>

View File

@@ -0,0 +1,136 @@
<template>
<PolarArea
:chart-options="chartOptions"
:chart-data="chartData"
:chart-id="chartId"
:dataset-id-key="datasetIdKey"
:plugins="plugins"
:css-classes="cssClasses"
:styles="styles"
:width="width"
:height="height"
/>
</template>
<script lang="ts">
import {PolarArea} from 'vue-chartjs/legacy'
import {ArcElement, Chart as ChartJS, Legend, RadialLinearScale, Title, Tooltip} from 'chart.js'
import {Component, Prop, Vue, Watch} from "nuxt-property-decorator";
ChartJS.register(Title, Tooltip, Legend, ArcElement, RadialLinearScale);
@Component({
components: {
PolarArea
}
})
export default class PolarAreaChart extends Vue {
@Prop({
default: 'polar-chart',
type: String
})
public chartId!: string;
@Prop({
default: 'label',
type: String
})
public datasetIdKey!: string;
@Prop({
default: 400,
type: Number
})
public width!: number;
@Prop({
default: 400,
type: Number
})
public height!: number;
@Prop({
default: '',
type: String
})
public cssClasses!: string;
@Prop({
type: Object
})
public styles!: Object;
@Prop({
default: () => [],
type: Array
})
public plugins!: [];
@Prop({
default: () => [],
type: Array<number>
})
public data!: number[];
readonly chartData = {
labels: [
"Pouvoir d'agir",
"Multi-secteur",
"Local global",
"Utilité (sociale et écologique)",
"Communs",
"Démocratie",
"Coopération",
"Finances",
"Moyens de production",
"Travail"
],
datasets: [
{
backgroundColor: [
"#CA8AE8",
"#22B9A6",
"#E7E145",
"#F39345",
"#9FCC8B",
"#FDA6C5",
"#7E91F1",
"#F37665",
"#E9D280",
"#7BD1F5"
],
data: this.data,
borderWidth: 1,
borderColor: "#666666"
}
]
};
readonly chartOptions = {
responsive: true,
maintainAspectRatio: false,
// animation: false,
scales: {
r: {
suggestedMin: 0,
suggestedMax: 10,
ticks: {
display: false
}
}
},
plugins: {
legend: {
display: false
}
}
};
@Watch("data", {})
private renderChart() {
this.chartData.datasets[0].data = this.data;
}
}
</script>

View File

@@ -0,0 +1,99 @@
<template>
<section :style="cssVars" class="axe-details">
<h2 class="title">
<span><span class="upper">{{ axe.identifier }}</span><span>{{ axe.shortTitle }}</span></span>
<span class="upper">{{ score.scoreAvg | formatRate }} / 10</span>
</h2>
<div v-for="response in responses" :key="response._links.self.href">
<p class="question">
<span>
{{ response.question }}
</span>
<span class="note">{{ response.score }} / 10</span>
</p>
<p v-if="response.comment" class="comment">{{ response.comment }}</p>
</div>
</section>
</template>
<script lang="ts">
import {Component, Prop, Vue} from "nuxt-property-decorator";
import {Axe} from "~/repositories/models/axe.model";
import {ResponseWithQuestion, Score} from "~/repositories/models/quiz.model";
@Component
export default class QuizAxeDetails extends Vue {
@Prop({
type: Object,
required: true
})
public axe!: Axe;
@Prop({
type: Object,
required: true
})
public score!: Score;
@Prop({
type: Array
})
public responses!: ResponseWithQuestion[];
get cssVars() {
return {
'--color': this.axe ? this.axe.color : "#ffffff"
}
}
}
</script>
<style lang="scss" scoped>
@import "assets/css/font";
@import "assets/css/color";
@import "assets/css/spacing";
.axe-details {
margin-top: $x_small;
}
.title {
display: flex;
justify-content: space-between;
font-size: $tertiary-font-size;
font-weight: 700;
color: black;
border-bottom: 3px solid var(--color);
margin: $xx_small 0;
line-height: 1rem;
}
.title .upper {
font-size: 28px;
font-weight: bold;
color: var(--color);
}
.title .upper:first-child {
margin-right: $xxx_small;
}
.question {
display: flex;
justify-content: space-between;
color: black;
margin: $xx_small 0 0 0;
}
.question .note {
color: var(--color);
}
.comment {
font-size: $small-font-size;
margin: $xxx_small 0 1rem 0;
font-style: italic;
}
</style>

View File

@@ -0,0 +1,173 @@
<template>
<article :style="cssVars">
<header>
<quiz-progress :number-steps=totalAxes :current-step=axeNumber :color="color"/>
<h1 class="title">
<span class="part-number">{{ axeNumber }}</span>
<span>{{ title }}</span>
</h1>
<div class="icon">
<img :src="icon" width="200px" alt="" aria-hidden="true"/>
</div>
</header>
<section v-for="question in questions" :key="question._links.self.href" class="question">
<details>
<summary> {{ question.label }}</summary>
<p>{{ question.description }}</p>
</details>
<div class="rating">
<rating :color="color" :initial-value="getCurrentScore(question)" @rate="rate => onRate(rate, question)" />
</div>
<input :value="getCurrentComment(question)" type="text" placeholder="Commentaire" maxlength="500" @input="event => onComment(event.target.value, question)"/>
</section>
</article>
</template>
<script lang="ts">
import {Component, Prop, Vue} from "nuxt-property-decorator";
import {Question} from "~/repositories/models/question.model";
import {quizStore} from "~/utils/store-accessor";
import {QuizRate} from "~/repositories/models/quiz.model";
@Component
export default class Quiz extends Vue {
@Prop({
required: true
})
private title!: string;
@Prop({
required: true
})
private axeNumber!: number;
@Prop({
required: true
})
private totalAxes!: number;
@Prop({
required: true,
type: String,
})
public icon !: string;
@Prop({
required: true,
type: Array<Question>
})
private questions!: Array<Question>;
@Prop({
required: true,
type: String,
})
public color !: string;
// private responses = new Map<string, {
// score?: number;
// comment?: string | null
// }>();
get cssVars() {
return {
'--color': this.color
}
}
onRate(score: number, question: Question) {
quizStore.updateScoreResponse({axeId: this.axeNumber, questionId: question._links.self.href, score});
this.emitRatingState();
}
onComment(comment: string, question: Question) {
quizStore.updateCommentResponse({axeId: this.axeNumber, questionId: question._links.self.href, comment});
}
getCurrentScore(question: Question): number | undefined {
const rate = quizStore.responses.get(question._links.self.href) as QuizRate;
return rate ? rate.score : undefined;
}
getCurrentComment(question: Question): string | undefined {
const rate = quizStore.responses.get(question._links.self.href) as QuizRate;
return rate ? rate.comment : undefined;
}
emitRatingState() {
const questions = quizStore.questionsRatedPerAxe.get(this.axeNumber);
const unratedQuestions = questions ? questions.filter(value => !value.rated) : [];
this.$emit('rate', {
isFullRated: unratedQuestions.length === 0
})
}
}
</script>
<style lang="scss" scoped>
@import "assets/css/color";
@import "assets/css/font";
@import "assets/css/spacing";
$size: 31px;
.part-number {
display: inline-flex;
justify-content: center;
align-items: center;
color: var(--color);
border: 3px solid var(--color);
font-size: 1rem;
line-height: $size;
width: $size;
height: $size;
}
.title {
display: flex;
flex-direction: row;
text-align: left;
> span {
padding: 0 $x_small;
}
}
.icon {
text-align: center;
margin: $small;
}
.question {
width: 100%;
}
.question details {
margin-bottom: $x_small;
}
.question details summary {
font-weight: 700;
font-size: $title-font-size;
margin-bottom: $xxx_small;
}
.question details p {
font-size: $secondary-font-size;
color: $gray_4;
margin-left: $x_small;
}
.question .rating {
margin: 0 auto;
max-width: 401px;
}
.question input[type="text"] {
width: 100%;
border: 1px solid $gray_2;
border-radius: 8px;
background: none;
margin: $small 0;
font-weight: 400;
font-size: $tertiary-font-size;
}
</style>

View File

@@ -0,0 +1,56 @@
<template>
<div :style="cssVars" class="progress">
<span v-for="step in numberSteps" :key="step" class="step" :class="{ active: step <= currentStep }"></span>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue} from "nuxt-property-decorator";
@Component
export default class QuizProgress extends Vue {
@Prop({
type: Number
})
private numberSteps!: number;
@Prop({
required: true,
type: Number
})
private currentStep!: number;
@Prop({
required: true,
type: String
})
private color!: string;
get cssVars() {
return {
'--color': this.color
}
}
}
</script>
<style lang="scss" scoped>
@import "assets/css/color";
@import "assets/css/spacing";
.progress {
display: flex;
justify-content: center;
}
.step {
border-radius: 2px;
border: 6px solid $gray_2;
margin: $x_small $xxx_small;
width: 30px;
}
.active {
border-color: var(--color);
}
</style>

View File

@@ -0,0 +1,158 @@
<template>
<div :style="cssVars">
<div class="rating" tabindex="0" @keyup="handleKeyUp">
<span v-for="i in index" :key="i" :class="{ checked: isChecked(i) }">
<input
:id="'rating' + componentId + '-' + i"
type="radio" name="rating" :value="i"
@click="setChecked(i)">
<label :for="'rating' + componentId + '-' + i" :aria-label="i"></label>
</span>
</div>
<div class="legend" aria-hidden="true">
<span>pas du tout</span>
<span>en partie</span>
<span>totalement</span>
</div>
</div>
</template>
<script lang="ts">
import {Component, Prop, Vue} from "nuxt-property-decorator";
@Component
export default class Rating extends Vue {
@Prop({
type: String,
default: "#DC6A00"
})
readonly color !: string;
@Prop({
type: Number,
required: false
})
readonly initialValue !: number | undefined;
readonly MAX_VALUE = 10;
created() {
if (this.initialValue) {
this.checkedValue = this.initialValue;
}
}
readonly index = Array.apply(0, Array(this.MAX_VALUE)).map((_, b) => {
return b + 1;
}).reverse();
public checkedValue = 0;
private _uid: any;
public setChecked(index: number) {
if (index < 1) {
index = 1;
} else if (index > this.MAX_VALUE) {
index = this.MAX_VALUE;
}
this.checkedValue = index;
this.$emit('rate', index);
}
public increment() {
this.setChecked(this.checkedValue + 1);
}
public decrement() {
this.setChecked(this.checkedValue - 1);
}
public isChecked(index: number) {
return this.checkedValue >= index;
}
get componentId() {
return this._uid;
}
handleKeyUp(event: KeyboardEvent) {
switch (event.key) {
case "ArrowLeft":
this.decrement();
break;
case "ArrowRight":
this.increment();
break;
}
event.preventDefault();
return false;
}
get cssVars() {
return {
'--color': this.color
}
}
}
</script>
<style lang="scss" scoped>
@import "assets/css/color";
@import "assets/css/spacing";
@import "assets/css/font";
$width: 401px;
$input_width: 32px;
.rating {
display: flex;
justify-content: space-around;
flex-direction: row-reverse;
max-width: $width;
}
.rating span {
position: relative;
}
.rating span input {
position: absolute;
top: 0;
left: 0;
opacity: 0;
}
.rating span label {
display: inline-block;
width: $input_width;
height: $input_width;
color: $white;
line-height: $input_width;
border-radius: 50%;
border: 1px solid $black;
}
.rating span:hover ~ span label,
.rating span:hover label,
.rating span.checked label,
.rating span.checked ~ span label {
background-color: var(--color);
color: #FFF;
border: 0;
cursor: pointer;
width: $input_width;
}
.legend {
display: flex;
justify-content: space-between;
max-width: $width;
color: $gray_3;
font-weight: 400;
font-size: $small-font-size;
}
</style>

View File

@@ -0,0 +1,56 @@
<template>
<header>
<nuxt-link to="/" class="title">
<svg width="13" height="16" viewBox="0 0 13 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M0 7.07348C0 6.81265 0.101912 6.56214 0.284 6.37538L5.784 0.73436C6.17647 0.331827 6.82353 0.331826 7.216 0.734359L12.716 6.37538C12.8981 6.56214 13 6.81265 13 7.07348V15C13 15.5523 12.5523 16 12 16H1C0.447715 16 0 15.5523 0 15V7.07348Z"
fill="#8BCDCD"/>
</svg>
Boussolle <span class="bold">PLUSS</span>
</nuxt-link>
<span class="team">
Équipe : {{ team }}
</span>
</header>
</template>
<script lang="ts">
import {Component, Vue} from "nuxt-property-decorator";
@Component
export default class TeamHeader extends Vue {
get team() {
return this.$auth.user ? this.$auth.user.username : "Non connecté";
}
}
</script>
<style lang="scss" scoped>
@import "assets/css/color";
@import "assets/css/spacing";
@import "assets/css/font";
header {
margin-top: $xx_small;
color: $black;
display: flex;
justify-content: space-between;
}
.title {
text-transform: uppercase;
color: $black;
text-decoration: none;
}
.title svg {
margin-right: $xxx_small;
}
.team {
font-size: $small-font-size;
}
</style>