feat: implement Weather Workshop feature with models, UI components, and session management for enhanced team visibility and personal well-being tracking
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 3m16s

This commit is contained in:
Julien Froidefond
2026-02-03 18:08:06 +01:00
parent 3a2eb83197
commit 163caa398c
20 changed files with 2287 additions and 28 deletions

View File

@@ -0,0 +1,146 @@
'use client';
import { useMemo } from 'react';
import { WeatherCard } from './WeatherCard';
interface WeatherEntry {
id: string;
userId: string;
performanceEmoji: string | null;
moralEmoji: string | null;
fluxEmoji: string | null;
valueCreationEmoji: string | null;
notes: string | null;
user: {
id: string;
name: string | null;
email: string;
};
}
interface Share {
id: string;
userId: string;
user: {
id: string;
name: string | null;
email: string;
};
}
interface WeatherBoardProps {
sessionId: string;
currentUserId: string;
currentUser: {
id: string;
name: string | null;
email: string;
};
entries: WeatherEntry[];
shares: Share[];
owner: {
id: string;
name: string | null;
email: string;
};
canEdit: boolean;
}
export function WeatherBoard({
sessionId,
currentUserId,
entries,
shares,
owner,
canEdit,
}: WeatherBoardProps) {
// Get all users who have access: owner + shared users
const allUsers = useMemo(() => {
const usersMap = new Map<string, { id: string; name: string | null; email: string }>();
// Add owner
usersMap.set(owner.id, owner);
// Add shared users
shares.forEach((share) => {
usersMap.set(share.userId, share.user);
});
return Array.from(usersMap.values());
}, [owner, shares]);
// Create entries map for quick lookup
const entriesMap = useMemo(() => {
const map = new Map<string, WeatherEntry>();
entries.forEach((entry) => {
map.set(entry.userId, entry);
});
return map;
}, [entries]);
// Create entries for all users (with placeholder entries for users without entries)
const allEntries = useMemo(() => {
return allUsers.map((user) => {
const existingEntry = entriesMap.get(user.id);
if (existingEntry) {
return existingEntry;
}
// Create placeholder entry for user without entry
return {
id: '',
userId: user.id,
performanceEmoji: null,
moralEmoji: null,
fluxEmoji: null,
valueCreationEmoji: null,
notes: null,
user,
};
});
}, [allUsers, entriesMap]);
// Sort: current user first, then owner, then others
const sortedEntries = useMemo(() => {
return [...allEntries].sort((a, b) => {
if (a.userId === currentUserId) return -1;
if (b.userId === currentUserId) return 1;
if (a.userId === owner.id) return -1;
if (b.userId === owner.id) return 1;
return (a.user.name || a.user.email).localeCompare(b.user.name || b.user.email, 'fr');
});
}, [allEntries, currentUserId, owner.id]);
return (
<div className="overflow-x-auto rounded-lg border border-border bg-card">
<table className="w-full">
<thead>
<tr className="border-b border-border bg-card-column">
<th className="px-4 py-3 text-left text-sm font-medium text-foreground">Membre</th>
<th className="px-4 py-3 text-center text-sm font-medium text-foreground">
Performance
</th>
<th className="px-4 py-3 text-center text-sm font-medium text-foreground">Moral</th>
<th className="px-4 py-3 text-center text-sm font-medium text-foreground">Flux</th>
<th className="px-4 py-3 text-center text-sm font-medium text-foreground">
Création de valeur
</th>
<th className="px-4 py-3 text-left text-sm font-medium text-foreground min-w-[300px]">
Notes
</th>
</tr>
</thead>
<tbody>
{sortedEntries.map((entry) => (
<WeatherCard
key={entry.userId}
sessionId={sessionId}
currentUserId={currentUserId}
entry={entry}
canEdit={canEdit}
/>
))}
</tbody>
</table>
</div>
);
}