From 83ed3ca84d8183277f43dcfc828c1291246f2fd6 Mon Sep 17 00:00:00 2001 From: Pierre-Jean CHANCELLIER <paidge_cs@hotmail.com> Date: Tue, 22 Feb 2022 19:44:23 +0100 Subject: [PATCH] v2.0.0 --- assets/css/_bootstrap.scss | 4 +- assets/css/style.scss | 50 ++-- components/{alerte.vue => alert/Default.vue} | 30 +- components/alert/Member.vue | 179 ++++++++++++ components/badge/Danger.vue | 32 ++- components/badge/Date.vue | 17 +- components/badge/Status.vue | 2 +- components/btn/Clipboard.vue | 3 +- components/btn/Favori.vue | 48 ++++ components/btn/Pagination.vue | 21 +- components/btn/Search.vue | 28 +- components/btn/Sort.vue | 10 +- components/btn/Theme.vue | 7 + components/certif/All.vue | 52 ++++ components/certif/Group.vue | 34 ++- components/certif/List.vue | 272 +++++++++++++------ components/member/Card.vue | 89 +++--- components/member/List.vue | 92 ++++--- components/navigation/Bar.vue | 1 + components/navigation/menu/Group.vue | 9 +- components/navigation/menu/Sidebar.vue | 5 +- graphql/cache.js | 6 + graphql/queries.js | 21 ++ i18n/locales/de.json | 49 +++- i18n/locales/en.json | 49 +++- i18n/locales/es.json | 47 +++- i18n/locales/fr.json | 47 +++- layouts/default.vue | 39 ++- nuxt.config.js | 2 +- package.json | 4 +- pages/a-propos.vue | 10 +- pages/favoris.vue | 9 +- pages/index.vue | 32 ++- pages/lexique.vue | 11 +- pages/membre.vue | 141 +++++----- pages/membres/index.vue | 11 +- pages/parametres.vue | 8 +- pages/previsions/futures_sorties.vue | 12 +- pages/previsions/index.vue | 8 +- plugins/bootstrap.js | 2 + 40 files changed, 1093 insertions(+), 400 deletions(-) rename components/{alerte.vue => alert/Default.vue} (54%) create mode 100644 components/alert/Member.vue create mode 100644 components/btn/Favori.vue create mode 100644 components/certif/All.vue diff --git a/assets/css/_bootstrap.scss b/assets/css/_bootstrap.scss index 89d914e..814bf81 100644 --- a/assets/css/_bootstrap.scss +++ b/assets/css/_bootstrap.scss @@ -65,10 +65,10 @@ // @import "~bootstrap/scss/helpers/ratio"; // @import "~bootstrap/scss/helpers/position"; // @import "~bootstrap/scss/helpers/stacks"; -// @import "~bootstrap/scss/helpers/visually-hidden"; +@import "~bootstrap/scss/helpers/visually-hidden"; // @import "~bootstrap/scss/helpers/stretched-link"; @import "~bootstrap/scss/helpers/text-truncation"; -// @import "~bootstrap/scss/helpers/vr"; +@import "~bootstrap/scss/helpers/vr"; // Utilities @import "~bootstrap/scss/utilities/api"; diff --git a/assets/css/style.scss b/assets/css/style.scss index fd488ff..089d7bc 100644 --- a/assets/css/style.scss +++ b/assets/css/style.scss @@ -1,14 +1,27 @@ // Bootstrap variables customisation +// Colors +$border-color: var(--border-color); +$secondary: #adb5bd; +$success: #75b798; +$info: #3d8bfd; +$danger: #b02a37; +$warning: #fd9843; + +// Mark +$mark-padding: 0.2em 0; +$mark-bg: yellow; + +// Forms +$form-check-input-checked-bg-color: $info; + // Fonts & Typo $link-decoration: none; $text-muted: var(--txt-muted-color); $font-family-base: Montserrat, Helvetica, Arial, serif; $small-font-size: 70%; +$code-font-size: 85%; $link-hover-decoration: underline; -// Forms -$form-check-input-checked-bg-color: var(--txt-muted-color); - // Badges $badge-font-size: 0.7em; $badge-font-weight: 500; @@ -24,6 +37,7 @@ $table-striped-bg: var(--bg-menu-color); $breadcrumb-bg: var(--bg-menu-color); $breadcrumb-active-color: var(--txt-primary-color); $breadcrumb-divider-color: var(--txt-primary-color); +$breadcrumb-divider: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'%3E%3Cpath d='M2.5 0L1 1.5 3.5 4 1 6.5 2.5 8l4-4-4-4z' fill='#6c757d'/%3E%3C/svg%3E"); // List-groups $list-group-bg: transparent; @@ -58,13 +72,6 @@ $pagination-disabled-border-color: $pagination-border-color; $pagination-disabled-bg: var(--bg-menu-color); $pagination-disabled-color: var(--txt-secondary-color); -// Mark -$mark-padding: 0.2em 0; -$mark-bg: yellow; - -// Colors -$border-color: var(--border-color); - @import "font"; @import "bootstrap"; @@ -83,7 +90,9 @@ a:hover { } .bg-warning, -.bg-info { +.bg-info, +.bg-success, +.bg-secondary { @extend .text-dark; } @@ -110,6 +119,10 @@ a:hover { &-hover tbody tr { cursor: pointer; + + &:hover { + filter: brightness(90%); + } } .td-date { @@ -122,23 +135,10 @@ a:hover { } .list-group-item { - &-action:not(.active) { - &:hover { - background: var(--bg-secondary-color); - color: var(--txt-secondary-color); - } - } - .menu-item { transition: left 0.3s ease-in-out; left: 0; - &::before { - content: "›"; - position: relative; - left: -0.5em; - } - &:hover { left: 0.5em; } @@ -146,5 +146,5 @@ a:hover { } .icon { - width: 1.15rem; + width: 1.3rem; } diff --git a/components/alerte.vue b/components/alert/Default.vue similarity index 54% rename from components/alerte.vue rename to components/alert/Default.vue index 3110135..4be74e7 100644 --- a/components/alerte.vue +++ b/components/alert/Default.vue @@ -1,16 +1,22 @@ <template> <div class="alert alert-dismissible fade show" - :class="classType" - v-if="error"> - {{ error }} + role="alert" + :class="classType"> + <h3 class="alert-heading h5" v-if="title"> + <span v-if="icon" + ><component + aria-hidden="true" + :is="'solid-' + icon + '-icon'" + style="width: 1.5rem" /> </span + >{{ title }} + </h3> + <slot></slot> <button type="button" - class="close" - data-dismiss="alert" - :aria-label="$t('aria.close')"> - <span aria-hidden="true">×</span> - </button> + class="btn-close" + data-bs-dismiss="alert" + :aria-label="$t('aria.close')"></button> </div> </template> @@ -35,9 +41,13 @@ export default { ) } }, - error: { + title: { type: String, - default: null + default: "" + }, + icon: { + type: String, + default: "" } }, computed: { diff --git a/components/alert/Member.vue b/components/alert/Member.vue new file mode 100644 index 0000000..49d42dd --- /dev/null +++ b/components/alert/Member.vue @@ -0,0 +1,179 @@ +<template> + <div v-if="displayAlert"> + <AlertDefault + class="col-xxl-10 mx-auto mt-3" + :title="getTitle" + :icon="getIcon" + :type="typeAlerte"> + <ul> + <li + v-if="hash.status == 'NEWCOMER' && !allCertifiersDispo" + v-html="$t('alert.notAllCertifiersDispo')"></li> + <li + v-if="this.hash.status == 'NEWCOMER' && this.isDossierOK" + v-html="$t('alert.dossierOK')"></li> + <li + v-if="hash.status == 'MISSING'" + v-html=" + $tc( + 'alert.missing', + Math.max( + 1, + Math.round( + (hash.limitDate - Math.round(Date.now() / 1000)) / 3600 / 24 + ) + ) + ) + "></li> + <li + v-if=" + hash.status == 'MEMBER' && + ['warning', 'danger'].includes( + $options.filters.dateStatus(hash.limitDate) + ) + " + v-html="$t('alert.renew')"></li> + <li + v-if="hash.status != 'REVOKED' && !hash.distanceE.dist_ok" + v-html="$t('alert.distKO')"></li> + <li + v-if=" + hash.status != 'REVOKED' && certifsNotExpired.received.length < 5 + " + v-html=" + $tc('alert.certifManquantes', 5 - certifsNotExpired.received.length) + "></li> + <li + v-if=" + ['MEMBER', 'MISSING'].includes(hash.status) && + this.$options.filters.dateStatus(this.hash.certsLimit) == 'warning' + " + v-html="$t('alert.certifManquantesBientot')"></li> + <li + v-if="hash.status == 'MEMBER' && !hash.minDatePassed" + v-html="$t('alert.notAvailable')"></li> + <li + v-if="certifsNotExpired.sent.length == 100" + v-html="$t('alert.noMoreCert')"></li> + <li + v-else-if="certifsNotExpired.sent.length > 80" + v-html=" + $tc('alert.certStockLim', 100 - certifsNotExpired.sent.length) + "></li> + <li v-if="hash.status == 'REVOKED'" v-html="$t('alert.revoked')"></li> + </ul> + </AlertDefault> + </div> +</template> + +<script> +export default { + props: { + hash: { + type: Object, + required: true + } + }, + computed: { + displayAlert() { + return ( + ["MISSING", "REVOKED", "NEWCOMER"].includes(this.hash.status) || + ["warning", "danger"].includes( + this.$options.filters.dateStatus(this.hash.limitDate) + ) || + !this.hash.minDatePassed || + this.certifsNotExpired.sent.length > 80 || + this.certifsNotExpired.received.length < 5 || + !this.hash.distanceE.dist_ok || + this.$options.filters.dateStatus(this.hash.certsLimit) == "warning" + ) + }, + certifsNotExpired() { + return { + received: this.hash.received_certifications.filter((el) => !el.expired), + sent: this.hash.sent_certifications.filter((el) => !el.expired) + } + }, + allCertifiersDispo() { + let nbCertifiersdispo = 0 + + for (const cert of this.certifsNotExpired.received) { + if (cert.from.minDatePassed) nbCertifiersdispo++ + } + + return nbCertifiersdispo > 4 + }, + isDossierOK() { + return ( + this.hash.distanceE.dist_ok && + this.hash.received_certifications.length > 4 + ) + }, + typeAlerte() { + switch (this.hash.status) { + case "NEWCOMER": + return this.isDossierOK + ? this.allCertifiersDispo + ? "success" + : "info" + : "danger" + case "MISSING": + return "danger" + case "MEMBER": + if ( + this.$options.filters.dateStatus(this.hash.certsLimit) == + "danger" || + this.$options.filters.dateStatus(this.hash.limitDate) == "danger" + ) + return "danger" + if (!this.hash.distanceE.dist_ok) return "danger" + return "warning" + case "REVOKED": + return "secondary" + } + }, + getTitle() { + let title = this.$t("alert.information") + + switch (this.hash.status) { + case "MEMBER": + if (this.typeAlerte == "danger") title = this.$t("alert.attention") + break + case "NEWCOMER": + title = this.isDossierOK + ? this.allCertifiersDispo + ? this.$t("alert.dossierOKtitle") + : this.$t("alert.attenteCertifiers") + : this.$t("alert.dossierKOtitle") + break + case "MISSING": + title = this.$t("alert.attention") + } + + return title + }, + getIcon() { + let icon = "information-circle" + + switch (this.hash.status) { + case "MEMBER": + if (this.typeAlerte == "danger") icon = "exclamation" + break + case "NEWCOMER": + if (!this.isDossierOK) { + icon = "exclamation" + break + } + + if (this.allCertifiersDispo) icon = "check-circle" + break + case "MISSING": + icon = "exclamation" + break + } + + return icon + } + } +} +</script> diff --git a/components/badge/Danger.vue b/components/badge/Danger.vue index d5a89ef..70154d9 100644 --- a/components/badge/Danger.vue +++ b/components/badge/Danger.vue @@ -1,13 +1,14 @@ <template> <span - :class="classWarning" + class="d-inline-block" + :class="classBadge" :title="title" - v-if=" - $options.filters.dateStatus(limitDate) != 'success' && - !['NEWCOMER', 'REVOKED'].includes(memberStatus) - " - ><outline-exclamation-icon class="icon" /> - <span class="sr-only" v-if="title">{{ title }}</span> + style="cursor: help" + v-if="!['NEWCOMER', 'REVOKED'].includes(memberStatus)" + ><solid-shield-check-icon + v-if="$options.filters.dateStatus(limitDate) == 'success'" /> + <solid-shield-exclamation-icon v-else /> + <span class="visually-hidden">{{ title }}</span> </span> </template> @@ -18,19 +19,28 @@ export default { type: Number, default: 0 }, - title: { - type: String - }, memberStatus: String }, computed: { - classWarning() { + classBadge() { return { + "text-success": + this.$options.filters.dateStatus(this.limitDate) == "success", "text-danger": this.$options.filters.dateStatus(this.limitDate) == "danger", "text-warning": this.$options.filters.dateStatus(this.limitDate) == "warning" } + }, + title() { + switch (this.$options.filters.dateStatus(this.limitDate)) { + case "success": + return this.$t("statut.allcertif") + case "warning": + return this.$t("statut.bientotmanquecertif") + case "danger": + return this.$t("statut.manquecertif") + } } } } diff --git a/components/badge/Date.vue b/components/badge/Date.vue index 51755b8..122b5c8 100644 --- a/components/badge/Date.vue +++ b/components/badge/Date.vue @@ -1,6 +1,12 @@ <template> <span class="badge" :class="'bg-' + $options.filters.dateStatus(date)"> - {{ !date ? "N/A" : $d(new Date(date * 1000), styleDate) }} + {{ + !date + ? "N/A" + : 0 > deltaDays + ? $tc("jours_avant", Math.abs(deltaDays)) + : $tc("jours", Math.abs(deltaDays)) + }} </span> </template> @@ -9,10 +15,11 @@ export default { props: { date: { type: Number - }, - styleDate: { - type: String, - required: true + } + }, + computed: { + deltaDays() { + return Math.ceil((this.date - Math.round(Date.now() / 1000)) / 3600 / 24) } } } diff --git a/components/badge/Status.vue b/components/badge/Status.vue index b8b1d47..fe79dfb 100644 --- a/components/badge/Status.vue +++ b/components/badge/Status.vue @@ -1,5 +1,5 @@ <template> - <span class="badge" :class="this.displayStatus(membre).class"> + <span class="badge" :class="displayStatus(membre).class"> {{ this.displayStatus(membre).str }} </span> </template> diff --git a/components/btn/Clipboard.vue b/components/btn/Clipboard.vue index 59f7e7e..2663b3c 100644 --- a/components/btn/Clipboard.vue +++ b/components/btn/Clipboard.vue @@ -6,7 +6,8 @@ type="button" v-tooltip="$t('copie') + ' !'" @click="copyText"> - <outline-clipboard-copy-icon class="icon" /> + <solid-share-icon class="icon" aria-hidden="true" /> + <span class="visually-hidden">{{ $t("aria.clipboard") }}</span> </button> <input type="text" diff --git a/components/btn/Favori.vue b/components/btn/Favori.vue new file mode 100644 index 0000000..e3c4f5d --- /dev/null +++ b/components/btn/Favori.vue @@ -0,0 +1,48 @@ +<template> + <button + id="favori" + class="btn btn-secondary" + v-tooltip=" + $favourites.list.includes(uid) + ? $t('favoris.enregistre') + : $t('favoris.supprime') + " + @click="$favourites.toggleFavourite(uid, $event)"> + <span class="visually-hidden">{{ + $favourites.list.includes(uid) + ? $t("favoris.supprimer") + : $t("favoris.ajouter") + }}</span> + <solid-user-add-icon + aria-hidden="true" + class="icon" + v-if="!$favourites.list.includes(uid)" /> + <solid-user-remove-icon + aria-hidden="true" + class="icon" + v-if="$favourites.list.includes(uid)" /> + </button> +</template> + +<script> +export default { + props: { + uid: { + type: String, + required: true + } + } +} +</script> + +<style lang="scss"> +#favori { + width: 50px; + height: 50px; + margin-bottom: 1rem; + + @media (min-width: 576px) { + margin-bottom: 0; + } +} +</style> diff --git a/components/btn/Pagination.vue b/components/btn/Pagination.vue index e28f0bc..d76bc19 100644 --- a/components/btn/Pagination.vue +++ b/components/btn/Pagination.vue @@ -1,8 +1,8 @@ <template> <nav :aria-label="$t('pagination.title')" v-if="nbPages > 1"> - <ul class="pagination justify-content-center"> + <ul class="pagination justify-content-center m-0"> <li class="page-item" v-if="currentPage > 2 && nbPages > 3"> - <a class="page-link" href="#" @click="firstPage($event)"> + <a class="page-link" href="#" @click="goto(1, $event)"> <span :aria-label="$t('pagination.page') + ' 1'">1</span> </a> </li> @@ -31,7 +31,7 @@ <span class="page-link"><span aria-hidden="true">...</span></span> </li> <li class="page-item" v-if="currentPage < nbPages - 1 && nbPages > 3"> - <a class="page-link" href="#" @click="lastPage($event)"> + <a class="page-link" href="#" @click="goto(nbPages, $event)"> <span :aria-label="$t('pagination.page') + ' ' + nbPages">{{ nbPages }}</span> @@ -44,6 +44,7 @@ <script> export default { props: { + target: String, currentPage: { type: Number, required: true @@ -60,15 +61,13 @@ export default { methods: { goto(i, e) { if (e !== undefined) e.preventDefault() + let query = { ...this.$route.query } + query["page-" + this.target] = i + this.$router.push({ + path: this.$route.path, + query: query + }) this.$emit("update:currentPage", i) - }, - firstPage: function (e) { - e.preventDefault() - this.$emit("update:currentPage", 1) - }, - lastPage: function (e) { - e.preventDefault() - this.$emit("update:currentPage", this.nbPages) } }, computed: { diff --git a/components/btn/Search.vue b/components/btn/Search.vue index e43616b..756f701 100644 --- a/components/btn/Search.vue +++ b/components/btn/Search.vue @@ -1,7 +1,9 @@ <template> <div> <div class="input-group mb-3"> - <span class="input-group-text"><outline-search-icon class="icon" /></span> + <span class="input-group-text" + ><solid-search-icon class="icon" aria-hidden="true" + /></span> <input type="text" class="form-control" @@ -11,7 +13,14 @@ @keyup="$emit('keyup', $event.keyCode)" :placeholder="$t('recherche.title')" :aria-label="$t('recherche.title')" - :aria-describedby="help ? rechHelp : null" /> + :aria-describedby="help ? 'rechHelp' : null" /> + <button + :title="$t('recherche.effacer')" + class="btn" + type="button" + @click="$emit('erase')"> + <solid-x-icon class="icon" aria-hidden="true" /> + </button> </div> <div v-if="help" @@ -34,3 +43,18 @@ export default { } } </script> + +<style lang="scss" scoped> +.input-group > * { + border-color: var(--border-color); +} + +.btn { + background: var(--bg-menu-color); + color: var(--txt-secondary-color); + + &:hover { + background: var(--bg-secondary-color); + } +} +</style> diff --git a/components/btn/Sort.vue b/components/btn/Sort.vue index 71cedae..e9200fc 100644 --- a/components/btn/Sort.vue +++ b/components/btn/Sort.vue @@ -1,10 +1,12 @@ <template> - <div class="btn-sort px-2" tabindex="0"> - <span>{{ title }}</span> - <outline-sort-ascending-icon + <div class="btn-sort px-2" tabindex="0" :title="$t('tri.action')"> + <span class="text-truncate">{{ title }}</span> + <solid-sort-ascending-icon + aria-hidden="true" class="ms-2 icon flex-shrink-0" v-if="currentSortDir == 'desc' && currentSort == fieldName" /> - <outline-sort-descending-icon + <solid-sort-descending-icon + aria-hidden="true" class="ms-2 icon flex-shrink-0" v-if="currentSortDir == 'asc' && currentSort == fieldName" /> </div> diff --git a/components/btn/Theme.vue b/components/btn/Theme.vue index 6638e0d..4b39f5c 100644 --- a/components/btn/Theme.vue +++ b/components/btn/Theme.vue @@ -9,6 +9,13 @@ for="checkbox" class="switch-label d-flex align-items-center justify-content-between position-relative mb-0 form-control" tabindex="0"> + <span class="visually-hidden"> + {{ + this.userTheme === "light-theme" + ? $t("aria.themedark") + : $t("aria.themelight") + }} + </span> <span>🌙</span> <span>☀ï¸</span> <div diff --git a/components/certif/All.vue b/components/certif/All.vue new file mode 100644 index 0000000..605be22 --- /dev/null +++ b/components/certif/All.vue @@ -0,0 +1,52 @@ +<template> + <div class="container-lg" v-if="idFromHash.status != 'REVOKED'"> + <div class="row mt-3"> + <div class="col-sm-10 col-md-8 col-lg-5 mx-auto"> + <h3 + class="h4 text-center" + :class="{ + 'text-success': + $options.filters.dateStatus(idFromHash.certsLimit) == 'success', + 'text-warning': + $options.filters.dateStatus(idFromHash.certsLimit) == 'warning', + 'text-danger': + $options.filters.dateStatus(idFromHash.certsLimit) == 'danger' + }"> + {{ $t("certification.recues") }} <BadgeDanger + style="width: 2rem" + :limitDate="idFromHash.certsLimit" + :memberStatus="idFromHash.status" /> + </h3> + <CertifGroup + :certifs="idFromHash.received_certifications" + type="received" + :certifStatus="$options.filters.dateStatus(idFromHash.certsLimit)" /> + </div> + <hr class="d-lg-none mt-4" style="height: 10px" /> + <div + class="col-1 d-none d-lg-flex" + v-if="['MISSING', 'MEMBER'].includes(idFromHash.status)"> + <div class="vr mx-auto"></div> + </div> + <div + class="col-sm-10 col-md-8 col-lg-5 mx-auto" + v-if="['MISSING', 'MEMBER'].includes(idFromHash.status)"> + <h3 class="h4 text-center text-info"> + {{ $t("certification.envoyees") }} + </h3> + <CertifGroup :certifs="idFromHash.sent_certifications" type="sent" /> + </div> + </div> + </div> +</template> + +<script> +export default { + props: { + idFromHash: { + type: Object, + required: true + } + } +} +</script> diff --git a/components/certif/Group.vue b/components/certif/Group.vue index 115deb6..90effc3 100644 --- a/components/certif/Group.vue +++ b/components/certif/Group.vue @@ -1,12 +1,21 @@ <template> <div> - <CertifList :certifs="certifsPending" :type="type" /> + <CertifList + :title="$t('certification.enattente')" + :certifs="certifsPending" + :collapseId="type + '-entraitement'" /> <hr v-if="certifsPending.length > 0 && certifsNotPending.length > 0" /> <CertifList + :certifStatus="certifStatus" + :openDefault="true" + :title="$t('certification.encours')" :certifs="certifsNotPending" - :type="type" - class="pb-3" - :class="{ 'pt-3': certifsPending.length > 0 }" /> + :collapseId="type + '-encours'" /> + <hr v-if="certifsExpired.length > 0" /> + <CertifList + :title="$t('certification.perimees')" + :certifs="certifsExpired" + :collapseId="type + '-perimees'" /> </div> </template> @@ -14,6 +23,10 @@ export default { props: { certifs: Array, + certifStatus: { + type: String, + default: "" + }, type: { type: String, required: true, @@ -24,17 +37,14 @@ export default { } }, computed: { - certifsMapped() { - return this.certifs.map((certif) => ({ - ...certif, - ...(this.type === "received" ? certif.from : certif.to) - })) - }, certifsNotPending() { - return this.certifsMapped.filter((el) => el.pending == false) + return this.certifs.filter((el) => el.pending == false) }, certifsPending() { - return this.certifsMapped.filter((el) => el.pending == true) + return this.certifs.filter((el) => el.pending == true) + }, + certifsExpired() { + return this.certifs.filter((el) => el.expired == true) } } } diff --git a/components/certif/List.vue b/components/certif/List.vue index 982ea72..43b8611 100644 --- a/components/certif/List.vue +++ b/components/certif/List.vue @@ -1,82 +1,127 @@ <template> - <div class="table-responsive" v-if="certifs.length > 0"> - <BtnSearch v-model="search" class="px-2 pt-2" v-if="certifs.length > 5" /> - <table class="table table-striped table-hover table-fixed sortable border"> - <thead class="thead-light"> - <tr> - <th class="p-0" @click="sort('uid')" @keyup.enter="sort('uid')"> - <BtnSort - :title="$t('membre.title')" - fieldName="uid" - :currentSort="currentSort" - :currentSortDir="currentSortDir" /> - </th> - <th - class="p-0 td-date" - @click="sort('expires_on')" - @keyup.enter="sort('expires_on')"> - <BtnSort - :title="$t('expire')" - fieldName="expires_on" - :currentSort="currentSort" - :currentSortDir="currentSortDir" /> - </th> - </tr> - </thead> - <tbody> - <tr - v-for="certif in certifsTriees" - :key="certif.uid" - tabindex="0" - @click=" - $router.push( - localePath({ - name: 'membre', - query: { hash: certif.hash } - }) - ) - " - @keyup.enter=" - $router.push( - localePath({ - name: 'membre', - query: { hash: certif.hash } - }) - ) - "> - <td class="py-1"> - <div class="d-flex"> - <span v-if="$favourites.list.includes(certif.uid)">★ </span> - <div class="text-truncate">{{ certif.uid }}</div> - - <BadgeDanger - :limitDate="certif.certsLimit" - :memberStatus="certif.status" /> - </div> - <BadgeStatus :membre="certif" /> - <BadgeQuality - :quality="certif.quality.ratio" - v-if="!['REVOKED', 'NEWCOMER'].includes(certif.status)" /> - <BadgeDispo - :isDispo="certif.minDatePassed" - :dateDispo="certif.minDate" - :certifs="certif.sent_certifications" - v-if="certif.status == 'MEMBER'" /> - </td> - <td class="py-1 text-center"> - <BadgeDate :date="certif.expires_on" styleDate="short" /> - <span class="badge bg-secondary" v-if="certif.pending">{{ - $t("traitement") - }}</span> - </td> - </tr> - </tbody> - </table> - <BtnPagination - :currentPage.sync="currentPage" - :pageSize="pageSize" - :arrayLength="certifsFiltrees.length" - v-if="certifsFiltrees.length > pageSize" /> + <div v-if="certifs.length > 0"> + <button + :title=" + isOpen ? $t('certification.masquer') : $t('certification.afficher') + " + @click="isOpen = !isOpen" + class="btn w-100 m-auto d-block rounded-0" + :class="btnClass" + type="button" + data-bs-toggle="collapse" + :data-bs-target="'#' + collapseId" + aria-expanded="false" + :aria-controls="collapseId"> + <span v-if="!isOpen"> + <solid-eye-icon class="icon" aria-hidden="true" /> + </span> + <span v-else> + <solid-eye-off-icon class="icon" aria-hidden="true" /> + </span> + <h4 class="d-inline align-middle"> + {{ title }} + <span class="badge rounded-pill bg-white opacity-75 text-dark">{{ + certifs.length + }}</span> + </h4> + </button> + <div + :id="collapseId" + class="table-responsive collapse p-3" + :class="collapseClass"> + <BtnSearch + @erase="search = ''" + v-model="search" + class="px-2" + v-if="certifs.length > 5" /> + <table + class="table table-striped table-hover table-fixed sortable border m-0"> + <thead class="thead-light"> + <tr> + <th class="p-0" @click="sort('uid')" @keyup.enter="sort('uid')"> + <BtnSort + :title="$t('membre.title')" + fieldName="uid" + :currentSort="currentSort" + :currentSortDir="currentSortDir" /> + </th> + <th + class="p-0 col-4" + @click="sort('expires_on')" + @keyup.enter="sort('expires_on')"> + <BtnSort + :title="$t('expire')" + fieldName="expires_on" + :currentSort="currentSort" + :currentSortDir="currentSortDir" /> + </th> + </tr> + </thead> + <tbody> + <tr + v-for="certif in certifsTriees" + :key="certif.uid" + tabindex="0" + :title="$t('membre.voirinfos')" + @click=" + $router.push( + localePath({ + name: 'membre', + query: { hash: certif.hash } + }) + ) + " + @keyup.enter=" + $router.push( + localePath({ + name: 'membre', + query: { hash: certif.hash } + }) + ) + "> + <td class="py-1"> + <div class="d-flex"> + <span v-if="$favourites.list.includes(certif.uid)" + >★ </span + > + <div class="text-truncate">{{ certif.uid }}</div> + + <BadgeDanger + style="width: 1.2rem" + :limitDate="certif.certsLimit" + :memberStatus="certif.status" /> + </div> + <BadgeStatus :membre="certif" /> + <BadgeQuality + :quality="certif.quality.ratio" + v-if="!['REVOKED', 'NEWCOMER'].includes(certif.status)" /> + <BadgeDispo + :isDispo="certif.minDatePassed" + :dateDispo="certif.minDate" + :certifs="certif.sent_certifications" + v-if="certif.status == 'MEMBER'" /> + </td> + <td class="p-0 text-center col-4"> + <div class="d-flex flex-column gap-1"> + <BadgeDate :date="certif.expires_on" /> + <span + class="badge bg-secondary text-truncate d-block" + v-if="certif.pending" + >{{ $t("traitement") }}</span + > + </div> + </td> + </tr> + </tbody> + </table> + <BtnPagination + class="mt-3" + :target="collapseId" + :currentPage.sync="currentPage" + :pageSize="pageSize" + :arrayLength="certifsFiltrees.length" + v-if="certifsFiltrees.length > pageSize" /> + </div> </div> </template> @@ -88,11 +133,19 @@ export default { currentSort: "expires_on", currentSortDir: "asc", currentPage: 1, - pageSize: 5 + pageSize: 5, + isOpen: this.openDefault } }, props: { - certifs: Array + certifs: Array, + certifStatus: String, + collapseId: String, + title: String, + openDefault: { + type: Boolean, + default: false + } }, methods: { sort(s) { @@ -100,6 +153,20 @@ export default { this.currentSortDir = this.currentSortDir === "asc" ? "desc" : "asc" } this.currentSort = s + }, + setPage() { + let page = this.$route.query["page-" + this.collapseId] + ? parseInt(this.$route.query["page-" + this.collapseId]) + : 1 + + if ( + isNaN(page) || + page > Math.ceil(this.certifsFiltrees.length / this.pageSize) || + page < 1 + ) + page = 1 + + this.currentPage = page } }, computed: { @@ -133,6 +200,33 @@ export default { let end = this.currentPage * this.pageSize if (index >= start && index < end) return true }) + }, + collapseClass() { + if (this.collapseId.includes("entraitement")) return "bg-secondary" + if (this.collapseId.includes("perimees")) return "bg-warning" + if (this.collapseId == "sent-encours") return "bg-info" + return { + "bg-success": this.certifStatus == "success", + "bg-warning": this.certifStatus == "warning", + "bg-danger": this.certifStatus == "danger" + } + }, + btnClass() { + if (this.collapseId.includes("entraitement")) return "btn-secondary" + if (this.collapseId.includes("perimees")) return "btn-warning" + if (this.collapseId == "sent-encours") return "btn-info" + return { + "btn-success": this.certifStatus == "success", + "btn-warning": this.certifStatus == "warning", + "btn-danger": this.certifStatus == "danger" + } + } + }, + mounted() { + this.setPage() + + if (this.openDefault && this.certifs.length > 0) { + document.querySelector("#" + this.collapseId).classList.add("show") } }, watch: { @@ -140,14 +234,26 @@ export default { handler(n, o) { this.search = "" } + }, + $route(to, from) { + this.setPage() } } } </script> <style lang="scss" scoped> -thead th:last-child { - padding-right: 1.5rem; - text-align: right; +tbody tr { + height: 80px; +} + +@media (min-width: 576px) { + button { + font-size: 1.3rem; + } + + tbody tr { + height: initial; + } } </style> diff --git a/components/member/Card.vue b/components/member/Card.vue index e4a6995..b0594f3 100644 --- a/components/member/Card.vue +++ b/components/member/Card.vue @@ -2,38 +2,27 @@ <div class="card member"> <div class="card-body"> <div - class="uid-wrapper d-flex align-items-center justify-content-between mb-4"> - <h2 class="h3 card-title text-center d-flex align-items-center m-0"> - <span class="text-truncate d-inline-block"> + class="d-flex align-items-center justify-content-between flex-column-reverse flex-sm-row mb-4"> + <h2 + class="h1 card-title text-center d-flex align-items-center flex-column m-0 w-100"> + <span class="text-truncate d-inline-block mw-100"> {{ hash.uid }} </span> <small><BadgeStatus class="ms-2" :membre="hash" /></small> </h2> - <button - id="favori" - class="btn btn-secondary" - v-tooltip=" - $favourites.list.includes(hash.uid) - ? $t('favoris.enregistre') - : $t('favoris.supprime') - " - @click="$favourites.toggleFavourite(hash.uid, $event)"> - <outline-user-add-icon - class="icon" - v-if="!$favourites.list.includes(hash.uid)" /> - <outline-user-remove-icon - class="icon" - v-if="$favourites.list.includes(hash.uid)" /> - </button> + <BtnFavori :uid="hash.uid" /> </div> <BtnClipboard :textContent="hash.pubkey" /> + <AlertMember :hash="hash" /> <div class="table-responsive"> <table class="table table-sm table-borderless" v-if="hash.status != 'REVOKED'"> <tbody> <tr v-if="hash.status == 'MEMBER'"> - <th scope="row">{{ $t("membre.referent.title") }} :</th> + <th scope="row" class="fw-normal"> + {{ $t("membre.referent.title") }} : + </th> <td :class="{ 'table-success': hash.sentry, @@ -43,7 +32,9 @@ </td> </tr> <tr v-if="hash.status != 'NEWCOMER'"> - <th scope="row">{{ $t("membre.qualite.title") }} :</th> + <th scope="row" class="fw-normal"> + {{ $t("membre.qualite.title") }} : + </th> <td :class="{ 'table-success': hash.quality.ratio >= 80, @@ -53,7 +44,9 @@ </td> </tr> <tr> - <th scope="row">{{ $t("membre.distance.title") }} :</th> + <th scope="row" class="fw-normal"> + {{ $t("membre.distance.title") }} : + </th> <td :class="{ 'table-success': @@ -73,7 +66,7 @@ </td> </tr> <tr> - <th scope="row"> + <th scope="row" class="fw-normal"> {{ hash.status != "MISSING" ? $t("membre.datelimadhesion") @@ -90,7 +83,9 @@ </td> </tr> <tr v-if="hash.status == 'MEMBER'"> - <th scope="row">{{ $t("membre.datemanquecertifs") }} :</th> + <th scope="row" class="fw-normal"> + {{ $t("membre.datemanquecertifs") }} : + </th> <td :class=" 'table-' + $options.filters.dateStatus(hash.certsLimit) @@ -99,7 +94,9 @@ </td> </tr> <tr v-if="hash.status == 'MEMBER'"> - <th scope="row">{{ $t("membre.dispocertif") }} :</th> + <th scope="row" class="fw-normal"> + {{ $t("membre.dispocertif") }} : + </th> <td :class="{ 'table-success': hash.minDatePassed, @@ -112,14 +109,16 @@ </td> </tr> <tr v-if="hash.status == 'MEMBER'"> - <th scope="row">{{ $t("membre.nb_certifs") }} :</th> + <th scope="row" class="fw-normal"> + {{ $t("membre.nb_certifs") }} : + </th> <td :class="{ - 'table-success': hash.sent_certifications.length <= 80, - 'table-warning': hash.sent_certifications.length > 80, - 'table-danger': hash.sent_certifications.length > 90 + 'table-success': sentCertNotExpired.length <= 80, + 'table-warning': sentCertNotExpired.length > 80, + 'table-danger': sentCertNotExpired.length > 90 }"> - {{ 100 - hash.sent_certifications.length }} + {{ 100 - sentCertNotExpired.length }} </td> </tr> </tbody> @@ -138,36 +137,16 @@ export default { }, props: { hash: Object + }, + computed: { + sentCertNotExpired() { + return this.hash.sent_certifications.filter((el) => !el.expired) + } } } </script> <style lang="scss"> -.uid-wrapper { - flex-direction: column-reverse; - - button { - margin-bottom: 1rem; - } - - .text-truncate { - max-width: 300px; - } - - @media (min-width: 576px) { - flex-direction: row; - - button { - margin-bottom: 0; - } - } -} - -#favori { - width: 50px; - height: 50px; -} - .member { .table { text-align: center; diff --git a/components/member/List.vue b/components/member/List.vue index ab1bec3..0e9538c 100644 --- a/components/member/List.vue +++ b/components/member/List.vue @@ -18,7 +18,7 @@ <th scope="col" class="d-none d-lg-table-cell p-0" - v-if="['favoris', 'search'].includes(type)" + v-if="id != 'default'" @click="sort('statut')" @keyup.enter="sort('statut')"> <BtnSort @@ -30,7 +30,7 @@ <th scope="col" class="td-quality d-none d-lg-table-cell p-0" - v-if="['favoris', 'search'].includes(type)" + v-if="['favoris', 'search'].includes(id)" @click="sort('quality')" @keyup.enter="sort('quality')"> <BtnSort @@ -42,12 +42,12 @@ <th scope="col" class="d-none d-xl-table-cell p-0" - v-if="['favoris', 'search'].includes(type)" + v-if="['favoris', 'search'].includes(id)" @click="sort('dispo')" @keyup.enter="sort('dispo')"> <BtnSort fieldName="dispo" - :title="$t('membre.dispo')" + :title="$t('membre.disponibilite')" :currentSort="currentSort" :currentSortDir="currentSortDir" /> </th> @@ -56,11 +56,11 @@ class="td-date d-none d-sm-table-cell p-0" @click="sort('date_membership')" @keyup.enter="sort('date_membership')" - v-if="['adhesion', 'favoris', 'search'].includes(type)"> + v-if="['adhesion', 'favoris', 'search'].includes(id)"> <BtnSort fieldName="date_membership" :title=" - ['certif', 'adhesion'].includes(this.type) + ['certif', 'adhesion'].includes(id) ? $t('date') : $t('membre.datelimpertestatut') " @@ -71,23 +71,23 @@ scope="col" class="td-date d-none p-0" :class="{ - 'd-sm-table-cell': type == 'certif', - 'd-md-table-cell': type != 'certif' + 'd-sm-table-cell': id == 'certif', + 'd-md-table-cell': id != 'certif' }" @click="sort('date_certs')" @keyup.enter="sort('date_certs')" - v-if="['certif', 'favoris', 'search'].includes(type)"> + v-if="['certif', 'favoris', 'search'].includes(id)"> <BtnSort fieldName="date_certs" :title=" - ['certif', 'adhesion'].includes(this.type) + ['certif', 'adhesion'].includes(id) ? $t('date') : $t('membre.datemanquecertifs') " :currentSort="currentSort" :currentSortDir="currentSortDir" /> </th> - <th v-if="type == 'favoris'" style="width: 60px"></th> + <th v-if="id == 'favoris'" style="width: 60px"></th> </tr> </thead> <tbody> @@ -95,6 +95,7 @@ v-for="member in membersSorted" :key="member.uid" tabindex="0" + :title="$t('membre.voirinfos')" @click="redirect(member.hash)" @keyup.enter="redirect(member.hash)"> <td> @@ -108,6 +109,7 @@ <div class="text-truncate">{{ member.uid }}</div> <BadgeDanger + style="width: 1.2rem" :limitDate="member.certsLimit" :memberStatus="member.status" /> </div> @@ -115,14 +117,14 @@ {{ member.pubkey.substring(0, 10) }} </div> <div - v-if="['adhesion', 'certif'].includes(type)" + v-if="['adhesion', 'certif'].includes(id)" class="d-sm-none"> - <BadgeDate :date="getDate(member)" styleDate="short" /> + <BadgeDate :date="getDate(member)" /> </div> </div> <div class="w-50 d-flex flex-column align-items-center justify-content-evenly gap-1 d-lg-none" - v-if="['favoris', 'search'].includes(type)"> + v-if="['favoris', 'search'].includes(id)"> <BadgeStatus :membre="member" class="mw-100 text-truncate" /> <BadgeQuality :quality="member.quality.ratio" /> <BadgeDispo @@ -133,19 +135,17 @@ </div> </div> </td> - <td - class="d-none d-lg-table-cell" - v-if="['favoris', 'search'].includes(type)"> + <td class="d-none d-lg-table-cell" v-if="id != 'default'"> <BadgeStatus :membre="member" /> </td> <td class="d-none d-lg-table-cell" - v-if="['favoris', 'search'].includes(type)"> + v-if="['favoris', 'search'].includes(id)"> <BadgeQuality :quality="member.quality.ratio" /> </td> <td class="d-none d-xl-table-cell" - v-if="['favoris', 'search'].includes(type)"> + v-if="['favoris', 'search'].includes(id)"> <BadgeDispo :isDispo="member.minDatePassed" :dateDispo="member.minDate" @@ -153,31 +153,32 @@ </td> <td class="d-none d-sm-table-cell" - v-if="['adhesion', 'favoris', 'search'].includes(type)"> - <BadgeDate :date="member.limitDate" styleDate="short" /> + v-if="['adhesion', 'favoris', 'search'].includes(id)"> + <BadgeDate :date="member.limitDate" /> </td> <td class="d-none" :class="{ - 'd-sm-table-cell': type == 'certif', - 'd-md-table-cell': type != 'certif' + 'd-sm-table-cell': id == 'certif', + 'd-md-table-cell': id != 'certif' }" - v-if="['certif', 'favoris', 'search'].includes(type)"> - <BadgeDate :date="member.certsLimit" styleDate="short" /> + v-if="['certif', 'favoris', 'search'].includes(id)"> + <BadgeDate :date="member.certsLimit" /> </td> - <td class="py-1" v-if="type == 'favoris'" style="width: 60px"> + <td class="py-1" v-if="id == 'favoris'" style="width: 60px"> <button class="btn btn-danger" v-if="$favourites.list.includes(member.uid)" @click="$favourites.toggleFavourite(member.uid, $event)" :title="$t('favoris.supprimer')"> - <outline-trash-icon class="icon" /> + <solid-trash-icon class="icon" aria-hidden="true" /> </button> </td> </tr> </tbody> </table> <BtnPagination + :target="id" :currentPage.sync="currentPage" :pageSize="pageSize" :arrayLength="members.length" /> @@ -199,16 +200,9 @@ export default { type: Array, required: true }, - type: { + id: { type: String, - default: "default", - validator: function (value) { - return ( - ["adhesion", "certif", "favoris", "search", "default"].indexOf( - value - ) !== -1 - ) - } + required: true }, defaultSortDir: { type: String, @@ -230,14 +224,28 @@ export default { this.currentSort = s }, getDate(member) { - if (this.type == "adhesion") return member.limitDate - if (this.type == "certif") return member.certsLimit + if (this.id == "adhesion") return member.limitDate + if (this.id == "certif") return member.certsLimit return Math.min(member.limitDate, member.certsLimit) }, getOrder(a, b, order) { if (a < b) return -1 * order if (a > b) return 1 * order return 0 + }, + setPage() { + let page = this.$route.query["page-" + this.id] + ? parseInt(this.$route.query["page-" + this.id]) + : 1 + + if ( + isNaN(page) || + page > Math.ceil(this.members.length / this.pageSize) || + page < 1 + ) + page = 1 + + this.currentPage = page } }, computed: { @@ -273,6 +281,14 @@ export default { if (index >= start && index < end) return true }) } + }, + mounted() { + this.setPage() + }, + watch: { + $route(to, from) { + this.setPage() + } } } </script> diff --git a/components/navigation/Bar.vue b/components/navigation/Bar.vue index 3e861f0..585e677 100644 --- a/components/navigation/Bar.vue +++ b/components/navigation/Bar.vue @@ -2,6 +2,7 @@ <header class="header position-fixed"> <div class="position-relative"> <button + :title="isOpen ? $t('aria.closemenu') : $t('aria.openmenu')" class="toggle btn border-secondary position-absolute p-1 ms-4" @click="toggleMenu"> <span></span> diff --git a/components/navigation/menu/Group.vue b/components/navigation/menu/Group.vue index 9d6056a..c90d22a 100644 --- a/components/navigation/menu/Group.vue +++ b/components/navigation/menu/Group.vue @@ -1,6 +1,6 @@ <template> <div class="mb-4"> - <h2 class="small text-muted text-uppercase ms-4 mb-0 pb-2"> + <h2 class="small text-muted text-uppercase ms-5 mb-0 pb-2"> {{ $t(menu.title) }} </h2> <div class="nav navbar-nav list-group list-group-flush"> @@ -10,7 +10,12 @@ v-for="item in menu.items" :key="item.path" @click.native="toggleMenu()"> - <div class="menu-item position-relative py-2">{{ $t(item.title) }}</div> + <div class="menu-item position-relative py-2"> + <component + aria-hidden="true" + :is="'solid-' + item.icon + '-icon'" + class="icon" /> {{ $t(item.title) }} + </div> </NuxtLink> </div> </div> diff --git a/components/navigation/menu/Sidebar.vue b/components/navigation/menu/Sidebar.vue index c75ebd0..90b3117 100644 --- a/components/navigation/menu/Sidebar.vue +++ b/components/navigation/menu/Sidebar.vue @@ -47,7 +47,10 @@ } $router.push(localePath('a-propos')) "> - v{{ $config.clientVersion }} | {{ $t("apropos.title") }} + <solid-terminal-icon + style="width: 0.9rem" + aria-hidden="true" /> v{{ $config.clientVersion }} | + {{ $t("apropos.title") }} </button> </div> </aside> diff --git a/graphql/cache.js b/graphql/cache.js index b8dff3a..435b892 100644 --- a/graphql/cache.js +++ b/graphql/cache.js @@ -25,6 +25,12 @@ export const cache = new InMemoryCache({ return `${object.member.hash}:${object.date}:${object.after}:${object.proba}` case "GroupId": return `${object.id.hash}` + case "CertHist": + return `${object.id.uid}:${object.hist[0].block.number}` + case "CertEvent": + return `${object.in}:${object.block.number}` + case "Block": + return `${object.number}` default: return defaultDataIdFromObject(object) // fall back to default handling } diff --git a/graphql/queries.js b/graphql/queries.js index fe68c14..671ff50 100644 --- a/graphql/queries.js +++ b/graphql/queries.js @@ -124,6 +124,12 @@ export const SEARCH_MEMBER = gql` isLeaving sentry membership_pending + all_certifiersIO { + ...IO + } + all_certifiedIO { + ...IO + } distanceE { __typename value { @@ -176,6 +182,21 @@ export const SEARCH_MEMBER = gql` pending } } + fragment IO on CertHist { + __typename + id { + ...attr + } + hist { + __typename + in + block { + __typename + number + utc0 + } + } + } ` // Pour la page parametres diff --git a/i18n/locales/de.json b/i18n/locales/de.json index 6d3c809..e3ab65f 100644 --- a/i18n/locales/de.json +++ b/i18n/locales/de.json @@ -4,9 +4,28 @@ "desc": "Aufnahme im Vertrauensnetz. Die Mitgliedschaft ist ein Jahr lang gültig und muss erneuert werden, wenn man weiterhin seine Universaldividende mitproduzieren möchte.", "title": "Mitgliedschaft" }, + "alert": { + "attenteCertifiers": "Warten auf Zertifizierer", + "attention": "Warnung", + "certStockLim": "Dieses Konto hat nur <b>{n} Zertifizierung</b> auf Lager. | Dieses Konto hat nur <b>{n} Zertifizierungen</b> auf Lager.", + "certifManquantes": "Diesem Konto fehlen <b>{n} Zertifizierung</b>, um Mitglied zu werden. | Diesem Konto fehlen <b>{n} Zertifizierungen</b>, um Mitglied zu werden.", + "certifManquantesBientot": "Dieses Konto hat bald <b>keine Zertifizierungen mehr</b>.", + "distKO": "Dieses Konto verstößt gegen die <b>Abstandsregel</b>.", + "dossierKOtitle": "Unvollständige Datei", + "dossierOK": "Die Datei für dieses Konto ist <b>vollständig</b> und wird <b>bald Mitglied</b>.", + "dossierOKtitle": "Komplette Datei", + "information": "Information", + "missing": "Dieses Konto hat seine Mitgliedschaft verloren. Er hat <b>{n} Tag</b> Zeit, um die Mitgliedschaft erneut zu beantragen, bevor er automatisch widerrufen wird. | Dieses Konto hat seine Mitgliedschaft verloren. Er hat <b>{n} Tage</b> Zeit, um die Mitgliedschaft erneut zu beantragen, bevor er automatisch widerrufen wird.", + "noMoreCert": "Dieses Konto hat <b>keine weiteren Zertifizierungen</b> auf Lager.", + "notAllCertifiersDispo": "Mindestens ein Zertifizierer ist <b>nicht verfügbar</b>.", + "notAvailable": "Dieses Mitglied ist zur Zertifizierung <b>nicht verfügbar</b>.", + "renew": "Dieses Konto muss seine Mitgliedschaft <b>erneuern</b>.", + "revoked": "Dieses Konto wurde <b>aufgehoben</b> und kann nicht länger Mitglied werden." + }, "apropos": { + "alert": "Die von der Anwendung angezeigten Daten stammen von einem <a href='https://git.duniter.org/gerard94/WotWizard' target='_blank' class='alert-link'>Wotwizard-Server mit graphQL-Technologie</a> und hängen vom abgefragten Duniter-Knoten ab. Aus diesem Grund können Daten, die noch nicht in die Blockchain geschrieben wurden, von Anwendung zu Anwendung unterschiedlich sein.", "bienvenue": "Willkommen im Wotwizard!", - "contribuer": "Der Quellcode von Wotwizard befindet sich auf <a href=\"https://git.duniter.org/clients/wotwizard-ui\" target=\"_blank\">unserem Gitlab-Server</a>. Zögern Sie nicht, dort Fehler oder Verbesserungsvorschläge auf der <a href=\"https://git.duniter.org/clients/wotwizard-ui/-/issues\" target=\"_blank\"><b>Issues</b>-Seite</a> zu melden. The API graphql source code can be found at <a href=\"https://git.duniter.org/gerard94/WotWizard\" target=\"_blank\">this URL</a>.", + "contribuer": "Der Quellcode von Wotwizard befindet sich auf <a href=\"https://git.duniter.org/clients/wotwizard-ui\" target=\"_blank\">unserem Gitlab-Server</a>. Zögern Sie nicht, dort Fehler oder Verbesserungsvorschläge auf der <a href=\"https://git.duniter.org/clients/wotwizard-ui/-/issues\" target=\"_blank\"><b>Issues</b>-Seite</a> zu melden.", "deploiement": "Deployment:", "desc": "Dieses Tool ermöglicht es Ihnen, die Bei- und Austritte des Vertrauensnetzes der freien Währung Äž1 zu verfolgen und ist das Ergebnis einer Zusammenarbeit zwischen verschiedenen Entwicklende und Nutzende der Währung. Zögern Sie nicht, sich bei ihnen mit einer Spende auf den folgenden öffentlichen Schlüssel zu bedanken:", "developpeurs": "Entwicklung:", @@ -17,7 +36,13 @@ }, "aria": { "ariane": "Brotkrümel", - "close": "Nah dran" + "clipboard": "In die Zwischenablage kopieren", + "close": "Nah dran", + "closemenu": "Menü schließen", + "openmenu": "Menü öffnen", + "scrolltotop": "Zurück nach oben", + "themedark": "Wechseln Sie zum dunklen Thema", + "themelight": "Wechseln Sie zum Lichtdesign" }, "aurevoir": "Auf Wiedersehen", "bienvenue": "Willkommen", @@ -34,8 +59,13 @@ "title": "Zentralität" }, "certification": { + "afficher": "Zertifizierungen Anzeigen", "desc": "In der Blockchain gespeicherte Bestätigung einer Person, dass sie einer anderen vertraut. Eine Zertifizierung ist zwei Jahre lang gültig.", + "enattente": "Warten auf Behandlung", + "encours": "Aktuell gültig", "envoyees": "Ausgestellte Zertifizierungen", + "masquer": "Zertifizierungen Verstecken", + "perimees": "Erneuern", "recues": "Erhaltene Zertifizierungen", "title": "Zertifizierung" }, @@ -79,6 +109,7 @@ }, "expire": "Ablaufdatum", "favoris": { + "ajouter": "Zu den Favoriten hinzufügen", "enregistre": "In den Favoriten gespeichert!", "none": "Sie haben noch keine Favoriten", "supprime": "Aus den Favoriten entfernt!", @@ -91,7 +122,8 @@ "infos": "Informationen", "inout": "Bei- und Austritte des Vertrauensnetz in den 2 letzten Tagen", "inpreparation": "In Vorbereitung", - "jours": "24 Stunden | {n} Tage", + "jours": "< 24 Stunden | {n} Tagen", + "jours_avant": "< 24h | vor {n} Tagen", "lang": "Wähle deine Sprache", "lexique": "Lexikon", "membre": { @@ -105,7 +137,8 @@ "datemanquecertifs": "Fehlende Zertifizierungen", "desc": "Einzelperson, die in das Vertrauensnetz beigetreten ist, die die fünf erforderlichen Zertifizierungen erhalten hat und die Distanzregel einhält. Ein Mitglied schöpft die Währung über seine Universaldividende mit.", "dispo": "Kann zertifizieren", - "dispocertif": "Kann zertifizieren", + "dispocertif": "Verfügbarkeit", + "disponibilite": "Disponibilidad", "distance": { "desc": "Anteil der referierenden Mitglieder, die in höchstens 5 Sprüngen im Vertrauensnetz erreichbar sind. Um Mitglied zu werden, muss man mindestens 5 Zertifizierungen sammeln, mit denen man 80% der referierenden Mitglieder in max. 5 Sprüngen erreichen kann.", "title": "Distanz" @@ -120,7 +153,8 @@ "desc": "Mitglied, das eine bestimmte Anzahl von Zertifizierungen ausgestellt UND erhalten hat, die von der Gesamtzahl der Mitglieder abhängt. Dieser Status bietet keine Vorteile, ermöglicht aber die Sicherung des Vertrauensnetzes, insbesondere durch die Distanzregel.", "title": "Referierendes Mitglied" }, - "title": "Mitglied" + "title": "Mitglied", + "voirinfos": "Mitgliedsinformationen anzeigen" }, "membres": "Mitglieder", "mot": "Wort oder Ausdruck", @@ -188,6 +222,7 @@ "lexicon": "Suche im Lexikon", "member": "Geben Sie den Anfang eines Pseudonyms oder eines öffentlichen Schlüssels ein" }, + "effacer": "Löschen Sie Ihre Suche", "title": "Ihre Suche" }, "revocation": { @@ -197,7 +232,8 @@ "revoila": "Hier sind sie wieder", "slogan": "Der Zauberer des Vertrauensnetzes", "statut": { - "bientotmanquecertif": "Wenig Zertifizierungen", + "allcertif": "5 Zertifizierungen erhalten", + "bientotmanquecertif": "Bald wenig Zertifizierungen", "manquecertif": "Zu wenig Zertifizierungen", "member": "Mitglied", "missing": "Verlorene Mitgliedschaft", @@ -211,6 +247,7 @@ }, "traitement": "In Bearbeitung", "tri": { + "action": "Sortieren", "pardate": "Nach Datum sortieren", "parmembres": "Alphabetisch sortieren" }, diff --git a/i18n/locales/en.json b/i18n/locales/en.json index e6eed8c..ad91e7a 100644 --- a/i18n/locales/en.json +++ b/i18n/locales/en.json @@ -4,9 +4,28 @@ "desc": "Registration in the web of trust. Membership is valid for one year and must be renewed if you wish to continue to co-produce your Universal Dividend", "title": "Membership" }, + "alert": { + "attenteCertifiers": "Waiting for certifiers", + "attention": "Warning", + "certStockLim": "This account has only <b>{n} certification</b> in stock. | This account has only <b>{n} certifications</b> in stock.", + "certifManquantes": "This account lacks <b>{n} certification</b> to become a member. | This account lacks <b>{n} certifications</b> to become a member.", + "certifManquantesBientot": "This account will soon <b>run out of certifications</b>.", + "distKO": "This account violates <b>the distance rule</b>.", + "dossierKOtitle": "Incomplete file", + "dossierOK": "The file for this account is <b>complete</b> and will <b>soon be a member</b>.", + "dossierOKtitle": "Complete file", + "information": "Information", + "missing": "This account has lost its membership. He has <b>{n} day</b> to reapply for membership before automatic revocation. | This account has lost its membership. He has <b>{n} days</b> to reapply for membership before automatic revocation.", + "noMoreCert": "This account has <b>no more certifications</b> in stock.", + "notAllCertifiersDispo": "One or more certifiers are <b>not available</b>.", + "notAvailable": "This member is <b>unavailable</b> to certify.", + "renew": "This account needs to <b>renew its membership</b>.", + "revoked": "This account is <b>revoked</b> and can no longer become a member." + }, "apropos": { + "alert": "The data displayed by the application comes from a <a href='https://git.duniter.org/gerard94/WotWizard' target='_blank' class='alert-link'>Wotwizard server using graphQL technology</a> and depends on the Duniter node queried. This is why data that is not yet written to the blockchain may differ from one application to another.", "bienvenue": "Welcome to Wotwizard !", - "contribuer": "The source code of the application is on <a href=\"https://git.duniter.org/clients/wotwizard-ui\" target=\"_blank\">our Gitlab server</a>. Don't hesitate to report bugs or suggestions for improvement in <a href=\"https://git.duniter.org/clients/wotwizard-ui/-/issues\" target=\"_blank\">the <b>Tickets</b> section</a>. The API graphql source code can be found at <a href=\"https://git.duniter.org/gerard94/WotWizard\" target=\"_blank\">this URL</a>.", + "contribuer": "The source code of the application is on <a href=\"https://git.duniter.org/clients/wotwizard-ui\" target=\"_blank\">our Gitlab server</a>. Don't hesitate to report bugs or suggestions for improvement in <a href=\"https://git.duniter.org/clients/wotwizard-ui/-/issues\" target=\"_blank\">the <b>Tickets</b> section</a>.", "deploiement": "Deployment :", "desc": "This tool allows you to track entries and exits from the Äž1 free currency web of trust and is the result of a collaboration between various developers and users of the currency. Do not hesitate to thank them by making a donation on the following public key:", "developpeurs": "Developers :", @@ -18,7 +37,13 @@ }, "aria": { "ariane": "Breadcrumb", - "close": "Close" + "clipboard": "Copy to clipboard", + "close": "Close", + "closemenu": "Close menu", + "openmenu": "Open menu", + "scrolltotop": "Back to top", + "themedark": "Toggle dark theme", + "themelight": "Toggle light theme" }, "aurevoir": "Goodbye to", "bienvenue": "Welcome to", @@ -35,8 +60,13 @@ "title": "Centrality" }, "certification": { + "afficher": "Display certificates", "desc": "Recognition link between an individual and another recorded in the blockchain. A certification is valid for 2 years", + "enattente": "Waiting for treatment", + "encours": "Currently valid", "envoyees": "Certificates sent", + "masquer": "Hide certificates", + "perimees": "To renew", "recues": "Certificates received ", "title": "Certification" }, @@ -80,6 +110,7 @@ }, "expire": "Expiry", "favoris": { + "ajouter": "Add to favorites", "enregistre": "Saved to favorites !", "none": "You don't have any favorites yet", "supprime": "Deleted from favourites !", @@ -92,7 +123,8 @@ "infos": "Informations", "inout": "Entries and exits of the web of trust for the last 2 days", "inpreparation": "In preparation", - "jours": "24h | {n} days", + "jours": "< 24h | {n} days", + "jours_avant": "< 24h | {n} days ago", "lang": "Choose your language", "lexique": "Lexicon", "membre": { @@ -107,6 +139,7 @@ "desc": "Individual whose membership is in progress and who has received the 5 necessary certifications allowing him to respect the distance rule. Which allows him to be co-creator of the currency via his Universal Dividend", "dispo": "Available", "dispocertif": "Available to certify", + "disponibilite": "Availablity", "distance": { "desc": "% of referring members reachable in 5 hops or less in the web of trust. To become a member, you must collect at least 5 certifications allowing you to reach 80% of the referring members in 5 jumps max.", "title": "Distance" @@ -121,7 +154,8 @@ "desc": "Member having issued AND received a certain number of certifications, variable according to the number of total members. This status does not offer any advantages but allows the security of the web of trust in particular thanks to the distance rule", "title": "Referring member" }, - "title": "Member" + "title": "Member", + "voirinfos": "See member informations" }, "membres": "Members", "mot": "Word or expression", @@ -189,6 +223,7 @@ "lexicon": "Search in the lexicon", "member": "Enter the start of a nickname or public key" }, + "effacer": "Erase your search", "title": "Your search" }, "revocation": { @@ -198,8 +233,9 @@ "revoila": "Here they are again", "slogan": "The Web of Trust’s wizard", "statut": { - "bientotmanquecertif": "Needs certifications", - "manquecertif": "Needs certifications", + "allcertif": "5 recieved certificates", + "bientotmanquecertif": "Needs certificates soon", + "manquecertif": "Needs certificates", "member": "Member", "missing": "Membership lost", "newcomer": "Future member", @@ -212,6 +248,7 @@ }, "traitement": "Ongoing treatment", "tri": { + "action": "Sort by", "pardate": "Sort by date", "parmembres": "Sort by members" }, diff --git a/i18n/locales/es.json b/i18n/locales/es.json index 527aba0..2146232 100644 --- a/i18n/locales/es.json +++ b/i18n/locales/es.json @@ -4,9 +4,28 @@ "desc": "Registro en la red de confianza. La membresÃa es válida por un año y debe renovarse si desea continuar cocreando su Dividendo Universal", "title": "MembresÃa" }, + "alert": { + "attenteCertifiers": "Esperando certificadores", + "attention": "Atención", + "certStockLim": "Esta cuenta solo tiene <b>{n} certificación</b> en stock. | Esta cuenta solo tiene <b>{n} certificaciones</b> en stock.", + "certifManquantes": "Esta cuenta carece de <b>{n} certificación</b> para convertirse en miembro. | Esta cuenta carece de <b>{n} certificaciones</b> para convertirse en miembro.", + "certifManquantesBientot": "Esta cuenta pronto <b>se quedará sin certificaciones</b>.", + "distKO": "Esta cuenta infringe la <b>regla de distancia</b>.", + "dossierKOtitle": "Archivo incompleto", + "dossierOK": "El archivo de esta cuenta está <b>completo</b> y <b>pronto será miembro</b>.", + "dossierOKtitle": "Archivo completo", + "information": "Información", + "missing": "Esta cuenta ha perdido su membresÃa. Tiene <b>{n} dÃa</b> para volver a solicitar la membresÃa antes de la revocación automática. | Esta cuenta ha perdido su membresÃa. Tiene <b>{n} dÃas</b> para volver a solicitar la membresÃa antes de la revocación automática.", + "noMoreCert": "Esta cuenta <b>no tiene más certificaciones</b> en stock.", + "notAllCertifiersDispo": "Uno o más certificadores <b>no están disponibles</b>.", + "notAvailable": "Este miembro <b>no está disponible</b> para certificar.", + "renew": "Esta cuenta necesita <b>renovar su membresÃa</b>.", + "revoked": "Esta cuenta está <b>revocada</b> y ya no puede convertirse en miembro." + }, "apropos": { + "alert": "Los datos que muestra la aplicación provienen de <a href='https://git.duniter.org/gerard94/WotWizard' target='_blank' class='alert-link'>un servidor Wotwizard con tecnologÃa graphQL</a> y dependen del nodo Duniter consultado. Esta es la razón por la que los datos que aún no se escriben en la cadena de bloques pueden diferir de una aplicación a otra.", "bienvenue": "¡Bienvenido a Wotwizard!", - "contribuer": "El código fuente de la aplicación está en <a href=\"https://git.duniter.org/clients/wotwizard-ui\" target=\"_blank\">nuestro servidor Gitlab</a>. No dudes en reportar errores o sugerencias de mejora en <a href=\"https://git.duniter.org/clients/wotwizard-ui/-/issues\" target=\"_blank\">la sección de <b>Tickets</b></a>. El código de la API Graphqlse encuentra en <a href=\"https://git.duniter.org/gerard94/WotWizard\" target=\"_blank\">esta URL</a>.", + "contribuer": "El código fuente de la aplicación está en <a href=\"https://git.duniter.org/clients/wotwizard-ui\" target=\"_blank\">nuestro servidor Gitlab</a>. No dudes en reportar errores o sugerencias de mejora en <a href=\"https://git.duniter.org/clients/wotwizard-ui/-/issues\" target=\"_blank\">la sección de <b>Tickets</b></a>.", "deploiement": "Despliegue :", "desc": "Esta herramienta le permite realizar un seguimiento de las entradas y salidas de la red de confianza de la moneda libre Äž1 y es el resultado de una colaboración entre varios desarrolladores y usuarios de la moneda. No dude en agradecerles haciendo una donación a la siguiente llave pública:", "developpeurs": "Desarrolladores :", @@ -18,7 +37,13 @@ }, "aria": { "ariane": "Hilo de Ariadna", - "close": "Cerrar" + "clipboard": "Copiar al portapapeles", + "close": "Cerrar", + "closemenu": "Cerrar menú", + "openmenu": "Menú abierto", + "scrolltotop": "Volver arriba", + "themedark": "Cambiar a tema oscuro", + "themelight": "Cambiar a tema claro" }, "aurevoir": "Salen", "bienvenue": "Entran", @@ -35,8 +60,13 @@ "title": "Centralidad" }, "certification": { + "afficher": "Para mostrar certificaciones", "desc": "VÃnculo de reconocimiento entre un individuo y otro registrado en la cadena de bloques. Una certificación es válida por 2 años.", + "enattente": "Esperando tratamiento", + "encours": "Actualmente válido", "envoyees": "Certificaciones enviadas", + "masquer": "Esconder certificaciones", + "perimees": "Renovar", "recues": "Certificaciones recibidas ", "title": "Certificacione" }, @@ -80,6 +110,7 @@ }, "expire": "Expiración", "favoris": { + "ajouter": "Agregar a los favoritos", "enregistre": "¡Guardado en favoritos!", "none": "Aún no tienes favoritos", "supprime": "¡Eliminado de favoritos!", @@ -92,7 +123,8 @@ "infos": "Informaciones", "inout": "Entradas y salidas de la red de confianza en los últimos 2 dÃas", "inpreparation": "En preparación", - "jours": "24 horas | {n} dÃas", + "jours": "< 24 horas | {n} dÃas", + "jours_avant": "< 24h | hay {n} dÃas", "lang": "Elige tu idioma", "lexique": "Léxico", "membre": { @@ -107,6 +139,7 @@ "desc": "Individuo cuya candidatura está en curso y que ha recibido las 5 certificaciones necesarias y que le permitan respetar la regla de la distancia. Lo que le permite ser co-creador de la moneda a través de su Dividendo Universal", "dispo": "Disponible", "dispocertif": "Disponible para certificar", + "disponibilite": "Disponibilidad", "distance": { "desc": "% de miembros de referencia o control accesibles en 5 pasos o menos en la Red de Confianza. Para convertirse en miembro, debe obtener al menos 5 certificaciones que le permitan llegar al 80% de los miembros de referencia o control en 5 pasos como máximo.", "title": "Distancia" @@ -121,7 +154,8 @@ "desc": "Miembro que haya emitido Y recibido un número determinado de certificaciones, variable según el número total de miembros. Este estatus no ofrece ninguna ventaja o privilegio personal pero permite la seguridad de la red de confianza en particular gracias a la regla de distancia", "title": "Miembro de referencia o control" }, - "title": "Miembro" + "title": "Miembro", + "voirinfos": "Ver información de miembros" }, "membres": "Miembros", "mot": "Palabra o expresión", @@ -189,6 +223,7 @@ "lexicon": "Buscar en el léxico", "member": "Introduce el comienzo de un pseudónimo o llave pública" }, + "effacer": "Limpia tu búsqueda", "title": "Buscar" }, "revocation": { @@ -198,6 +233,7 @@ "revoila": "Regresan", "slogan": "El mago de la Red de Confianza", "statut": { + "allcertif": "5 certificaciones recibidas", "bientotmanquecertif": "Necesitará certificaciones", "manquecertif": "Faltan certificaciones", "member": "Miembro", @@ -212,8 +248,9 @@ }, "traitement": "Tratamiento en curso", "tri": { + "action": "Ordenar por", "pardate": "Ordenar por fecha", - "parmembres": "Clasificar por miembros" + "parmembres": "Ordenar por miembros" }, "trm": { "desc": "TeorÃa que demuestra que es posible una moneda libre con una propiedad de invariancia y que la solución a este problema es única: Una moneda es libre si y solo si es coproducida por igual en el espacio y en el tiempo por todos sus miembros", diff --git a/i18n/locales/fr.json b/i18n/locales/fr.json index 6bf6c4d..6d9e057 100644 --- a/i18n/locales/fr.json +++ b/i18n/locales/fr.json @@ -4,9 +4,28 @@ "desc": "Inscription dans la toile de confiance. L'adhésion est valable un an et doit être renouvelée si on souhaite continuer à co-produire son Dividende Universel", "title": "Adhésion" }, + "alert": { + "attenteCertifiers": "En attente des certificateurs", + "attention": "Attention", + "certStockLim": "Ce compte n'a plus que <b>{n} certification</b> en stock. | Ce compte n'a plus que <b>{n} certifications</b> en stock.", + "certifManquantes": "Il manque <b>{n} certification</b> à ce compte pour devenir membre. | Il manque <b>{n} certifications</b> à ce compte pour devenir membre.", + "certifManquantesBientot": "Ce compte va bientôt <b>manquer de certifications</b>.", + "distKO": "Ce compte ne respecte pas <b>la règle de distance</b>.", + "dossierKOtitle": "Dossier incomplet", + "dossierOK": "Le dossier de ce compte est <b>complet</b> et sera <b>bientôt membre</b>.", + "dossierOKtitle": "Dossier complet", + "information": "Information", + "missing": "Ce compte a perdu son adhésion. Il lui reste <b>{n} jour</b> pour refaire une demande d'adhésion avant la révocation automatique. | Ce compte a perdu son adhésion. Il lui reste <b>{n} jours</b> pour refaire une demande d'adhésion avant la révocation automatique.", + "noMoreCert": "Ce compte n'a <b>plus de certifications</b> en stock.", + "notAllCertifiersDispo": "Un ou plusieurs certificateurs ne sont <b>pas disponibles</b>.", + "notAvailable": "Ce membre n'est <b>pas disponible</b> pour certifier.", + "renew": "Ce compte doit <b>renouveler son adhésion</b>.", + "revoked": "Ce compte est <b>révoqué</b> et ne peut plus devenir membre." + }, "apropos": { + "alert": "Les données affichées par l'application sont issues d'un <a href='https://git.duniter.org/gerard94/WotWizard' target='_blank' class='alert-link'>serveur Wotwizard utilisant la technologie graphQL</a> et dépendent du noeud Duniter interrogé. C'est pourquoi les données qui ne sont pas encore inscrites en blockchain peuvent différer d'une application à une autre.", "bienvenue": "Bienvenue dans Wotwizard !", - "contribuer": "Le code source de l'application se trouve sur <a href=\"https://git.duniter.org/clients/wotwizard-ui\" target=\"_blank\">notre serveur Gitlab</a>. N'hésitez pas à y remonter les bugs ou les propositions d'amélioration dans <a href=\"https://git.duniter.org/clients/wotwizard-ui/-/issues\" target=\"_blank\">la section <b>Tickets</b></a>. Le code source de l'API graphQL se trouve à <a href=\"https://git.duniter.org/gerard94/WotWizard\" target=\"_blank\">cette adresse</a>.", + "contribuer": "Le code source de l'application se trouve sur <a href=\"https://git.duniter.org/clients/wotwizard-ui\" target=\"_blank\">notre serveur Gitlab</a>. N'hésitez pas à y remonter les bugs ou les propositions d'amélioration dans <a href=\"https://git.duniter.org/clients/wotwizard-ui/-/issues\" target=\"_blank\">la section <b>Tickets</b></a>.", "deploiement": "Déploiement :", "desc": "Cet outil vous permet de suivre les entrées et les sorties de la toile de confiance de la monnaie libre Äž1 et est le fruit d'une collaboration entre différents développeurs et utilisateurs de la monnaie. N'hésitez pas à les remercier en faisant un don sur la clé publique suivante:", "developpeurs": "Développeurs :", @@ -18,7 +37,13 @@ }, "aria": { "ariane": "Fil d'ariane", - "close": "Fermer" + "clipboard": "Copier dans le presse-papier", + "close": "Fermer", + "closemenu": "Fermer le menu", + "openmenu": "Ouvrir le menu", + "scrolltotop": "Retour en haut", + "themedark": "Basculer sur le thème foncé", + "themelight": "Basculer sur le thème clair" }, "aurevoir": "Au revoir à ", "bienvenue": "Bienvenue à ", @@ -35,8 +60,13 @@ "title": "Centralité" }, "certification": { + "afficher": "Afficher les certifications", "desc": "Lien de reconnaissance entre un individu et un autre enregistré dans la blockchain. Une certification est valable 2 ans", + "enattente": "En attente de traitement", + "encours": "En cours de validité", "envoyees": "Certifications envoyées", + "masquer": "Masquer les certifications", + "perimees": "A renouveler", "recues": "Certifications reçues ", "title": "Certification" }, @@ -80,6 +110,7 @@ }, "expire": "Expiration", "favoris": { + "ajouter": "Ajouter aux favoris", "enregistre": "Enregistré dans les favoris !", "none": "Vous n'avez pas encore de favoris", "supprime": "Supprimé des favoris !", @@ -92,7 +123,8 @@ "infos": "Informations", "inout": "Entrées et sorties de la toile de confiance des 2 derniers jours", "inpreparation": "En préparation", - "jours": "24h | {n} jours", + "jours": "< 24h | {n} jours", + "jours_avant": "< 24h | il y a {n} jours", "lang": "Choisissez votre langue", "lexique": "Lexique", "membre": { @@ -107,6 +139,7 @@ "desc": "Individu dont l'adhésion est en cours et ayant reçu les 5 certifications nécessaires lui permettant de respecter la règle de distance. Ce qui lui permet d'être co-créateur de la monnaie via son Dividende Universel", "dispo": "Disponible", "dispocertif": "Disponible pour certifier", + "disponibilite": "Disponibilité", "distance": { "desc": "% des membres référents atteignables en 5 sauts ou moins dans la toile de confiance. Pour devenir membre, il faut récolter au minimum 5 certifications permettant de rejoindre 80% des membres référents en 5 sauts max", "title": "Distance" @@ -121,7 +154,8 @@ "desc": "Membre ayant émis ET reçu un certain nombre de certifications, variable en fonction du nombre de membres total. Ce statut n'offre pas d'avantages mais permet la sécurisation de la toile de confiance notammen grâce à la règle de distance", "title": "Membre référent" }, - "title": "Membre" + "title": "Membre", + "voirinfos": "Voir les informations du membre" }, "membres": "Membres", "mot": "Mot ou expression", @@ -189,6 +223,7 @@ "lexicon": "Recherchez dans le lexique", "member": "Saisissez le début d'un pseudo ou d'une clé publique" }, + "effacer": "Effacer la recherche", "title": "Votre recherche" }, "revocation": { @@ -198,7 +233,8 @@ "revoila": "Les revoilà ", "slogan": "Le magicien de la Toile de Confiance", "statut": { - "bientotmanquecertif": "En manque de certifications", + "allcertif": "5 certifications reçues", + "bientotmanquecertif": "Bientôt en manque de certifications", "manquecertif": "En manque de certifications", "member": "Membre", "missing": "Adhésion perdue", @@ -212,6 +248,7 @@ }, "traitement": "En cours de traitement", "tri": { + "action": "Trier par", "pardate": "Tri par date", "parmembres": "Tri par membres" }, diff --git a/layouts/default.vue b/layouts/default.vue index 1eaca3d..7c8e85b 100644 --- a/layouts/default.vue +++ b/layouts/default.vue @@ -1,12 +1,13 @@ <template> - <div class="app"> + <div class="app pb-3"> <NavigationBar :breadcrumb="breadcrumb" :menus="menus" /> <Nuxt /> <a class="scrollToTop fade bg-secondary position-fixed text-white d-flex align-items-center justify-content-center rounded" + :title="$t('aria.scrolltotop')" href="#" @click="scrollToTop($event)"> - <outline-chevron-double-up-icon class="icon" /> + <solid-chevron-double-up-icon class="icon" aria-hidden="true" /> </a> </div> </template> @@ -17,26 +18,39 @@ export default { return { breadcrumb: [], // Les title correspondent aux chaînes de traduction dans /i18n/locales + // Les icônes disponibles sont ici : https://vue-hero-icons.netlify.app/ menus: [ { title: "wot.title", items: [ - { path: "/membres", title: "membres" }, - { path: "/favoris", title: "favoris.title" } + { path: "/membres", title: "membres", icon: "search" }, + { path: "/favoris", title: "favoris.title", icon: "user-group" } ] }, { title: "previsions.title", items: [ - { path: "/previsions", title: "futuremembers" }, - { path: "/previsions/futures_sorties", title: "futureexits" } + { + path: "/previsions", + title: "futuremembers", + icon: "login" + }, + { + path: "/previsions/futures_sorties", + title: "futureexits", + icon: "logout" + } ] }, { title: "infos", items: [ - { path: "/lexique", title: "lexique" }, - { path: "/parametres", title: "params.title" } + { + path: "/lexique", + title: "lexique", + icon: "question-mark-circle" + }, + { path: "/parametres", title: "params.title", icon: "adjustments" } ] } ] @@ -72,11 +86,11 @@ export default { :root { --txt-primary-color: #000; --txt-secondary-color: #435770; - --txt-muted-color: #5e7690; + --txt-muted-color: #5b728e; --txt-link: #6e7881; - --bg-primary-color: #fff; - --bg-secondary-color: #e9ecef; - --bg-menu-color: #fafafa; + --bg-primary-color: #e9ecef; + --bg-secondary-color: #dee2e6; + --bg-menu-color: #ced4da; --border-color: #d8d8d8; } @@ -129,7 +143,6 @@ main { width: 50px; height: 50px; opacity: 0.7; - transition: all 0.3s ease-in; &.show { right: 1rem; diff --git a/nuxt.config.js b/nuxt.config.js index d7b5715..c683b76 100644 --- a/nuxt.config.js +++ b/nuxt.config.js @@ -44,7 +44,7 @@ export default { // https://go.nuxtjs.dev/pwa "@nuxtjs/pwa", // https://github.com/whardier/nuxt-hero-icons - "@nuxt-hero-icons/outline/nuxt" + "@nuxt-hero-icons/solid/nuxt" ], // Modules: https://go.nuxtjs.dev/config-modules diff --git a/package.json b/package.json index 070623a..ba506dc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wotwizard-ui", - "version": "1.0.1", + "version": "2.0.0", "private": true, "scripts": { "dev": "nuxt", @@ -15,7 +15,7 @@ "nuxt-generate": "nuxt generate" }, "dependencies": { - "@nuxt-hero-icons/outline": "^1.0.1", + "@nuxt-hero-icons/solid": "^1.0.1", "@nuxtjs/apollo": "^4.0.1-rc.5", "@nuxtjs/i18n": "^7.2.0", "@nuxtjs/pwa": "^3.3.5", diff --git a/pages/a-propos.vue b/pages/a-propos.vue index 8323121..d34d076 100644 --- a/pages/a-propos.vue +++ b/pages/a-propos.vue @@ -1,10 +1,17 @@ <template> <main class="container"> <h2 class="display-5 my-5">{{ $t("apropos.bienvenue") }}</h2> + <AlertDefault + icon="information-circle" + type="info" + :title="$t('alert.information')"> + <span v-html="$t('apropos.alert')"></span> + </AlertDefault> <p class="lead"> {{ $t("apropos.desc") }} </p> <BtnClipboard textContent="78ZwwgpgdH5uLZLbThUQH7LKwPgjMunYfLiCfUCySkM8" /> + <p v-html="$t('apropos.contribuer')"></p> <hr class="my-4" /> <h3 class="h4 mb-4">{{ $t("apropos.participants") }}</h3> <div class="row"> @@ -19,7 +26,7 @@ <NuxtLink v-for="contrib in cat.contrib" :key="contrib" - class="list-group-item list-group-item-action" + class="list-group-item list-group-item-action list-group-item-dark" :to="chemin(contrib)"> {{ contrib }} </NuxtLink> @@ -41,7 +48,6 @@ </div> </div> </div> - <p v-html="$t('apropos.contribuer')"></p> </main> </template> diff --git a/pages/favoris.vue b/pages/favoris.vue index b1d8f6b..fd2387d 100644 --- a/pages/favoris.vue +++ b/pages/favoris.vue @@ -1,13 +1,15 @@ <template> <main class="container"> <h2 class="text-center my-5 font-weight-light"> - {{ $t("favoris.title") }} + <solid-user-group-icon style="width: 2rem" aria-hidden="true" /> {{ + $t("favoris.title") + }} </h2> <NavigationLoader :isLoading="$apollo.queries.favoris.loading" /> <div class="row"> <div class="col"> <transition name="fade"> - <Alerte type="danger" :error="error" /> + <AlertDefault type="danger" v-if="error">{{ error }}</AlertDefault> </transition> <transition name="fade"> <div @@ -16,13 +18,14 @@ "> <BtnSearch v-if="favoris.length > 5" + @erase="search = ''" v-model="search" :help="$t('recherche.desc.favourites')" class="col-sm-7 col-md-6 col-lg-5 col-xl-4 mx-auto mb-4" /> <MemberList defaultSort="date_membership" :members="filteredFavoris" - type="favoris" /> + id="favoris" /> </div> </transition> <transition name="fade"> diff --git a/pages/index.vue b/pages/index.vue index e57d641..dd7acdb 100644 --- a/pages/index.vue +++ b/pages/index.vue @@ -1,24 +1,40 @@ <template> <main class="container"> - <h2 class="text-center my-5 font-weight-light">{{ $t("inout") }}</h2> + <h2 class="text-center my-5 font-weight-light"> + <solid-home-icon style="width: 1.5rem" aria-hidden="true" /> {{ + $t("inout") + }} + </h2> <NavigationLoader :isLoading="$apollo.queries.newMembers.loading" /> <transition name="fade"> - <Alerte type="danger" :error="error" /> + <AlertDefault type="danger" v-if="error">{{ error }}</AlertDefault> </transition> <transition name="fade"> <div class="result" v-if="newMembers"> <div class="row text-center"> <div class="col-md-6 col-lg-4 mb-5"> - <h2 class="h4 text-success">{{ $t("bienvenue") }}</h2> - <MemberList :members="newMembers['entrees']" /> + <h2 class="h4 text-success"> + <solid-login-icon class="icon" aria-hidden="true" /> {{ + $t("bienvenue") + }} + </h2> + <MemberList id="entrees" :members="newMembers['entrees']" /> </div> <div class="col-md-6 col-lg-4 mb-5"> - <h2 class="h4 text-danger">{{ $t("aurevoir") }}</h2> - <MemberList :members="newMembers['sorties']" /> + <h2 class="h4 text-danger"> + <solid-logout-icon class="icon" aria-hidden="true" /> {{ + $t("aurevoir") + }} + </h2> + <MemberList id="sorties" :members="newMembers['sorties']" /> </div> <div class="col-lg-4 mb-5"> - <h2 class="h4 text-info">{{ $t("revoila") }}</h2> - <MemberList :members="newMembers['renew']" /> + <h2 class="h4 text-info"> + <solid-reply-icon class="icon" aria-hidden="true" /> {{ + $t("revoila") + }} + </h2> + <MemberList id="renew" :members="newMembers['renew']" /> </div> </div> </div> diff --git a/pages/lexique.vue b/pages/lexique.vue index 9061987..9072a44 100644 --- a/pages/lexique.vue +++ b/pages/lexique.vue @@ -2,8 +2,15 @@ <main class="container"> <div class="rech-lexique row pb-3"> <div class="col-md-6 mx-auto"> - <h2 class="text-center font-weight-light">{{ $t("lexique") }}</h2> - <BtnSearch v-model="search" :help="$t('recherche.desc.lexicon')" /> + <h2 class="text-center font-weight-light"> + <solid-question-mark-circle-icon + style="width: 2rem" + aria-hidden="true" /> {{ $t("lexique") }} + </h2> + <BtnSearch + @erase="search = ''" + v-model="search" + :help="$t('recherche.desc.lexicon')" /> </div> </div> <div class="table-responsive mt-1"> diff --git a/pages/membre.vue b/pages/membre.vue index 809a0e3..864b386 100644 --- a/pages/membre.vue +++ b/pages/membre.vue @@ -2,7 +2,7 @@ <main> <NavigationLoader :isLoading="$apollo.queries.idFromHash.loading" /> <transition name="fade"> - <Alerte type="danger" :error="error" /> + <AlertDefault type="danger" v-if="error">{{ error }}</AlertDefault> </transition> <transition name="fade"> <div v-if="idFromHash && !$apollo.queries.idFromHash.loading"> @@ -13,48 +13,7 @@ </div> </div> </div> - <div class="container"> - <div class="row mt-3" v-if="idFromHash.status != 'REVOKED'"> - <div class="col-sm-10 col-md-8 col-lg-5 mb-3 mx-auto"> - <h3 - class="h5 text-center" - :class="{ - 'text-success': - ['NEWCOMER', 'MISSING'].includes(idFromHash.status) && - idFromHash.received_certifications.length >= 5, - 'text-danger': - ['NEWCOMER', 'MISSING'].includes(idFromHash.status) && - idFromHash.received_certifications.length < 5 - }"> - {{ $t("certification.recues") }} ({{ nbCertifsReceived - }}<span v-if="nbCertifsPendingReceived != 0">{{ - " + " + nbCertifsPendingReceived - }}</span - >) - <BadgeDanger - :limitDate="idFromHash.certsLimit" - :memberStatus="idFromHash.status" /> - </h3> - <CertifGroup - :certifs="idFromHash.received_certifications" - type="received" /> - </div> - <div - class="col-sm-10 col-md-8 col-lg-5 mx-auto" - v-if="['MISSING', 'MEMBER'].includes(idFromHash.status)"> - <h3 class="h5 text-center"> - {{ $t("certification.envoyees") }} ({{ nbCertifsSent - }}<span v-if="nbCertifsPendingSent != 0">{{ - " + " + nbCertifsPendingSent - }}</span - >) - </h3> - <CertifGroup - :certifs="idFromHash.sent_certifications" - type="sent" /> - </div> - </div> - </div> + <CertifAll :idFromHash="idFromHash" /> </div> </transition> </main> @@ -90,11 +49,77 @@ export default { variables() { return { hash: this.$route.query.hash } }, + update(data) { + let idFromHash = data.idFromHash + let received_certifications = idFromHash.received_certifications.map( + (certif) => ({ + ...certif, + ...certif.from + }) + ) + let sent_certifications = idFromHash.sent_certifications.map( + (certif) => ({ + ...certif, + ...certif.to + }) + ) + + return { + ...idFromHash, + sent_certifications: this.compileArrays( + idFromHash.all_certifiedIO, + sent_certifications + ), + received_certifications: this.compileArrays( + idFromHash.all_certifiersIO, + received_certifications + ) + } + }, error(err) { this.error = err.message } } }, + methods: { + compileArrays(allIO, actualIO) { + let all = allIO.map(function (CertHist) { + return { + ...CertHist.id, + dateout: CertHist.hist + .filter((CertEvent) => !CertEvent.in) + .reduce( + function (prev, current) { + return prev.block.utc0 > current.block.utc0 ? prev : current + }, + { block: { utc0: 0 } } + ).block.utc0 + } + }) + + all.forEach((certifier) => { + let isFound = false + + for (const certif of actualIO) { + if (certifier.uid == certif.uid) { + isFound = true + break + } + } + + if (!isFound && certifier.status != "REVOKED") { + actualIO.push({ + __typename: "Certification", + expires_on: certifier.dateout, + ...certifier, + expired: true + }) + } + }) + + return actualIO + } + }, nuxtI18n: { paths: { fr: "/membre", @@ -102,36 +127,6 @@ export default { es: "/miembro" } }, - computed: { - classWarning() { - return { - "text-danger": !this.idFromHash.certsLimit, - "text-warning": - this.$options.filters.dateStatus(this.idFromHash.certsLimit) == - "warning" - } - }, - nbCertifsSent() { - return this.idFromHash.sent_certifications.filter((el) => { - return el.pending == false - }).length - }, - nbCertifsReceived() { - return this.idFromHash.received_certifications.filter((el) => { - return el.pending == false - }).length - }, - nbCertifsPendingSent() { - return this.idFromHash.sent_certifications.filter((el) => { - return el.pending == true - }).length - }, - nbCertifsPendingReceived() { - return this.idFromHash.received_certifications.filter((el) => { - return el.pending == true - }).length - } - }, mounted() { $nuxt.$emit("changeRoute", this.breadcrumb) }, diff --git a/pages/membres/index.vue b/pages/membres/index.vue index 6fbe0b0..7d7d864 100644 --- a/pages/membres/index.vue +++ b/pages/membres/index.vue @@ -1,9 +1,14 @@ <template> <main class="container"> - <h2 class="text-center my-5 font-weight-light">{{ $t("membres") }}</h2> + <h2 class="text-center my-5 font-weight-light"> + <solid-search-icon style="width: 2rem" aria-hidden="true" /> {{ + $t("membres") + }} + </h2> <div class="row mb-4"> <div class="col-sm-8 col-md-6 col-lg-5 m-auto"> <BtnSearch + @erase="param = ''" v-model="param" :help="$t('recherche.desc.member')" @keyup="save" /> @@ -11,7 +16,7 @@ </div> <NavigationLoader :isLoading="$apollo.queries.idSearch.loading" /> <transition name="fade"> - <Alerte type="danger" :error="error" /> + <AlertDefault type="danger" v-if="error">{{ error }}</AlertDefault> </transition> <transition name="fade"> <div @@ -20,7 +25,7 @@ idSearch && param.length > 2 && !$apollo.queries.idSearch.loading "> <div class="col m-auto"> - <MemberList :members="idSearch.ids" type="search" /> + <MemberList :members="idSearch.ids" id="search" /> </div> </div> </transition> diff --git a/pages/parametres.vue b/pages/parametres.vue index ed8afe4..9d36ef3 100644 --- a/pages/parametres.vue +++ b/pages/parametres.vue @@ -1,9 +1,13 @@ <template> <main class="container"> - <h2 class="text-center my-5 font-weight-light">{{ $t("params.title") }}</h2> + <h2 class="text-center my-5 font-weight-light"> + <solid-adjustments-icon style="width: 2rem" aria-hidden="true" /> {{ + $t("params.title") + }} + </h2> <NavigationLoader :isLoading="$apollo.queries.allParameters.loading" /> <transition name="fade"> - <Alerte type="danger" :error="error" /> + <AlertDefault type="danger" v-if="error">{{ error }}</AlertDefault> </transition> <transition name="fade"> <div class="table-responsive" v-if="allParameters"> diff --git a/pages/previsions/futures_sorties.vue b/pages/previsions/futures_sorties.vue index eb0e97a..88cd908 100644 --- a/pages/previsions/futures_sorties.vue +++ b/pages/previsions/futures_sorties.vue @@ -1,6 +1,10 @@ <template> <main class="container"> - <h2 class="text-center my-5 font-weight-light">{{ $t("futureexits") }}</h2> + <h2 class="text-center my-5 font-weight-light"> + <solid-logout-icon style="width: 2rem" aria-hidden="true" /> {{ + $t("futureexits") + }} + </h2> <div class="form-check form-switch mb-4 d-flex justify-content-center"> <input type="checkbox" @@ -15,7 +19,7 @@ </div> <NavigationLoader :isLoading="$apollo.loading" /> <transition name="fade"> - <Alerte type="danger" :error="error" /> + <AlertDefault type="danger" v-if="error">{{ error }}</AlertDefault> </transition> <transition name="fade"> <div class="row" v-if="!$apollo.loading"> @@ -26,7 +30,7 @@ <MemberList defaultSort="date_membership" :members="memEnds" - type="adhesion" /> + id="adhesion" /> </div> <div class="col-md-6 mb-5" v-if="certEnds"> <h2 class="h4 text-danger text-center text-truncate"> @@ -35,7 +39,7 @@ <MemberList defaultSort="date_certs" :members="certEnds" - type="certif" /> + id="certif" /> </div> </div> </transition> diff --git a/pages/previsions/index.vue b/pages/previsions/index.vue index 1f3810d..ec0d9e5 100644 --- a/pages/previsions/index.vue +++ b/pages/previsions/index.vue @@ -2,12 +2,14 @@ <main class="container"> <NavigationLoader :isLoading="$apollo.queries.wwResult.loading" /> <transition name="fade"> - <Alerte type="danger" :error="error" /> + <AlertDefault type="danger" v-if="error">{{ error }}</AlertDefault> </transition> <transition name="fade"> <div v-if="wwResult"> <h2 class="text-center my-5 font-weight-light"> - {{ $t("previsions.title") }} + <solid-login-icon style="width: 2rem" aria-hidden="true" /> {{ + $t("previsions.title") + }} <small ><span class="badge bg-secondary">{{ $tc("dossier.attente", wwResult.dossiers_nb) @@ -52,6 +54,7 @@ v-for="forecast in wwResult.forecastsByNames" :key="forecast.member.uid" tabindex="0" + :title="$t('membre.voirinfos')" @click=" $router.push( localePath({ @@ -130,6 +133,7 @@ <div class="list-group rounded-0"> <nuxt-link class="list-group-item list-group-item-action border-0 d-flex justify-content-between align-items-center" + :title="$t('membre.voirinfos')" :to=" localePath('/membre/?hash=' + member.member.hash) " diff --git a/plugins/bootstrap.js b/plugins/bootstrap.js index 2e44162..86f2fc2 100644 --- a/plugins/bootstrap.js +++ b/plugins/bootstrap.js @@ -1,5 +1,7 @@ import Vue from "vue" import Tooltip from "~/node_modules/bootstrap/js/dist/tooltip" +import Collapse from "~/node_modules/bootstrap/js/dist/collapse" +import Alert from "~/node_modules/bootstrap/js/dist/alert" Vue.directive("tooltip", function (el, binding) { let tooltip = new Tooltip(el, { -- GitLab