feat(weather): show trend indicators on team averages

This commit is contained in:
2026-03-04 08:34:23 +01:00
parent 4aea17124e
commit 8bff21bede
2 changed files with 93 additions and 8 deletions

View File

@@ -40,6 +40,16 @@ export default async function WeatherSessionPage({ params }: WeatherSessionPageP
getUserTeams(authSession.user.id),
getWeatherSessionsHistory(authSession.user.id),
]);
const currentHistoryIndex = history.findIndex((point) => point.sessionId === session.id);
const previousTeamAverages =
currentHistoryIndex > 0
? {
performance: history[currentHistoryIndex - 1].performance,
moral: history[currentHistoryIndex - 1].moral,
flux: history[currentHistoryIndex - 1].flux,
valueCreation: history[currentHistoryIndex - 1].valueCreation,
}
: null;
return (
<main className="mx-auto max-w-7xl px-4">
@@ -70,7 +80,7 @@ export default async function WeatherSessionPage({ params }: WeatherSessionPageP
canEdit={session.canEdit}
userTeams={userTeams}
>
<WeatherAverageBar entries={session.entries} />
<WeatherAverageBar entries={session.entries} previousAverages={previousTeamAverages} />
<WeatherBoard
sessionId={session.id}
currentUserId={authSession.user.id}

View File

@@ -1,5 +1,5 @@
// src/components/weather/WeatherAverageBar.tsx
import { getAverageEmoji } from '@/lib/weather-utils';
import { getAverageEmoji, getEmojiScore } from '@/lib/weather-utils';
interface WeatherEntry {
performanceEmoji: string | null;
@@ -10,22 +10,93 @@ interface WeatherEntry {
interface WeatherAverageBarProps {
entries: WeatherEntry[];
previousAverages?: {
performance: number | null;
moral: number | null;
flux: number | null;
valueCreation: number | null;
} | null;
}
const AXES = [
{ key: 'performanceEmoji' as const, label: 'Performance' },
{ key: 'moralEmoji' as const, label: 'Moral' },
{ key: 'fluxEmoji' as const, label: 'Flux' },
{ key: 'valueCreationEmoji' as const, label: 'Création de valeur' },
{ key: 'performanceEmoji' as const, scoreKey: 'performance' as const, label: 'Performance' },
{ key: 'moralEmoji' as const, scoreKey: 'moral' as const, label: 'Moral' },
{ key: 'fluxEmoji' as const, scoreKey: 'flux' as const, label: 'Flux' },
{
key: 'valueCreationEmoji' as const,
scoreKey: 'valueCreation' as const,
label: 'Création de valeur',
},
];
export function WeatherAverageBar({ entries }: WeatherAverageBarProps) {
function getAverageScore(emojis: (string | null)[]): number | null {
const scores = emojis.map(getEmojiScore).filter((score): score is number => score !== null);
if (scores.length === 0) return null;
return scores.reduce((sum, score) => sum + score, 0) / scores.length;
}
function AverageEvolutionIndicator({
currentScore,
previousScore,
}: {
currentScore: number | null;
previousScore: number | null | undefined;
}) {
if (currentScore === null || previousScore === null || previousScore === undefined) return null;
const delta = currentScore - previousScore;
if (delta < 0) {
return (
<span
className="inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-full text-[10px] font-bold"
style={{
backgroundColor: 'color-mix(in srgb, var(--success) 18%, transparent)',
color: 'var(--success)',
}}
title="En progression"
>
</span>
);
}
if (delta > 0) {
return (
<span
className="inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-full text-[10px] font-bold"
style={{
backgroundColor: 'color-mix(in srgb, var(--destructive) 18%, transparent)',
color: 'var(--destructive)',
}}
title="En baisse"
>
</span>
);
}
return (
<span
className="inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-full text-[10px] font-bold"
style={{
backgroundColor: 'color-mix(in srgb, var(--muted) 15%, transparent)',
color: 'var(--muted)',
}}
title="Stable"
>
</span>
);
}
export function WeatherAverageBar({ entries, previousAverages }: WeatherAverageBarProps) {
if (entries.length === 0) return null;
return (
<div className="flex flex-wrap items-center gap-3 mb-4">
<span className="text-xs font-medium text-muted uppercase tracking-wide">Moyenne équipe</span>
{AXES.map(({ key, label }) => {
{AXES.map(({ key, scoreKey, label }) => {
const currentScore = getAverageScore(entries.map((e) => e[key]));
const avg = getAverageEmoji(entries.map((e) => e[key]));
return (
<div
@@ -34,6 +105,10 @@ export function WeatherAverageBar({ entries }: WeatherAverageBarProps) {
>
<span className="text-lg leading-none">{avg ?? '—'}</span>
<span className="text-xs text-muted">{label}</span>
<AverageEvolutionIndicator
currentScore={currentScore}
previousScore={previousAverages?.[scoreKey]}
/>
</div>
);
})}