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:
2024-07-03 15:55:34 +02:00
parent f86d794239
commit b6e86f0641
207 changed files with 5570 additions and 40453 deletions

View 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 dadministrateurs et de salariés de lApes baptisé
Veille/Plaidoyer à chercher à répondre à cette préoccupation : « comment agir pour que le monde daprès soit
meilleur que celui davant ? ». Dans le prolongement des ConstructivESS que lApes 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 dautres façons de faire de léconomie. Nous avons choisi de
commencer par le sujet de la production locale. Car cest à léchelle locale quon innove, quon
expérimente,
quon donne de la place aux initiatives citoyennes, et que lon 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é dune é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 didé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 dinterventions ou daides 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 dorganiser ou de réguler la connexion entre loffre et la demande.</li>
<li>Il ne suffira pas de sensibiliser et de motiver les consommateurs à modifier leurs habitudes dachats
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 lautonomie et du pouvoir dagir à 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 à laffirmation dun bien-vivre partagé et dun
prendre-soin
universel.</p>
<p>« Si nous voulons que nos résistances soient créatrices et porteuses de la démonstration quun autre monde
est non seulement possible mais nécessaire, il nous faut dautant 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 lensemble des chaînes de valeur</h2>
<p>La relocalisation des productions nécessite de revisiter lensemble 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 dactivités,
industrielles ou agricoles. Cest loccasion de procéder à une réévaluation des modes dorganisation des
activités selon des logiques fonctionnelles : salimenter, se déplacer, se loger, se soigner, ...</p>
<p>Intelligence collective, projets collaboratifs, initiatives solidaires, la relocalisation dépendra de notre
capacité dinnovation 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 quon consomme ou utilise. Production et consommation locales doivent aussi se faire dans une
complémentarité avec dautres territoires.
</p>
<p>Des partenariats contractualisés avec dautres é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 lutilité 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 dune 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 sassurer de lutilité 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, cest-à-dire des ressources partagées et maintenues collectivement par une
communauté, propres à chaque territoire est au centre des questionnements dune 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 sil est nécessaire et légitime den
disposer en pleine propriété.
</p>
<h2>6. agir démocratiquement</h2>
<p>Le territoire, lieu de production, de vie, didentité et dinteractions humaines, sociales et économiques,
doit saffirmer également comme lieu dexpression et de débat dune 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 lopportunité aux
représentants de ces groupes dexprimer 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>Lensemble de ces procédés est un levier précieux pour le développement dindispensables coopérations.
Celles-ci favorisent la coconstruction des solutions répondant aux besoins du territoire, grâce à la
mobilisation dune diversité de compétences et de ressources matérielles, immatérielles, monétaires et non
monétaires.
</p>
<p>La création dalliances 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 dentreprises).
</p>
<h2> 8. agir pour maîtriser les outils de production</h2>
<p>Les choix stratégiques dinvestisseurs 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 lemploi local (course à la productivité et
au
rendement, délocalisations).
</p>
<p>La propriété collective de moyens de production, telle quelle 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
dune économie de proximité : mobilisation de lépargne solidaire, doutils 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 dacteurs 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 lutilité sociale et écologique du travail, on contribue à la démocratisation de
lentreprise, et à la -marchandisation du travail.
</p>
<p>Cest par ce biais que les politiques de lutte contre les exclusions et dinsertion par lactivité
économique
pourront prendre leurs pleines mesures, et offrir à chaque citoyen de nos territoires la possibilité
dexercer
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 lESS
nont
pas attendu dêtre confrontés à la pandémie pour ouvrir dautres voies et contribuer à la transformation
dune
société centrée sur le profit immédiat.
</p>
<p>En mettant lhumain au cœur de leurs projets, en répondant dabord
aux besoins fondamentaux des personnes par des solutions ancrées sur les territoires, ils ont démontré
limportance de la proximité, de la coopération et de la participation des citoyens aux décisions qui les
concernent
</p>
<p>
Le moment na sans doute jamais été aussi propice pour sinspirer
de ces pratiques et coopérer avec les entreprises de léconomie ordinaire au profit dune 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>

