diff --git a/assets/css/style.scss b/assets/css/style.scss index a21027513b13f7b3a1ddfb51d26251b5f22ef7cc..011bcc26ff467027b50a9ce9b3facd22155a3cf6 100644 --- a/assets/css/style.scss +++ b/assets/css/style.scss @@ -37,6 +37,7 @@ $close-font-weight: 500; @import 'font'; @import 'bootstrap'; +@import '~bootstrap-vue/src/index.scss'; .card-subtitle { font-style: italic; diff --git a/assets/img/loader.gif b/assets/img/loader.gif new file mode 100644 index 0000000000000000000000000000000000000000..dd79535d677e66874b33fa0ee21f4f232c25838f Binary files /dev/null and b/assets/img/loader.gif differ diff --git a/components/navigation/Loader.vue b/components/navigation/Loader.vue index d04b8f2bd253040f8fc946831ef98fd4dc3a2037..252defdf1b334701f728ba8530f2b7aaa451d9ea 100644 --- a/components/navigation/Loader.vue +++ b/components/navigation/Loader.vue @@ -1,5 +1,5 @@ <template> - <div class="spinner-border text-primary mx-auto" role="status" v-if="isLoading"> + <div class="loader mx-auto" role="status" v-if="isLoading"> <span class="sr-only">Chargement...</span> </div> </template> @@ -8,4 +8,12 @@ export default { props: {isLoading: Boolean} } -</script> \ No newline at end of file +</script> + +<style lang="scss" scoped> +.loader { + background-image: url('~@/assets/img/loader.gif'); + height: 300px; + width: 400px; +} +</style> \ No newline at end of file diff --git a/layouts/default.vue b/layouts/default.vue index 906f4fcebd7180997b47a68bd60a8e116cc5481c..cbc088c1ccb72eb570953458e6fb52e012c86eb4 100644 --- a/layouts/default.vue +++ b/layouts/default.vue @@ -10,12 +10,15 @@ export default { data() { return { breadcrumb: [], - menus : [ + menus : [{ + title: 'Prévisions', + items : [ + {path: '/previsions',title: 'En préparation'}, + {path: '/previsions/newcomers',title: 'Futurs membres'} + ]}, { title: 'Toile de confiance', items : [ - {path: '/previsions',title: 'Prévisions'}, - {path: '/inout',title: 'Entrées et sorties'}, {path: '/membres',title: 'Membres'} ]}, { diff --git a/nuxt.config.js b/nuxt.config.js index ec819ebf619d188d22ce15888319dc52199bbed9..d99a638ac6a5618579768a4874df6e37fd1944b7 100644 --- a/nuxt.config.js +++ b/nuxt.config.js @@ -43,6 +43,7 @@ export default { modules: [ // https://github.com/nuxt-community/apollo-module '@nuxtjs/apollo', + 'bootstrap-vue/nuxt' ], // PWA module configuration: https://go.nuxtjs.dev/pwa @@ -58,8 +59,13 @@ export default { }, }, + bootstrapVue: { + css: false, + bvCSS: false + }, + router: { - linkActiveClass: 'active' + linkExactActiveClass: 'active' }, // Build Configuration: https://go.nuxtjs.dev/config-build diff --git a/package-lock.json b/package-lock.json index 1b527d91ed73f223dafdbd69f18e543954e024b8..4e6840756e13844bf6a727c521f9869bb8e5bef2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,13 +10,15 @@ "dependencies": { "@nuxtjs/apollo": "^4.0.1-rc.5", "@nuxtjs/pwa": "^3.3.5", - "bootstrap": "^4.6.0", + "bootstrap": "^4.6.1", + "bootstrap-vue": "^2.21.2", "chart.js": "^3.6.2", "core-js": "^3.15.1", "dayjs": "^1.10.7", "graphql-tag": "^2.12.6", "nuxt": "^2.15.8", - "nuxt-purgecss": "^1.0.0" + "nuxt-purgecss": "^1.0.0", + "vue": "^2.6.14" }, "devDependencies": { "sass": "^1.45.0", @@ -7627,6 +7629,19 @@ "popper.js": "^1.16.1" } }, + "node_modules/bootstrap-vue": { + "version": "2.21.2", + "resolved": "https://registry.npmjs.org/bootstrap-vue/-/bootstrap-vue-2.21.2.tgz", + "integrity": "sha512-0Exe+4MZysqhZNXIKf4TzkvXaupxh9EHsoCRez0o5Dc0J7rlafayOEwql63qXv74CgZO8E4U8ugRNJko1vMvNw==", + "hasInstallScript": true, + "dependencies": { + "@nuxt/opencollective": "^0.3.2", + "bootstrap": ">=4.5.3 <5.0.0", + "popper.js": "^1.16.1", + "portal-vue": "^2.1.7", + "vue-functional-data-merge": "^3.1.0" + } + }, "node_modules/boxen": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", @@ -13095,6 +13110,12 @@ "jiti": "bin/jiti.js" } }, + "node_modules/jquery": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz", + "integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==", + "peer": true + }, "node_modules/js-message": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/js-message/-/js-message-1.0.7.tgz", @@ -15783,6 +15804,24 @@ "node": ">=6" } }, + "node_modules/popper.js": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", + "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", + "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/portal-vue": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/portal-vue/-/portal-vue-2.1.7.tgz", + "integrity": "sha512-+yCno2oB3xA7irTt0EU5Ezw22L2J51uKAacE/6hMPMoO/mx3h4rXFkkBkT4GFsMDv/vEe8TNKC3ujJJ0PTwb6g==", + "peerDependencies": { + "vue": "^2.5.18" + } + }, "node_modules/posix-character-classes": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", @@ -20534,6 +20573,11 @@ "resolved": "https://registry.npmjs.org/vue-client-only/-/vue-client-only-2.1.0.tgz", "integrity": "sha512-vKl1skEKn8EK9f8P2ZzhRnuaRHLHrlt1sbRmazlvsx6EiC3A8oWF8YCBrMJzoN+W3OnElwIGbVjsx6/xelY1AA==" }, + "node_modules/vue-functional-data-merge": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vue-functional-data-merge/-/vue-functional-data-merge-3.1.0.tgz", + "integrity": "sha512-leT4kdJVQyeZNY1kmnS1xiUlQ9z1B/kdBFCILIjYYQDqZgLqCLa0UhjSSeRX6c3mUe6U5qYeM8LrEqkHJ1B4LA==" + }, "node_modules/vue-hot-reload-api": { "version": "2.3.4", "resolved": "https://registry.npmjs.org/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz", @@ -27632,6 +27676,18 @@ "integrity": "sha512-0dj+VgI9Ecom+rvvpNZ4MUZJz8dcX7WCX+eTID9+/8HgOkv3dsRzi8BGeZJCQU6flWQVYxwTQnEZFrmJSEO7og==", "requires": {} }, + "bootstrap-vue": { + "version": "2.21.2", + "resolved": "https://registry.npmjs.org/bootstrap-vue/-/bootstrap-vue-2.21.2.tgz", + "integrity": "sha512-0Exe+4MZysqhZNXIKf4TzkvXaupxh9EHsoCRez0o5Dc0J7rlafayOEwql63qXv74CgZO8E4U8ugRNJko1vMvNw==", + "requires": { + "@nuxt/opencollective": "^0.3.2", + "bootstrap": ">=4.5.3 <5.0.0", + "popper.js": "^1.16.1", + "portal-vue": "^2.1.7", + "vue-functional-data-merge": "^3.1.0" + } + }, "boxen": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", @@ -31846,6 +31902,12 @@ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.12.9.tgz", "integrity": "sha512-TdcJywkQtcwLxogc4rSMAi479G2eDPzfW0fLySks7TPhgZZ4s/tM6stnzayIh3gS/db3zExWJyUx4cNWrwAmoQ==" }, + "jquery": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz", + "integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==", + "peer": true + }, "js-message": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/js-message/-/js-message-1.0.7.tgz", @@ -33928,6 +33990,17 @@ "ts-pnp": "^1.1.6" } }, + "popper.js": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", + "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==" + }, + "portal-vue": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/portal-vue/-/portal-vue-2.1.7.tgz", + "integrity": "sha512-+yCno2oB3xA7irTt0EU5Ezw22L2J51uKAacE/6hMPMoO/mx3h4rXFkkBkT4GFsMDv/vEe8TNKC3ujJJ0PTwb6g==", + "requires": {} + }, "posix-character-classes": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", @@ -37699,6 +37772,11 @@ "resolved": "https://registry.npmjs.org/vue-client-only/-/vue-client-only-2.1.0.tgz", "integrity": "sha512-vKl1skEKn8EK9f8P2ZzhRnuaRHLHrlt1sbRmazlvsx6EiC3A8oWF8YCBrMJzoN+W3OnElwIGbVjsx6/xelY1AA==" }, + "vue-functional-data-merge": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vue-functional-data-merge/-/vue-functional-data-merge-3.1.0.tgz", + "integrity": "sha512-leT4kdJVQyeZNY1kmnS1xiUlQ9z1B/kdBFCILIjYYQDqZgLqCLa0UhjSSeRX6c3mUe6U5qYeM8LrEqkHJ1B4LA==" + }, "vue-hot-reload-api": { "version": "2.3.4", "resolved": "https://registry.npmjs.org/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz", diff --git a/package.json b/package.json index 569e35757a39b3fd5ed2cd97fb25cb83effb429f..4660095ca8ef17077fd68e61c09ab92602339425 100644 --- a/package.json +++ b/package.json @@ -12,13 +12,15 @@ "dependencies": { "@nuxtjs/apollo": "^4.0.1-rc.5", "@nuxtjs/pwa": "^3.3.5", - "bootstrap": "^4.6.0", + "bootstrap": "^4.6.1", + "bootstrap-vue": "^2.21.2", "chart.js": "^3.6.2", "core-js": "^3.15.1", "dayjs": "^1.10.7", "graphql-tag": "^2.12.6", "nuxt": "^2.15.8", - "nuxt-purgecss": "^1.0.0" + "nuxt-purgecss": "^1.0.0", + "vue": "^2.6.14" }, "devDependencies": { "sass": "^1.45.0", diff --git a/pages/chartjs.vue b/pages/chartjs.vue index 063ca88b12431fe9116fbf258d8953add0392eb1..f3f71c8328305dedfcafe85653c1c766a696b89c 100644 --- a/pages/chartjs.vue +++ b/pages/chartjs.vue @@ -1,6 +1,11 @@ <template> <main class="container-fluid"> <h2 class="display-2 text-center mb-5">Test ChartJS</h2> + <div class="row"> + <div class="col-md-6 mx-auto"> + <b-alert variant="danger" show>En développement</b-alert> + </div> + </div> <div class="demo p-5 m-auto"> <select class="form-control my-4" v-model="chartType"> <option v-for="type in allTypes" :key="type">{{ type }}</option> diff --git a/pages/index.vue b/pages/index.vue index f37e317ca7b61bb23826731b43f849314f123d12..f1a3177a2e6ac1e078786168ff0c1fc5dd28ee2b 100644 --- a/pages/index.vue +++ b/pages/index.vue @@ -1,11 +1,25 @@ <template> <main class="container"> - <h2 class="text-center mb-5 font-weight-light">Bienvenue</h2> - + <h2 class="text-center mb-5 font-weight-light">Entrées et sorties de la toile de confiance des 2 derniers jours</h2> + <NavigationLoader :isLoading="$apollo.queries.newMembers.loading" class="d-block mx-auto" /> + <div class="row text-center" v-if="!$apollo.queries.newMembers.loading && newMembers"> + <div class="col-lg-6"> + <h2 class="h4 text-success">Bienvenue à </h2> + <MemberList :members="newMembers['entrees']" :displayPubkey="false" :displayHead="false" /> + </div> + <div class="col-lg-6"> + <h2 class="h4 text-danger">Au revoir à </h2> + <MemberList :members="newMembers['sorties']" :displayPubkey="false" :displayHead="false" /> + </div> + </div> </main> </template> <script> +import gql from 'graphql-tag' + +const today = Math.round(Date.now() /1000) + export default { data() { // Variables locales @@ -14,11 +28,59 @@ export default { breadcrumb: [ { text: 'Accueil', - active: true + to: '/' } ] } }, + // Fonctions locales + methods: { + + }, + apollo: { + newMembers : { + query: gql`query LastEvents($start: Int64, $end: Int64) { + membersCount(start: $start, end: $end) { + idList { + id { + pubkey + uid + status + hash + limitDate + received_certifications { + limit + } + } + inOut + } + } + } `, + variables(){return {start:today-86400*2,end:today}}, + update (data) { + let result = {'entrees':[],'sorties':[]} + + for (let i = 0; i < data.membersCount.length; i++) { + let member = data.membersCount[i].idList[0] + member.id.inOut = member.inOut + + if (member.inOut || member.id.status=='MEMBER') { + if (result['entrees'].filter(function(e) { return e.hash === member.id.hash; }).length == 0) { + result['entrees'].push(member.id) + } + } else { + result['sorties'].push(member.id) + } + } + + for (let list in result) { + result[list].sort((a, b) => (a.uid.toLowerCase() > b.uid.toLowerCase()) ? 1 : -1) + } + + return result + } + } + }, mounted () { // Mise à jour du fil d'ariane au chargement $nuxt.$emit('changeRoute',this.breadcrumb) diff --git a/pages/inout.vue b/pages/inout.vue deleted file mode 100644 index 8cc1fb10a5d10a3a5286172dbeafac839ef3b7a4..0000000000000000000000000000000000000000 --- a/pages/inout.vue +++ /dev/null @@ -1,93 +0,0 @@ -<template> -<main class="container"> - <h2 class="text-center mb-5 font-weight-light">Entrées et sorties de la toile de confiance des 2 derniers jours</h2> - <NavigationLoader :isLoading="$apollo.queries.newMembers.loading" class="d-block mx-auto" /> - <div class="row text-center" v-if="!$apollo.queries.newMembers.loading && newMembers"> - <div class="col-lg-6"> - <h2 class="h4 text-success">Bienvenue à </h2> - <MemberList :members="newMembers['entrees']" :displayPubkey="false" :displayHead="false" /> - </div> - <div class="col-lg-6"> - <h2 class="h4 text-danger">Au revoir à </h2> - <MemberList :members="newMembers['sorties']" :displayPubkey="false" :displayHead="false" /> - </div> - </div> -</main> -</template> - -<script> -import gql from 'graphql-tag' - -const today = Math.round(Date.now() /1000) - -export default { - data() { - // Variables locales - return { - // Fil d'ariane - breadcrumb: [ - { - text: 'Accueil', - to: '/' - }, - { - text: 'Entrées et sorties', - active: true - } - ] - } - }, - // Fonctions locales - methods: { - - }, - apollo: { - newMembers : { - query: gql`query LastEvents($start: Int64, $end: Int64) { - membersCount(start: $start, end: $end) { - idList { - id { - pubkey - uid - status - hash - limitDate - received_certifications { - limit - } - } - inOut - } - } - } `, - variables(){return {start:today-86400*2,end:today}}, - update (data) { - let result = {'entrees':[],'sorties':[]} - - for (let i = 0; i < data.membersCount.length; i++) { - let member = data.membersCount[i].idList[0] - member.id.inOut = member.inOut - - if (member.inOut || member.id.status=='MEMBER') { - if (result['entrees'].filter(function(e) { return e.hash === member.id.hash; }).length == 0) { - result['entrees'].push(member.id) - } - } else { - result['sorties'].push(member.id) - } - } - - for (let list in result) { - result[list].sort((a, b) => (a.uid.toLowerCase() > b.uid.toLowerCase()) ? 1 : -1) - } - - return result - } - } - }, - mounted () { - // Mise à jour du fil d'ariane au chargement - $nuxt.$emit('changeRoute',this.breadcrumb) - } -} -</script> \ No newline at end of file diff --git a/pages/previsions/_hash.vue b/pages/previsions/_hash.vue new file mode 100644 index 0000000000000000000000000000000000000000..717ec9d3dc38e5a4b4363c7e228d3d8e8f1f232d --- /dev/null +++ b/pages/previsions/_hash.vue @@ -0,0 +1,112 @@ +<template> + <main class="content container"> + <NavigationLoader :isLoading="$apollo.queries.idFromHash.loading" class="d-block mb-3" /> + <div v-if="!$apollo.queries.idFromHash.loading"> + <div class="row"> + <div class="col-md-10 col-lg-8 col-xl-6 mx-auto mt-3"> + <h2 class="text-center mb-5 font-weight-light">Prévisions <small><span class="badge badge-secondary">{{ idFromHash.uid }}</span></small></h2> + <MemberCard :hash="idFromHash" /> + </div> + </div> + </div> + </main> +</template> + +<script> +import gql from "graphql-tag" + +export default { + data() { + // Variables locales + return { + breadcrumb: [ + { + text: 'Accueil', + to: '/' + }, + { + text: 'Prévisions', + to: '/previsions' + }, + { + text: '', + active: true + } + ] + }; + }, + // Fonctions locales + methods: {}, + apollo: { + idFromHash: { + query: gql` + query Search($hash: Hash!) { + idFromHash(hash: $hash) { + ...memberAttributes + pubkey + isLeaving + sentry + minDate + minDatePassed + membership_pending + limitDate + distance { + value { + ratio + } + dist_ok + } + received_certifications { + certifications { + from { + ...memberAttributes + } + expires_on + } + } + sent_certifications { + to { + ...memberAttributes + } + expires_on + } + } + } + fragment memberAttributes on Identity { + uid + hash + status + quality { + ratio + } + received_certifications { + limit + } + } + `, + variables() { + return { hash: this.$route.params.hash }; + }, + skip() { + return false; + }, + }, + }, + computed: { + classWarning: function() { + return { + 'text-danger' : !this.idFromHash.received_certifications.limit, + 'text-warning' : this.$options.filters.dateStatus(this.idFromHash.received_certifications.limit) == 'warning' + } + } + }, + watch: { + idFromHash: { + handler(n,o) { + this.breadcrumb[2].text = this.idFromHash.uid + $nuxt.$emit("changeRoute", this.breadcrumb); + } + } + } +}; +</script> \ No newline at end of file diff --git a/pages/previsions/index.vue b/pages/previsions/index.vue new file mode 100644 index 0000000000000000000000000000000000000000..ca47a0e9c12e12052c77386b32b943890673072f --- /dev/null +++ b/pages/previsions/index.vue @@ -0,0 +1,113 @@ +<template> +<main class="container"> + <h2 class="text-center mb-5 font-weight-light">En préparation</h2> + <b-alert variant="danger" show>En développement</b-alert> + <NavigationLoader :isLoading="$apollo.queries.test.loading" class="d-block mb-3" /> + <div v-if="test"> + {{ test }} + </div> +</main> +</template> + +<script> +import gql from 'graphql-tag' + +export default { + data() { + // Variables locales + return { + // Fil d'ariane + breadcrumb: [ + { + text: 'Accueil', + to: '/' + }, + { + text: 'Prévisions', + active: true + } + ], + display: 'forecastsByNames' + } + }, + // Fonctions locales + methods: { + + }, + apollo: { + test : { + query: gql`query { + now { + number + bct + } + parameter(name: sigQty) { + sigQty:value + } + wwFile(full: true) { + certifs_dossiers { + ... on MarkedDatedCertification { + datedCertification { + date + certification { + from { + uid + } + to { + uid + } + expires_on + } + } + } + ... on MarkedDossier { + dossier { + main_certifs + newcomer { + uid + lastApplication { + lastAppDate: bct + } + distance: distanceE { + value { + ratio + } + dist_ok + } + } + date + minDate + expires_on:limit + certifications { + date + certification { + from { + uid + quality { + ratio + } + } + expires_on + } + } + } + } + } + } + } `, + update (data) { + + return { + now: data.now, + sigQty: data.parameter.sigQty, + certifs_dossiers: data.wwFile.certifs_dossiers + } + } + } + }, + mounted () { + // Mise à jour du fil d'ariane au chargement + $nuxt.$emit('changeRoute',this.breadcrumb) + } +} +</script> \ No newline at end of file diff --git a/pages/previsions.vue b/pages/previsions/newcomers.vue similarity index 97% rename from pages/previsions.vue rename to pages/previsions/newcomers.vue index 1559c787c0b8d0558c5cd207724da152217d46f3..68c30a0f6538ee0cc176949ff425923466774dbb 100644 --- a/pages/previsions.vue +++ b/pages/previsions/newcomers.vue @@ -30,7 +30,7 @@ <div class="table-responsive"> <table class="table table-striped table-hover"> <tbody> - <tr v-for="forecast in wwResult.forecastsByNames" :key="forecast.member.uid" @click="$router.push({ path: '/membres/' + forecast.member.hash })"> + <tr v-for="forecast in wwResult.forecastsByNames" :key="forecast.member.uid" @click="$router.push({ path: '/previsions/' + forecast.member.hash })"> <th scope="row"> {{ forecast.member.uid }} <BadgeStatus :membre="forecast.member" /> @@ -87,6 +87,10 @@ export default { }, { text: 'Prévisions', + to: '/previsions' + }, + { + text: 'Futurs membres', active: true } ], @@ -124,7 +128,6 @@ export default { } } `, update (data) { - console.log(data) let result = {'byName':[],'byDate':[]} let forecasts = data.wwResult.forecastsByNames