Version 2.1.0 - Update UI
This commit is contained in:
158 files changed
+24215
-7686
No files matched your search
@@ -1,16 +1,33 @@
|
||||
<template>
|
||||
<div class="stat">
|
||||
<Icon class="stat-icon" :icon="icon" :style="{ color: color }"/>
|
||||
<div class="stat-info">
|
||||
<p class="stat-label">{{ label }}</p>
|
||||
<p class="stat-value">{{ value }}</p>
|
||||
<p v-if="sub" class="stat-sub">{{ sub }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<component :is="isClickable() ? 'button' : 'div'" :type="isClickable() ? 'button' : undefined"
|
||||
:class="['adm-stat', `adm-tone-${toneName}`, { 'adm-tone-custom': !!color }]"
|
||||
:style="color ? { '--adm-tone': color } : null"
|
||||
@click="onClick">
|
||||
<span v-if="icon" class="adm-stat__chip" aria-hidden="true"><Icon :icon="icon"/></span>
|
||||
<span class="adm-stat__info">
|
||||
<span class="adm-stat__label">{{ label }}</span>
|
||||
<span :class="['adm-stat__value', { 'adm-stat__value--mono': mono }]"><slot>{{ value }}</slot></span>
|
||||
<span v-if="sub || $slots.sub" class="adm-stat__sub"><slot name="sub">{{ sub }}</slot></span>
|
||||
<AdminMeter v-if="hasProgress" :value="progress" :label="progressLabel || label" :tone="progressTone"/>
|
||||
</span>
|
||||
<template v-if="isClickable()">
|
||||
<Icon class="adm-stat__go" icon="fa-solid fa-chevron-right" aria-hidden="true"/>
|
||||
<span v-if="to" class="sr-only">, ouvrir la section {{ targetLabel }}</span>
|
||||
</template>
|
||||
</component>
|
||||
</template>
|
||||
<script setup>
|
||||
// Tuile de chiffre clé (même hiérarchie de surfaces que Metric.vue)
|
||||
defineProps({
|
||||
/**
|
||||
* Tuile de chiffre clé. `to` (id de section) la rend cliquable (bouton → adminStore.setSection),
|
||||
* tout comme un écouteur @click. `progress` (0..1) ajoute une jauge sous la valeur.
|
||||
*/
|
||||
import { computed, inject, useAttrs } from 'vue';
|
||||
import { useAdminStore } from '@/stores/adminStore';
|
||||
import AdminMeter from './AdminMeter.vue';
|
||||
import { ADMIN_NAV_KEY, normalizeTone, toneValidator } from './adminUi';
|
||||
import './admin.scss';
|
||||
|
||||
const props = defineProps({
|
||||
label: {
|
||||
type: String,
|
||||
required: true
|
||||
@@ -26,56 +43,60 @@ defineProps({
|
||||
type: String,
|
||||
default: "fa-solid fa-chart-bar"
|
||||
},
|
||||
// Historique : couleur libre de la pastille (prioritaire sur `tone`)
|
||||
color: {
|
||||
type: String,
|
||||
default: "var(--text)"
|
||||
default: null
|
||||
},
|
||||
tone: {
|
||||
type: String,
|
||||
default: 'default',
|
||||
validator: toneValidator
|
||||
},
|
||||
// Id de section ('players', 'users'…) : la tuile ouvre cette section
|
||||
to: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
// Libellé lisible de la section cible, pour les lecteurs d'écran
|
||||
toLabel: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
progress: {
|
||||
type: Number,
|
||||
default: null
|
||||
},
|
||||
progressLabel: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
// 'auto' : rose, orange au-delà de 75 %, rouge au-delà de 90 %
|
||||
progressTone: {
|
||||
type: String,
|
||||
default: 'auto'
|
||||
},
|
||||
mono: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
});
|
||||
|
||||
const attrs = useAttrs();
|
||||
const adminStore = useAdminStore();
|
||||
const nav = inject(ADMIN_NAV_KEY, null);
|
||||
|
||||
const toneName = computed(() => normalizeTone(props.tone));
|
||||
// Pas de defineEmits('click') : l'écouteur du parent reste dans attrs et passe au bouton racine.
|
||||
// useAttrs() n'est pas réactif : fonction relue à chaque rendu plutôt qu'un computed
|
||||
function isClickable() {
|
||||
return !!props.to || !!attrs.onClick;
|
||||
}
|
||||
|
||||
const hasProgress = computed(() => typeof props.progress === 'number' && Number.isFinite(props.progress));
|
||||
const targetLabel = computed(() => props.toLabel || nav?.sections?.find(s => s.id === props.to)?.label || props.to);
|
||||
|
||||
function onClick() {
|
||||
if (props.to) adminStore.setSection(props.to);
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.stat {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px;
|
||||
background-color: var(--tertiary);
|
||||
border-radius: 10px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
background-color: var(--secondary);
|
||||
padding: 15px;
|
||||
border-radius: 100%;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.stat-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.stat-info p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-weight: bold;
|
||||
font-size: 1.3em;
|
||||
font-family: monospace;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.stat-sub {
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.7em;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user