Initial commit — Diabetix V2
Application Laravel 12 + Inertia + Vue 3 + Tailwind. Fonctionnalités : dashboard glycémique, saisie de mesures, courbe SVG, statistiques (jour/semaine/mois/trimestre), défis & badges, chat coach IA (Gemini), paramètres profil avec palette de couleurs, pages auth redessinées, emails transactionnels via Resend avec thème Diabetix. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
117
app/resources/js/Pages/Diabetix/Challenges.vue
Normal file
117
app/resources/js/Pages/Diabetix/Challenges.vue
Normal file
@@ -0,0 +1,117 @@
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { Head } from '@inertiajs/vue3';
|
||||
import DiabetixLayout from '@/Layouts/DiabetixLayout.vue';
|
||||
import ProgressBar from '@/Components/Diabetix/ProgressBar.vue';
|
||||
import BadgeChip from '@/Components/Diabetix/BadgeChip.vue';
|
||||
|
||||
const props = defineProps({
|
||||
challenges: Array,
|
||||
badges: Array,
|
||||
level: Object,
|
||||
});
|
||||
|
||||
const tab = ref('defis');
|
||||
|
||||
const unlocked = computed(() => props.badges.filter(b => b.unlocked));
|
||||
const locked = computed(() => props.badges.filter(b => !b.unlocked));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Head title="Défis & Badges" />
|
||||
<DiabetixLayout v-slot="{ tok, font }">
|
||||
<div :style="{ background: tok.white, padding: '14px 20px 18px', borderBottom: '1px solid ' + tok.border }">
|
||||
<div :style="{ fontFamily: font.title, fontSize: '20px', fontWeight: 600, color: tok.text, marginBottom: '14px' }">Défis & Badges</div>
|
||||
|
||||
<div :style="{ background: tok.light, borderRadius: '18px', padding: '16px' }">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;">
|
||||
<div>
|
||||
<div :style="{ fontSize: '11px', color: tok.muted }">Niveau actuel</div>
|
||||
<div :style="{ fontFamily: font.title, fontSize: '17px', fontWeight: 600, color: tok.text }">{{ level.icon }} {{ level.label }}</div>
|
||||
</div>
|
||||
<div style="text-align:right;">
|
||||
<div :style="{ fontSize: '11px', color: tok.muted }">Points</div>
|
||||
<div :style="{ fontSize: '22px', fontWeight: 800, color: tok.amber }">{{ level.points.toLocaleString('fr-FR') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<ProgressBar :value="level.points" :max="level.next_at" :color="tok.amber" :tok="tok" :height="8" />
|
||||
<div style="display:flex;justify-content:space-between;margin-top:5px;">
|
||||
<span :style="{ fontSize: '10px', color: tok.muted }">{{ level.icon }} {{ level.label }}</span>
|
||||
<span :style="{ fontSize: '10px', color: tok.amber }">{{ level.remaining }} pts → {{ level.next_icon }} {{ level.next_label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="padding:14px 20px 0;">
|
||||
<div :style="{ display: 'flex', gap: '4px', background: tok.bgAlt, borderRadius: '12px', padding: '4px' }">
|
||||
<button v-for="t in [['defis','🎯 Défis actifs'],['badges','🏅 Badges']]" :key="t[0]"
|
||||
@click="tab = t[0]"
|
||||
:style="{
|
||||
flex: 1, padding: '8px', borderRadius: '9px', border: 'none',
|
||||
background: tab === t[0] ? tok.primary : 'transparent',
|
||||
color: tab === t[0] ? '#fff' : tok.muted,
|
||||
fontSize: '12px', fontWeight: tab === t[0] ? 600 : 400, cursor: 'pointer',
|
||||
}">{{ t[1] }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="padding:14px 20px;display:flex;flex-direction:column;gap:14px;">
|
||||
<template v-if="tab === 'defis'">
|
||||
<div v-for="c in challenges" :key="c.id"
|
||||
:style="{ background: tok.white, borderRadius: '18px', padding: '16px', boxShadow: '0 2px 12px rgba(42,53,51,0.06)' }">
|
||||
<div style="display:flex;align-items:flex-start;gap:12px;">
|
||||
<div style="font-size:28px;">{{ c.icon }}</div>
|
||||
<div style="flex:1;">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;">
|
||||
<div :style="{ fontSize: '14px', fontWeight: 600, color: tok.text }">{{ c.title }}</div>
|
||||
<div :style="{ background: tok.amberLight, borderRadius: '20px', padding: '3px 8px', fontSize: '10px', color: tok.amber, fontWeight: 600 }">+{{ c.points }} pts</div>
|
||||
</div>
|
||||
<div :style="{ fontSize: '11px', color: tok.muted, marginBottom: '10px' }">{{ c.description }}</div>
|
||||
<ProgressBar :value="c.progress" :tok="tok" :height="7" />
|
||||
<div style="display:flex;justify-content:flex-end;margin-top:5px;">
|
||||
<span :style="{ fontSize: '11px', color: tok.primary }">{{ c.progress }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<template v-if="unlocked.length">
|
||||
<div :style="{ fontSize: '11px', color: tok.muted, fontWeight: 700, letterSpacing: '0.8px' }">DÉBLOQUÉS ({{ unlocked.length }})</div>
|
||||
<div v-for="b in unlocked" :key="b.id"
|
||||
:style="{ background: tok.white, borderRadius: '16px', padding: '14px 16px', boxShadow: '0 2px 10px rgba(42,53,51,0.06)', display: 'flex', alignItems: 'center', gap: '14px' }">
|
||||
<div :style="{
|
||||
width: '48px', height: '48px', borderRadius: '50%', flexShrink: 0,
|
||||
background: tok.light, border: '2px solid ' + tok.primary,
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '22px',
|
||||
}">{{ b.icon }}</div>
|
||||
<div style="flex:1;min-width:0;">
|
||||
<div :style="{ fontSize: '13px', fontWeight: 700, color: tok.text }">{{ b.label }}</div>
|
||||
<div :style="{ fontSize: '11px', color: tok.muted, marginTop: '2px', lineHeight: 1.4 }">{{ b.criteria }}</div>
|
||||
</div>
|
||||
<div :style="{ fontSize: '16px', flexShrink: 0 }">✅</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="locked.length">
|
||||
<div :style="{ fontSize: '11px', color: tok.muted, fontWeight: 700, letterSpacing: '0.8px', marginTop: unlocked.length ? '4px' : 0 }">À DÉBLOQUER ({{ locked.length }})</div>
|
||||
<div v-for="b in locked" :key="b.id"
|
||||
:style="{ background: tok.white, borderRadius: '16px', padding: '14px 16px', boxShadow: '0 2px 10px rgba(42,53,51,0.04)', display: 'flex', alignItems: 'center', gap: '14px', opacity: 0.7 }">
|
||||
<div :style="{
|
||||
width: '48px', height: '48px', borderRadius: '50%', flexShrink: 0,
|
||||
background: tok.bgAlt, border: '2px solid ' + tok.border,
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '22px',
|
||||
filter: 'grayscale(1)',
|
||||
}">{{ b.icon }}</div>
|
||||
<div style="flex:1;min-width:0;">
|
||||
<div :style="{ fontSize: '13px', fontWeight: 600, color: tok.muted }">{{ b.label }}</div>
|
||||
<div :style="{ fontSize: '11px', color: tok.muted, marginTop: '2px', lineHeight: 1.4 }">{{ b.criteria }}</div>
|
||||
</div>
|
||||
<div :style="{ fontSize: '16px', flexShrink: 0 }">🔒</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</DiabetixLayout>
|
||||
</template>
|
||||
Reference in New Issue
Block a user