feat: review backend and frontend
- update to the latest version of Java/SpringBoot - update to the latest version NuxtJS - add account/password update - add account creation - add account password reset - add bundle to regroup questions and add default questions on user creation - add bundle creation
This commit is contained in:
213
frontend/components/AxeDefinitionModal.vue
Normal file
213
frontend/components/AxeDefinitionModal.vue
Normal file
@@ -0,0 +1,213 @@
|
||||
<script lang="ts" setup>
|
||||
const emit = defineEmits(["close", "validate"]);
|
||||
defineProps({
|
||||
visible: Boolean
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div :class="`modal${visible ? ' visible' : ''}`">
|
||||
<section class="modal-content">
|
||||
<header class="modal-content-header">
|
||||
<h1>Pour une production locale utile, solidaire et soutenable</h1>
|
||||
<button class="close_modal" @click="$emit('close')">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.6 16L0 14.4L6.4 8L0 1.6L1.6 0L8 6.4L14.4 0L16 1.6L9.6 8L16 14.4L14.4 16L8 9.6L1.6 16Z"
|
||||
fill="#1C1B1F"/>
|
||||
</svg>
|
||||
<span>Fermer</span>
|
||||
</button>
|
||||
</header>
|
||||
<main class="modal-content-body">
|
||||
<section>
|
||||
<p>La crise sanitaire de la COVID-19 a amené un groupe d’administrateurs et de salariés de l’Apes baptisé
|
||||
Veille/Plaidoyer à chercher à répondre à cette préoccupation : « comment agir pour que le monde d’après soit
|
||||
meilleur que celui d’avant ? ». Dans le prolongement des Constructiv’ESS que l’Apes a animées en novembre
|
||||
2019, nous voulions surtout être force de propositions, pour agir concrètement aux côtés de ceux qui veulent
|
||||
que le monde change. Nous souhaitons stimuler d’autres façons de faire de l’économie. Nous avons choisi de
|
||||
commencer par le sujet de la production locale. Car c’est à l’échelle locale qu’on innove, qu’on
|
||||
expérimente,
|
||||
qu’on donne de la place aux initiatives citoyennes, et que l’on ouvre la voie aux transformations sociétales
|
||||
auxquelles on aspire.</p>
|
||||
<p>La crise sanitaire que nous subissons, et la crise sociale et économique qui se profile, révèlent encore
|
||||
plus
|
||||
la fragilité d’une économie productive prioritairement mondialisée. Pour une meilleure autonomie face aux
|
||||
marchés internationaux et pour favoriser la transition écologique et sociale, il est impératif que les
|
||||
producteurs locaux reprennent la main sur certaines filières.</p>
|
||||
<p>Mais les représentations courantes se complaisent d’idées reçues sur ce que la production locale est ou
|
||||
devrait être et sur la manière de la stimuler.</p>
|
||||
<ul>
|
||||
<li>Il ne suffira pas d’interventions ou d’aides publiques pour relocaliser, y compris dans des secteurs
|
||||
stratégiques pour conforter notre sécurité alimentaire, sanitaire et énergétique.
|
||||
</li>
|
||||
<li>Il ne suffira pas d’organiser ou de réguler la connexion entre l’offre et la demande.</li>
|
||||
<li>Il ne suffira pas de sensibiliser et de motiver les consommateurs à modifier leurs habitudes d’achats
|
||||
pour
|
||||
en faire des locavores convaincus.
|
||||
</li>
|
||||
</ul>
|
||||
<p>Pour renforcer la production locale quantitativement et qualitativement, il faut agir conjointement et
|
||||
collectivement à l’échelle des différents écosystèmes de production locale solidaire sur un territoire. Et
|
||||
pour cela, il nous paraît indispensable de nous référer à plusieurs balises, au nombre de 10.</p>
|
||||
<h2>1. agir en (re)donnant de l’autonomie et du pouvoir d’agir à chaque territoire et à ses habitants</h2>
|
||||
<p>En redonnant du sens à l'acte de produire et de consommer, on développe ou conforte des activités, du
|
||||
travail
|
||||
et des emplois locaux, et on renforce la cohérence, la vitalité, la durabilité des territoires.</p>
|
||||
<p>En produisant et en consommant localement, on réintègre les externalités négatives dans le périmètre de
|
||||
l'écosystème productif local, on contribue mieux aux enjeux environnementaux (diminution de la production de
|
||||
gaz à effets de serre, protection de la biodiversité) et au bien-être des populations locales.
|
||||
</p>
|
||||
<p>En donnant à chacun les moyens de son émancipation personnelle, sociale, professionnelle, relationnelle,
|
||||
créative, on lui permet de participer activement à l’affirmation d’un bien-vivre partagé et d’un
|
||||
prendre-soin
|
||||
universel.</p>
|
||||
<p>« Si nous voulons que nos résistances soient créatrices et porteuses de la démonstration qu’un autre monde
|
||||
est non seulement possible mais nécessaire, il nous faut d’autant plus placer le bien-vivre en actes au cœur
|
||||
de nos projets » (Patrick Viveret)</p>
|
||||
|
||||
<h2>2. agir sur tous les secteurs de l’économie et l’ensemble des chaînes de valeur</h2>
|
||||
<p>La relocalisation des productions nécessite de revisiter l’ensemble des chaînes de valeur aboutissant à la
|
||||
commercialisation et la distribution des biens et des services.</p>
|
||||
|
||||
<p>La (re)localisation des productions ne peut se réduire à quelques filières ou secteurs d’activités,
|
||||
industrielles ou agricoles. C’est l’occasion de procéder à une réévaluation des modes d’organisation des
|
||||
activités selon des logiques fonctionnelles : s’alimenter, se déplacer, se loger, se soigner, ...</p>
|
||||
|
||||
<p>Intelligence collective, projets collaboratifs, initiatives solidaires, la relocalisation dépendra de notre
|
||||
capacité d’innovation afin de réduire nos vulnérabilités et les coûts sociaux et environnementaux du
|
||||
commerce
|
||||
mondial et de revenir à un mode d’échanges plus responsable et plus soutenable.</p>
|
||||
|
||||
<h2>3. agir localement en pensant globalement</h2>
|
||||
<p>Ne pas confondre autonomie avec autarcie. Il est illusoire d'imaginer que l'on puisse produire sur place
|
||||
tout
|
||||
ce qu’on consomme ou utilise. Production et consommation locales doivent aussi se faire dans une
|
||||
complémentarité avec d’autres territoires.
|
||||
</p>
|
||||
<p>Des partenariats contractualisés avec d’autres écosystèmes, parfois éloignés, instaurent des modalités
|
||||
d’échanges solidaires villes-campagnes, ou des échanges équilibrés entre le Nord et le Sud, également
|
||||
nécessaires.
|
||||
</p>
|
||||
<h2>4. agir de manière utile et sobre, socialement et écologiquement</h2>
|
||||
<p>La satisfaction de nos besoins par la consommation interroge la question de l’utilité sociale et écologique
|
||||
des biens et des services produits et consommés. Il ne peut y avoir de dogme universel en la matière et
|
||||
chaque
|
||||
territoire doit garder le droit de définir ce qui lui paraît utile.
|
||||
</p>
|
||||
<p>La recherche de sobriété en premier lieu, puis celle du recours à des systèmes coopératifs d’économie
|
||||
circulaire sont à examiner avant de chercher à créer toute nouvelle condition de production locale.
|
||||
</p>
|
||||
<p>Une commande publique responsable est un moteur exemplaire d’une consommation utile et raisonnable. La
|
||||
co-définition des besoins et des solutions à mettre en œuvre, en mobilisant les différentes parties
|
||||
prenantes,
|
||||
permet de s’assurer de l’utilité et des impacts sociaux et environnementaux des achats publics.
|
||||
</p>
|
||||
<h2>5. agir pour préserver et se réapproprier les communs</h2>
|
||||
<p>La gestion des communs, c’est-à-dire des ressources partagées et maintenues collectivement par une
|
||||
communauté, propres à chaque territoire est au centre des questionnements d’une consommation responsable.
|
||||
</p>
|
||||
<p>L'accès aux services et aux ressources que la communauté locale souhaite préserver et gérer, ainsi que
|
||||
l'usage de certains produits, doivent être réexaminés pour savoir s’il est nécessaire et légitime d’en
|
||||
disposer en pleine propriété.
|
||||
</p>
|
||||
<h2>6. agir démocratiquement</h2>
|
||||
<p>Le territoire, lieu de production, de vie, d’identité et d’interactions humaines, sociales et économiques,
|
||||
doit s’affirmer également comme lieu d’expression et de débat d’une démocratie au quotidien.
|
||||
</p>
|
||||
<p>La création de lieux d’échanges et de débats démocratiques entre citoyens, représentants élus et
|
||||
producteurs,
|
||||
permet de revitaliser les solidarités entre les groupes de population et la concertation entre ces groupes.
|
||||
</p>
|
||||
<p>Pour chaque projet de développement, une gouvernance ouverte et participative donne l’opportunité aux
|
||||
représentants de ces groupes d’exprimer leur point de vue, de participer à la définition des priorités de
|
||||
développement, de prendre part aux décisions et aux processus de suivi et d’évaluation.
|
||||
</p>
|
||||
<h2>7. agir en encourageant la coopération à tous les niveaux</h2>
|
||||
<p>L’ensemble de ces procédés est un levier précieux pour le développement d’indispensables coopérations.
|
||||
Celles-ci favorisent la coconstruction des solutions répondant aux besoins du territoire, grâce à la
|
||||
mobilisation d’une diversité de compétences et de ressources matérielles, immatérielles, monétaires et non
|
||||
monétaires.
|
||||
</p>
|
||||
<p>La création d’alliances entre les acteurs locaux sur les territoires est à promouvoir, et on pourra pour
|
||||
cela
|
||||
mobiliser les outils de dynamiques de coopérations territoriales qui ont fait leur preuve (pôles
|
||||
territoriaux
|
||||
de coopération économique, SCIC territoriales, pôles économiques, grappes d’entreprises…).
|
||||
</p>
|
||||
<h2> 8. agir pour maîtriser les outils de production</h2>
|
||||
<p>Les choix stratégiques d’investisseurs fondés sur la seule maximisation du profit, qui plus est à court
|
||||
terme, ont entraîné progressivement un éloignement des centres de décisions des territoires de production de
|
||||
biens comme de services, avec des conséquences dramatiques sur l’emploi local (course à la productivité et
|
||||
au
|
||||
rendement, délocalisations).
|
||||
</p>
|
||||
<p>La propriété collective de moyens de production, telle qu’elle se pratique dans les sociétés coopératives,
|
||||
notamment les SCOP et les SCIC, est un exemple à suivre.
|
||||
</p>
|
||||
<h2> 9. agir sur les instruments financiers</h2>
|
||||
<p>Le recours aux outils de financements solidaires et participatifs est essentiel pour maîtriser le pilotage
|
||||
d’une économie de proximité : mobilisation de l’épargne solidaire, d’outils de financement participatif
|
||||
comme
|
||||
moyens de soutenir financièrement les initiatives au niveau local.
|
||||
</p>
|
||||
<p>En complément, la mise en place de monnaies locales citoyennes, de fondations territoriales et de systèmes
|
||||
d’échanges locaux (SEL) favorisent une consommation responsable en fléchant les flux de consommation vers un
|
||||
réseau d’acteurs engagés dans le développement de leur territoire.
|
||||
</p>
|
||||
<h2>10. agir pour redonner au travail sa valeur véritable</h2>
|
||||
<p>Tout ceci nécessite de remettre le travail au centre de la production de richesses et de leur
|
||||
redistribution.
|
||||
En reconnaissant l’utilité sociale et écologique du travail, on contribue à la démocratisation de
|
||||
l’entreprise, et à la dé-marchandisation du travail.
|
||||
</p>
|
||||
<p>C’est par ce biais que les politiques de lutte contre les exclusions et d’insertion par l’activité
|
||||
économique
|
||||
pourront prendre leurs pleines mesures, et offrir à chaque citoyen de nos territoires la possibilité
|
||||
d’exercer
|
||||
son droit fondamental au travail.
|
||||
</p>
|
||||
<p>
|
||||
Cela passe aussi par la reconnaissance de l'importance du travail bénévole, en complémentarité avec le
|
||||
travail
|
||||
rémunéré, et de leur contribution au bien-être de chacun.
|
||||
</p>
|
||||
</section>
|
||||
<section>
|
||||
<hr/>
|
||||
<p>Innovations, expérimentations, évaluations et amélioration continue des pratiques, les acteurs de l’ESS
|
||||
n’ont
|
||||
pas attendu d’être confrontés à la pandémie pour ouvrir d’autres voies et contribuer à la transformation
|
||||
d’une
|
||||
société centrée sur le profit immédiat.
|
||||
</p>
|
||||
<p>En mettant l’humain au cœur de leurs projets, en répondant d’abord
|
||||
aux besoins fondamentaux des personnes par des solutions ancrées sur les territoires, ils ont démontré
|
||||
l’importance de la proximité, de la coopération et de la participation des citoyens aux décisions qui les
|
||||
concernent
|
||||
</p>
|
||||
<p>
|
||||
Le moment n’a sans doute jamais été aussi propice pour s’inspirer
|
||||
de ces pratiques et coopérer avec les entreprises de l’économie ordinaire au profit d’une production locale
|
||||
qui soit utile, solidaire et soutenable. Les balises ici proposées sont autant de repères pour y parvenir.
|
||||
</p>
|
||||
</section>
|
||||
</main>
|
||||
</section>
|
||||
<div class="modal-overlay" @click="$emit('close')"></div>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
@import "assets/css/modal";
|
||||
|
||||
h2 {
|
||||
font-size: $title-font-size;
|
||||
line-height: 150%;
|
||||
margin: $x-small 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: inside;
|
||||
}
|
||||
hr {
|
||||
margin: $small $large;
|
||||
}
|
||||
</style>
|
243
frontend/components/BundleAxeModal.vue
Normal file
243
frontend/components/BundleAxeModal.vue
Normal file
@@ -0,0 +1,243 @@
|
||||
<script lang="ts" setup>
|
||||
import type {PropType} from "vue";
|
||||
import type {Axe} from "~/store/axe";
|
||||
import type {Question} from "~/store/question";
|
||||
|
||||
const questions = ref<Question[]>([]);
|
||||
const newQuestionMode = ref(false);
|
||||
const newQuestionLabel = ref("");
|
||||
const newQuestionDescription = ref("");
|
||||
|
||||
const emit = defineEmits(["changed", "close"]);
|
||||
|
||||
const props = defineProps({
|
||||
axe: {
|
||||
type: Object as PropType<Axe>,
|
||||
},
|
||||
questions: {
|
||||
type: Object as PropType<Question[]>,
|
||||
},
|
||||
questionsExample: {
|
||||
type: Object as PropType<Question[]>,
|
||||
default: [{
|
||||
label: "",
|
||||
description: ""
|
||||
}]
|
||||
},
|
||||
visible: Boolean
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
questions.value = props.questions ? props.questions.map(q => {
|
||||
return {
|
||||
label: q.label,
|
||||
description: q.description
|
||||
}
|
||||
}) : [];
|
||||
});
|
||||
|
||||
function removeQuestion(index: number) {
|
||||
questions.value.splice(index, 1);
|
||||
}
|
||||
|
||||
function moveQuestion(fromIndex, toIndex) {
|
||||
if (toIndex > questions.value.length || toIndex < 0) {
|
||||
return;
|
||||
}
|
||||
const element = questions.value[fromIndex];
|
||||
questions.value.splice(fromIndex, 1);
|
||||
questions.value.splice(toIndex, 0, element);
|
||||
}
|
||||
|
||||
function questionChanged(event, {index, newQuestion}) {
|
||||
questions.value[index] = newQuestion;
|
||||
}
|
||||
|
||||
function addNewQuestion() {
|
||||
questions.value.push({label: newQuestionLabel.value, description: newQuestionDescription.value});
|
||||
newQuestionMode.value = false;
|
||||
}
|
||||
|
||||
function changeToNewQuestionMode() {
|
||||
newQuestionLabel.value = "";
|
||||
newQuestionDescription.value = "";
|
||||
newQuestionMode.value = true;
|
||||
}
|
||||
|
||||
function validate() {
|
||||
emit("changed", {axeId: props.axe.id, newQuestions: questions.value});
|
||||
emit("close");
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="`modal${visible ? ' visible' : ''}`" v-if="axe">
|
||||
<section class="modal-content">
|
||||
<header class="modal-content-header">
|
||||
<h1>{{ axe.identifier }} - {{ axe.shortTitle }}</h1>
|
||||
<p>{{ axe.title }}</p>
|
||||
<p v-if="axe.description">{{ axe.description }}</p>
|
||||
<button class="close_modal" @click="$emit('close')">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.6 16L0 14.4L6.4 8L0 1.6L1.6 0L8 6.4L14.4 0L16 1.6L9.6 8L16 14.4L14.4 16L8 9.6L1.6 16Z"
|
||||
fill="#1C1B1F"/>
|
||||
</svg>
|
||||
<span>Fermer</span>
|
||||
</button>
|
||||
</header>
|
||||
<main class="modal-content-body">
|
||||
<div v-if="newQuestionMode" class="new-question">
|
||||
<div class="new-question__inputs">
|
||||
<label for="label-new-question">Libellé *</label>
|
||||
<input id="label-new-question" type="text" list="question-labels" v-model="newQuestionLabel" maxlength="200"/>
|
||||
<label for="description-new-question">Description</label>
|
||||
<input id="description-new-question" type="text" list="question-labels" v-model="newQuestionDescription" maxlength="500"/>
|
||||
</div>
|
||||
<h2>Historique des questions</h2>
|
||||
<ul class="new-question__example-list">
|
||||
<li v-for="(question, index) in questionsExample">
|
||||
<input :id="'example-'+index" type="radio" name="example-radio" @input="() => {newQuestionLabel = question.label;newQuestionDescription = question.description;}">
|
||||
<label :for="'example-'+index">
|
||||
<span class="new-question__example-list__item-label">{{ question.label }}</span>
|
||||
<span class="new-question__example-list__item-description" v-if="question.description">{{ question.description }}</span>
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-else>
|
||||
<ul class="question-list">
|
||||
<li v-for="(question, index) in questions" class="question-list__item">
|
||||
<div class="question-list__item__inputs">
|
||||
<span class="question-list__item__inputs__title">Question {{index+1}}</span>
|
||||
<label :for="'label-'+index">Libellé *</label>
|
||||
<input :id="'label-'+index" type="text" list="question-labels" :value="question.label" maxlength="200"
|
||||
@change="event => questionChanged(event, {index, newQuestion: {label: event.target.value, description: question.description}})"/>
|
||||
<label :for="'description-'+index">Description</label>
|
||||
<input :id="'description-'+index" type="text" list="question-descriptions" :value="question.description" maxlength="500"
|
||||
@change="event => questionChanged(event, {index, newQuestion: {label: question.label, description: event.target.value}})"/>
|
||||
|
||||
</div>
|
||||
<div class="question-list__item__buttons">
|
||||
<button class="button-icon button-up" aria-label="Déplacer vers le haut"
|
||||
@click="moveQuestion(index, index-1)" :disabled="index === 0">
|
||||
➜
|
||||
</button>
|
||||
<button class="button-icon button-down" aria-label="Déplacer vers le bas"
|
||||
@click="moveQuestion(index, index+1)" :disabled="index === questions.length -1">
|
||||
➜
|
||||
</button>
|
||||
<button class="button-icon" @click="removeQuestion(index)">
|
||||
🗑
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</main>
|
||||
<footer class="modal-content-footer">
|
||||
<button v-if="newQuestionMode" class="button gray button-back"
|
||||
@click="newQuestionMode = false">❮
|
||||
</button>
|
||||
<button v-if="newQuestionMode" class="button orange" :disabled="!newQuestionLabel"
|
||||
@click="addNewQuestion()">Ajouter
|
||||
</button>
|
||||
<button v-if="!newQuestionMode" class="button blue" @click="changeToNewQuestionMode()">Ajouter une question
|
||||
</button>
|
||||
<button v-if="!newQuestionMode" class="button orange" @click="validate()">Valider</button>
|
||||
</footer>
|
||||
</section>
|
||||
<div class="modal-overlay" @click="$emit('close')"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "assets/css/modal";
|
||||
.modal-content-body {
|
||||
//overflow: unset;
|
||||
}
|
||||
.question-list,
|
||||
.new-question__example-list {
|
||||
//overflow-y: scroll;
|
||||
//overflow-x: hidden;
|
||||
//height: 30%;
|
||||
}
|
||||
.new-question {
|
||||
&__inputs {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $xx_small;
|
||||
}
|
||||
h2 {
|
||||
margin: $xx_small 0;
|
||||
font-size: $secondary-font-size;
|
||||
}
|
||||
|
||||
&__example-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $xx_small;
|
||||
|
||||
&__item-description {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
input {
|
||||
//align-content: start;
|
||||
}
|
||||
|
||||
li {
|
||||
display: flex;
|
||||
gap: $xxx_small;
|
||||
//align-items: start;
|
||||
|
||||
}
|
||||
|
||||
label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $xxxx_small;
|
||||
}
|
||||
}
|
||||
}
|
||||
.question-list {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $small;
|
||||
|
||||
|
||||
&__item {
|
||||
display: flex;
|
||||
gap: $xxx_small;
|
||||
|
||||
&__inputs {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
gap: $xxx_small;
|
||||
|
||||
&__title {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
&__buttons {
|
||||
//align-self: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-self: end;
|
||||
//gap: $xxx_small;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.button-up {
|
||||
rotate: -90deg;
|
||||
}
|
||||
|
||||
.button-down {
|
||||
rotate: 90deg;
|
||||
}
|
||||
</style>
|
148
frontend/components/Cgu.vue
Normal file
148
frontend/components/Cgu.vue
Normal file
@@ -0,0 +1,148 @@
|
||||
<template>
|
||||
<h2>Bienvenue sur la Boussole PLUSS</h2>
|
||||
|
||||
<p>
|
||||
« Une application de valorisation des ressources mobilisées dans les projets locaux. Le petit plus c’est l’identification collective des axes à améliorer. » </p>
|
||||
<p>
|
||||
Vous souhaitez engager votre action ou votre entreprise dans une démarche responsable, utile, solidaire ? 10 Balises comme 10 repères pour vous aider à identifier les points à améliorer et visualiser concrètement où vous en êtes !
|
||||
Le principe : il s’agit d’auto-évaluer une dynamique territoriale ou un projet en répondant à des questions pré-formatées portant sur chacune des 10 Balises du référentiel <a href="https://apes-hdf.org/_docs/Fichier/2022/21-220610094246.pdf">« Agir pour une production locale »</a>. Les participants choisissent au moins 3 questions par balise.
|
||||
Cette application fait partie d’une boîte à outils plus vaste, initiée par l’<a href="https://www.apes-hdf.org/page-0-0-0.html">Apes</a> et ses adhérents. En savoir <a href="https://pluss-hdf.org/">PLUSS.</a></p>
|
||||
<p>
|
||||
Toute utilisation effectuée a quel que titre que ce soit de la plateforme implique obligatoirement l'acceptation sans réserve des présentes conditions générales d’utilisation (CGU). </p>
|
||||
<h2>1. Définitions</h2>
|
||||
<p>Les termes mentionnés ci-dessous ont, dans les présentes Conditions Générales d’Utilisation, la signification suivante, qu’ils soient utilisés au singulier ou au pluriel :</p>
|
||||
<ul>
|
||||
<li>Balise : il s’agit d’un axe du référentiel PLUSS accessible ici : <a href="https://apes-hdf.org/_docs/Fichier/2022/21-220610094246.pdf">https://cd -hdf.org/_docs/Fichier/2022/21-220610094246.pdf</a></li>
|
||||
<li>Boussole : le présent outil d’évaluation accessible sur le site éponyme</li>
|
||||
<li>Boussole de référence : une boussole déjà configurée avec des questions qui ont été écrites par l’Apes</li>
|
||||
<li>Compte : désigne l’espace personnel de l’Utilisateur sur le Site. L’accès au Compte se fait grâce aux Identifiants.</li>
|
||||
<li>Équipe : il s’agit du collectif qui va utiliser la Boussole pour s’évaluer</li>
|
||||
<li>Identifiants : désigne l’adresse courriel de l’Utilisateur et le mot de passe associé à son Compte, nécessaires pour y accéder.</li>
|
||||
<li>Site : le présent site internet Boussole PLUSS</li>
|
||||
<li>Tableau de Bord : la page d’accueil accessible après identification sur laquelle l’Utilisateur retrouve toutes ses évaluations et peut en réaliser de nouvelles</li>
|
||||
<li>Utilisateur : désigne toute personne qui accède et navigue sur le Site</li>
|
||||
</ul>
|
||||
<h2>2. Objet</h2>
|
||||
Les présentes Conditions Générales d’Utilisation ont pour objet de définir les conditions et les
|
||||
modalités d’accès à la Boussole PLUSS
|
||||
|
||||
<h2>3. Acceptation des Conditions Générales d’Utilisation</h2>
|
||||
L’utilisation des fonctionnalités de la Plateforme implique l’acceptation des présentes CGU.
|
||||
L’Utilisateur s’engage à lire attentivement les présentes Conditions Générales d’Utilisation lors de
|
||||
l’accès au site et est invité à les télécharger ou les imprimer et à en conserver une copie.
|
||||
Les présentes CGU sont référencées sur la page d’accueil au moyen d’un lien
|
||||
hypertexte et peuvent ainsi être consultées à tout moment
|
||||
|
||||
|
||||
<h3>3. 1. Durée</h3>
|
||||
<p>Les Conditions Générales d'Utilisation entrent en vigueur à compter de leur publication sur le site et s’appliquent pendant toute la durée de l’utilisation des fonctionnalités du site (c’est-à-dire, notamment, tant que l’Utilisateur est titulaire d’un Compte sur le site).</p>
|
||||
<p>L'Utilisateur peut mettre fin à l'utilisation de son Compte à tout moment, sans frais et sans devoir
|
||||
donner de notice préalable, en demandant la suppression de son Compte à l’adresse suivante :
|
||||
contact@apes-hdf.org</p>
|
||||
<p>Les présentes CGU sont susceptibles d’évoluer dans le temps. Les Utilisateurs en seront informés par mail.</p>
|
||||
|
||||
<h2>4. Fonctionnalités de la Boussole</h2>
|
||||
<p>La Boussole est un outil d’auto-évaluation qui offre aux Utilisateurs les fonctionnalités suivantes :</p>
|
||||
<ul>
|
||||
<li>Créer un compte et pouvoir ensuite en modifier les accès et le nom d’Utilisateur</li>
|
||||
<li>Générer une évaluation à partir d’une série de questions programmée dans le Site, autant de fois que nécessaire</li>
|
||||
<li>Créer de nouveaux supports d’évaluation en créant de nouvelles questions et/ ou en modifiant et/ou sélectionnant des questions existantes</li>
|
||||
<li>Générer une évaluation à partir des nouveaux supports d’évaluations créés</li>
|
||||
<li>Suivre les évaluations faites sur tous les supports utilisés pour s’évaluer</li>
|
||||
</ul>
|
||||
<h3>4.1 Création d’un Compte, gestion et accès à l’espace « Mon Compte »</h3>
|
||||
<h4>4.1.1 Création d’un Compte</h4>
|
||||
<p>Sur la page d’accueil de la Boussole PLUSS il est possible de se créer un Compte en cliquant sur « créer un compte ». L’Utilisateur est alors invité à remplir les champs du formulaire : nom de l’équipe, adresse e-mail et mot de passe.
|
||||
Un mail automatique est envoyé à l’Utilisateur pour confirmer son inscription.
|
||||
L’Utilisateur est seul responsable de l’utilisation de ses Identifiants. Il doit veiller à conserver secret
|
||||
son mot de passe et à ne pas le divulguer.
|
||||
Il sera responsable de l’utilisation de ses Identifiants par des tiers, qu’elle soit frauduleuse ou non.</p>
|
||||
|
||||
<h4>4.1.2 Connexion à l’espace personnel si le Compte est déjà créé</h4>
|
||||
<p>Sur la page d’accueil de la Boussole PLUSS, l’Utilisateur peut se connecter si son Compte est déjà créé.
|
||||
</p>
|
||||
<h4>4.1.3 Gestion du Compte</h4>
|
||||
<p>Dans le menu « Mon compte » en haut à droite, l’utilisateur peut modifier le nom de son équipe, son adresse e-mail de connexion et son mot de passe.
|
||||
</p>
|
||||
<h4>4.1.4 Mot de passe oublié</h4>
|
||||
<p>Sur la page d’accueil de la Boussole PLUSS un lien « j’ai oublié mon mot de passe » permet de réinitialiser le mot de passe d’accès au Compte. L’e-mail utilisé lors de la création du Compte est demandé afin qu’un e-mail permettant la réinitialisation mot de passe y sera envoyé.
|
||||
</p>
|
||||
<h4>4.1.5 Suppression du Compte</h4>
|
||||
<p>Pour supprimer votre compte, merci de contacter l’administrateur à contact@apes-hdf.org</p>
|
||||
|
||||
<h3>4.2 Réalisation d’une évaluation</h3>
|
||||
|
||||
<h4 id="section-421">4.2.1 Réalisation d’une évaluation sur la Boussole de référence</h4>
|
||||
<p>Dès la première connexion l’Utilisateur peut réaliser une première évaluation en utilisant le lot de question existant. Pour y accéder il suffit de cliquer sur la tuile « Boussole de référence ». L’Utilisateur est alors invité à répondre aux questions. A la fin de l’évaluation, un radar lui permettra de mesurer sa position sur les 10 balises de la PLUSS. Les réponses aux questions sont accessibles en cliquant sur la date de l’évaluation.</p>
|
||||
<p>Les évaluations passées restent disponibles. Les nouvelles évaluations sur la même Boussole viennent s’ajouter sous forme de liste en-dessous de la première, permettant de mesurer son évolution. Pour réaliser une nouvelle évaluation sur la même Boussole cliquez sur le bouton « nouveau ».</p>
|
||||
<p>Vous pouvez sauvegarder en pdf ou imprimer les résultats de votre évaluation en cliquant sur « imprimer ».</p>
|
||||
|
||||
<h4>4.2.2 Création d’une nouvelle Boussole</h4>
|
||||
<p>La création d’une nouvelle Boussole suit les étapes suivantes :</p>
|
||||
<ul>
|
||||
<li>cliquez sur « Nouvelle Boussole » sur votre tableau de bord</li>
|
||||
<li>pour chaque Balise, cliquez sur « configurez les questions » afin d’accéder aux questions existantes, les modifier, ou en écrire de nouvelles. Pour chaque question vous pouvez écrire une description.</li>
|
||||
<li>le nombre maximale de questions par Balise est de 10 questions</li>
|
||||
<li>les Balises ne sont pas modifiables</li>
|
||||
<li>une fois toutes les Balises configurées cliquez sur valider</li>
|
||||
<li>la nouvelle Boussole est maintenant accessible sur votre tableau de bord</li>
|
||||
</ul>
|
||||
<p>Vous pouvez réaliser autant de nouvelles Boussoles que vous le désirez. Les Boussoles ne sont pas supprimables une fois configurées.
|
||||
</p>
|
||||
<h4>4.2.3 Réalisation d’une évaluation sur une nouvelle Boussole</h4>
|
||||
<p>
|
||||
Le mode opératoire est le même que pour la Boussole de référence (voir <nuxt-link to="#section-421">4.2.1</nuxt-link>).
|
||||
</p>
|
||||
|
||||
<h2>5. Mentions légales</h2>
|
||||
<p>Ce site a été produit et est administré par APES Hauts-de-France, association 1901, située au 235 boulevard Paul Painlevé, 59000 LILLE
|
||||
pour nous contacter : contact@apes-hdf.org</p>
|
||||
<p>Rédaction du site : Marie-Charlotte WOETS pour APES HDF</p>
|
||||
<p>Réalisation du site : It’s on us</p>
|
||||
<p>Hébergement : Cliss XXI</p>
|
||||
<p>Ces site est développé et maintenu sous licence AGPL 3. Il est disponible sur un dépôt public : <a href="https://git.itsonus.fr/client_projects/boussole-pluss">https://git.itsonus.fr/client_projects/boussole-pluss</a></p>
|
||||
|
||||
<h2>6. Protection des données personnelles</h2>
|
||||
<p>Les données personnelles collectées se limitent à l’adresse mail de l’Utilisateur, pour la création du Compte. Nous nous inscrivons dans le respect de la RGPD en vigueur.</p>
|
||||
<p>Les données produites lors de la création de Compte et de la réalisation d’évaluation sont hébergées par Cliss XXI sur un serveur interne au 23 avenue Jean Jaurès 62800 LIEVIN</p>
|
||||
<p>Ce site ne contient pas d’outil analytique ou de mesure d’audience.</p>
|
||||
|
||||
<h2>7. Accessibilité du site</h2>
|
||||
<p>L’Administration fait de son mieux pour s’assurer du bon fonctionnement du Site et des services y figurant, dans les limites de responsabilité des présentes conditions générales</p>
|
||||
<p>Le Site est en principe accessible 24 heures sur 24 et 7 jours sur 7, cependant, le Site décline toute responsabilité, dans les cas suivants, sans que cette liste soit limitative :</p>
|
||||
<ul>
|
||||
<li>interruption du Site pour des opérations de maintenance techniques ou d’actualisation des informations publiées.</li>
|
||||
<li>impossibilité momentanée d’accès au Site (et/ou aux sites internet et applications lui étant liés) en raison de problèmes techniques et ce quelles qu’en soient l’origine et la provenance.</li>
|
||||
<li>indisponibilité ou de surcharge ou toute autre cause empêchant le fonctionnement normal du réseau de téléphonie mobile utilisé pour accéder au Site</li>
|
||||
<li>contamination par des éventuels virus informatiques circulant sur le réseau.</li>
|
||||
<li>dommages directs ou indirects causés à l’Utilisateur, quelle qu’en soit la nature, résultant de l’accès, ou de l’utilisation du Site (et/ou des sites ou applications qui lui sont liés)</li>
|
||||
<li>utilisation anormale ou d’une exploitation illicite du Site</li>
|
||||
</ul>
|
||||
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
|
||||
h2 {
|
||||
margin: $small 0;
|
||||
}
|
||||
h3 {
|
||||
font-size: $secondary-font-size;
|
||||
margin: $small 0;
|
||||
}
|
||||
h4 {
|
||||
margin: $xx_small 0;
|
||||
}
|
||||
|
||||
p, li {
|
||||
line-height: 150%;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-position: inside;
|
||||
margin: $xxx_small 0;
|
||||
li {
|
||||
padding: $xxxx_small 0;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
32
frontend/components/CguModal.vue
Normal file
32
frontend/components/CguModal.vue
Normal file
@@ -0,0 +1,32 @@
|
||||
<script lang="ts" setup>
|
||||
const emit = defineEmits(["close", "validate"]);
|
||||
defineProps({
|
||||
visible: Boolean
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div :class="`modal${visible ? ' visible' : ''}`">
|
||||
<section class="modal-content">
|
||||
<header class="modal-content-header">
|
||||
<h1>Conditions Générales d’Utilisation de la Boussole PLUSS</h1>
|
||||
<button class="close_modal" @click="$emit('close')">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.6 16L0 14.4L6.4 8L0 1.6L1.6 0L8 6.4L14.4 0L16 1.6L9.6 8L16 14.4L14.4 16L8 9.6L1.6 16Z"
|
||||
fill="#1C1B1F"/>
|
||||
</svg>
|
||||
<span>Fermer</span>
|
||||
</button>
|
||||
</header>
|
||||
<main class="modal-content-body">
|
||||
<cgu />
|
||||
</main>
|
||||
<footer class="modal-content-footer">
|
||||
<button class="button orange" @click="emit('validate')">Accepter</button>
|
||||
</footer>
|
||||
</section>
|
||||
<div class="modal-overlay" @click="$emit('close')"></div>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
@import "assets/css/modal";
|
||||
</style>
|
@@ -1,42 +0,0 @@
|
||||
<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>
|
@@ -1,29 +1,36 @@
|
||||
<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>
|
||||
<section>
|
||||
<h1>La Boussole PLUSS, c’est quoi ?</h1>
|
||||
<p>
|
||||
« Une application de valorisation des ressources mobilisées dans les projets locaux. Le petit plus c’est
|
||||
l’identification collective des axes à améliorer. »
|
||||
</p>
|
||||
<p>
|
||||
Vous souhaitez engager votre action ou votre entreprise dans une démarche responsable, utile, solidaire ? 10
|
||||
balises comme 10 repères pour vous aider à identifier les points à améliorer et visualiser concrètement où vous en
|
||||
êtes !
|
||||
Le principe : il s’agit d’auto-évaluer une dynamique territoriale ou un projet en répondant à des questions
|
||||
pré-formatées portant sur chacune des 10 balises du référentiel <nuxt-link to="https://apes-hdf.org/_docs/Fichier/2022/21-220610094246.pdf" target="_blank">« Agir pour une production locale » (1,24 Mo)</nuxt-link>. Les
|
||||
participants choisissent 3 questions par balise.
|
||||
Cette application fait partie d’une boîte à outils plus vaste, initiée par l’<nuxt-link to="https://www.apes-hdf.org/page-0-0-0.html" target="_blank">Apes</nuxt-link> et ses adhérents. En savoir
|
||||
<nuxt-link to="https://pluss-hdf.org/" target="_blank">PLUSS</nuxt-link> ?
|
||||
</p>
|
||||
</section>
|
||||
<section>
|
||||
<h1>Comment ça marche ?</h1>
|
||||
<p>
|
||||
Après avoir créé un compte pour votre équipe, vous pouvez commencer votre évaluation sur un premier lot de
|
||||
questions disponible. Vous pouvez également décider de configurer vous même vos questions, et même de piocher
|
||||
parmi celles imaginées par les autres utilisateurs !
|
||||
Vous pouvez créer autant de boussoles que vous le désirez.<br/>
|
||||
Une fois la boussole configurée, vous pouvez réaliser autant d’évaluations que vous le souhaiter, afin de mesurer
|
||||
votre progression sur chacune des balises. Les évaluations passées restent disponibles.
|
||||
</p>
|
||||
</section>
|
||||
<div class="button-container">
|
||||
<nuxt-link class="button orange" to="/login">Démarrer</nuxt-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script setup lang="ts">
|
||||
</script>
|
||||
|
@@ -19,9 +19,6 @@
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@import "assets/css/color";
|
||||
@import "assets/css/spacing";
|
||||
|
||||
ol {
|
||||
margin: 0;
|
||||
position: relative;
|
||||
|
31
frontend/components/MainFooter.vue
Normal file
31
frontend/components/MainFooter.vue
Normal file
@@ -0,0 +1,31 @@
|
||||
<template>
|
||||
<footer class="footer">
|
||||
<nuxt-link to="https://www.apes-hdf.org" target="_blank">
|
||||
<img src="/images/logo/logo_apes.png" height="125" width="200" alt="Logo APES"/>
|
||||
</nuxt-link>
|
||||
<nuxt-link to="/cgu">Conditions Générales d’Utilisation de la Boussole PLUSS</nuxt-link>
|
||||
<nuxt-link to="mailto:contact@apes.org">Contactez-nous !</nuxt-link>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.footer {
|
||||
width: 100vw;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $xxx_small;
|
||||
padding-bottom: $xx_small;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: #ededf0;
|
||||
|
||||
margin-top: auto;
|
||||
|
||||
clear: both;
|
||||
height: $footer_height;
|
||||
|
||||
background: $white url(/images/decoration/cube.png) repeat left top;;
|
||||
}
|
||||
</style>
|
||||
<script setup lang="ts">
|
||||
</script>
|
31
frontend/components/MainHeader.vue
Normal file
31
frontend/components/MainHeader.vue
Normal file
@@ -0,0 +1,31 @@
|
||||
<template>
|
||||
<header class="header">
|
||||
<div class="main-logo">
|
||||
<nuxt-link :to="useRoute().name === 'index' ? '' : '/'">
|
||||
<img src="/images/logo/main_logo.svg" width="245px" alt="Boussole PLUSS"/>
|
||||
</nuxt-link>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.header {
|
||||
width: 100%;
|
||||
height: 115px;
|
||||
margin-bottom: $xxx_medium;
|
||||
background-repeat: no-repeat;
|
||||
background-image: url("/images/decoration/background.svg");
|
||||
background-position: bottom center, 50%;
|
||||
background-size: cover;
|
||||
|
||||
.main-logo {
|
||||
position: absolute;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
top: 40px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 225px;
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -1,80 +1,15 @@
|
||||
<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" setup>
|
||||
import {PolarArea} from 'vue-chartjs';
|
||||
import type {PropType} from "vue";
|
||||
|
||||
<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
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object as PropType<number[]>
|
||||
}
|
||||
})
|
||||
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 = {
|
||||
const chartData = computed(() => {
|
||||
return {
|
||||
labels: [
|
||||
"1. Pouvoir d'agir",
|
||||
"2. Multi-secteur",
|
||||
@@ -101,36 +36,45 @@ export default class PolarAreaChart extends Vue {
|
||||
"#E9D280",
|
||||
"#7BD1F5"
|
||||
],
|
||||
data: this.data,
|
||||
data: props.data,
|
||||
borderWidth: 1,
|
||||
borderColor: "#666666"
|
||||
}
|
||||
]
|
||||
};
|
||||
});
|
||||
|
||||
readonly chartOptions = {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
// animation: false,
|
||||
scales: {
|
||||
r: {
|
||||
suggestedMin: 0,
|
||||
suggestedMax: 10,
|
||||
ticks: {
|
||||
display: false
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
const chartOptions = ref({
|
||||
responsive: true,
|
||||
maintainAspectRatio: true,
|
||||
scales: {
|
||||
r: {
|
||||
suggestedMin: 0,
|
||||
suggestedMax: 10,
|
||||
ticks: {
|
||||
display: false
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Watch("data", {})
|
||||
private renderChart() {
|
||||
this.chartData.datasets[0].data = this.data;
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<div class="chart-container">
|
||||
<polar-area
|
||||
:options="chartOptions"
|
||||
:data="chartData"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.chart-container {
|
||||
position: relative;
|
||||
max-width: 60vh;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
@@ -1,10 +1,34 @@
|
||||
<script lang="ts" setup>
|
||||
import type {PropType} from "vue";
|
||||
import type {QuizResponse} from "~/store/quiz";
|
||||
import type {Axe} from "~/store/axe";
|
||||
|
||||
const props = defineProps({
|
||||
axe: {
|
||||
type: Object as PropType<Axe>,
|
||||
required: true
|
||||
},
|
||||
average: Number,
|
||||
responses: {
|
||||
type: Object as PropType<QuizResponse[]>,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const cssVars = computed(() => {
|
||||
return {
|
||||
'--color': props.axe ? props.axe.color : "#ffffff"
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<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>
|
||||
<span class="upper">{{ Number((average).toFixed(1)) }} / 10</span>
|
||||
</h2>
|
||||
<div v-for="response in responses" :key="response._links.self.href">
|
||||
<div v-for="response in responses">
|
||||
<p class="question">
|
||||
<span>
|
||||
{{ response.question }}
|
||||
@@ -16,38 +40,6 @@
|
||||
</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";
|
||||
@@ -55,7 +47,7 @@ export default class QuizAxeDetails extends Vue {
|
||||
@import "assets/css/spacing";
|
||||
|
||||
.axe-details {
|
||||
margin-top: $x_small;
|
||||
margin-top: $small;
|
||||
}
|
||||
|
||||
.title {
|
||||
|
@@ -1,3 +1,58 @@
|
||||
<script lang="ts" setup>
|
||||
import type {PropType} from "vue";
|
||||
import {useQuizStore} from "~/store/quiz";
|
||||
import type {Question} from "~/store/question";
|
||||
|
||||
const props = defineProps({
|
||||
title: String,
|
||||
description: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
axeNumber: Number,
|
||||
totalAxes: Number,
|
||||
icon: String,
|
||||
color: String,
|
||||
questions: {
|
||||
type: Object as PropType<Question[]>,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(["rate"]);
|
||||
|
||||
const store = useQuizStore();
|
||||
|
||||
const cssVars = computed(() => {
|
||||
return {
|
||||
'--color': props.color
|
||||
}
|
||||
});
|
||||
|
||||
function onRate(score: number, question: Question) {
|
||||
store.updateScoreResponse({axeId: props.axeNumber, questionId: question.id, score});
|
||||
const questions = store.questionsRatedPerAxe.get(props.axeNumber);
|
||||
const unratedQuestions = questions ? questions.filter(value => !value.rated) : [];
|
||||
emit('rate', {
|
||||
isFullRated: unratedQuestions.length === 0
|
||||
});
|
||||
}
|
||||
|
||||
function onComment(comment: string, question: Question) {
|
||||
store.updateCommentResponse({axeId: props.axeNumber, questionId: question.id, comment});
|
||||
}
|
||||
|
||||
function getCurrentScore(question: Question): number | undefined {
|
||||
const rate = store.responses.get(question.id) as QuizRate;
|
||||
return rate ? rate.score : undefined;
|
||||
}
|
||||
|
||||
function getCurrentComment(question: Question): string | undefined {
|
||||
const rate = store.responses.get(question.id) as QuizRate;
|
||||
return rate ? rate.comment : undefined;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<article :style="cssVars">
|
||||
<header>
|
||||
@@ -11,102 +66,23 @@
|
||||
<img :src="icon" width="200px" alt="" aria-hidden="true"/>
|
||||
</div>
|
||||
</header>
|
||||
<section v-for="question in questions" :key="question._links.self.href" class="question">
|
||||
<section v-if="questions" v-for="question in questions" :key="question.id" class="question">
|
||||
<div class="title">
|
||||
{{ question.label }}
|
||||
</div>
|
||||
<details v-if="question.description">
|
||||
<summary> {{ question.label }}</summary>
|
||||
<summary>Description</summary>
|
||||
<p>{{ question.description }}</p>
|
||||
</details>
|
||||
<div v-else class="title">
|
||||
{{ question.label }}
|
||||
</div>
|
||||
<div class="rating">
|
||||
<rating :color="color" :initial-value="getCurrentScore(question)" @rate="rate => onRate(rate, question)" />
|
||||
<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)"/>
|
||||
<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: false
|
||||
})
|
||||
private description!: 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;
|
||||
|
||||
get cssVars() {
|
||||
return {
|
||||
'--color': this.color
|
||||
}
|
||||
}
|
||||
|
||||
onRate(score: number, question: Question) {
|
||||
quizStore.updateScoreResponse({axeId: this.axeNumber, questionId: question.id, score});
|
||||
this.emitRatingState();
|
||||
}
|
||||
|
||||
onComment(comment: string, question: Question) {
|
||||
quizStore.updateCommentResponse({axeId: this.axeNumber, questionId: question.id, comment});
|
||||
}
|
||||
|
||||
getCurrentScore(question: Question): number | undefined {
|
||||
const rate = quizStore.responses.get(question.id) as QuizRate;
|
||||
return rate ? rate.score : undefined;
|
||||
}
|
||||
|
||||
getCurrentComment(question: Question): string | undefined {
|
||||
const rate = quizStore.responses.get(question.id) 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";
|
||||
@@ -131,14 +107,15 @@ $size: 31px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
text-align: left;
|
||||
|
||||
> span {
|
||||
padding: 0 $x_small;
|
||||
padding: 0 $small;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
text-align: center;
|
||||
margin: $small;
|
||||
margin: $medium;
|
||||
}
|
||||
|
||||
.description {
|
||||
@@ -149,40 +126,35 @@ $size: 31px;
|
||||
|
||||
.question {
|
||||
width: 100%;
|
||||
}
|
||||
.question details,
|
||||
.question .title {
|
||||
margin-bottom: $x_small;
|
||||
}
|
||||
.question details summary,
|
||||
.question .title {
|
||||
font-weight: 700;
|
||||
font-size: $title-font-size;
|
||||
}
|
||||
|
||||
.question details summary {
|
||||
margin-bottom: $xxx_small;
|
||||
}
|
||||
details,
|
||||
.title {
|
||||
margin-bottom: $small;
|
||||
}
|
||||
.title {
|
||||
font-weight: 700;
|
||||
font-size: $title-font-size;
|
||||
}
|
||||
|
||||
.question details p {
|
||||
font-size: $secondary-font-size;
|
||||
color: $gray_4;
|
||||
margin-left: $x_small;
|
||||
}
|
||||
details summary {
|
||||
list-style-position: inside;
|
||||
margin-bottom: $xxx_small;
|
||||
}
|
||||
|
||||
.question .rating {
|
||||
margin: 0 auto;
|
||||
max-width: 401px;
|
||||
}
|
||||
.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;
|
||||
input[type="text"] {
|
||||
width: 100%;
|
||||
border: 1px solid $gray_2;
|
||||
border-radius: 8px;
|
||||
background: none;
|
||||
margin: $medium 0;
|
||||
font-weight: 400;
|
||||
font-size: $tertiary-font-size;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
@@ -1,38 +1,24 @@
|
||||
<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";
|
||||
<script lang="ts" setup>
|
||||
|
||||
@Component
|
||||
export default class QuizProgress extends Vue {
|
||||
const props = defineProps({
|
||||
numberSteps: Number,
|
||||
currentStep: Number,
|
||||
color: String
|
||||
});
|
||||
|
||||
@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
|
||||
}
|
||||
const cssVars = computed(() => {
|
||||
return {
|
||||
'--color': props.color
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :style="cssVars" class="progress">
|
||||
<span v-for="step in numberSteps" :key="step" class="step" :class="{ active: step <= currentStep }"></span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@import "assets/css/color";
|
||||
@@ -42,10 +28,11 @@ export default class QuizProgress extends Vue {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.step {
|
||||
border-radius: 2px;
|
||||
border: 6px solid $gray_2;
|
||||
margin: $x_small $xxx_small;
|
||||
margin: $small $xxx_small;
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
|
@@ -1,3 +1,76 @@
|
||||
<script lang="ts" setup>
|
||||
|
||||
import {randomUUID} from "uncrypto";
|
||||
|
||||
const MAX_VALUE = 10;
|
||||
|
||||
const props = defineProps({
|
||||
initialValue: Number,
|
||||
color: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "#DC6A00"
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(["rate"]);
|
||||
|
||||
const checkedValue = ref(0);
|
||||
const componentId = ref();
|
||||
|
||||
onMounted(() => {
|
||||
componentId.value = randomUUID();
|
||||
checkedValue.value = props.initialValue;
|
||||
});
|
||||
|
||||
const index = computed(() =>
|
||||
Array.apply(0, Array(MAX_VALUE)).map((_, b) => {
|
||||
return b + 1;
|
||||
}).reverse()
|
||||
);
|
||||
|
||||
const cssVars = computed(() => {
|
||||
return {
|
||||
'--color': props.color
|
||||
}
|
||||
});
|
||||
|
||||
function setChecked(index: number) {
|
||||
if (index < 1) {
|
||||
index = 1;
|
||||
} else if (index > MAX_VALUE) {
|
||||
index = MAX_VALUE;
|
||||
}
|
||||
checkedValue.value = index;
|
||||
emit('rate', index);
|
||||
}
|
||||
|
||||
function increment() {
|
||||
setChecked(checkedValue.value + 1);
|
||||
}
|
||||
|
||||
function decrement() {
|
||||
setChecked(checkedValue.value - 1);
|
||||
}
|
||||
|
||||
function isChecked(index: number) {
|
||||
return checkedValue.value >= index;
|
||||
}
|
||||
|
||||
function handleKeyUp(event: KeyboardEvent) {
|
||||
switch (event.key) {
|
||||
case "ArrowLeft":
|
||||
decrement();
|
||||
break;
|
||||
case "ArrowRight":
|
||||
increment();
|
||||
break;
|
||||
}
|
||||
event.preventDefault();
|
||||
return false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :style="cssVars">
|
||||
<div class="rating" tabindex="0" @keyup="handleKeyUp">
|
||||
@@ -17,91 +90,9 @@
|
||||
</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";
|
||||
|
||||
|
@@ -1,56 +1,101 @@
|
||||
<script lang="ts" setup>
|
||||
import {useAuthStore} from "~/store/auth";
|
||||
|
||||
function logout() {
|
||||
useAuthStore().logout();
|
||||
navigateTo("/");
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<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">
|
||||
<nuxt-link to="/bundle" class="title">
|
||||
<svg width="26" height="32" 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>
|
||||
Boussole <span class="bold">PLUSS</span>
|
||||
</nuxt-link>
|
||||
<div class="menu-container">
|
||||
<button class="button-icon">
|
||||
<svg class="svg-icon" width="32" viewBox="0 0 1024 1024" 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"
|
||||
d="M723.43 508.6c-54.123 47.75-125.977 77.056-205.163 77.056-80.364 0-153.4-30.259-207.765-79.421C184.05 539.325 105.81 652.308 105.81 787.277v68.782c0 160.968 812.39 160.968 812.39 0v-68.782c-0.005-131.415-74.22-242.509-194.77-278.677z m-205.163 28.13c140.165 0 254.095-109.44 254.095-244.64S658.668 47.218 518.267 47.218c-139.93 0-253.855 109.675-253.855 244.874 0 135.204 113.925 244.639 253.855 244.639z m0 0"
|
||||
fill="#8BCDCD"/>
|
||||
</svg>
|
||||
Boussole <span class="bold">PLUSS</span>
|
||||
</nuxt-link>
|
||||
<span class="team">
|
||||
Équipe : {{ team }}
|
||||
</span>
|
||||
</svg>
|
||||
</button>
|
||||
<ul class="menu-container__content">
|
||||
<li v-if="useAuthStore().user">
|
||||
{{ useAuthStore().user.username }}
|
||||
</li>
|
||||
<li>
|
||||
<nuxt-link to="/account">Mon compte</nuxt-link>
|
||||
</li>
|
||||
<li>
|
||||
<button class="button-link" @click="logout">Me déconnecter</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {Component, Vue} from "nuxt-property-decorator";
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@Component
|
||||
export default class TeamHeader extends Vue {
|
||||
header {
|
||||
padding: 0 $xx_small;
|
||||
color: $black;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
//position: absolute;
|
||||
//height: $header_height;
|
||||
border-bottom: 2px solid $gray_3;
|
||||
}
|
||||
|
||||
get team() {
|
||||
return this.$auth.user ? this.$auth.user.username : "Non connecté";
|
||||
.title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $xxx_small;
|
||||
text-transform: uppercase;
|
||||
color: $black;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.title svg {
|
||||
margin-right: $xxx_small;
|
||||
}
|
||||
|
||||
.team {
|
||||
font-size: $small-font-size;
|
||||
}
|
||||
|
||||
.menu-container {
|
||||
position: relative;
|
||||
|
||||
& > .menu-container__content {
|
||||
display: none;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 52px;
|
||||
background: $white;
|
||||
}
|
||||
|
||||
&:focus-within > .menu-container__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.menu-container__content {
|
||||
gap: $xx_small;
|
||||
align-items: flex-end;
|
||||
list-style: none;
|
||||
min-width: 150px;
|
||||
padding: $xx_small $xx_small;
|
||||
|
||||
border: 2px solid $gray_3;
|
||||
}
|
||||
}
|
||||
</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>
|
||||
|
91
frontend/components/Toaster.vue
Normal file
91
frontend/components/Toaster.vue
Normal file
@@ -0,0 +1,91 @@
|
||||
<script lang="ts" setup>
|
||||
import type {PropType} from "vue";
|
||||
|
||||
defineProps({
|
||||
"title": {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
"type": {
|
||||
type: String as PropType<'warn' | 'info' | 'success'>,
|
||||
default: 'info'
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<aside :class="'toaster ' + type">
|
||||
<div class="toaster-content">
|
||||
<div class="toaster-content-title">{{ title }}</div>
|
||||
<div class="toaster-content-body">
|
||||
<slot/>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</template>
|
||||
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
.toaster {
|
||||
display: flex;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
translate: -50% 0;
|
||||
padding: $medium $medium $xx_small;
|
||||
gap: $xxx_small;
|
||||
|
||||
border-radius: 0 0 16px 16px;
|
||||
|
||||
animation-name: fadeInRight;
|
||||
animation-duration: 300ms;
|
||||
animation-fill-mode: both;
|
||||
|
||||
@include border-shadow();
|
||||
|
||||
&.info {
|
||||
color: $info_text;
|
||||
background: $info_background;
|
||||
}
|
||||
&.warn {
|
||||
color: $warn_text;
|
||||
background: $warn_background;
|
||||
}
|
||||
&.success {
|
||||
color: $success_text;
|
||||
background: $success_background;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeInRight {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translate3d(0, -100%, 0);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
.toaster-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $xxx_small;
|
||||
|
||||
font-size: $small-font-size;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.toaster-content-title {
|
||||
font-weight: bold;
|
||||
line-height: 120%;
|
||||
font-size: $tertiary-font-size;
|
||||
}
|
||||
|
||||
.toaster-content-body {
|
||||
line-height: 150%;
|
||||
}
|
||||
</style>
|
Reference in New Issue
Block a user