View 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
View 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 cest lidentification 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 vous en êtes !
Le principe : il sagit dauto-é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 dune 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 dutilisation (CGU). </p>
<h2>1. Définitions</h2>
<p>Les termes mentionnés ci-dessous ont, dans les présentes Conditions Générales dUtilisation, la signification suivante, quils soient utilisés au singulier ou au pluriel :</p>
<ul>
<li>Balise : il sagit dun 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 lApes</li>
<li>Compte : désigne lespace personnel de lUtilisateur sur le Site. Laccès au Compte se fait grâce aux Identifiants.</li>
<li>Équipe : il sagit du collectif qui va utiliser la Boussole pour sévaluer</li>
<li>Identifiants : désigne ladresse courriel de lUtilisateur 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 daccueil accessible après identification sur laquelle lUtilisateur 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 dUtilisation ont pour objet de définir les conditions et les
modalités daccès à la Boussole PLUSS
<h2>3. Acceptation des Conditions Générales dUtilisation</h2>
Lutilisation des fonctionnalités de la Plateforme implique lacceptation des présentes CGU.
LUtilisateur sengage à lire attentivement les présentes Conditions Générales dUtilisation lors de
laccè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 daccueil au moyen dun 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 sappliquent pendant toute la durée de lutilisation des fonctionnalités du site (cest-à-dire, notamment, tant que lUtilisateur est titulaire dun 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 à ladresse 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 dauto-é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 dUtilisateur</li>
<li>Générer une évaluation à partir dune 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 dun Compte, gestion et accès à lespace « Mon Compte »</h3>
<h4>4.1.1 Création dun Compte</h4>
<p>Sur la page daccueil de la Boussole PLUSS il est possible de se créer un Compte en cliquant sur « créer un compte ». LUtilisateur est alors invité à remplir les champs du formulaire : nom de léquipe, adresse e-mail et mot de passe.
Un mail automatique est envoyé à lUtilisateur pour confirmer son inscription.
LUtilisateur est seul responsable de lutilisation de ses Identifiants. Il doit veiller à conserver secret
son mot de passe et à ne pas le divulguer.
Il sera responsable de lutilisation de ses Identifiants par des tiers, quelle soit frauduleuse ou non.</p>
<h4>4.1.2 Connexion à lespace personnel si le Compte est déjà créé</h4>
<p>Sur la page daccueil de la Boussole PLUSS, lUtilisateur 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, lutilisateur 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 daccueil de la Boussole PLUSS un lien « jai oublié mon mot de passe » permet de réinitialiser le mot de passe daccès au Compte. Le-mail utilisé lors de la création du Compte est demandé afin quun 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 ladministrateur à contact@apes-hdf.org</p>
<h3>4.2 Réalisation dune évaluation</h3>
<h4 id="section-421">4.2.1 Réalisation dune évaluation sur la Boussole de référence</h4>
<p>Dès la première connexion lUtilisateur 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 ». LUtilisateur 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 sajouter 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 dune nouvelle Boussole</h4>
<p>La création dune 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 daccé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 dune é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 : Its 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 à ladresse mail de lUtilisateur, 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 doutil analytique ou de mesure daudience.</p>
<h2>7. Accessibilité du site</h2>
<p>LAdministration fait de son mieux pour sassurer 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 dactualisation des informations publiées.</li>
<li>impossibilité momentanée daccès au Site (et/ou aux sites internet et applications lui étant liés) en raison de problèmes techniques et ce quelles quen soient lorigine 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 à lUtilisateur, quelle quen soit la nature, résultant de laccès, ou de lutilisation du Site (et/ou des sites ou applications qui lui sont liés)</li>
<li>utilisation anormale ou dune 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>

View 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 dUtilisation 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>

View File

@@ -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>

View File

@@ -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, cest quoi ?</h1>
<p>
« Une application de valorisation des ressources mobilisées dans les projets locaux. Le petit plus cest
lidentification 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 vous en
êtes !
Le principe : il sagit dauto-é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 dune 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>

View File

@@ -19,9 +19,6 @@
<style lang="scss" scoped>
@import "assets/css/color";
@import "assets/css/spacing";
ol {
margin: 0;
position: relative;

View 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 dUtilisation 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>

View 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>

View File

@@ -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>

View File

@@ -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 {

View File

@@ -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>

View File

@@ -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;
}

View File

@@ -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";

View File

@@ -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>

View 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>