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,182 @@
<script lang="ts" setup>
import {type QuestionCreation, useBundleStore} from "~/store/bundle";
import {useNotificationStore} from "~/store/notification";
import type {ApiError} from "~/composables/fetch-api";
import {type Axe, useAxeStore} from "~/store/axe";
import {type Question, useQuestionStore} from "~/store/question";
const axes = ref<Axe[]>();
const label = ref();
const presentation = ref();
const questions = ref<Map<number, QuestionCreation[]>>(new Map());
const questionsExample = ref<Map<number, QuestionCreation[]>>(new Map());
const modalVisible = ref(false);
const currentAxe = ref<Axe>();
const currentQuestions = ref<Question[]>();
onMounted(() => {
useAxeStore().findAxes().then(response => {
response.forEach(axe => {
useQuestionStore().findDefaults(axe.id).then(response => {
questions.value.set(axe.id, response);
});
useQuestionStore().findAll(axe.id).then(response => {
questionsExample.value.set(axe.id, response);
});
});
axes.value = response;
});
});
function createBundle() {
const newQuestions = [];
let errors = [];
questions.value.forEach((value, axeId) => {
if (value.length === 0) {
const axeNumber = axes.value.filter(a => a.id === axeId)[0].identifier;
errors.push(`L'axe ${axeNumber} n'a pas de question.`);
}
value.forEach((q: Question, index)=> {
newQuestions.push({
axeId: axeId,
label: q.label,
description: q.description,
index: index+1
});
if (!q.label.trim()) {
const axeNumber = axes.value.filter(a => a.id === axeId)[0].identifier;
errors.push(`L'une des question de l'axe ${axeNumber} contient un libellé non rempli.`);
}
});
});
if (errors.length > 0) {
useNotificationStore().pushNotification("warn",
{message: `La boussole contient des erreurs !`, details: errors});
}else {
useBundleStore()
.create({
label: label.value,
presentation: presentation.value,
questions: newQuestions
})
.then(() => {
useNotificationStore().pushNotification("success",{message: "La boussole a bien été créée."});
navigateTo("/bundle");
})
.catch((apiError: ApiError) => {
let details;
if (apiError.fieldErrors) {
details = apiError.fieldErrors.map(error => `${error.fields!.join(", ")} ${error.detail}`);
}
useNotificationStore().pushNotification("warn",{message: apiError.message, details});
});
}
}
function css(axe: Axe) {
return {
'--color': axe.color
}
}
function showAxeModal(axe: Axe) {
currentAxe.value = axe;
modalVisible.value = true;
currentQuestions.value = questions.value.get(axe.id);
document.body.style.overflowY = "hidden";
}
function hideAxeModal() {
currentAxe.value = undefined;
currentQuestions.value = undefined;
modalVisible.value = false;
document.body.style.overflowY = "auto";
}
function onQuestionsChange({axeId, newQuestions}) {
questions.value.set(axeId, newQuestions);
}
function numberOfQuestions(axe: Axe) {
const q = questions.value.get(axe.id)
return q ? q.length: 0;
}
</script>
<template>
<h1>Créer une nouvelle Boussole</h1>
<section>
<form class="form" @submit.prevent="createBundle">
<label for="label">Nom de la boussole *</label>
<input id="label" v-model="label" type="text" required maxlength="50">
<label for="label">Présentation</label>
<input id="label" v-model="presentation" type="text" maxlength="100">
<ul class="axe-list">
<li class="axe-list__item" v-for="axe in axes" :style="css(axe)">
<h2>{{ axe.identifier }} - {{ axe.shortTitle }}</h2>
<div class="axe-list__item__content">
<div class="axe-list__item__content-text">
<p>{{ axe.title }}</p>
<p>{{ numberOfQuestions(axe) }} question{{numberOfQuestions(axe) > 1? 's': ''}} configurée{{numberOfQuestions(axe) > 1? 's': ''}}</p>
</div>
<button class="button blue" type="button" @click="showAxeModal(axe)">
Configurer
</button>
</div>
</li>
</ul>
<div class="button-container">
<nuxt-link class="button gray button-back" to="/bundle" aria-label="Précédent"></nuxt-link>
<button class="button orange">Valider</button>
</div>
</form>
</section>
<bundle-axe-modal v-if="currentAxe" :visible="modalVisible"
:axe="currentAxe"
:questions="questions.get(currentAxe.id)"
:questions-example="questionsExample.get(currentAxe.id)"
@close="hideAxeModal()"
@changed="(axeId, newQuestions) => onQuestionsChange(axeId, newQuestions)"/>
</template>
<style lang="scss" scoped>
@import "assets/css/color";
@import "assets/css/spacing";
@import "assets/css/font";
.axe-list {
display: flex;
flex-direction: column;
list-style: none;
gap: $xxx_small;
margin: 0;
&__item {
padding: $xx_small 0;
border-bottom: 2px solid var(--color);
h2 {
font-size: $secondary-font-size;
margin: 0;
}
&__content {
display: flex;
align-items: center;
justify-content: space-between;
gap: $xx_small;
&-text p + p {
margin: $xxx_small 0;
}
&-text p:last-child {
font-style: italic;
}
}
}
}
</style>

