Files
workshop-manager/src/app/sessions/workshop-session-helpers.ts
Froidefond Julien 66ac190c15
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 3m17s
feat: redesign sessions dashboard with multi-view layout and sortable table
- 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>
2026-03-03 13:54:23 +01:00

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;
});
}