All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 3m17s
- Redesign session cards with colored left border (Figma-style), improved visual hierarchy, hover states, and stats in footer - Add 4 switchable view modes: grid, list, sortable table, and timeline - Table view: unified flat table with clickable column headers for sorting (Type, Titre, Créateur, Participant, Stats, Date) - Add Créateur column showing the workshop owner with Gravatar avatar - Widen Type column to 160px for better readability - Improve tabs navigation with pill-shaped active state and shadow - Fix TypeFilterDropdown to exclude 'Équipe' from type list - Make filter tabs visually distinct with bg-card + border + shadow-sm - Split WorkshopTabs.tsx into 4 focused modules: workshop-session-types.ts, workshop-session-helpers.ts, SessionCard.tsx, WorkshopTabs.tsx Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
94 lines
4.7 KiB
TypeScript
94 lines
4.7 KiB
TypeScript
import type {
|
|
AnySession, SortCol, ResolvedCollaborator,
|
|
SwotSession, MotivatorSession, YearReviewSession,
|
|
WeeklyCheckInSession, WeatherSession, GifMoodSession,
|
|
} from './workshop-session-types';
|
|
|
|
export function getResolvedCollaborator(session: AnySession): ResolvedCollaborator {
|
|
if (session.workshopType === 'swot') return (session as SwotSession).resolvedCollaborator;
|
|
if (session.workshopType === 'year-review') return (session as YearReviewSession).resolvedParticipant;
|
|
if (session.workshopType === 'weekly-checkin') return (session as WeeklyCheckInSession).resolvedParticipant;
|
|
if (session.workshopType === 'weather') {
|
|
const s = session as WeatherSession;
|
|
return { raw: s.user.name || s.user.email, matchedUser: { id: s.user.id, email: s.user.email, name: s.user.name } };
|
|
}
|
|
if (session.workshopType === 'gif-mood') {
|
|
const s = session as GifMoodSession;
|
|
return { raw: s.user.name || s.user.email, matchedUser: { id: s.user.id, email: s.user.email, name: s.user.name } };
|
|
}
|
|
return (session as MotivatorSession).resolvedParticipant;
|
|
}
|
|
|
|
export function getGroupKey(session: AnySession): string {
|
|
const r = getResolvedCollaborator(session);
|
|
return r.matchedUser ? `user:${r.matchedUser.id}` : `raw:${r.raw.trim().toLowerCase()}`;
|
|
}
|
|
|
|
export function groupByPerson(sessions: AnySession[]): Map<string, AnySession[]> {
|
|
const grouped = new Map<string, AnySession[]>();
|
|
sessions.forEach((s) => {
|
|
const key = getGroupKey(s);
|
|
const existing = grouped.get(key);
|
|
if (existing) existing.push(s);
|
|
else grouped.set(key, [s]);
|
|
});
|
|
grouped.forEach((arr) => arr.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()));
|
|
return grouped;
|
|
}
|
|
|
|
export function formatDate(date: Date | string): string {
|
|
return new Date(date).toLocaleDateString('fr-FR', { day: 'numeric', month: 'short' });
|
|
}
|
|
|
|
export function getMonthGroup(date: Date | string): string {
|
|
return new Date(date).toLocaleDateString('fr-FR', { month: 'long', year: 'numeric' });
|
|
}
|
|
|
|
export function getStatsText(session: AnySession): string {
|
|
const isSwot = session.workshopType === 'swot';
|
|
const isYearReview = session.workshopType === 'year-review';
|
|
const isWeeklyCheckIn = session.workshopType === 'weekly-checkin';
|
|
const isWeather = session.workshopType === 'weather';
|
|
const isGifMood = session.workshopType === 'gif-mood';
|
|
|
|
if (isSwot) return `${(session as SwotSession)._count.items} items · ${(session as SwotSession)._count.actions} actions`;
|
|
if (isYearReview) return `${(session as YearReviewSession)._count.items} items · ${(session as YearReviewSession).year}`;
|
|
if (isWeeklyCheckIn) return `${(session as WeeklyCheckInSession)._count.items} items · ${formatDate((session as WeeklyCheckInSession).date)}`;
|
|
if (isWeather) return `${(session as WeatherSession)._count.entries} membres · ${formatDate((session as WeatherSession).date)}`;
|
|
if (isGifMood) return `${(session as GifMoodSession)._count.items} GIFs · ${formatDate((session as GifMoodSession).date)}`;
|
|
return `${(session as MotivatorSession)._count.cards}/10 motivateurs`;
|
|
}
|
|
|
|
function getStatsSortValue(session: AnySession): number {
|
|
if (session.workshopType === 'swot') return (session as SwotSession)._count.items;
|
|
if (session.workshopType === 'year-review') return (session as YearReviewSession)._count.items;
|
|
if (session.workshopType === 'weekly-checkin') return (session as WeeklyCheckInSession)._count.items;
|
|
if (session.workshopType === 'weather') return (session as WeatherSession)._count.entries;
|
|
if (session.workshopType === 'gif-mood') return (session as GifMoodSession)._count.items;
|
|
return (session as MotivatorSession)._count.cards;
|
|
}
|
|
|
|
function getParticipantSortName(session: AnySession): string {
|
|
const r = getResolvedCollaborator(session);
|
|
return (r.matchedUser?.name || r.matchedUser?.email?.split('@')[0] || r.raw).toLowerCase();
|
|
}
|
|
|
|
function getCreatorName(session: AnySession): string {
|
|
return (session.user.name || session.user.email).toLowerCase();
|
|
}
|
|
|
|
export function sortSessions(sessions: AnySession[], col: SortCol, dir: 'asc' | 'desc'): AnySession[] {
|
|
return [...sessions].sort((a, b) => {
|
|
let cmp = 0;
|
|
switch (col) {
|
|
case 'type': cmp = a.workshopType.localeCompare(b.workshopType); break;
|
|
case 'titre': cmp = a.title.localeCompare(b.title, 'fr'); break;
|
|
case 'createur': cmp = getCreatorName(a).localeCompare(getCreatorName(b), 'fr'); break;
|
|
case 'participant': cmp = getParticipantSortName(a).localeCompare(getParticipantSortName(b), 'fr'); break;
|
|
case 'stats': cmp = getStatsSortValue(a) - getStatsSortValue(b); break;
|
|
case 'date': cmp = new Date(a.updatedAt).getTime() - new Date(b.updatedAt).getTime(); break;
|
|
}
|
|
return dir === 'asc' ? cmp : -cmp;
|
|
});
|
|
}
|