View File

@@ -0,0 +1,126 @@
<script lang="ts" setup>
import {type Bundle, useBundleStore} from "~/store/bundle";
const showModal = ref(false);
const bundles = ref<Bundle[]>([]);
const loading = ref(false);
onMounted(() => {
loading.value = true;
useBundleStore().findAll().then((response: Quiz[]) => {
bundles.value = response;
}).finally(() => {
loading.value = false;
});
});
function navigateToDashboard(bundle: Bundle) {
useBundleStore().setCurrentBundle(bundle);
navigateTo("/dashboard");
}
function navigateToQuiz(bundle: Bundle) {
useBundleStore().setCurrentBundle(bundle);
navigateTo("/quiz");
}
</script>
<template>
<section v-if="!loading" class="section">
<h1>Bienvenue sur votre espace dauto-évaluation Boussole&nbsp;!</h1>
<p>Vous allez pouvoir dès maintenant évaluer votre projet de Production Locale Utile Solidaire et Soutenable au
regard des <button class="button-link" @click="showModal = true">10 balises du référentiel</button>.
</p>
<p>Cliquez ci-dessous sur la <strong>Boussole de référence</strong>, pour évaluer votre projet sur des questions
déjà configurées. Pour configurez vous-même vos questions, cliquez sur <strong>Personnaliser votre Boussole</strong>.</p>
<p>Ce tableau de bord vous permet de suivre vos différentes auto-évaluations sur tous vos projets&nbsp;!</p>
<ul class="bundle-list">
<li v-for="bundle in bundles">
<article class="bundle-list__item">
<main>
<h1>{{ bundle.label }}</h1>
<p>{{ bundle.presentation }}</p>
<dl class="bundle-list__item__attribute">
<dt>Dernière auto-évaluation réalisée</dt>
<dd>{{ bundle.lastQuizzDate ? formatDate(bundle.lastQuizzDate) : 'NA' }}</dd>
<dt>Nombre d'auto-évaluation</dt>
<dd>{{ bundle.numberOfQuizzes || 0 }}</dd>
</dl>
</main>
<footer class="bundle-list__item__button-container">
<button class="button blue" @click="navigateToDashboard(bundle)">Voir mes évaluations</button>
<button class="button orange" @click="navigateToQuiz(bundle)">S'évaluer</button>
</footer>
</article>
</li>
</ul>
<div class="button-container">
<nuxt-link to="/bundle/create">Personnaliser votre Boussole</nuxt-link>
</div>
</section>
<loader v-else/>
<axe-definition-modal :visible="showModal" @close="showModal=false" />
</template>
<style lang="scss" scoped>
.section {
margin-block: $medium;
}
.bundle-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(22.5rem, 1fr));
grid-gap: $small;
list-style: none;
margin-top: $x_medium;
&__item {
display: flex;
flex-direction: column;
height: 100%;
gap: $small;
padding: $small;
border-radius: 20px;
@include border-shadow();
h1 {
font-size: $title-font-size;
margin: 0 0 $xxx_small 0;
}
p {
margin: 0 0 $xxx_small 0;
}
&__attribute {
dt {
font-weight: bold;
float: left;
clear: left;
&:after {
content: ' :';
margin-right: $xxxx_small;
}
}
dd {
padding-bottom: $xxxx_small;
}
}
&__button-container {
display: flex;
justify-content: center;
gap: $xxx_small;
margin-top: auto;
}
}
}
</style>