Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • geocontrib/geocontrib-frontend
  • ext_matthieu/geocontrib-frontend
  • fnecas/geocontrib-frontend
  • MatthieuE/geocontrib-frontend
4 results
Show changes
<template>
<div
id="project-members"
class="page"
>
<div id="project-members">
<h1 class="ui header">
Gérer les membres
</h1>
......@@ -49,7 +46,10 @@
:disabled="!newMember.user.name"
@click="addMember"
>
<i class="white add icon" />
<i
class="white add icon"
aria-hidden="true"
/>
<span class="padding-1">Ajouter</span>
</button>
</div>
......@@ -59,10 +59,13 @@
id="form-members"
class="ui form"
>
<table class="ui red table">
<table
class="ui red table"
aria-describedby="Table des membres du projet"
>
<thead>
<tr>
<th>
<th scope="col">
Membre
<i
:class="{
......@@ -70,10 +73,11 @@
up: isSortedDesc('member'),
}"
class="icon sort"
aria-hidden="true"
@click="changeSort('member')"
/>
</th>
<th>
<th scope="col">
Niveau d'autorisation
<i
:class="{
......@@ -81,6 +85,7 @@
up: isSortedDesc('role'),
}"
class="icon sort"
aria-hidden="true"
@click="changeSort('role')"
/>
</th>
......@@ -92,7 +97,7 @@
:key="member.username"
>
<td>
{{ member.user.last_name }} {{ member.user.first_name }}<br><i>{{ member.user.username }}</i>
{{ member.user.last_name }} {{ member.user.first_name }}<br><em>{{ member.user.username }}</em>
</td>
<td>
<div class="required field online">
......@@ -108,7 +113,10 @@
data-tooltip="Retirer ce membre"
@click="removeMember(member)"
>
<i class="times icon" />
<i
class="times icon"
aria-hidden="true"
/>
</button>
</div>
</td>
......@@ -123,7 +131,10 @@
class="ui teal icon button"
@click="saveMembers"
>
<i class="white save icon" />&nbsp;Enregistrer les changements
<i
class="white save icon"
aria-hidden="true"
/>&nbsp;Enregistrer les changements
</button>
</div>
</div>
......@@ -132,9 +143,10 @@
<script>
import axios from '@/axios-client.js';
import { mapState } from 'vuex';
import { mapMutations, mapState } from 'vuex';
import Dropdown from '@/components/Dropdown.vue';
import { formatUserOption } from '@/utils';
export default {
name: 'ProjectMembers',
......@@ -177,16 +189,7 @@ export default {
userOptions: function () {
return this.projectUsers
.filter((el) => el.userLevel.value === 'logged_user')
.map((el) => {
let name = el.user.first_name || '';
if (el.user.last_name) {
name = name + ' ' + el.user.last_name;
}
return {
name: [name, el.user.username],
value: el.user.id,
};
});
.map((el) => formatUserOption(el.user)); // Format user data to fit dropdown option structure
},
levelOptions: function () {
......@@ -236,10 +239,16 @@ export default {
destroyed() {
//* allow user to change page if ever stuck on loader
this.$store.commit('DISCARD_LOADER');
this.DISCARD_LOADER();
},
methods: {
...mapMutations([
'DISPLAY_MESSAGE',
'DISPLAY_LOADER',
'DISCARD_LOADER'
]),
validateNewMember() {
this.newMember.errors = [];
if (!this.newMember.user.value) {
......@@ -289,60 +298,69 @@ export default {
});
},
/**
* Saves the updated members and their roles for a project.
* Displays a loader while the update is in progress and provides feedback upon completion or error.
*/
saveMembers() {
// Display a loader to indicate that the update process is ongoing
this.DISPLAY_LOADER('Mise à jour des membres du projet en cours ...');
// Prepare the data to be sent in the API request
const data = this.projectUsers.map((member) => {
return {
user: member.user,
level: {
display: member.userLevel.name,
codename: member.userLevel.value,
display: member.userLevel.name, // Display name of the user level
codename: member.userLevel.value, // Codename of the user level
},
};
});
// Make an API request to update the project members
axios
.put(
`${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}projects/${this.project.slug}/utilisateurs/`,
data
)
.then((response) => {
// Check if the response status is 200 (OK)
if (response.status === 200) {
this.$store.dispatch('GET_USER_LEVEL_PROJECTS'); //* update user status in top right menu
this.$store.commit('DISPLAY_MESSAGE', { comment: 'Permissions mises à jour', level: 'positive' });
// Dispatch an action to update the user status in the top right menu
this.$store.dispatch('GET_USER_LEVEL_PROJECTS');
// Display a positive message indicating success
this.DISPLAY_MESSAGE({ comment: 'Permissions mises à jour avec succès', level: 'positive' });
} else {
this.$store.commit(
'DISPLAY_MESSAGE',
{
comment : "Une erreur s'est produite pendant la mises à jour des permissions",
level: 'negative'
}
);
// Display a generic error message if the response status is not 200
this.DISPLAY_MESSAGE({
comment: "Une erreur s'est produite pendant la mises à jour des permissions",
level: 'negative'
});
}
// Hide the loader regardless of the request result
this.DISCARD_LOADER();
})
.catch((error) => {
throw error;
});
},
fetchMembers() {
// todo: move function to a service
return axios
.get(
`${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}projects/${this.$route.params.slug}/utilisateurs/`
)
.then((response) => response.data)
.catch((error) => {
throw error;
// Hide the loader if an error occurs
this.DISCARD_LOADER();
// Determine the error message to display
const errorMessage = error.response && error.response.data && error.response.data.error
? error.response.data.error
: "Une erreur s'est produite pendant la mises à jour des permissions";
// Display the error message
this.DISPLAY_MESSAGE({
comment: errorMessage,
level: 'negative'
});
// Log the error to the console for debugging
console.error(error);
});
},
populateMembers() {
this.$store.commit(
'DISPLAY_LOADER',
'Récupération des membres en cours...'
);
this.fetchMembers().then((members) => {
this.$store.commit('DISCARD_LOADER');
this.DISPLAY_LOADER('Récupération des membres en cours...');
this.$store.dispatch('projects/GET_PROJECT_USERS', this.$route.params.slug).then((members) => {
this.DISCARD_LOADER();
this.projectUsers = members.map((el) => {
return {
userLevel: { name: el.level.display, value: el.level.codename },
......
<template>
<div
id="projects"
class="page"
>
<div id="projects">
<h2 class="ui horizontal divider header">
PROJETS
</h2>
......@@ -12,8 +9,13 @@
v-if="user && user.can_create_project && isOnline"
:to="{ name: 'project_create', params: { action: 'create' } }"
class="ui green basic button"
data-test="create-project"
>
<i class="plus icon" /> Créer un nouveau projet
<i
class="plus icon"
aria-hidden="true"
/>
Créer un nouveau projet
</router-link>
<router-link
v-if="user && user.can_create_project && isOnline"
......@@ -21,25 +23,33 @@
name: 'project_type_list',
}"
class="ui blue basic button"
data-test="to-project-models"
>
<i class="copy icon" /> Accéder à la liste des modèles de projets
<i
class="copy icon"
aria-hidden="true"
/>
Accéder à la liste des modèles de projets
</router-link>
</div>
<!-- FILTRES DES PROJETS -->
<ProjectsMenu
:loading="loading"
@filter="setProjectsFilters"
@getData="getData"
@loading="setLoader"
/>
<div
v-if="configuration.DISPLAY_FORBIDDEN_PROJECTS"
id="forbidden-projects"
class="ui toggle checkbox"
class="ui toggle checkbox margin-top"
>
<input
v-model="displayForbiddenProjects"
:checked="displayForbiddenProjects"
type="checkbox"
@input="toggleForbiddenProjects"
>
<label>
N'afficher que les projets disponibles à la consultation
......@@ -50,14 +60,12 @@
<div
v-if="projects"
class="ui divided items dimmable dimmed"
data-test="project-list"
>
<div
:class="{ active: loading }"
class="ui inverted dimmer"
>
<div :class="['ui inverted dimmer', { active: loading }]">
<div class="ui loader" />
</div>
<ProjectsListItem
v-for="project in projects"
:key="project.slug"
......@@ -66,21 +74,15 @@
<span
v-if="!projects || projects.length === 0"
>Vous n'avez accès à aucun projet.</span>
<div
:class="{ active: loading }"
class="ui inverted dimmer"
>
<div class="ui loader" />
</div>
Vous n'avez accès à aucun projet.
</span>
<!-- PAGINATION -->
<Pagination
v-if="count"
:nb-pages="Math.ceil(count/10)"
:on-page-change="SET_CURRENT_PAGE"
@change-page="changePage"
:nb-pages="nbPages"
@page-update="changePage"
/>
</div>
</div>
......@@ -123,49 +125,30 @@ export default {
DJANGO_BASE_URL() {
return this.$store.state.configuration.VUE_APP_DJANGO_BASE;
},
},
watch: {
filters: {
deep: true,
handler(newValue) {
if (newValue) {
this.getData();
}
}
},
displayForbiddenProjects(newValue) {
if (newValue) {
this.SET_PROJECTS_FILTER({
filter: 'accessible',
value: 'true'
});
} else {
this.SET_PROJECTS_FILTER({
filter: 'accessible',
value: null
});
}
this.getData();
nbPages() {
return Math.ceil(this.count / 10);
}
},
created() {
this.SET_CURRENT_PAGE(1);
// Empty stored text to search
this.SET_PROJECTS_SEARCH_STATE({ text: null });
// Empty stored project list
this.$store.commit('projects/SET_PROJECT', null);
this.SET_PROJECTS_FILTER({
filter: 'accessible',
value: 'true'
});
// Init display of restricted access projects
this.displayForbiddenProjects = this.configuration.DISPLAY_FORBIDDEN_PROJECTS_DEFAULT;
this.setForbiddenProjectsFilter(true);
},
methods: {
...mapMutations('projects', [
'SET_CURRENT_PAGE',
'SET_PROJECTS_FILTER'
'SET_PROJECTS_FILTER',
'SET_PROJECTS_SEARCH_STATE',
]),
...mapActions('projects', [
'GET_PROJECTS'
'GET_PROJECTS',
]),
getData(page) {
......@@ -187,8 +170,26 @@ export default {
this.getData(e);
},
setProjectsFilters(e) {
setProjectsFilters(e, noUpdate) {
this.SET_PROJECTS_FILTER(e);
// Reset the page number at filter change
this.SET_CURRENT_PAGE(1);
// Wait that all filters are set in store to fetch data when component is created
if (!noUpdate) {
this.getData();
}
},
toggleForbiddenProjects(e) {
this.displayForbiddenProjects = e.target.checked;
this.setForbiddenProjectsFilter();
},
setForbiddenProjectsFilter(noUpdate) {
this.setProjectsFilters({
filter: 'accessible',
value: this.displayForbiddenProjects ? 'true' : null
}, noUpdate);
},
}
};
......@@ -198,6 +199,16 @@ export default {
#projects {
margin: 0 auto;
.dimmable {
.dimmer {
.loader {
top: 25%;
}
}
}
}
.flex {
......@@ -217,10 +228,10 @@ export default {
color: rgb(94, 94, 94);
}
input:checked ~ label::before {
background-color: teal !important;
background-color: var(--primary-color, #008c86) !important;
}
input:checked ~ label {
color: teal !important;
color: var(--primary-color, #008c86) !important;
}
}
......
<template>
<div
id="projects-types"
class="page"
>
<div id="projects-types">
<h3 class="ui header">
Créer un projet à partir d'un modèle disponible:
</h3>
......@@ -19,6 +16,7 @@
? require('@/assets/img/default.png')
: DJANGO_BASE_URL + project.thumbnail + refreshId()
"
alt="Image associé au projet"
>
</div>
<div class="middle aligned content">
......@@ -37,27 +35,25 @@
<strong>Projet {{ project.moderation ? '' : 'non' }} modéré</strong>
</div>
<div class="meta">
<span data-tooltip="Délai avant archivage">
{{ project.archive_feature }}&nbsp;<i class="box icon" />
</span>
<span data-tooltip="Délai avant suppression">
{{ project.archive_feature }}&nbsp;<i
class="trash alternate icon"
/>
</span>
<span data-tooltip="Date de création">
{{ project.created_on }}&nbsp;<i class="calendar icon" />
{{ project.created_on }}&nbsp;
<i
class="calendar icon"
aria-hidden="true"
/>
</span>
</div>
<div class="meta">
<span data-tooltip="Visibilité des signalement publiés">
{{ project.access_level_pub_feature }}&nbsp;<i
class="eye icon"
aria-hidden="true"
/>
</span>
<span data-tooltip="Visibilité des signalement archivés">
{{ project.access_level_arch_feature }}&nbsp;<i
class="archive icon"
aria-hidden="true"
/>
</span>
</div>
......@@ -94,13 +90,17 @@ export default {
mounted() {
projectAPI.getProjectTypes(this.API_BASE_URL)
.then((data) => {
if (data) this.project_types = data;
if (data) {
this.project_types = data;
}
});
},
methods: {
refreshId() {
return '?ver=' + Math.random();
const crypto = window.crypto || window.msCrypto;
var array = new Uint32Array(1);
return '?ver=' + crypto.getRandomValues(array); // Compliant for security-sensitive use cases
},
},
};
......
......@@ -2,6 +2,7 @@ const webpack = require('webpack');
const fs = require('fs');
const packageJson = fs.readFileSync('./package.json');
const version = JSON.parse(packageJson).version || 0;
module.exports = {
publicPath: '/geocontrib/',
devServer: {
......@@ -34,6 +35,7 @@ module.exports = {
themeColor: '#1da025'
},
configureWebpack: {
devtool: 'source-map',
plugins: [
new webpack.DefinePlugin({
'process.env': {
......@@ -42,5 +44,10 @@ module.exports = {
})
]
},
// the rest of your original module.exports code goes here
transpileDependencies: [
// Add dependencies that use modern JavaScript syntax, based on encountered errors
'ol',
'color-rgba',
'color-parse'
]
};
module.exports = {
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
},
{
test: /\.vue$/,
loader: 'vue-loader'
}
]
}
};
\ No newline at end of file