Skip to content
Snippets Groups Projects

Compare revisions

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

Source

Select target project
No results found

Target

Select target project
  • clients/wotwizard-ui
  • manutopik/wotwizard-ui
  • wellno1/wotwizard-ui
3 results
Show changes
Showing
with 1702 additions and 482 deletions
<template>
<span
class="badge"
:class="{
'bg-success': isDispo,
'bg-danger': !isDispo
}">
<span v-if="isDispo">{{ $t("membre.dispo") }}</span>
<span v-else
>&nbsp;{{
dateDispo == null
? "N/A"
: nbPendingCertifs +
" (&nbsp;" +
$d(new Date(dateDispo * 1000)) +
")"
}}
</span>
</span>
</template>
<script>
export default {
props: {
isDispo: {
type: Boolean
},
dateDispo: {
type: Number
},
certifs: {
type: Array,
required: true
}
},
computed: {
nbPendingCertifs() {
return this.certifs.filter((el) => {
return el.pending == true
}).length
}
}
}
</script>
<template>
<small>
<span class="badge"
:class="{
'badge-success': quality>=80,
'badge-warning': quality<80,
}">
{{ Math.round(quality*100)/100 }}
</span>
</small>
<span
class="badge"
:class="{
'bg-success': quality >= 80,
'bg-warning': quality < 80
}">
{{ Math.round(quality * 100) / 100 }}
</span>
</template>
<script>
export default {
props: {
quality: {
type: Number,
required: true
}
}
props: {
quality: {
type: Number,
required: true
}
}
}
</script>
\ No newline at end of file
</script>
<template>
<small>
<span class="badge" :class="this.displayStatus(membre).class">
{{ this.displayStatus(membre).str }}
</span>
</small>
<span class="badge" :class="displayStatus(membre).class">
{{ this.displayStatus(membre).str }}
</span>
</template>
<script>
export default {
props: {
membre: {
type: Object,
required: true
}
},
methods: {
displayStatus: function(member){
switch (member.status) {
case 'NEWCOMER':
return {str: this.$i18n.t('statut.newcomer'),class: 'badge-info'}
case 'MISSING':
return {str: this.$i18n.t('statut.missing'),class: 'badge-danger'}
case 'MEMBER':
if (this.$options.filters.dateStatus(member.limitDate) == 'warning') {
return {str: this.$i18n.t('statut.renew'),class: 'badge-warning'}
} else {
return {str: this.$i18n.t('statut.member'),class: 'badge-success'}
}
case 'REVOKED':
return {str: this.$i18n.t('statut.revoked'),class: 'badge-secondary'}
default:
return 'N/A'
}
}
}
props: {
membre: {
type: Object,
required: true
}
},
methods: {
displayStatus: function (member) {
switch (member.status) {
case "NEWCOMER":
return { str: this.$i18n.t("statut.newcomer"), class: "bg-info" }
case "MISSING":
return { str: this.$i18n.t("statut.missing"), class: "bg-danger" }
case "MEMBER":
return {
str: this.$i18n.t("statut.member"),
class: "bg-success"
}
case "RENEW":
return { str: this.$i18n.t("statut.renew"), class: "bg-warning" }
case "REVOKED":
return {
str: this.$i18n.t("statut.revoked"),
class: "bg-secondary"
}
default:
return "N/A"
}
}
}
}
</script>
\ No newline at end of file
</script>
<template>
<div class="clipboard input-group mb-3 mx-auto">
<button
id="btncopy"
class="btn btn-secondary px-4 py-1"
type="button"
v-tooltip-click="$t('copie') + ' !'"
@click="copyText">
<solid-share-icon class="icon" aria-hidden="true" />
<span class="visually-hidden">{{ $t("aria.clipboard") }}</span>
</button>
<input
type="text"
class="form-control text-truncate"
:value="textContent"
disabled />
</div>
</template>
<script>
export default {
props: {
textContent: {
type: String,
required: true
}
},
methods: {
copyText() {
navigator.clipboard.writeText(this.textContent)
}
}
}
</script>
<style lang="scss">
.clipboard {
max-width: 500px;
input {
user-select: none;
}
}
</style>
<template>
<button
class="btn btn-secondary"
v-tooltip-click="
$favourites.list.includes(uid)
? $t('suivis.enregistre')
: $t('suivis.supprime')
"
@click="$favourites.toggleFavourite(uid, $event)">
<span class="visually-hidden">{{
$favourites.list.includes(uid)
? $t("suivis.supprimer")
: $t("suivis.ajouter")
}}</span>
<solid-user-add-icon
aria-hidden="true"
style="width: 2rem"
v-if="!$favourites.list.includes(uid)" />
<solid-user-remove-icon
aria-hidden="true"
style="width: 2rem"
v-if="$favourites.list.includes(uid)" />
</button>
</template>
<script>
export default {
props: {
uid: {
type: String,
required: true
}
}
}
</script>
<template>
<button
v-if="membersToAdd.length != 0"
class="btn btn-secondary position-relative"
v-tooltip="{ title: $t('suivis.ajouter'), placement: 'left' }"
@click="$favourites.addFavorisArray(membersToAdd, $event)">
<solid-user-group-icon aria-hidden="true" class="icon" />
<span
aria-hidden="true"
class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">
+ {{ membersToAdd.length }}
</span>
</button>
</template>
<script>
export default {
props: {
listUID: {
type: Array,
required: true
}
},
computed: {
membersToAdd() {
return this.listUID.filter(
(el) => !this.$favourites.list.includes(el.uid)
)
}
}
}
</script>
<template>
<button type="submit" class="btn btn-primary" :disabled="isWaiting || disabled">
<span v-if="isWaiting">
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
Chargement...
</span>
<span v-else>Go !</span>
</button>
<button
type="submit"
class="btn btn-primary"
:disabled="isWaiting || disabled">
<span v-if="isWaiting">
<span
class="spinner-border spinner-border-sm"
role="status"
aria-hidden="true"></span>
{{ $t("chargement") }}...
</span>
<span v-else>Go !</span>
</button>
</template>
<script>
export default {
props: {
isWaiting: Boolean,
disabled: Boolean
}
props: {
isWaiting: Boolean,
disabled: Boolean
}
}
</script>
\ No newline at end of file
</script>
<template>
<div>
<div class="input-group mb-3">
<span class="input-group-text"
><solid-search-icon class="icon" aria-hidden="true"
/></span>
<input
type="text"
class="form-control"
:value="value"
autocomplete="off"
@input="$emit('input', $event.target.value)"
@keyup="$emit('keyup', $event.keyCode)"
:placeholder="$t('recherche.title')"
:aria-label="$t('recherche.title')"
:aria-describedby="help ? 'rechHelp' : null" />
<button
v-if="value != ''"
: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"
id="rechHelp"
class="small form-text text-muted text-center">
{{ help }}
</div>
</div>
</template>
<script>
export default {
props: {
value: {
type: String
},
help: {
type: String
}
}
}
</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>
<template>
<div
class="btn-sort pointer px-2"
tabindex="0"
:title="$t('tri.action')"
@click="sort(fieldName)"
@keyup.enter="sort(fieldName)">
<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" />
<solid-sort-descending-icon
aria-hidden="true"
class="ms-2 icon flex-shrink-0"
v-if="currentSortDir == 'asc' && currentSort == fieldName" />
</div>
</template>
<script>
export default {
props: {
title: {
type: String,
required: true
},
fieldName: {
type: String,
required: true
},
tableName: {
type: String,
required: true
},
currentSort: {
type: String,
required: true
},
currentSortDir: {
type: String,
required: true,
validator: function (value) {
return ["asc", "desc"].indexOf(value) !== -1
}
}
},
methods: {
sort(s) {
if (s === this.currentSort) {
this.$parent.currentSortDir =
this.currentSortDir === "asc" ? "desc" : "asc"
} else {
this.$parent.currentSortDir = "asc"
}
this.$parent.currentSort = s
let query = this.$route.query
let newQuery = {}
let same_array = false
if (Object.keys(query).length !== 0) {
for (const param in query) {
if (param.includes(this.tableName + "_")) {
same_array = true
newQuery[this.tableName + "_" + this.fieldName] =
this.$parent.currentSortDir
} else {
newQuery[param] = query[param]
}
}
}
if (!same_array) {
newQuery[this.tableName + "_" + this.fieldName] =
this.$parent.currentSortDir
}
this.$router.push({
hash: this.$route.hash,
query: newQuery
})
},
retrieveQuery() {
let query = this.$route.query
if (Object.keys(query).length !== 0) {
for (const param in query) {
if (param == this.tableName + "_" + this.fieldName) {
this.$parent.currentSort = this.fieldName
this.$parent.currentSortDir = query[param]
}
}
}
}
},
mounted() {
this.retrieveQuery()
},
watch: {
$route(n, o) {
this.retrieveQuery()
}
}
}
</script>
<style lang="scss">
.btn-sort {
display: flex;
justify-content: center;
align-items: center;
min-height: 50px;
background: var(--bg-secondary-color);
color: var(--txt-secondary-color);
&:focus,
&:hover {
filter: brightness(90%);
}
}
</style>
<template>
<div>
<input @change="toggleTheme" id="checkbox" type="checkbox" class="switch-checkbox" />
<label for="checkbox" class="switch-label d-flex align-items-center justify-content-between position-relative mb-0 ">
<span>🌙</span>
<span>☀️</span>
<div class="switch-toggle position-absolute rounded-circle" :class="{ 'switch-toggle-checked': userTheme === 'dark-theme' }"></div>
</label>
</div>
<div>
<input
@change="toggleTheme"
id="checkbox"
type="checkbox"
class="switch-checkbox" />
<label
for="checkbox"
class="switch-label pointer 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
class="switch-toggle position-absolute rounded-circle"
:class="{ 'switch-toggle-checked': userTheme === 'dark-theme' }"></div>
</label>
</div>
</template>
<script>
export default {
mounted() {
const initUserTheme = this.getMediaPreference();
this.setTheme(initUserTheme);
},
mounted() {
this.setTheme(localStorage.getItem("user-theme"))
},
data() {
return {
userTheme: "light-theme",
};
},
data() {
return {
userTheme: "light-theme"
}
},
methods: {
toggleTheme() {
const activeTheme = localStorage.getItem("user-theme");
if (activeTheme === "light-theme") {
this.setTheme("dark-theme");
} else {
this.setTheme("light-theme");
}
},
methods: {
toggleTheme() {
if (this.userTheme === "light-theme") {
this.setTheme("dark-theme")
} else {
this.setTheme("light-theme")
}
},
setTheme(theme) {
localStorage.setItem("user-theme", theme);
this.userTheme = theme;
document.documentElement.className = theme;
},
getMediaPreference() {
return localStorage.getItem("user-theme");
},
},
};
setTheme(theme) {
if (theme == null) {
theme = "light-theme"
}
localStorage.setItem("user-theme", theme)
this.userTheme = theme
document.documentElement.className = theme
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
*, ::before, ::after {
box-sizing: initial;
*,
::before,
::after {
box-sizing: initial;
}
.switch-checkbox {
display: none;
display: none;
}
.switch-label {
background: var(--text-primary-color);
border-radius: var(--element-size);
cursor: pointer;
font-size: calc(var(--element-size) * 0.3);
height: calc(var(--element-size) * 0.35);
padding: calc(var(--element-size) * 0.1);
transition: background 0.5s ease;
width: var(--element-size);
z-index: 1;
--element-size: 4rem;
background: var(--txt-primary-color);
border-radius: var(--element-size);
font-size: calc(var(--element-size) * 0.3);
height: calc(var(--element-size) * 0.35);
padding: calc(var(--element-size) * 0.1);
transition: background 0.5s ease;
width: var(--element-size);
z-index: 1;
}
.switch-toggle {
background-color: var(--background-color-primary);
top: calc(var(--element-size) * 0.07);
left: calc(var(--element-size) * 0.07);
height: calc(var(--element-size) * 0.4);
width: calc(var(--element-size) * 0.4);
transform: translateX(0);
transition: transform 0.3s ease, background-color 0.5s ease;
background-color: var(--bg-primary-color);
top: calc(var(--element-size) * 0.07);
left: calc(var(--element-size) * 0.07);
height: calc(var(--element-size) * 0.4);
width: calc(var(--element-size) * 0.4);
transform: translateX(0);
transition: transform 0.3s ease, background-color 0.5s ease;
}
.switch-toggle-checked {
transform: translateX(calc(var(--element-size) * 0.6)) !important;
transform: translateX(calc(var(--element-size) * 0.6)) !important;
}
</style>
<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">
<CertifGroup
:limitDate="idFromHash.certsLimit"
:memberStatus="idFromHash.status"
: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)">
<CertifGroup :certifs="idFromHash.sent_certifications" type="sent" />
</div>
</div>
</div>
</template>
<script>
export default {
props: {
idFromHash: {
type: Object,
required: true
}
}
}
</script>
<template>
<div>
<div
class="d-flex align-items-center justify-content-between flex-column flex-sm-row mb-4">
<h3
class="h4 text-center d-flex"
style="min-width: 0"
:class="{
'text-success': certifStatus == 'success',
'text-warning': certifStatus == 'warning',
'text-danger': certifStatus == 'danger',
'text-info': type == 'sent'
}">
<span class="text-truncate d-block">
{{
type == "sent"
? $t("certification.envoyees")
: $t("certification.recues")
}}
</span>
&nbsp;<BadgeDanger
v-if="type == 'received'"
class="flex-shrink-0"
style="width: 2rem"
:limitDate="limitDate"
:memberStatus="memberStatus" />
</h3>
<BtnFavoriArray
:listUID="certifsNotPending"
v-if="$parent.$parent.registeredAccount.hash != $route.query.hash" />
</div>
<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"
:collapseId="type + '-encours'" />
<hr v-if="certifsExpired.length > 0" />
<CertifList
:title="$t('certification.perimees')"
:certifs="certifsExpired"
:collapseId="type + '-perimees'" />
</div>
</template>
<script>
export default {
props: {
limitDate: Number,
memberStatus: String,
certifs: Array,
certifStatus: {
type: String,
default: ""
},
type: {
type: String,
required: true,
validator: function (value) {
const types = ["received", "sent"]
return types.indexOf(value) !== -1
}
}
},
computed: {
certifsNotPending() {
return this.certifs.filter((el) => el.pending == false)
},
certifsPending() {
return this.certifs.filter((el) => el.pending == true)
},
certifsExpired() {
return this.certifs.filter((el) => el.expired == true)
}
}
}
</script>
<template>
<div class="table-responsive">
<table class="table table-striped table-hover">
<tbody>
<tr v-for="certif in certifsTriees" :key="getNeighbor(certif).uid"
@click="$router.push(localePath({name:'membres-hash', params: {hash: getNeighbor(certif).hash}}))">
<th scope="row">
{{ getNeighbor(certif).uid }}
<BadgeCertifStatus :limitDate="getNeighbor(certif).received_certifications.limit" :memberStatus="getNeighbor(certif).status" />
<BadgeStatus :membre="getNeighbor(certif)" />
<BadgeQuality :quality="getNeighbor(certif).quality.ratio" v-if="getNeighbor(certif).status != 'REVOKED'" />
</th>
<td class="text-right">
<small><span class="badge" :class="'badge-'+ $options.filters.dateStatus(certif.expires_on)">{{ $t('expire') }} {{ $d(new Date(certif.expires_on*1000), 'long') }}</span></small>
</td>
</tr>
</tbody>
</table>
</div>
<div class="certifList" 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 }}&nbsp;&nbsp;
<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">
<BtnSort
:title="$t('membre.title')"
fieldName="uid"
:tableName="collapseId"
:currentSort="currentSort"
:currentSortDir="currentSortDir" />
</th>
<th class="p-0 col-4">
<BtnSort
:title="$t('expire')"
fieldName="expires_on"
:tableName="collapseId"
: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: 'membres-profil',
query: { hash: certif.hash }
})
)
"
@keyup.enter="
$router.push(
localePath({
name: 'membres-profil',
query: { hash: certif.hash }
})
)
">
<td class="py-1">
<div class="d-flex">
<span v-if="$favourites.list.includes(certif.uid)"
>&nbsp;</span
>
<div class="text-truncate">{{ certif.uid }}</div>
&nbsp;
<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-inline-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>
</div>
</div>
</template>
<script>
export default {
props : {
certifs : Array,
type : {
type: String,
required: true,
validator: function (value) {
const types = ['received','sent']
return types.indexOf(value) !== -1
}
}
},
methods : {
getNeighbor(certif) {
return this.type == "received" ? certif.from : certif.to
}
},
computed : {
certifsTriees() {
return this.certifs.sort(
(a, b) => a.expires_on - b.expires_on
)
data() {
return {
search: "",
currentSort: "expires_on",
currentSortDir: "asc",
isOpen: this.openDefault
}
},
props: {
certifs: Array,
certifStatus: String,
collapseId: String,
title: String,
openDefault: {
type: Boolean,
default: false
}
},
computed: {
certifsFiltrees() {
return this.certifs.filter((row, index) => {
return (
this.search == "" ||
row.uid.toLowerCase().includes(this.search.toLowerCase())
)
})
},
certifsTriees() {
return this.certifsFiltrees
.map((el) => {
el.status =
this.$options.filters.dateStatus(el.limitDate) == "warning"
? "RENEW"
: el.status
return el
})
.sort((a, b) => {
let modifier = this.currentSortDir === "desc" ? -1 : 1
}
}
if (this.currentSort == "expires_on") {
if (a["expires_on"] < b["expires_on"]) return -1 * modifier
if (a["expires_on"] > b["expires_on"]) return 1 * modifier
} else {
if (a["uid"].toLowerCase() < b["uid"].toLowerCase())
return -1 * modifier
if (a["uid"].toLowerCase() > b["uid"].toLowerCase())
return 1 * modifier
}
return 0
})
},
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() {
if (this.openDefault && this.certifs.length > 0) {
document.querySelector("#" + this.collapseId).classList.add("show")
}
},
watch: {
certifs: {
handler(n, o) {
this.search = ""
}
}
}
}
</script>
<style lang="scss">
.certifList {
.table-responsive tbody {
max-height: 456px;
}
tbody tr {
height: 80px;
}
@media (min-width: 576px) {
button {
font-size: 1.3rem;
}
tbody tr {
height: initial;
}
}
}
</script>
\ No newline at end of file
</style>
<template>
<div class="card member">
<div class="card-body">
<h2 class="card-title">
{{ hash.uid }}
<BadgeStatus :membre="hash" />
</h2>
<div class="card-subtitle mb-2 text-muted">{{ hash.pubkey }}</div>
<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') }} :</th>
<td :class="{'list-group-item-success': isReferent, 'list-group-item-warning': !isReferent}">{{ isReferent ? $t('oui') : $t('non') }}</td>
</tr>
<tr v-if="hash.status != 'NEWCOMER'">
<th scope="row">{{ $t('membre.qualite') }} :</th>
<td :class="{
'list-group-item-success': hash.quality.ratio >= 80,
'list-group-item-warning': hash.quality.ratio < 80,
}">{{ Math.round(hash.quality.ratio*100)/100 }}</td>
</tr>
<tr v-if="hash.status != 'NEWCOMER'">
<th scope="row">{{ $t('membre.distance') }} :</th>
<td :class="{
'list-group-item-success': hash.distance.dist_ok,
'list-group-item-danger': !hash.distance.dist_ok,
}">{{ Math.round(hash.distance.value.ratio*100)/100 }}</td>
</tr>
<tr>
<th scope="row">{{ hash.status != 'MISSING' ? $t('membre.datelimadhesion') : $t('membre.datelimrevoc')}} :</th>
<td :class="hash.status != 'MISSING' ? 'list-group-item-'+ $options.filters.dateStatus(hash.limitDate) : 'list-group-item-danger'">{{ $d(new Date(hash.limitDate*1000), 'long') }}</td>
</tr>
<tr v-if="hash.status == 'MEMBER'">
<th scope="row">{{ $t('membre.datemanquecertifs') }} :</th>
<td :class="'list-group-item-'+ $options.filters.dateStatus(hash.received_certifications.limit)">{{ $d(new Date(hash.received_certifications.limit*1000), 'long') }}</td>
</tr>
<tr v-if="hash.status == 'MEMBER'">
<th scope="row">{{ $t('membre.dispocertif') }} :</th>
<td :class="{
'list-group-item-success': hash.minDatePassed,
'list-group-item-danger': !hash.minDatePassed,
}">{{ hash.minDatePassed ? $t('oui') : $t('non') }} <small v-if="!hash.minDatePassed">( > {{ $d(new Date(hash.minDate*1000), 'long') }} )</small></td>
</tr>
<tr v-if="hash.status == 'MEMBER'">
<th scope="row">{{ $t('membre.nb_certifs') }} :</th>
<td :class="{
'list-group-item-success': hash.sent_certifications.length<=80,
'list-group-item-warning': hash.sent_certifications.length>80,
'list-group-item-danger': hash.sent_certifications.length>90,
}">{{ 100-hash.sent_certifications.length }}</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="card member">
<div class="card-body">
<div class="d-flex align-items-center justify-content-between mb-4">
<h2
class="h1 card-title text-center d-flex align-items-center flex-column m-0">
<span class="text-truncate d-inline-block mw-100">
{{ hash.uid }}
</span>
<small><BadgeStatus class="ms-2" :membre="hash" /></small>
</h2>
<div class="btn-group" role="group">
<button
v-if="
$parent.registeredAccount.uid == '' ||
$parent.registeredAccount.uid == hash.uid
"
class="btn iam"
:class="{
'btn-success': $parent.registeredAccount.uid != hash.uid,
'btn-warning': $parent.registeredAccount.uid == hash.uid
}"
@click="updateCurrentHash($event)">
<span v-if="$parent.registeredAccount.uid != hash.uid">{{
$t("suivis.iam")
}}</span>
<span v-else>{{ $t("suivis.iamnot") }}</span>
</button>
<BtnFavori
:uid="hash.uid"
v-if="$parent.registeredAccount.uid != hash.uid" />
</div>
</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>
<MemberProp
v-if="hash.status == 'MEMBER'"
:title="$t('membre.referent.title')"
:tooltip="$t('membre.referent.desc')"
:classClor="{
'table-success': hash.sentry,
'table-warning': !hash.sentry
}">
{{ hash.sentry ? $t("oui") : $t("non") }}
</MemberProp>
<MemberProp
v-if="hash.status != 'NEWCOMER'"
:title="$t('membre.qualite.title')"
:tooltip="$t('membre.qualite.desc')"
:classClor="{
'table-success': hash.quality.ratio >= 80,
'table-warning': hash.quality.ratio < 80
}">
{{ Math.round(hash.quality.ratio * 100) / 100 }}</MemberProp
>
<MemberProp
:title="$t('membre.distance.title')"
:tooltip="$t('membre.distance.desc')"
:classClor="{
'table-success':
hash.status != 'NEWCOMER'
? hash.distanceE.dist_ok
: hash.distanceE.dist_ok,
'table-danger':
hash.status != 'NEWCOMER'
? !hash.distanceE.dist_ok
: !hash.distanceE.dist_ok
}">
{{ Math.round(hash.distanceE.value.ratio * 100) / 100 }}
</MemberProp>
<MemberProp
:title="
hash.status != 'MISSING'
? $t('membre.datelimadhesion.title')
: $t('membre.datelimrevoc.title')
"
:tooltip="
hash.status != 'MISSING'
? $t('membre.datelimadhesion.desc')
: $t('membre.datelimrevoc.desc')
"
:classClor="
hash.status != 'MISSING'
? 'table-' + $options.filters.dateStatus(hash.limitDate)
: 'table-danger'
">
{{ $d(new Date(hash.limitDate * 1000)).toLocaleString() }}
</MemberProp>
<MemberProp
v-if="hash.status == 'MEMBER'"
:title="$t('membre.datemanquecertifs.title')"
:tooltip="$t('membre.datemanquecertifs.desc')"
:classClor="
'table-' + $options.filters.dateStatus(hash.certsLimit)
">
{{ $d(new Date(hash.certsLimit * 1000)).toLocaleString() }}
</MemberProp>
<MemberProp
v-if="hash.status == 'MEMBER'"
:title="$t('membre.dispocertif.title')"
:tooltip="$t('membre.dispocertif.desc')"
:classClor="{
'table-success': hash.minDatePassed,
'table-danger': !hash.minDatePassed
}">
{{ hash.minDatePassed ? $t("oui") : $t("non") }}
<small v-if="!hash.minDatePassed"
>( > {{ $d(new Date(hash.minDate * 1000)).toLocaleString() }} )</small
>
</MemberProp>
<MemberProp
v-if="hash.status == 'MEMBER'"
:title="$t('membre.nb_certifs.title')"
:tooltip="$t('membre.nb_certifs.desc')"
:classClor="{
'table-success': sentCertNotExpired.length <= 80,
'table-warning': sentCertNotExpired.length > 80,
'table-danger': sentCertNotExpired.length > 90
}">
{{ 100 - sentCertNotExpired.length }}
</MemberProp>
</tbody>
</table>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
hash: Object
},
computed: {
isReferent () {
const nb_certifs_referent = 5
return this.hash.received_certifications.certifications.length > nb_certifs_referent && this.hash.sent_certifications.length > nb_certifs_referent
}
},
props: {
hash: Object
},
computed: {
sentCertNotExpired() {
return this.hash.sent_certifications.filter((el) => !el.expired)
}
},
methods: {
updateCurrentHash(e) {
if (this.$parent.registeredAccount.uid == this.hash.uid) {
this.$parent.registeredAccount = { hash: "", uid: "" }
} else {
this.$parent.registeredAccount = {
hash: this.hash.hash,
uid: this.hash.uid
}
if (this.$favourites.list.includes(this.hash.uid)) {
this.$favourites.toggleFavourite(this.hash.uid, e)
}
}
}
}
}
</script>
<style lang="sass" scoped>
.member
th
text-align: right
</style>
\ No newline at end of file
<style lang="scss">
.btn.iam:not(.btn-warning) {
z-index: 10;
animation: zoom-in-zoom-out 1s ease infinite;
}
@keyframes zoom-in-zoom-out {
50% {
opacity: 0.3;
}
}
.member {
h2 {
min-width: 0;
}
.table {
text-align: center;
width: auto;
margin: auto;
}
}
@media (min-width: 576px) {
.member .table {
th {
text-align: right;
}
td {
text-align: left;
}
}
}
</style>
<template>
<div class="text-muted">
<p id="filterStatutTitle">{{ $t("filter.statut") }}&nbsp;:</p>
<ul class="p-0 m-0" aria-labelledby="filterStatutTitle" role="group">
<li
class="form-check form-check-inline mb-3"
v-if="type != 'certificateurs'">
<input
class="btn-check"
v-model="checkedStatus"
type="checkbox"
:id="'check-newcomer-' + _uid"
value="NEWCOMER"
@change="fixColumns" />
<label class="btn btn-outline-info" :for="'check-newcomer-' + _uid">{{
$t("statut.newcomer")
}}</label>
</li>
<li class="form-check form-check-inline mb-3">
<input
class="btn-check"
v-model="checkedStatus"
type="checkbox"
:id="'check-member-' + _uid"
value="MEMBER"
@change="fixColumns" />
<label class="btn btn-outline-success" :for="'check-member-' + _uid">{{
$t("statut.member")
}}</label>
</li>
<li class="form-check form-check-inline mb-3">
<input
class="btn-check"
v-model="checkedStatus"
type="checkbox"
:id="'check-renew-' + _uid"
value="RENEW"
@change="fixColumns" />
<label class="btn btn-outline-warning" :for="'check-renew-' + _uid">{{
$t("statut.renew")
}}</label>
</li>
<li class="form-check form-check-inline mb-3">
<input
class="btn-check"
v-model="checkedStatus"
type="checkbox"
:id="'check-missing-' + _uid"
value="MISSING"
@change="fixColumns" />
<label class="btn btn-outline-danger" :for="'check-missing-' + _uid">{{
$t("statut.missing")
}}</label>
</li>
<li class="form-check form-check-inline mb-3" v-if="type == 'favoris'">
<input
class="btn-check"
v-model="checkedStatus"
type="checkbox"
:id="'check-revoked-' + _uid"
value="REVOKED"
@change="fixColumns" />
<label
class="btn btn-outline-secondary"
:for="'check-revoked-' + _uid"
>{{ $t("statut.revoked") }}</label
>
</li>
</ul>
<div v-if="type != 'favoris'">
<p>{{ $t("filter.certif") }}&nbsp;:</p>
<input
class="btn-check"
v-model="certifStatus"
type="radio"
:id="'radio-current-' + _uid"
value="current"
autocomplete="off" />
<label
class="btn btn-outline-success me-3 mb-3"
:for="'radio-current-' + _uid">
{{ $t("certification.title") + " " + $t("certification.encours") }}
</label>
<input
class="btn-check"
v-model="certifStatus"
type="radio"
:id="'radio-outdated-' + _uid"
value="outdated"
autocomplete="off" />
<label
class="btn btn-outline-danger mb-3"
:for="'radio-outdated-' + _uid">
{{ $t("certification.title") + " " + $t("certification.perimees") }}
</label>
</div>
</div>
</template>
<script>
export default {
data() {
return {
checkedStatus: ["NEWCOMER", "MEMBER", "RENEW", "MISSING", ""],
certifStatus: "current"
}
},
props: {
type: {
type: String,
default: ""
}
},
methods: {
fixColumns(e) {
setTimeout(() => {
this.$favourites.fixColumns()
}, 5)
}
},
mounted() {
this.$emit("update:selectedStatus", this.checkedStatus)
this.$emit("update:selectedCertifStatus", this.certifStatus)
},
watch: {
checkedStatus(n, o) {
this.$emit("update:selectedStatus", n)
},
certifStatus(n, o) {
this.$emit("update:selectedCertifStatus", n)
}
}
}
</script>
<template>
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead v-if="displayHead">
<tr>
<th scope="col">UID</th>
<th scope="col" class="d-none d-xl-table-cell" v-if="displayPubkey">PUBKEY</th>
</tr>
</thead>
<tbody>
<tr v-for="member in members" :key="member.uid"
@click="redirect(member.hash)">
<th scope="row">
{{ member.uid }}
<BadgeCertifStatus :limitDate="member.received_certifications.limit" :memberStatus="member.status" />
<BadgeStatus :membre="member" />
</th>
<td class="d-none d-xl-table-cell" v-if="displayPubkey">{{ member.pubkey }}</td>
</tr>
</tbody>
</table>
</div>
<div class="table-responsive pb-3">
<table
class="table table-striped table-hover table-fixed sortable border text-center">
<thead class="thead-light">
<tr>
<th class="p-0" scope="col">
<BtnSort
fieldName="uid"
:tableName="id"
title="UID"
:currentSort="currentSort"
:currentSortDir="currentSortDir" />
</th>
<th
scope="col"
class="d-none d-lg-table-cell p-0"
v-if="id != 'default'">
<BtnSort
fieldName="statut"
:tableName="id"
:title="$t('statut.title')"
:currentSort="currentSort"
:currentSortDir="currentSortDir" />
</th>
<th
scope="col"
class="td-quality d-none d-lg-table-cell p-0"
v-if="
['favoris', 'search', 'certificateurs', 'certifies'].includes(id)
">
<BtnSort
fieldName="quality"
:tableName="id"
:title="$t('membre.qualite.title')"
:currentSort="currentSort"
:currentSortDir="currentSortDir" />
</th>
<th
scope="col"
class="d-none d-xl-table-cell p-0"
v-if="
['favoris', 'search', 'certificateurs', 'certifies'].includes(id)
">
<BtnSort
fieldName="dispo"
:tableName="id"
:title="$t('membre.disponibilite')"
:currentSort="currentSort"
:currentSortDir="currentSortDir" />
</th>
<th
scope="col"
class="td-date d-none d-sm-table-cell p-0"
v-if="
[
'adhesion',
'favoris',
'search',
'certificateurs',
'certifies'
].includes(id)
">
<BtnSort
fieldName="date_membership"
:tableName="id"
:title="
['certif', 'adhesion'].includes(id)
? $t('date')
: $t('membre.datelimpertestatut')
"
:currentSort="currentSort"
:currentSortDir="currentSortDir" />
</th>
<th
scope="col"
class="td-date d-none p-0"
:class="{
'd-sm-table-cell': id == 'certif',
'd-md-table-cell': id != 'certif'
}"
v-if="
[
'certif',
'favoris',
'search',
'certificateurs',
'certifies'
].includes(id)
">
<BtnSort
fieldName="date_certs"
:tableName="id"
:title="
['certif', 'adhesion'].includes(id)
? $t('date')
: $t('membre.datemanquecertifs.title')
"
:currentSort="currentSort"
:currentSortDir="currentSortDir" />
</th>
<th v-if="id == 'favoris'" style="width: 60px"></th>
</tr>
</thead>
<tbody>
<tr
v-for="member in membersSorted"
:key="member.uid"
tabindex="0"
:title="$t('membre.voirinfos')"
@click="redirect(member.hash)"
@keyup.enter="redirect(member.hash)">
<td>
<div class="d-flex">
<div
class="d-flex flex-column align-items-center justify-content-evenly flex-grow-1 mw-100">
<div class="d-flex justify-content-center mw-100">
<span v-if="$favourites.list.includes(member.uid)"
>&nbsp;</span
>
<div class="text-truncate">{{ member.uid }}</div>
&nbsp;
<BadgeDanger
style="width: 1.2rem"
:limitDate="member.certsLimit"
:memberStatus="member.status" />
</div>
<div class="text-muted small">
{{ member.pubkey.substring(0, 10) }}
</div>
<div
v-if="['adhesion', 'certif'].includes(id)"
class="d-sm-none">
<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', 'certificateurs', 'certifies'].includes(
id
)
">
<BadgeStatus :membre="member" class="mw-100 text-truncate" />
<BadgeQuality :quality="member.quality.ratio" />
<BadgeDispo
:isDispo="member.minDatePassed"
:dateDispo="member.minDate"
:certifs="member.sent_certifications"
class="mw-100 text-truncate" />
</div>
</div>
</td>
<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', 'certificateurs', 'certifies'].includes(id)
">
<BadgeQuality :quality="member.quality.ratio" />
</td>
<td
class="d-none d-xl-table-cell"
v-if="
['favoris', 'search', 'certificateurs', 'certifies'].includes(id)
">
<BadgeDispo
:isDispo="member.minDatePassed"
:dateDispo="member.minDate"
:certifs="member.sent_certifications" />
</td>
<td
class="d-none d-sm-table-cell"
v-if="
[
'adhesion',
'favoris',
'search',
'certificateurs',
'certifies'
].includes(id)
">
<BadgeDate :date="member.limitDate" />
</td>
<td
class="d-none"
:class="{
'd-sm-table-cell': id == 'certif',
'd-md-table-cell': id != 'certif'
}"
v-if="
[
'certif',
'favoris',
'search',
'certificateurs',
'certifies'
].includes(id)
">
<BadgeDate :date="member.certsLimit" />
</td>
<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('suivis.supprimer')">
<solid-trash-icon class="icon" aria-hidden="true" />
</button>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
export default {
props: {
members: {
type: Array,
required: true
},
displayPubkey: {
type: Boolean,
default: true
},
displayHead: {
type: Boolean,
default: true
}
},
methods: {
redirect(hash) {
this.$router.push(this.localePath({name:'membres-hash', params: {hash}}))
}
},
data() {
return {
currentSort: this.defaultSort,
currentSortDir: this.defaultSortDir
}
},
props: {
members: {
type: Array,
required: true
},
id: {
type: String,
required: true
},
defaultSortDir: {
type: String,
default: "asc"
},
defaultSort: {
type: String,
default: "uid"
}
},
methods: {
redirect(hash) {
this.$router.push(
this.localePath({ name: "membres-profil", query: { hash } })
)
},
getDate(member) {
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
}
},
computed: {
membersSorted() {
return this.members.sort((a, b) => {
let modifier = this.currentSortDir === "desc" ? -1 : 1
if (this.currentSort == "uid") {
return this.getOrder(
a["uid"].toLowerCase(),
b["uid"].toLowerCase(),
modifier
)
} else if (this.currentSort == "dispo") {
if (a.minDate == null) return 1 * modifier
if (b.minDate == null) return -1 * modifier
return this.getOrder(a.minDate, b.minDate, modifier)
} else if (this.currentSort == "quality") {
return this.getOrder(a.quality.ratio, b.quality.ratio, modifier)
} else if (this.currentSort == "statut") {
return this.getOrder(a["status"], b["status"], modifier)
} else if (this.currentSort == "date_membership") {
return this.getOrder(a["limitDate"], b["limitDate"], modifier)
} else if (this.currentSort == "date_certs") {
return this.getOrder(a["certsLimit"], b["certsLimit"], modifier)
}
return 0
})
}
},
mounted() {
this.$favourites.fixColumns()
}
}
</script>
\ No newline at end of file
</script>
<template>
<tr class="help" v-tooltip="{ title: tooltip, placement: 'right' }">
<th scope="row" class="fw-normal">{{ title }}&nbsp;:</th>
<td :class="classClor">
<slot></slot>
</td>
</tr>
</template>
<script>
export default {
props: {
title: String,
tooltip: {
type: String,
default: ""
},
classClor: [String, Object]
}
}
</script>
<style lang="scss" scoped>
tr {
display: flex;
flex-direction: column;
user-select: none;
}
@media (min-width: 576px) {
tr {
display: table-row;
}
}
</style>
<template>
<header class="header position-fixed">
<div class="position-relative">
<button class="toggle btn border-secondary position-absolute p-1 m-1 ml-3" @click="toggleMenu"><span></span></button>
<NavigationBreadcrumb :breadcrumb="breadcrumb" />
</div>
<NavigationMenuSidebar @toggleMenu="toggleMenu" :menus="menus" />
<div class="bg_overlay position-fixed" @click="toggleMenu"></div>
</header>
<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>
</button>
<NavigationBreadcrumb :breadcrumb="breadcrumb" />
</div>
<NavigationMenuSidebar @toggleMenu="toggleMenu" :menus="menus" />
<div class="bg_overlay position-fixed" @click="toggleMenu"></div>
</header>
</template>
<script>
export default {
props: {
breadcrumb: Array,
menus: Array
},
methods: {
toggleMenu() {
document.querySelector('.app').classList.toggle('open')
}
}
data() {
return {
isOpen: false
}
},
props: {
breadcrumb: Array,
menus: Array
},
methods: {
toggleMenu() {
document.querySelector(".app").classList.toggle("open")
this.isOpen = !this.isOpen
localStorage.setItem("menu-open", this.isOpen)
}
},
mounted() {
this.isOpen = localStorage.getItem("menu-open") == "true"
if (this.isOpen) {
document.querySelector(".app").classList.add("open")
}
}
}
</script>
......@@ -27,187 +45,172 @@ export default {
$btn-width: 50px;
.header {
--menu-width: 0px;
width: 100%;
z-index: 100;
background: var(--background-color-primary);
transition: width .5s ease-in-out;
.open & {
--menu-width: 320px;
}
@media (min-width:1200px) {
width: calc(99vw - var(--menu-width));
}
--menu-width: 0px;
width: 100%;
z-index: 100;
background: var(--bg-primary-color);
transition: width 0.5s ease-in-out;
.open & {
--menu-width: 320px;
}
@media (min-width: 1200px) {
width: calc(99vw - var(--menu-width));
}
}
nav.breadcrumb-wrapper {
margin: 8px 15px 8px 80px;
display: flex;
flex-direction: column;
gap: 1rem;
background: var(--background-color-secondary);
a {color: var(--text-primary-color)}
.breadcrumb-item.active {
opacity: .7;
}
@media (min-width:768px) {
flex-direction: row;
justify-content: space-between;
align-items: center;
}
min-height: 80px;
margin: 8px 15px;
padding: 1rem 1rem 1rem 4.5rem;
display: flex;
flex-direction: column;
gap: 1rem;
background: var(--bg-menu-color);
a {
color: var(--txt-primary-color);
}
.breadcrumb-item.active {
opacity: 0.7;
}
@media (min-width: 768px) {
flex-direction: row;
justify-content: space-between;
align-items: center;
}
}
%hamburger-line {
display: block;
height: 4px;
width: .8 * $btn-width;
background: var(--text-primary-color);
content: "";
position: absolute;
transition-property: transform;
border-radius: 4px;
display: block;
height: 4px;
width: 0.8 * $btn-width;
background: var(--txt-primary-color);
content: "";
position: absolute;
transition-property: transform;
border-radius: 4px;
}
.toggle {
height: $btn-width;
width: $btn-width;
line-height: $btn-width;
span {
@extend %hamburger-line;
top: 50%;
transform: translateY(-50%);
transition-timing-function: cubic-bezier(.55,.055,.675,.19);
transition-duration: 75ms;
.open & {
transform: rotate(45deg);
display: block;
margin-top: -2px;
}
&::before {
transition: top 75ms ease .12s,opacity 75ms ease;
@extend %hamburger-line;
top: -10px;
.open & {
opacity: 0;
}
}
&::after {
transition: bottom 75ms ease .12s,transform 75ms cubic-bezier(.55,.055,.675,.19);
@extend %hamburger-line;
bottom: -10px;
.open & {
top: 0;
transform: rotate(-90deg);
}
}
}
height: $btn-width;
width: $btn-width;
line-height: $btn-width;
top: 1rem;
span {
@extend %hamburger-line;
top: 50%;
transform: translateY(-50%);
transition-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
transition-duration: 75ms;
.open & {
transform: rotate(45deg);
display: block;
margin-top: -2px;
}
&::before {
transition: top 75ms ease 0.12s, opacity 75ms ease;
@extend %hamburger-line;
top: -10px;
.open & {
opacity: 0;
}
}
&::after {
transition: bottom 75ms ease 0.12s,
transform 75ms cubic-bezier(0.55, 0.055, 0.675, 0.19);
@extend %hamburger-line;
bottom: -10px;
.open & {
top: 0;
transform: rotate(-90deg);
}
}
}
}
.menu {
background: var(--background-color-primary);
width: var(--menu-size);
top: 0;
z-index: 1200;
height: 100vh;
padding: 1.1rem 0.5rem;
overflow-y: scroll;
scrollbar-color: #6969dd #e0e0e0;
scrollbar-width: thin;
transition: left .5s ease-in-out;
left: -400px;
h1 {color: var(--text-primary-color);}
.list-group-item {
&-action:not(.active) {
background: transparent;
&:hover {
background: rgba(0, 0, 255, 0.075);
color: var(--text-primary-color);
}
}
div {
transition: left .3s ease-in-out;
left: 0;
&::before {
content: "›";
position: relative;
left: -.5em;
}
&:hover {
left: .5em;
}
}
}
.open & {
left: 0;
}
.close {
--size: 50px;
width: var(--size);
height: var(--size);
top: .8rem;
right: 0;
font-size: 2rem;
}
h2 {
cursor: default;
}
width: var(--menu-size);
top: 0;
z-index: 1200;
height: 100%;
transition: left 0.5s ease-in-out;
left: -400px;
h1 {
color: var(--txt-primary-color);
}
.open & {
left: 0;
}
.btn-close {
--size: 30px;
width: var(--size);
height: var(--size);
top: 0.8rem;
right: 0.8rem;
.dark-theme & {
filter: invert(1) grayscale(100%) brightness(200%);
}
}
h2 {
cursor: default;
}
}
.bg_overlay {
top: 0;
left: 0;
right: 0;
height: 120vh;
z-index: 1100;
visibility: hidden;
opacity: 0;
transition: all .5s ease;
background-color: rgba(34,41,47,.5);
.open & {
opacity: 1;
visibility: visible;
}
top: 0;
left: 0;
right: 0;
height: 120vh;
z-index: 1100;
visibility: hidden;
opacity: 0;
transition: all 0.5s ease;
background-color: rgba(34, 41, 47, 0.5);
.open & {
opacity: 1;
visibility: visible;
}
}
.logo {
max-width: 75px;
&:hover {
text-decoration: none;
}
img {
max-width: 75px;
}
}
@media (min-width:1200px) {
.open {
&.app {
margin-left: var(--menu-size);
}
.menu {
left: 0;
}
.bg_overlay {
visibility: hidden;
opacity: 0;
}
}
@media (min-width: 1200px) {
.open {
&.app {
margin-left: var(--menu-size);
}
.menu {
left: 0;
}
.bg_overlay {
visibility: hidden;
opacity: 0;
}
}
}
</style>
<template>
<nav aria-label="Fil d'Ariane" class="breadcrumb-wrapper rounded p-3">
<ol class="breadcrumb m-0 p-0">
<li class="breadcrumb-item" :class="{ 'active': item.active }" :aria-current="item.active ? 'page' : null" v-for="item in breadcrumb" :key="item.text">
<NuxtLink :to="item.to" v-if="item.to">{{ item.text }}</NuxtLink>
<span v-else>{{ item.text }}</span>
</li>
</ol>
<div class="d-flex justify-content-between align-items-center">
<NavigationLanguage class="mr-3" />
<BtnTheme />
</div>
</nav>
<nav
:aria-label="$t('aria.ariane')"
class="breadcrumb-wrapper rounded border">
<ol class="breadcrumb m-0 p-0 d-none d-sm-flex">
<li
class="breadcrumb-item"
:class="{ active: item.active }"
:aria-current="item.active ? 'page' : null"
v-for="item in breadcrumb"
:key="item.text">
<NuxtLink :to="localePath(item.to)" v-if="item.to">{{
item.text
}}</NuxtLink>
<span v-else>{{ item.text }}</span>
</li>
</ol>
<div class="d-flex justify-content-between align-items-center">
<NavigationLanguage class="me-3" />
<BtnTheme />
</div>
</nav>
</template>
<script>
export default {
props: {
breadcrumb: Array
}
props: {
breadcrumb: Array
}
}
</script>
\ No newline at end of file
</script>
<template>
<div>
<select class="form-control" @change="saveLocale($event)" v-model="lang">
<option v-for="lang in $i18n.locales" :key="lang.code" :value="lang.code">{{ lang.name }}</option>
</select>
</div>
<div>
<select
class="form-select"
:aria-label="$t('lang')"
@change="saveLocale($event)"
v-model="activeLang">
<option
v-for="lang in $i18n.locales"
:key="lang.code"
:value="lang.code"
:selected="lang.code === activeLang">
{{ lang.name }}
</option>
</select>
</div>
</template>
<script>
export default {
data() {
return {
lang: this.$i18n.locale
}
},
methods: {
saveLocale(e) {
this.lang = e.target.value
this.$i18n.setLocaleCookie(e.target.value)
this.$router.push(this.switchLocalePath(e.target.value))
}
}
data() {
return {
activeLang: "en"
}
},
methods: {
saveLocale(e) {
// this.$i18n.locale = e.target.value
this.$i18n.setLocale(e.target.value)
// this.$i18n.setLocaleCookie(e.target.value)
// this.$router.push(this.switchLocalePath(e.target.value))
// this.$router.replace(this.switchLocalePath(e))
}
},
mounted() {
this.activeLang = this.$i18n.locale
}
}
</script>
\ No newline at end of file
</script>