feat: add weather trend chart showing indicator averages over time
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 3m6s
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 3m6s
Adds a collapsible SVG line graph on weather session pages displaying the evolution of all 4 indicators (Performance, Moral, Flux, Création de valeur) across sessions, with per-session average scores, hover tooltips, and a marker on the current session. Also fixes pre-existing lint errors: non-null assertion on optional chain in Header and eslint-disable for intentional hydration pattern in ThemeToggle. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -8,8 +8,19 @@ import {
|
||||
getSessionByIdGeneric,
|
||||
} from '@/services/session-queries';
|
||||
import { getWeekBounds } from '@/lib/date-utils';
|
||||
import { getEmojiScore } from '@/lib/weather-utils';
|
||||
import type { ShareRole } from '@prisma/client';
|
||||
|
||||
export type WeatherHistoryPoint = {
|
||||
sessionId: string;
|
||||
title: string;
|
||||
date: Date;
|
||||
performance: number | null;
|
||||
moral: number | null;
|
||||
flux: number | null;
|
||||
valueCreation: number | null;
|
||||
};
|
||||
|
||||
const weatherInclude = {
|
||||
user: { select: { id: true, name: true, email: true } },
|
||||
shares: { include: { user: { select: { id: true, name: true, email: true } } } },
|
||||
@@ -343,3 +354,58 @@ export type WeatherSessionEventType =
|
||||
export const createWeatherSessionEvent = weatherShareEvents.createEvent;
|
||||
export const getWeatherSessionEvents = weatherShareEvents.getEvents;
|
||||
export const getLatestWeatherEventTimestamp = weatherShareEvents.getLatestEventTimestamp;
|
||||
|
||||
// ============================================
|
||||
// Weather History (for trend chart)
|
||||
// ============================================
|
||||
|
||||
function avgScore(emojis: (string | null)[]): number | null {
|
||||
const scores = emojis.map(getEmojiScore).filter((s): s is number => s !== null);
|
||||
if (scores.length === 0) return null;
|
||||
return scores.reduce((sum, s) => sum + s, 0) / scores.length;
|
||||
}
|
||||
|
||||
export async function getWeatherSessionsHistory(userId: string): Promise<WeatherHistoryPoint[]> {
|
||||
const entrySelect = {
|
||||
performanceEmoji: true,
|
||||
moralEmoji: true,
|
||||
fluxEmoji: true,
|
||||
valueCreationEmoji: true,
|
||||
} as const;
|
||||
|
||||
const [ownSessions, sharedRaw] = await Promise.all([
|
||||
prisma.weatherSession.findMany({
|
||||
where: { userId },
|
||||
select: { id: true, title: true, date: true, entries: { select: entrySelect } },
|
||||
}),
|
||||
prisma.weatherSessionShare.findMany({
|
||||
where: { userId },
|
||||
select: {
|
||||
session: {
|
||||
select: { id: true, title: true, date: true, entries: { select: entrySelect } },
|
||||
},
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
const seen = new Set<string>();
|
||||
const all: { id: string; title: string; date: Date; entries: typeof ownSessions[0]['entries'] }[] = [];
|
||||
for (const s of [...ownSessions, ...sharedRaw.map((r) => r.session)]) {
|
||||
if (!seen.has(s.id)) {
|
||||
seen.add(s.id);
|
||||
all.push(s);
|
||||
}
|
||||
}
|
||||
|
||||
all.sort((a, b) => a.date.getTime() - b.date.getTime());
|
||||
|
||||
return all.map((s) => ({
|
||||
sessionId: s.id,
|
||||
title: s.title,
|
||||
date: s.date,
|
||||
performance: avgScore(s.entries.map((e) => e.performanceEmoji)),
|
||||
moral: avgScore(s.entries.map((e) => e.moralEmoji)),
|
||||
flux: avgScore(s.entries.map((e) => e.fluxEmoji)),
|
||||
valueCreation: avgScore(s.entries.map((e) => e.valueCreationEmoji)),
|
||||
}));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user