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>
129 lines
7.9 KiB
Vue
129 lines
7.9 KiB
Vue
<script setup>
|
||
import { computed } from 'vue';
|
||
import { Head, Link, usePage } from '@inertiajs/vue3';
|
||
import DiabetixLayout from '@/Layouts/DiabetixLayout.vue';
|
||
import ProgressBar from '@/Components/Diabetix/ProgressBar.vue';
|
||
import CoachAvatar from '@/Components/Diabetix/CoachAvatar.vue';
|
||
import BadgeChip from '@/Components/Diabetix/BadgeChip.vue';
|
||
import GlucoseChart from '@/Components/Diabetix/GlucoseChart.vue';
|
||
|
||
const props = defineProps({
|
||
metrics: Object,
|
||
day_chart: Array,
|
||
challenges: Array,
|
||
badges: Array,
|
||
});
|
||
|
||
const page = usePage();
|
||
const userName = computed(() => page.props.auth.user?.first_name ?? 'ami');
|
||
|
||
const latest = computed(() => props.metrics.latest);
|
||
const inRange = computed(() => latest.value?.in_range);
|
||
</script>
|
||
|
||
<template>
|
||
<Head title="Accueil" />
|
||
<DiabetixLayout v-slot="{ tok, font, openInput }">
|
||
<!-- Header -->
|
||
<div :style="{ background: tok.white, padding: '14px 20px 18px', borderBottom: '1px solid ' + tok.border }">
|
||
<div style="display:flex;align-items:center;gap:12px;margin-bottom:14px;">
|
||
<CoachAvatar :size="42" :tok="tok" />
|
||
<div style="flex:1;">
|
||
<div :style="{ fontFamily: font.title, fontSize: '19px', color: tok.text, fontWeight: 600 }">
|
||
Bonjour <Link :href="route('profile.edit')" :style="{ color: tok.text, textDecoration: 'none', borderBottom: '1.5px dashed ' + tok.primary }">{{ userName }}</Link> ! 👋
|
||
</div>
|
||
<div :style="{ fontSize: '11px', color: tok.muted }">Coach IA · En ligne</div>
|
||
</div>
|
||
<div v-if="latest" :style="{
|
||
background: inRange ? tok.light : tok.amberLight,
|
||
borderRadius: '14px', padding: '8px 14px', textAlign: 'center',
|
||
border: '2px solid ' + (inRange ? tok.primary : tok.amber),
|
||
}">
|
||
<div :style="{ fontSize: '20px', fontWeight: 800, color: tok.text, lineHeight: 1 }">
|
||
{{ latest.value }}
|
||
</div>
|
||
<div :style="{ fontSize: '9px', color: inRange ? tok.primary : tok.amber, marginTop: '2px' }">
|
||
mg/dL {{ inRange ? '✓' : '⚠' }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div :style="{ background: tok.light, borderRadius: '16px', padding: '12px 14px' }">
|
||
<div :style="{ fontSize: '12px', color: tok.text, lineHeight: 1.6 }">
|
||
🤖 Votre glycémie actuelle est
|
||
<strong>{{ inRange ? 'dans la cible' : 'à surveiller' }}</strong>.
|
||
Essayez une marche de 10 min ce soir — je vous rappellerai à 19h30 !
|
||
</div>
|
||
<button :style="{
|
||
marginTop: '10px', padding: '5px 14px', borderRadius: '20px',
|
||
background: tok.white, border: '1.5px solid ' + tok.primary,
|
||
fontSize: '11px', color: tok.primary, fontWeight: 600, cursor: 'pointer',
|
||
}">Ajouter ce défi ✓</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="padding:14px 20px;display:flex;flex-direction:column;gap:14px;">
|
||
<!-- Stats row -->
|
||
<div style="display:flex;gap:14px;">
|
||
<div :style="{ flex: 1, background: tok.white, borderRadius: '16px', padding: '14px', textAlign: 'center', boxShadow: '0 2px 10px rgba(42,53,51,0.05)' }">
|
||
<div :style="{ fontSize: '26px', fontWeight: 800, color: tok.text }">🔥 {{ metrics.streak_days }}</div>
|
||
<div :style="{ fontSize: '10px', color: tok.muted, marginTop: '3px' }">jours dans la cible</div>
|
||
</div>
|
||
<div :style="{ flex: 1, background: tok.white, borderRadius: '16px', padding: '14px', textAlign: 'center', boxShadow: '0 2px 10px rgba(42,53,51,0.05)' }">
|
||
<div :style="{ fontSize: '26px', fontWeight: 800, color: tok.primary }">{{ metrics.time_in_range }}%</div>
|
||
<div :style="{ fontSize: '10px', color: tok.muted, marginTop: '3px' }">temps dans la cible</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Day chart -->
|
||
<div :style="{ background: tok.white, borderRadius: '20px', padding: '18px 18px 12px', boxShadow: '0 2px 16px rgba(42,53,51,0.06)' }">
|
||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;">
|
||
<div :style="{ fontSize: '13px', fontWeight: 600, color: tok.text }">Aujourd'hui</div>
|
||
<Link :href="route('stats')" :style="{ fontSize: '11px', color: tok.primary, textDecoration: 'none' }">Détails →</Link>
|
||
</div>
|
||
<div v-if="day_chart.length === 0" :style="{ fontSize: '12px', color: tok.muted, textAlign: 'center', padding: '18px 0' }">
|
||
Aucune mesure aujourd'hui — tapez + pour en ajouter une.
|
||
</div>
|
||
<GlucoseChart v-else :data="day_chart" :tok="tok" :width="316" :height="120" :target-min="metrics.target_min" :target-max="metrics.target_max" />
|
||
</div>
|
||
|
||
<!-- Active challenges -->
|
||
<div :style="{ background: tok.white, borderRadius: '20px', padding: '20px', boxShadow: '0 2px 16px rgba(42,53,51,0.06)' }">
|
||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:14px;">
|
||
<div :style="{ fontSize: '13px', fontWeight: 600, color: tok.text }">Défis actifs</div>
|
||
<Link :href="route('challenges')" :style="{ fontSize: '11px', color: tok.primary, textDecoration: 'none' }">Voir tout →</Link>
|
||
</div>
|
||
<div v-if="!challenges.length" :style="{ fontSize: '12px', color: tok.muted }">Aucun défi en cours.</div>
|
||
<div v-for="(c, i) in challenges" :key="c.id" :style="{ marginBottom: i < challenges.length - 1 ? '14px' : 0, display: 'flex', alignItems: 'center', gap: '10px' }">
|
||
<div style="font-size:24px;">{{ c.icon }}</div>
|
||
<div style="flex:1;">
|
||
<div style="display:flex;justify-content:space-between;align-items:center;">
|
||
<div :style="{ fontSize: '13px', fontWeight: 600, color: tok.text }">{{ c.title }}</div>
|
||
<div :style="{ fontSize: '11px', color: tok.amber, fontWeight: 600 }">+{{ c.points }} pts</div>
|
||
</div>
|
||
<div :style="{ fontSize: '11px', color: tok.muted, marginBottom: '6px' }">{{ c.description }}</div>
|
||
<ProgressBar :value="c.progress" :tok="tok" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Badges -->
|
||
<div :style="{ background: tok.white, borderRadius: '20px', padding: '20px', boxShadow: '0 2px 16px rgba(42,53,51,0.06)' }">
|
||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:14px;">
|
||
<div :style="{ fontSize: '13px', fontWeight: 600, color: tok.text }">Badges débloqués</div>
|
||
<Link :href="route('challenges')" :style="{ fontSize: '11px', color: tok.primary, textDecoration: 'none' }">Voir tout →</Link>
|
||
</div>
|
||
<div style="display:flex;justify-content:space-around;">
|
||
<BadgeChip v-for="(b, i) in badges.slice(0, 4)" :key="i" :icon="b.icon" :label="b.label" :unlocked="b.unlocked" :tok="tok" />
|
||
</div>
|
||
</div>
|
||
|
||
<button @click="openInput" :style="{
|
||
width: '100%', padding: '14px', background: tok.primary, color: '#fff',
|
||
borderRadius: '16px', border: 'none', fontSize: '15px', fontWeight: 600,
|
||
cursor: 'pointer', boxShadow: '0 6px 18px rgba(123,191,181,0.35)',
|
||
}">+ Saisir une mesure</button>
|
||
</div>
|
||
</DiabetixLayout>
|
||
</template>
|