diff --git a/public/config/config.json b/public/config/config.json index 537e865a61060fc9fb1cb617d31a0b89c50d93db..4593a147766d307f0891937622a7b0ee3b46dee3 100644 --- a/public/config/config.json +++ b/public/config/config.json @@ -7,7 +7,7 @@ "VUE_APP_APPLICATION_FAVICO":"/geocontrib/img/geo2f.ico", "VUE_APP_APPLICATION_ABSTRACT":"Application de saisie d'informations géographiques contributive", "VUE_APP_LOGO_PATH":"/geocontrib/img/logo-neogeo-circle.png", - "VUE_APP_DJANGO_BASE":"http://localhost:8010", + "VUE_APP_DJANGO_BASE":"http://localhost:8010/", "VUE_APP_DJANGO_API_BASE":"http://localhost:8010/api/", "VUE_APP_RELOAD_INTERVAL": 15000, "VUE_APP_DISABLE_LOGIN_BUTTON":false, diff --git a/src/App.vue b/src/App.vue index 964cfd77ac5e2480361fb249dc59952ae2d3d255..6213eb57f3c95d6abfa19fa522fd36c8f87a9e69 100644 --- a/src/App.vue +++ b/src/App.vue @@ -118,6 +118,11 @@ </div> </div> </div> + <div class="desktop flex push-right-desktop item title"> + <span> + {{ APPLICATION_ABSTRACT }} + </span> + </div> <div class="desktop flex push-right-desktop"> <router-link v-if="user" :to="{name: 'my_account'}" class="item"> @@ -218,17 +223,24 @@ export default { computed: { ...mapState([ - "projects", "user", "USER_LEVEL_PROJECTS", "configuration", "messages", "loader", ]), - ...mapGetters(["project"]), + ...mapState('projects', [ + 'projects' + ]), + ...mapGetters( 'projects', [ + 'project' + ]), APPLICATION_NAME() { return this.configuration.VUE_APP_APPLICATION_NAME; }, + APPLICATION_ABSTRACT() { + return this.$store.state.configuration.VUE_APP_APPLICATION_ABSTRACT; + }, DISABLE_LOGIN_BUTTON() { return this.configuration.VUE_APP_DISABLE_LOGIN_BUTTON; }, @@ -384,6 +396,28 @@ footer { transition: none !important; } +.item.title::before { + background: none !important; +} + + +.bounce-enter-active { + animation: bounce-in .5s; +} +.bounce-leave-active { + animation: bounce-in .5s reverse; +} +@keyframes bounce-in { + 0% { + transform: scale(0); + } + 50% { + transform: scale(1.5); + } + 100% { + transform: scale(1); + } +} .ui.grid > .row.over-content { position: absolute; z-index: 99; diff --git a/src/assets/styles/base.css b/src/assets/styles/base.css index 6f7a4e7c4823bf725373cdbbab5450879bdf84ed..54967e93dfa00977620d28a5f91802b0bee582ec 100644 --- a/src/assets/styles/base.css +++ b/src/assets/styles/base.css @@ -168,7 +168,57 @@ footer .ui.text.menu .item:not(:first-child) { } /* ---------------------------------- */ - /* ERROR LIST */ + /* PAGINATION */ +/* ---------------------------------- */ + +.custom-pagination { + display: flex; + align-items: center; + list-style: none; + font-size: 1.3em; +} + +.custom-pagination > .page-item > .page-link { + border: none; + font-weight: 400; + color: #008080; +} +.custom-pagination > .page-item.active > .page-link { + color: #008080; + background-color: transparent; + font-weight: bolder; + text-shadow: 0 0 2px #008080; + padding: 0.325em 0.75em; + pointer-events: none; +} +.custom-pagination > .page-item.disabled > .page-link { + opacity: 0.5; + pointer-events: none; +} + +.custom-pagination > div > .page-item > .page-link { + border: none; + font-weight: 400; + color: #008080; + padding: 0.325em 0.75em; +} +.custom-pagination > div > .page-item.active > .page-link { + color: #008080; + background-color: transparent; + font-weight: bolder; + font-size: 1.2em; + text-shadow: 0 0 2px #008080; + padding: 0.325em 0.75em; + pointer-events: none; +} +.custom-pagination > div > .page-item.disabled > .page-link { + opacity: 0.5; + padding: 0.325em 0.75em; + pointer-events: none; +} + +/* ---------------------------------- */ + /* MULTISELECT */ /* ---------------------------------- */ .multiselect__tags { border: 2px solid #ced4da; @@ -209,4 +259,15 @@ footer .ui.text.menu .item:not(:first-child) { background-color: #fff; opacity: 1; top: 2px; +} + +#search-projects > .multiselect > .multiselect__tags { + border-radius: 0 !important; +} + +.menu.projects > .item > .multiselect { + min-height: 0px !important; +} +.menu.projects > .item > .multiselect > .multiselect__tags { + min-height: 0px !important; } \ No newline at end of file diff --git a/src/components/Pagination.vue b/src/components/Pagination.vue new file mode 100644 index 0000000000000000000000000000000000000000..dfee522be8eeed3dd10e7e80bd988f12bf79ca0f --- /dev/null +++ b/src/components/Pagination.vue @@ -0,0 +1,137 @@ +<template> + <div style="display: flex;"> + <nav style="margin: 0 auto;"> + <ul class="custom-pagination"> + <li + class="page-item" + :class="{ disabled: page === 1 }" + > + <a + class="page-link" + href="#" + @click="page -= 1" + > + <i class="ui icon big angle left" /> + </a> + </li> + <div v-if="nbPages > 5" style="display: contents;"> + <li + v-for="index in pagination(page, nbPages)" + :key="index" + class="page-item" + :class="{ active: page === index }" + > + <a + class="page-link" + href="#" + @click="changePage(index)" + > + {{ index }} + </a> + </li> + </div> + <div v-else style="display: contents;"> + <li + v-for="index in nbPages" + :key="index" + class="page-item" + :class="{ active: page === index }" + > + <a + class="page-link" + href="#" + @click="page = index" + > + {{ index }} + </a> + </li> + </div> + <li + class="page-item" + :class="{ disabled: page === nbPages }" + > + <a + class="page-link" + href="#" + @click="page += 1" + > + <i class="ui icon big angle right" /> + </a> + </li> + </ul> + </nav> + </div> +</template> + +<script> +export default { + name: 'Pagination', + + props: { + nbPages: { + type: Number, + default: 1 + }, + + onPageChange: { + type: Function, + default: () => { + return () => 1; + } + } + }, + + data() { + return { + page: 1 + } + }, + + watch: { + page: function(newValue, oldValue) { + if (newValue !== oldValue) { + this.onPageChange(newValue); + this.$emit('change-page', newValue); + } + } + }, + + methods: { + pagination(c, m) { + const current = c, + last = m, + delta = 2, + left = current - delta, + right = current + delta + 1, + range = [], + rangeWithDots = []; + let l; + + for (let i = 1; i <= last; i++) { + if (i === 1 || i === last || i >= left && i < right) { + range.push(i); + } + } + for (const i of range) { + if (l) { + if (i - l === 2) { + rangeWithDots.push(l + 1); + } else if (i - l !== 1) { + rangeWithDots.push('...'); + } + } + rangeWithDots.push(i); + l = i; + } + + return rangeWithDots; + }, + + changePage(num) { + if (typeof num === 'number') { + this.page = num; + } + } + } +} +</script> diff --git a/src/components/Projects/DropdownMenuItem.vue b/src/components/Projects/DropdownMenuItem.vue new file mode 100644 index 0000000000000000000000000000000000000000..ae0ebdb63ad019c1cee232e43df0c10b6de8c580 --- /dev/null +++ b/src/components/Projects/DropdownMenuItem.vue @@ -0,0 +1,89 @@ +<template> + <Multiselect + v-model="selection" + :options="options" + :allow-empty="true" + track-by="label" + label="label" + :reset-after="false" + select-label="" + selected-label="" + deselect-label="" + :searchable="false" + :placeholder="placeholder" + :clear-on-select="false" + :preserve-search="true" + @select="select" + @close="close" + > + <!-- <template slot="clear"> + <div + v-if="selection" + class="multiselect__clear" + @click.prevent.stop="selection = options[0]" + > + <i class="close icon"></i> + </div> + </template> --> + </Multiselect> +</template> + +<script> +import Multiselect from 'vue-multiselect'; + +export default { + name: 'DropdownMenuItem', + + components: { + Multiselect + }, + + props: { + placeholder: { + type: String, + default: 'Sélectionnez une valeur' + }, + options: { + type: Array, + default: () => { + return []; + } + } + }, + + data() { + return { + selection: null, + } + }, + + watch: { + selection: { + deep: true, + handler(newValue) { + if (!newValue) { + this.selection = this.options[0]; + this.$emit('filter', this.selection); + } + } + } + }, + + created() { + this.selection = this.options[0]; + }, + + methods: { + select(e) { + this.$emit('filter', e); + }, + close() { + this.$emit('close', this.selection); + } + } +} +</script> + +<style lang="less" scoped> + +</style> diff --git a/src/components/Projects/ProjectsMenu.vue b/src/components/Projects/ProjectsMenu.vue new file mode 100644 index 0000000000000000000000000000000000000000..8eea8a9a7fb4354dd571bbea1ac0a9ce636c3b42 --- /dev/null +++ b/src/components/Projects/ProjectsMenu.vue @@ -0,0 +1,155 @@ +<template> + <div class="ui menu projects"> + <div class="item"> + <label> + Modération + </label> + <DropdownMenuItem + :options="moderationOptions" + v-on="$listeners" + /> + </div> + <div class="item"> + <label> + Niveau d'autorisation requis + </label> + <DropdownMenuItem + :options="accessLevelOptions" + v-on="$listeners" + /> + </div> + <div class="item"> + <label> + Mon niveau d'autorisation + </label> + <DropdownMenuItem + :options="userAccessLevelOptions" + v-on="$listeners" + /> + </div> + <div class="right item"> + <search-projects + :search-function="SEARCH_PROJECTS" + /> + </div> + </div> +</template> + +<script> +import { mapActions } from 'vuex'; + +import DropdownMenuItem from '@/components/Projects/DropdownMenuItem.vue'; +import SearchProjects from '@/components/Projects/SearchProjects.vue'; + +export default { + name: 'ProjectsMenu', + + components: { + DropdownMenuItem, + SearchProjects, + }, + + data() { + return { + moderationOptions: [ + { + label: 'Tous', + filter: 'moderation', + value: null + }, + { + label: 'Projet modéré', + filter: 'moderation', + value: 'true' + }, + { + label: 'Projet non modéré', + filter: 'moderation', + value: 'false' + }, + ], + accessLevelOptions: [ + { + label: 'Tous', + filter: 'access_level', + value: null + }, + { + label: 'Utilisateur connecté', + filter: 'access_level', + value: 'logged_user' + }, + { + label: 'Contributeur', + filter: 'access_level', + value: 'contributor' + }, + { + label: 'Modérateur', + filter: 'access_level', + value: 'moderator' + }, + { + label: 'Administrateur projet', + filter: 'access_level', + value: 'admin' + }, + ], + userAccessLevelOptions: [ + { + label: 'Tous', + filter: 'user_access_level', + value: null + }, + { + label: 'Utilisateur connecté', + filter: 'user_access_level', + value: '1' + }, + { + label: 'Contributeur', + filter: 'user_access_level', + value: '2' + }, + { + label: 'Modérateur', + filter: 'user_access_level', + value: '3' + }, + { + label: 'Administrateur projet', + filter: 'user_access_level', + value: '4' + }, + ] + } + }, + + methods: { + ...mapActions('projects', [ + 'SEARCH_PROJECTS' + ]) + } +} +</script> + +<style lang="less" scoped> +.projects { + .item { + display: flex; + flex-direction: column; + align-items: flex-start !important; + + padding: 0.4em 0.6em; + + label { + margin-bottom: 0.2em; + font-size: 0.9em; + font-weight: 600; + } + } + .item { + width: 25%; + } +} +</style> diff --git a/src/components/Projects/SearchProjects.vue b/src/components/Projects/SearchProjects.vue new file mode 100644 index 0000000000000000000000000000000000000000..3fa8e94b4188ed80475c7d7c97628a87784d5b49 --- /dev/null +++ b/src/components/Projects/SearchProjects.vue @@ -0,0 +1,67 @@ +<template> + <div id="search-projects"> + <input + v-model="text" + type="search" + placeholder="Rechercher..." + > + </div> +</template> + +<script> +import _ from 'lodash'; +import { mapMutations } from 'vuex'; + +export default { + name: 'SearchProjects', + + props: { + searchFunction: { + type: Function, + default: () => { return {} } + } + }, + + data() { + return { + text: null + } + }, + + watch: { + text: _.debounce(function(newValue) { + this.$emit('loading', true); + this.SET_CURRENT_PAGE(1); + this.searchFunction(newValue) + .then(() => { + this.$emit('loading', false); + }); + }, 100) + }, + + methods: { + ...mapMutations('projects', [ + 'SET_CURRENT_PAGE' + ]) + } +} +</script> + +<style lang="less" scoped> +#search-projects { + height: 100%; + display: flex; + flex-direction: column; + justify-content: flex-end; + input { + width: 100%; + height: 72%; + text-align: left; + color: #35495e; + padding: 8px 40px 0 8px; + border-radius: 5px; + border: 1px solid #e8e8e8; + font-size: 14px; + } +} +</style> \ No newline at end of file diff --git a/src/main.js b/src/main.js index 3ff59325ded1f4dff75020ca8abb256e0edcdd0b..864c9a69f193ed2fb775913c379530f3fa719fe7 100644 --- a/src/main.js +++ b/src/main.js @@ -3,8 +3,8 @@ import Vue from 'vue' import App from './App.vue' import './registerServiceWorker' -import router from './router' -import store from './store' +import router from '@/router' +import store from '@/store' import 'leaflet/dist/leaflet.css'; import 'leaflet-draw/dist/leaflet.draw.css'; import '@/assets/resources/leaflet-control-geocoder-1.13.0/Control.Geocoder.css'; @@ -48,8 +48,9 @@ let onConfigLoaded = function(config){ document.head.appendChild(link); window.proxy_url=config.VUE_APP_DJANGO_API_BASE+"proxy/"; - axios.all([store.dispatch("USER_INFO"), - store.dispatch("GET_ALL_PROJECTS"), + axios.all([ + store.dispatch("USER_INFO"), + store.dispatch('projects/GET_ALL_PROJECTS'), store.dispatch("GET_STATIC_PAGES"), store.dispatch("GET_USER_LEVEL_PROJECTS"), store.dispatch("map/GET_AVAILABLE_LAYERS"), diff --git a/src/router/index.js b/src/router/index.js index f335b9e01be58a26d80966e245a9a7895ee048c7..9aaed61eb1a304565beac70c149c901afb72e30c 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -1,6 +1,6 @@ import Vue from 'vue' import VueRouter from 'vue-router' -import Index from '../views/Index.vue' +import Projects from '../views/Projects.vue' Vue.use(VueRouter) @@ -13,7 +13,7 @@ const routes = [ { path: '/', name: 'index', - component: Index + component: Projects }, { path: '/connexion/', diff --git a/src/services/project-api.js b/src/services/project-api.js index 6c9e7a83746313983b4feb658012b2ea4a985d3f..93c19e017d8ceb9bc5fcd7a4537dfa9274e36419 100644 --- a/src/services/project-api.js +++ b/src/services/project-api.js @@ -1,13 +1,9 @@ import axios from '@/axios-client.js'; -import store from '../store' - - -const baseUrl = store.state.configuration.VUE_APP_DJANGO_API_BASE; - const projectAPI = { - async getProjectSubscription({ projectSlug }) { + + async getProjectSubscription({ baseUrl, projectSlug }) { const response = await axios.get( `${baseUrl}projects/${projectSlug}/subscription/` ); @@ -21,7 +17,7 @@ const projectAPI = { } }, - async subscribeProject({ projectSlug, suscribe }) { + async subscribeProject({ baseUrl, projectSlug, suscribe }) { const response = await axios.put( `${baseUrl}projects/${projectSlug}/subscription/`, { is_suscriber: suscribe } @@ -36,6 +32,30 @@ const projectAPI = { } }, + async getProjects(baseUrl, filters, page) { + try { + const url = `${baseUrl}projects/?page=${page}`; + + let filteredUrl; + if (Object.values(filters).some(el => el && el.length > 0)) { + filteredUrl = url; + for (const filter in filters) { + if (filters[filter]) { + filteredUrl = filteredUrl.concat('', `&${filter}=${filters[filter]}`); + } + } + } + + const response = await axios.get(filteredUrl ? filteredUrl : url); + if (response.status === 200 && response.data) { + return response.data; + } + } catch (error) { + console.error(error); + throw error; + } + }, + async deleteProject(projectSlug) { const response = await axios.delete( `${baseUrl}projects/${projectSlug}` diff --git a/src/store/index.js b/src/store/index.js index a6c2008102fb6e5ace554cbc6339f6303893e276..661b004bc6774f414a06c74c719c65255f7985f5 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -2,9 +2,7 @@ import axios from '@/axios-client.js'; import Vue from 'vue'; import Vuex from 'vuex'; import router from '../router' -import feature_type from "./modules/feature_type" -import feature from "./modules/feature" -import map from "./modules/map" +import modules from './modules'; Vue.use(Vuex); @@ -24,17 +22,12 @@ const noPermissions = { }; export default new Vuex.Store({ - modules: { - feature_type, - feature, - map - }, + modules, + state: { logged: false, user: false, configuration: null, - project_slug: null, - projects: [], last_comments: [], staticPages: null, USER_LEVEL_PROJECTS: null, @@ -51,15 +44,6 @@ export default new Vuex.Store({ }, mutations: { - SET_PROJECTS(state, projects) { - state.projects = projects; - }, - ADD_PROJECT(state, project) { - state.projects = [project, ...state.projects]; - }, - SET_PROJECT_SLUG(state, slug) { - state.project_slug = slug; - }, SET_USER(state, payload) { state.user = payload; }, @@ -133,31 +117,10 @@ export default new Vuex.Store({ }, getters: { - project: state => state.projects.find((project) => project.slug === state.project_slug), - permissions: state => state.user_permissions ? state.user_permissions[state.project_slug] : noPermissions, - // TODO: utiliser et créer point api depuis ProjectTypeListView lors du passage à la pagination des projets - project_types: state => state.projects.filter(projet => projet.is_project_type), - project_user: state => state.projects.filter(projet => projet.creator === state.user.id), + permissions: state => state.user_permissions ? state.user_permissions[state.projects.project_slug] : noPermissions, }, actions: { - GET_ALL_PROJECTS({ commit }) { - function parseDate(date) { - let dateArr = date.split("/").reverse(); - return new Date(dateArr[0], dateArr[1] - 1, dateArr[2]); - } - return axios - .get(`${this.state.configuration.VUE_APP_DJANGO_API_BASE}projects/`) - .then((response) => { - if (response.status === 200 && response.data) { - const orderedProjects = response.data.sort((a, b) => parseDate(b.created_on) - parseDate(a.created_on)); - commit("SET_PROJECTS", orderedProjects); - } - }) - .catch((error) => { - throw error; - }); - }, GET_STATIC_PAGES({ commit }) { return axios .get(`${this.state.configuration.VUE_APP_DJANGO_API_BASE}flat-pages/`) @@ -301,7 +264,7 @@ export default new Vuex.Store({ }, async GET_PROJECT_INFO({ state, commit, dispatch }, slug) { - commit("SET_PROJECT_SLUG", slug); + commit('projects/SET_PROJECT_SLUG', slug, { root: true }); let promises = [ dispatch("GET_PROJECT_LAST_MESSAGES", slug).then(response => response), dispatch("feature_type/GET_PROJECT_FEATURE_TYPES", slug).then(response => response), diff --git a/src/store/modules/feature.js b/src/store/modules/feature.store.js similarity index 99% rename from src/store/modules/feature.js rename to src/store/modules/feature.store.js index 436e57097da5f67c7cb72d13e3aa6f1436305790..0ae63837993517da7a8e93e8a3ff574983956226 100644 --- a/src/store/modules/feature.js +++ b/src/store/modules/feature.store.js @@ -190,7 +190,7 @@ const feature = { message: routeName === "editer-signalement" ? "Le signalement a été mis à jour" : "Le signalement a été crée" }, }); - dispatch("GET_ALL_PROJECTS", null, {root:true}) //* & refresh project list + dispatch('projects/GET_ALL_PROJECTS', null, { root:true }) //* & refresh project list }); } diff --git a/src/store/modules/feature_type.js b/src/store/modules/feature_type.store.js similarity index 100% rename from src/store/modules/feature_type.js rename to src/store/modules/feature_type.store.js diff --git a/src/store/modules/index.js b/src/store/modules/index.js new file mode 100644 index 0000000000000000000000000000000000000000..d6e07fc7dbab29baf8d622222c06f2b98896fe1c --- /dev/null +++ b/src/store/modules/index.js @@ -0,0 +1,15 @@ +/** + * Automatically imports all the modules and exports as a single module object +**/ +const requireModule = require.context('.', false, /\.store\.js$/); +const modules = {}; + +requireModule.keys().forEach(filename => { + // create the module name from fileName + // remove the store.js extension + const moduleName = filename.replace(/(\.\/|\.store\.js)/g, ''); + + modules[moduleName] = requireModule(filename).default || requireModule(filename); +}); + +export default modules; \ No newline at end of file diff --git a/src/store/modules/map.js b/src/store/modules/map.store.js similarity index 100% rename from src/store/modules/map.js rename to src/store/modules/map.store.js diff --git a/src/store/modules/projects.store.js b/src/store/modules/projects.store.js new file mode 100644 index 0000000000000000000000000000000000000000..194a9973632709f974d637f6aa31a965ee6291a8 --- /dev/null +++ b/src/store/modules/projects.store.js @@ -0,0 +1,139 @@ +import axios from '@/axios-client.js'; +import projectAPI from '@/services/project-api'; + +const projects = { + + namespaced: true, + + state: { + currentPage: 1, + projects: [], + count: 0, + project_slug: null, + filters: { + moderation: null, + access_level: null, + user_access_level: null + }, + searchProjectsFilter: null, + isProjectsListSearched: null, + }, + + getters: { + project: state => state.projects.find((project) => project.slug === state.project_slug), + project_types: state => state.projects.filter(projet => projet.is_project_type), + project_user: state => state.projects.filter(projet => projet.creator === state.user.id), + }, + + mutations: { + SET_CURRENT_PAGE (state, payload) { + state.currentPage = payload; + }, + + SET_PROJECTS(state, projects) { + state.projects = projects.results; + state.count = projects.count; + }, + + ADD_PROJECT(state, project) { + state.projects = [project, ...state.projects]; + }, + + SET_PROJECT_SLUG(state, slug) { + state.project_slug = slug; + }, + + SET_PROJECTS_FILTER(state, payload) { + state.filters[payload.filter] = payload.value; + }, + + SET_PROJECTS_SEARCH_STATE(state, payload) { + state.isProjectsListSearched = payload.isSearched; + state.searchProjectsFilter = payload.text; + }, + }, + + actions: { + async GET_ALL_PROJECTS({ rootState, commit }) { + try { + const response = await axios + .get(`${rootState.configuration.VUE_APP_DJANGO_API_BASE}projects/`); + if (response.status === 200 && response.data) { + // const orderedProjects = response.data.sort((a, b) => parseDate(b.created_on) - parseDate(a.created_on)); + commit('SET_PROJECTS', response.data); + } + } catch (error) { + console.error(error); + throw error; + } + }, + + async GET_PROJECTS({ state, rootState, commit }, page) { + if (!page) { + page = state.currentPage; + } + const baseUrl = rootState.configuration.VUE_APP_DJANGO_API_BASE; + const projects = await projectAPI.getProjects(baseUrl, state.filters, page); + commit('SET_PROJECTS', projects); + }, + + async SEARCH_PROJECTS({ commit, dispatch }, text) { + if (text) { + await dispatch('HANDLE_PROJECTS_SEARCH_REQUEST', text); + } else { + commit('SET_PROJECTS_SEARCH_STATE', { + isSearched: false, + text: null + }); + await dispatch('GET_PROJECTS'); + } + }, + + async HANDLE_PROJECTS_SEARCH_REQUEST({ state, rootState, commit }, text) { + + if (rootState.cancellableSearchRequest.length > 0) { + const currentRequestCancelToken = + rootState.cancellableSearchRequest[rootState.cancellableSearchRequest.length - 1]; + currentRequestCancelToken.cancel(); + } + + const cancelToken = axios.CancelToken.source(); + commit('SET_CANCELLABLE_SEARCH_REQUEST', cancelToken, { root: true }); + + const url = `${rootState.configuration.VUE_APP_DJANGO_API_BASE}projects/?search=${text}`; + let filteredUrl; + if (Object.values(state.filters).some(el => el && el.length > 0)) { + filteredUrl = url; + for (const filter in state.filters) { + if (state.filters[filter]) { + filteredUrl = filteredUrl.concat('', `&${filter}=${state.filters[filter]}`); + } + } + } + + try { + const response = await axios.get( + filteredUrl ? filteredUrl : url, + { + cancelToken: cancelToken.token, + } + ); + if (response.status === 200) { + const projects = response.data; + if (projects) { + commit('SET_PROJECTS', projects); + commit('SET_PROJECTS_SEARCH_STATE', { + isSearched: true, + text: text + }); + } + } + } catch(err) { + console.error(err); + } + }, + } + +}; + +export default projects; diff --git a/src/utils/index.js b/src/utils/index.js new file mode 100644 index 0000000000000000000000000000000000000000..f4e38a208239a1dea567de3f9059854fcbd7048e --- /dev/null +++ b/src/utils/index.js @@ -0,0 +1,4 @@ +export function parseDate(date) { + let dateArr = date.split("/").reverse(); + return new Date(dateArr[0], dateArr[1] - 1, dateArr[2]); +} \ No newline at end of file diff --git a/src/views/My_account.vue b/src/views/My_account.vue index 162eedc20f53ad512982c19a8ff700730f82791a..857eaf99b04122f5f4083e0d32d661b59e634c9d 100644 --- a/src/views/My_account.vue +++ b/src/views/My_account.vue @@ -291,10 +291,12 @@ export default { // todo : filter projects to user ...mapState([ "user", - "projects", "USER_LEVEL_PROJECTS", "user_permissions", ]), + ...mapState('projects', [ + 'projects' + ]), DJANGO_BASE_URL: function () { return this.$store.state.configuration.VUE_APP_DJANGO_BASE; }, diff --git a/src/views/Index.vue b/src/views/Projects.vue similarity index 73% rename from src/views/Index.vue rename to src/views/Projects.vue index 96f4a9b021ac0c93a4ba879913061afbbf09986c..c55e238de208a6b2542d04a5bff17f8476261506 100644 --- a/src/views/Index.vue +++ b/src/views/Projects.vue @@ -1,15 +1,10 @@ <template> <div class="fourteen wide column"> - <img class="ui centered small image" :src="logo" /> - <!-- :src="LOGO_PATH" --> - <h2 class="ui center aligned icon header"> - <div class="content"> - {{ APPLICATION_NAME }} - <div class="sub header">{{ APPLICATION_ABSTRACT }}</div> - </div> + + <h2 class="ui horizontal divider header"> + PROJETS </h2> - <h4 id="les_projets" class="ui horizontal divider header">PROJETS</h4> <div class="flex"> <router-link v-if="user && user.can_create_project && isOffline() != true" @@ -29,6 +24,12 @@ </router-link> </div> + <!-- FILTRES DES PROJETS --> + <projects-menu + @filter="setProjectsFilters" + /> + + <!-- LISTE DES PROJETS --> <div v-if="projects" class="ui divided items"> <div v-for="project in projects" class="item" :key="project.slug"> <div class="ui tiny image"> @@ -99,17 +100,41 @@ <div class="item"></div> </div> + + <!-- PAGINATION --> + <pagination + :nbPages="Math.ceil(count/10)" + :on-page-change="SET_CURRENT_PAGE" + @change-page="changePage" + /> + </div> </template> <script> -import { mapState } from "vuex"; +import { mapState, mapMutations, mapActions } from 'vuex'; + +import ProjectsMenu from '@/components/Projects/ProjectsMenu.vue'; +import Pagination from '@/components/Pagination.vue'; export default { - name: "Index", + name: 'Projects', + + components: { + ProjectsMenu, + Pagination + }, computed: { - ...mapState(["projects", "user", "USER_LEVEL_PROJECTS"]), + ...mapState([ + 'user', + 'USER_LEVEL_PROJECTS' + ]), + ...mapState('projects', [ + 'projects', + 'count', + 'filters' + ]), APPLICATION_NAME() { return this.$store.state.configuration.VUE_APP_APPLICATION_NAME; }, @@ -124,7 +149,26 @@ export default { }, }, + watch: { + filters: { + deep: true, + handler(newValue) { + if (newValue) { + this.getData(); + } + } + } + }, + methods: { + ...mapMutations('projects', [ + 'SET_CURRENT_PAGE', + 'SET_PROJECTS_FILTER' + ]), + ...mapActions('projects', [ + 'GET_PROJECTS' + ]), + isOffline() { return navigator.onLine == false; }, @@ -132,6 +176,25 @@ export default { //* change path of thumbnail to update image return "?ver=" + Math.random(); }, + + getData(page) { + this.loading = true; + this.GET_PROJECTS(page) + .then(() => { + this.loading = false; + }) + .catch(() => { + this.loading = false; + }); + }, + + changePage(e) { + this.getData(e); + }, + + setProjectsFilters(e) { + this.SET_PROJECTS_FILTER(e); + } }, created() { diff --git a/src/views/project/Project_detail.vue b/src/views/project/Project_detail.vue index a0cd446abded78b8bc41a6cc956d65f3ee7fa81b..52a6604639da7a6468a906ba2079452451a08cd7 100644 --- a/src/views/project/Project_detail.vue +++ b/src/views/project/Project_detail.vue @@ -745,8 +745,10 @@ export default { computed: { ...mapGetters([ - 'project', - 'permissions', + 'permissions' + ]), + ...mapGetters('projects', [ + 'project' ]), ...mapState('feature_type', [ 'feature_types', @@ -811,8 +813,11 @@ export default { if (this.user) { projectAPI - .getProjectSubscription({ projectSlug: this.$route.params.slug }) - .then((data) => (this.is_suscriber = data.is_suscriber)); + .getProjectSubscription({ + baseUrl: this.$store.state.configuration.VUE_APP_DJANGO_API_BASE, + projectSlug: this.$route.params.slug + }) + .then((data) => (this.is_suscriber = data.is_suscriber)); } this.$store.commit("feature/SET_FEATURES", []); //* empty features remaining in case they were in geojson format and will be fetch after map initialization anyway this.$store.commit("feature_type/SET_FEATURE_TYPES", []); //* empty feature_types remaining from previous project @@ -1029,6 +1034,7 @@ export default { subscribeProject() { projectAPI .subscribeProject({ + baseUrl: this.$store.state.configuration.VUE_APP_DJANGO_API_BASE, suscribe: !this.is_suscriber, projectSlug: this.$route.params.slug, }) diff --git a/src/views/project/Project_edit.vue b/src/views/project/Project_edit.vue index 715b5c8cbe2492ca7e17d8a1c745c2d631221038..f8ad6836291f32b91d8060c5b17d6c57ab0218d4 100644 --- a/src/views/project/Project_edit.vue +++ b/src/views/project/Project_edit.vue @@ -207,7 +207,7 @@ import axios from '@/axios-client.js'; import Dropdown from "@/components/Dropdown.vue"; -import { mapState, mapGetters } from "vuex"; +import { mapState, mapGetters, mapActions } from "vuex"; export default { name: "Project_edit", @@ -302,6 +302,9 @@ export default { }, methods: { + ...mapActions('projects', [ + 'GET_ALL_PROJECTS' + ]), definePageType() { if (this.$router.history.current.name === "project_create") { this.action = "create"; @@ -381,7 +384,7 @@ export default { Promise.all([ this.$store.dispatch("GET_USER_LEVEL_PROJECTS"), //* refresh projects user levels this.$store.dispatch("GET_USER_LEVEL_PERMISSIONS"), //* refresh projects permissions - this.$store.dispatch("GET_ALL_PROJECTS"), //* & refresh project list + this.GET_ALL_PROJECTS(), //* & refresh project list ]).then(() => // * go back to project list this.$router.push({