Version 2.1.0 - Update UI

This commit is contained in:
raphix committed 2026-10-08 10:06:45 +02:00
1 parent 6d81a8c1c0
commit 54bbb2ca04
158 files changed
+24215 -7686

No files matched your search

+79 -58
View File
@@ -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>