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> <template>
<small> <span
<span class="badge" class="badge"
:class="{ :class="{
'badge-success': quality>=80, 'bg-success': quality >= 80,
'badge-warning': quality<80, 'bg-warning': quality < 80
}"> }">
{{ Math.round(quality*100)/100 }} {{ Math.round(quality * 100) / 100 }}
</span> </span>
</small>
</template> </template>
<script> <script>
export default { export default {
props: { props: {
quality: { quality: {
type: Number, type: Number,
required: true required: true
} }
} }
} }
</script> </script>
\ No newline at end of file
<template> <template>
<small> <span class="badge" :class="displayStatus(membre).class">
<span class="badge" :class="this.displayStatus(membre).class"> {{ this.displayStatus(membre).str }}
{{ this.displayStatus(membre).str }} </span>
</span>
</small>
</template> </template>
<script> <script>
export default { export default {
props: { props: {
membre: { membre: {
type: Object, type: Object,
required: true required: true
} }
}, },
methods: { methods: {
displayStatus: function(member){ displayStatus: function (member) {
switch (member.status) { switch (member.status) {
case 'NEWCOMER': case "NEWCOMER":
return {str: this.$i18n.t('statut.newcomer'),class: 'badge-info'} return { str: this.$i18n.t("statut.newcomer"), class: "bg-info" }
case 'MISSING': case "MISSING":
return {str: this.$i18n.t('statut.missing'),class: 'badge-danger'} return { str: this.$i18n.t("statut.missing"), class: "bg-danger" }
case 'MEMBER': case "MEMBER":
if (this.$options.filters.dateStatus(member.limitDate) == 'warning') { return {
return {str: this.$i18n.t('statut.renew'),class: 'badge-warning'} str: this.$i18n.t("statut.member"),
} else { class: "bg-success"
return {str: this.$i18n.t('statut.member'),class: 'badge-success'} }
} case "RENEW":
case 'REVOKED': return { str: this.$i18n.t("statut.renew"), class: "bg-warning" }
return {str: this.$i18n.t('statut.revoked'),class: 'badge-secondary'} case "REVOKED":
default: return {
return 'N/A' str: this.$i18n.t("statut.revoked"),
} class: "bg-secondary"
} }
} default:
return "N/A"
}
}
}
} }
</script> </script>
\ No newline at end of file
<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> <template>
<button type="submit" class="btn btn-primary" :disabled="isWaiting || disabled"> <button
<span v-if="isWaiting"> type="submit"
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> class="btn btn-primary"
Chargement... :disabled="isWaiting || disabled">
</span> <span v-if="isWaiting">
<span v-else>Go !</span> <span
</button> class="spinner-border spinner-border-sm"
role="status"
aria-hidden="true"></span>
{{ $t("chargement") }}...
</span>
<span v-else>Go !</span>
</button>
</template> </template>
<script> <script>
export default { export default {
props: { props: {
isWaiting: Boolean, isWaiting: Boolean,
disabled: Boolean disabled: Boolean
} }
} }
</script> </script>
\ No newline at end of file
<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> <template>
<div> <div>
<input @change="toggleTheme" id="checkbox" type="checkbox" class="switch-checkbox" /> <input
<label for="checkbox" class="switch-label d-flex align-items-center justify-content-between position-relative mb-0 "> @change="toggleTheme"
<span>🌙</span> id="checkbox"
<span>☀️</span> type="checkbox"
<div class="switch-toggle position-absolute rounded-circle" :class="{ 'switch-toggle-checked': userTheme === 'dark-theme' }"></div> class="switch-checkbox" />
</label> <label
</div> 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> </template>
<script> <script>
export default { export default {
mounted() { mounted() {
const initUserTheme = this.getMediaPreference(); this.setTheme(localStorage.getItem("user-theme"))
this.setTheme(initUserTheme); },
},
data() { data() {
return { return {
userTheme: "light-theme", userTheme: "light-theme"
}; }
}, },
methods: { methods: {
toggleTheme() { toggleTheme() {
const activeTheme = localStorage.getItem("user-theme"); if (this.userTheme === "light-theme") {
if (activeTheme === "light-theme") { this.setTheme("dark-theme")
this.setTheme("dark-theme"); } else {
} else { this.setTheme("light-theme")
this.setTheme("light-theme"); }
} },
},
setTheme(theme) { setTheme(theme) {
localStorage.setItem("user-theme", theme); if (theme == null) {
this.userTheme = theme; theme = "light-theme"
document.documentElement.className = theme; }
}, localStorage.setItem("user-theme", theme)
this.userTheme = theme
getMediaPreference() { document.documentElement.className = theme
return localStorage.getItem("user-theme"); }
}, }
}, }
};
</script> </script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped> <style scoped>
*, ::before, ::after { *,
box-sizing: initial; ::before,
::after {
box-sizing: initial;
} }
.switch-checkbox { .switch-checkbox {
display: none; display: none;
} }
.switch-label { .switch-label {
background: var(--text-primary-color); --element-size: 4rem;
border-radius: var(--element-size); background: var(--txt-primary-color);
cursor: pointer; border-radius: var(--element-size);
font-size: calc(var(--element-size) * 0.3); font-size: calc(var(--element-size) * 0.3);
height: calc(var(--element-size) * 0.35); height: calc(var(--element-size) * 0.35);
padding: calc(var(--element-size) * 0.1); padding: calc(var(--element-size) * 0.1);
transition: background 0.5s ease; transition: background 0.5s ease;
width: var(--element-size); width: var(--element-size);
z-index: 1; z-index: 1;
} }
.switch-toggle { .switch-toggle {
background-color: var(--background-color-primary); background-color: var(--bg-primary-color);
top: calc(var(--element-size) * 0.07); top: calc(var(--element-size) * 0.07);
left: calc(var(--element-size) * 0.07); left: calc(var(--element-size) * 0.07);
height: calc(var(--element-size) * 0.4); height: calc(var(--element-size) * 0.4);
width: calc(var(--element-size) * 0.4); width: calc(var(--element-size) * 0.4);
transform: translateX(0); transform: translateX(0);
transition: transform 0.3s ease, background-color 0.5s ease; transition: transform 0.3s ease, background-color 0.5s ease;
} }
.switch-toggle-checked { .switch-toggle-checked {
transform: translateX(calc(var(--element-size) * 0.6)) !important; transform: translateX(calc(var(--element-size) * 0.6)) !important;
} }
</style> </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> <template>
<div class="table-responsive"> <div class="certifList" v-if="certifs.length > 0">
<table class="table table-striped table-hover"> <button
<tbody> :title="
<tr v-for="certif in certifsTriees" :key="getNeighbor(certif).uid" isOpen ? $t('certification.masquer') : $t('certification.afficher')
@click="$router.push(localePath({name:'membres-hash', params: {hash: getNeighbor(certif).hash}}))"> "
<th scope="row"> @click="isOpen = !isOpen"
{{ getNeighbor(certif).uid }} class="btn w-100 m-auto d-block rounded-0"
<BadgeCertifStatus :limitDate="getNeighbor(certif).received_certifications.limit" :memberStatus="getNeighbor(certif).status" /> :class="btnClass"
<BadgeStatus :membre="getNeighbor(certif)" /> type="button"
<BadgeQuality :quality="getNeighbor(certif).quality.ratio" v-if="getNeighbor(certif).status != 'REVOKED'" /> data-bs-toggle="collapse"
</th> :data-bs-target="'#' + collapseId"
<td class="text-right"> aria-expanded="false"
<small><span class="badge" :class="'badge-'+ $options.filters.dateStatus(certif.expires_on)">{{ $t('expire') }} {{ $d(new Date(certif.expires_on*1000), 'long') }}</span></small> :aria-controls="collapseId">
</td> <span v-if="!isOpen">
</tr> <solid-eye-icon class="icon" aria-hidden="true" />
</tbody> </span>
</table> <span v-else>
</div> <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> </template>
<script> <script>
export default { export default {
props : { data() {
certifs : Array, return {
type : { search: "",
type: String, currentSort: "expires_on",
required: true, currentSortDir: "asc",
validator: function (value) { isOpen: this.openDefault
const types = ['received','sent'] }
return types.indexOf(value) !== -1 },
} props: {
} certifs: Array,
}, certifStatus: String,
methods : { collapseId: String,
getNeighbor(certif) { title: String,
return this.type == "received" ? certif.from : certif.to openDefault: {
} type: Boolean,
}, default: false
computed : { }
certifsTriees() { },
return this.certifs.sort( computed: {
(a, b) => a.expires_on - b.expires_on 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> </style>
\ No newline at end of file
<template> <template>
<div class="card member"> <div class="card member">
<div class="card-body"> <div class="card-body">
<h2 class="card-title"> <div class="d-flex align-items-center justify-content-between mb-4">
{{ hash.uid }} <h2
<BadgeStatus :membre="hash" /> class="h1 card-title text-center d-flex align-items-center flex-column m-0">
</h2> <span class="text-truncate d-inline-block mw-100">
<div class="card-subtitle mb-2 text-muted">{{ hash.pubkey }}</div> {{ hash.uid }}
<table class="table table-sm table-borderless" v-if="hash.status != 'REVOKED'"> </span>
<tbody> <small><BadgeStatus class="ms-2" :membre="hash" /></small>
<tr v-if="hash.status == 'MEMBER'"> </h2>
<th scope="row">{{ $t('membre.referent') }} :</th> <div class="btn-group" role="group">
<td :class="{'list-group-item-success': isReferent, 'list-group-item-warning': !isReferent}">{{ isReferent ? $t('oui') : $t('non') }}</td> <button
</tr> v-if="
<tr v-if="hash.status != 'NEWCOMER'"> $parent.registeredAccount.uid == '' ||
<th scope="row">{{ $t('membre.qualite') }} :</th> $parent.registeredAccount.uid == hash.uid
<td :class="{ "
'list-group-item-success': hash.quality.ratio >= 80, class="btn iam"
'list-group-item-warning': hash.quality.ratio < 80, :class="{
}">{{ Math.round(hash.quality.ratio*100)/100 }}</td> 'btn-success': $parent.registeredAccount.uid != hash.uid,
</tr> 'btn-warning': $parent.registeredAccount.uid == hash.uid
<tr v-if="hash.status != 'NEWCOMER'"> }"
<th scope="row">{{ $t('membre.distance') }} :</th> @click="updateCurrentHash($event)">
<td :class="{ <span v-if="$parent.registeredAccount.uid != hash.uid">{{
'list-group-item-success': hash.distance.dist_ok, $t("suivis.iam")
'list-group-item-danger': !hash.distance.dist_ok, }}</span>
}">{{ Math.round(hash.distance.value.ratio*100)/100 }}</td> <span v-else>{{ $t("suivis.iamnot") }}</span>
</tr> </button>
<tr> <BtnFavori
<th scope="row">{{ hash.status != 'MISSING' ? $t('membre.datelimadhesion') : $t('membre.datelimrevoc')}} :</th> :uid="hash.uid"
<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> v-if="$parent.registeredAccount.uid != hash.uid" />
</tr> </div>
<tr v-if="hash.status == 'MEMBER'"> </div>
<th scope="row">{{ $t('membre.datemanquecertifs') }} :</th> <BtnClipboard :textContent="hash.pubkey" />
<td :class="'list-group-item-'+ $options.filters.dateStatus(hash.received_certifications.limit)">{{ $d(new Date(hash.received_certifications.limit*1000), 'long') }}</td> <AlertMember :hash="hash" />
</tr> <div class="table-responsive">
<tr v-if="hash.status == 'MEMBER'"> <table
<th scope="row">{{ $t('membre.dispocertif') }} :</th> class="table table-sm table-borderless"
<td :class="{ v-if="hash.status != 'REVOKED'">
'list-group-item-success': hash.minDatePassed, <tbody>
'list-group-item-danger': !hash.minDatePassed, <MemberProp
}">{{ hash.minDatePassed ? $t('oui') : $t('non') }} <small v-if="!hash.minDatePassed">( > {{ $d(new Date(hash.minDate*1000), 'long') }} )</small></td> v-if="hash.status == 'MEMBER'"
</tr> :title="$t('membre.referent.title')"
<tr v-if="hash.status == 'MEMBER'"> :tooltip="$t('membre.referent.desc')"
<th scope="row">{{ $t('membre.nb_certifs') }} :</th> :classClor="{
<td :class="{ 'table-success': hash.sentry,
'list-group-item-success': hash.sent_certifications.length<=80, 'table-warning': !hash.sentry
'list-group-item-warning': hash.sent_certifications.length>80, }">
'list-group-item-danger': hash.sent_certifications.length>90, {{ hash.sentry ? $t("oui") : $t("non") }}
}">{{ 100-hash.sent_certifications.length }}</td> </MemberProp>
</tr> <MemberProp
</tbody> v-if="hash.status != 'NEWCOMER'"
</table> :title="$t('membre.qualite.title')"
</div> :tooltip="$t('membre.qualite.desc')"
</div> :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> </template>
<script> <script>
export default { export default {
props: { props: {
hash: Object hash: Object
}, },
computed: { computed: {
isReferent () { sentCertNotExpired() {
const nb_certifs_referent = 5 return this.hash.sent_certifications.filter((el) => !el.expired)
return this.hash.received_certifications.certifications.length > nb_certifs_referent && this.hash.sent_certifications.length > nb_certifs_referent }
} },
}, 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> </script>
<style lang="sass" scoped> <style lang="scss">
.member .btn.iam:not(.btn-warning) {
th z-index: 10;
text-align: right animation: zoom-in-zoom-out 1s ease infinite;
</style> }
\ No newline at end of file
@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> <template>
<div class="table-responsive"> <div class="table-responsive pb-3">
<table class="table table-striped table-hover"> <table
<thead v-if="displayHead"> class="table table-striped table-hover table-fixed sortable border text-center">
<tr> <thead class="thead-light">
<th scope="col">UID</th> <tr>
<th scope="col" class="d-none d-xl-table-cell" v-if="displayPubkey">PUBKEY</th> <th class="p-0" scope="col">
</tr> <BtnSort
</thead> fieldName="uid"
<tbody> :tableName="id"
<tr v-for="member in members" :key="member.uid" title="UID"
@click="redirect(member.hash)"> :currentSort="currentSort"
<th scope="row"> :currentSortDir="currentSortDir" />
{{ member.uid }} </th>
<BadgeCertifStatus :limitDate="member.received_certifications.limit" :memberStatus="member.status" /> <th
<BadgeStatus :membre="member" /> scope="col"
</th> class="d-none d-lg-table-cell p-0"
<td class="d-none d-xl-table-cell" v-if="displayPubkey">{{ member.pubkey }}</td> v-if="id != 'default'">
</tr> <BtnSort
</tbody> fieldName="statut"
</table> :tableName="id"
</div> :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> </template>
<script> <script>
export default { export default {
props: { data() {
members: { return {
type: Array, currentSort: this.defaultSort,
required: true currentSortDir: this.defaultSortDir
}, }
displayPubkey: { },
type: Boolean, props: {
default: true members: {
}, type: Array,
displayHead: { required: true
type: Boolean, },
default: true id: {
} type: String,
}, required: true
methods: { },
redirect(hash) { defaultSortDir: {
this.$router.push(this.localePath({name:'membres-hash', params: {hash}})) 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> </script>
\ No newline at end of file
<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> <template>
<header class="header position-fixed"> <header class="header position-fixed">
<div class="position-relative"> <div class="position-relative">
<button class="toggle btn border-secondary position-absolute p-1 m-1 ml-3" @click="toggleMenu"><span></span></button> <button
<NavigationBreadcrumb :breadcrumb="breadcrumb" /> :title="isOpen ? $t('aria.closemenu') : $t('aria.openmenu')"
</div> class="toggle btn border-secondary position-absolute p-1 ms-4"
<NavigationMenuSidebar @toggleMenu="toggleMenu" :menus="menus" /> @click="toggleMenu">
<div class="bg_overlay position-fixed" @click="toggleMenu"></div> <span></span>
</header> </button>
<NavigationBreadcrumb :breadcrumb="breadcrumb" />
</div>
<NavigationMenuSidebar @toggleMenu="toggleMenu" :menus="menus" />
<div class="bg_overlay position-fixed" @click="toggleMenu"></div>
</header>
</template> </template>
<script> <script>
export default { export default {
props: { data() {
breadcrumb: Array, return {
menus: Array isOpen: false
}, }
methods: { },
toggleMenu() { props: {
document.querySelector('.app').classList.toggle('open') 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> </script>
...@@ -27,187 +45,172 @@ export default { ...@@ -27,187 +45,172 @@ export default {
$btn-width: 50px; $btn-width: 50px;
.header { .header {
--menu-width: 0px; --menu-width: 0px;
width: 100%; width: 100%;
z-index: 100; z-index: 100;
background: var(--background-color-primary); background: var(--bg-primary-color);
transition: width .5s ease-in-out; transition: width 0.5s ease-in-out;
.open & { .open & {
--menu-width: 320px; --menu-width: 320px;
} }
@media (min-width:1200px) { @media (min-width: 1200px) {
width: calc(99vw - var(--menu-width)); width: calc(99vw - var(--menu-width));
} }
} }
nav.breadcrumb-wrapper { nav.breadcrumb-wrapper {
margin: 8px 15px 8px 80px; min-height: 80px;
display: flex; margin: 8px 15px;
flex-direction: column; padding: 1rem 1rem 1rem 4.5rem;
gap: 1rem; display: flex;
background: var(--background-color-secondary); flex-direction: column;
gap: 1rem;
a {color: var(--text-primary-color)} background: var(--bg-menu-color);
.breadcrumb-item.active { a {
opacity: .7; color: var(--txt-primary-color);
} }
@media (min-width:768px) { .breadcrumb-item.active {
flex-direction: row; opacity: 0.7;
justify-content: space-between; }
align-items: center;
} @media (min-width: 768px) {
flex-direction: row;
justify-content: space-between;
align-items: center;
}
} }
%hamburger-line { %hamburger-line {
display: block; display: block;
height: 4px; height: 4px;
width: .8 * $btn-width; width: 0.8 * $btn-width;
background: var(--text-primary-color); background: var(--txt-primary-color);
content: ""; content: "";
position: absolute; position: absolute;
transition-property: transform; transition-property: transform;
border-radius: 4px; border-radius: 4px;
} }
.toggle { .toggle {
height: $btn-width; height: $btn-width;
width: $btn-width; width: $btn-width;
line-height: $btn-width; line-height: $btn-width;
top: 1rem;
span {
@extend %hamburger-line; span {
top: 50%; @extend %hamburger-line;
transform: translateY(-50%); top: 50%;
transition-timing-function: cubic-bezier(.55,.055,.675,.19); transform: translateY(-50%);
transition-duration: 75ms; transition-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
transition-duration: 75ms;
.open & {
transform: rotate(45deg); .open & {
display: block; transform: rotate(45deg);
margin-top: -2px; display: block;
} margin-top: -2px;
}
&::before {
transition: top 75ms ease .12s,opacity 75ms ease; &::before {
@extend %hamburger-line; transition: top 75ms ease 0.12s, opacity 75ms ease;
top: -10px; @extend %hamburger-line;
top: -10px;
.open & {
opacity: 0; .open & {
} opacity: 0;
} }
}
&::after {
transition: bottom 75ms ease .12s,transform 75ms cubic-bezier(.55,.055,.675,.19); &::after {
@extend %hamburger-line; transition: bottom 75ms ease 0.12s,
bottom: -10px; transform 75ms cubic-bezier(0.55, 0.055, 0.675, 0.19);
@extend %hamburger-line;
.open & { bottom: -10px;
top: 0;
transform: rotate(-90deg); .open & {
} top: 0;
} transform: rotate(-90deg);
} }
}
}
} }
.menu { .menu {
background: var(--background-color-primary); width: var(--menu-size);
width: var(--menu-size); top: 0;
top: 0; z-index: 1200;
z-index: 1200; height: 100%;
height: 100vh; transition: left 0.5s ease-in-out;
padding: 1.1rem 0.5rem; left: -400px;
overflow-y: scroll;
scrollbar-color: #6969dd #e0e0e0; h1 {
scrollbar-width: thin; color: var(--txt-primary-color);
transition: left .5s ease-in-out; }
left: -400px;
.open & {
h1 {color: var(--text-primary-color);} left: 0;
}
.list-group-item {
&-action:not(.active) { .btn-close {
background: transparent; --size: 30px;
width: var(--size);
&:hover { height: var(--size);
background: rgba(0, 0, 255, 0.075); top: 0.8rem;
color: var(--text-primary-color); right: 0.8rem;
} .dark-theme & {
} filter: invert(1) grayscale(100%) brightness(200%);
}
div { }
transition: left .3s ease-in-out;
left: 0; h2 {
cursor: default;
&::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;
}
} }
.bg_overlay { .bg_overlay {
top: 0; top: 0;
left: 0; left: 0;
right: 0; right: 0;
height: 120vh; height: 120vh;
z-index: 1100; z-index: 1100;
visibility: hidden; visibility: hidden;
opacity: 0; opacity: 0;
transition: all .5s ease; transition: all 0.5s ease;
background-color: rgba(34,41,47,.5); background-color: rgba(34, 41, 47, 0.5);
.open & { .open & {
opacity: 1; opacity: 1;
visibility: visible; visibility: visible;
} }
} }
.logo { .logo {
max-width: 75px; &:hover {
text-decoration: none;
}
img {
max-width: 75px;
}
} }
@media (min-width:1200px) { @media (min-width: 1200px) {
.open { .open {
&.app { &.app {
margin-left: var(--menu-size); margin-left: var(--menu-size);
} }
.menu { .menu {
left: 0; left: 0;
} }
.bg_overlay { .bg_overlay {
visibility: hidden; visibility: hidden;
opacity: 0; opacity: 0;
} }
} }
} }
</style> </style>
<template> <template>
<nav aria-label="Fil d'Ariane" class="breadcrumb-wrapper rounded p-3"> <nav
<ol class="breadcrumb m-0 p-0"> :aria-label="$t('aria.ariane')"
<li class="breadcrumb-item" :class="{ 'active': item.active }" :aria-current="item.active ? 'page' : null" v-for="item in breadcrumb" :key="item.text"> class="breadcrumb-wrapper rounded border">
<NuxtLink :to="item.to" v-if="item.to">{{ item.text }}</NuxtLink> <ol class="breadcrumb m-0 p-0 d-none d-sm-flex">
<span v-else>{{ item.text }}</span> <li
</li> class="breadcrumb-item"
</ol> :class="{ active: item.active }"
<div class="d-flex justify-content-between align-items-center"> :aria-current="item.active ? 'page' : null"
<NavigationLanguage class="mr-3" /> v-for="item in breadcrumb"
<BtnTheme /> :key="item.text">
</div> <NuxtLink :to="localePath(item.to)" v-if="item.to">{{
</nav> 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> </template>
<script> <script>
export default { export default {
props: { props: {
breadcrumb: Array breadcrumb: Array
} }
} }
</script> </script>
\ No newline at end of file
<template> <template>
<div> <div>
<select class="form-control" @change="saveLocale($event)" v-model="lang"> <select
<option v-for="lang in $i18n.locales" :key="lang.code" :value="lang.code">{{ lang.name }}</option> class="form-select"
</select> :aria-label="$t('lang')"
</div> @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> </template>
<script> <script>
export default { export default {
data() { data() {
return { return {
lang: this.$i18n.locale activeLang: "en"
} }
}, },
methods: { methods: {
saveLocale(e) { saveLocale(e) {
this.lang = e.target.value // this.$i18n.locale = e.target.value
this.$i18n.setLocaleCookie(e.target.value) this.$i18n.setLocale(e.target.value)
this.$router.push(this.switchLocalePath(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> </script>
\ No newline at end of file