feat: add Weekly Check-in feature with models, UI components, and session management for enhanced team collaboration
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 6m24s

This commit is contained in:
Julien Froidefond
2026-01-14 10:23:58 +01:00
parent 67d685d346
commit 53ee344ae7
23 changed files with 2753 additions and 24 deletions

View File

@@ -15,10 +15,18 @@ import {
import { deleteSwotSession, updateSwotSession } from '@/actions/session';
import { deleteMotivatorSession, updateMotivatorSession } from '@/actions/moving-motivators';
import { deleteYearReviewSession, updateYearReviewSession } from '@/actions/year-review';
import { deleteWeeklyCheckInSession, updateWeeklyCheckInSession } from '@/actions/weekly-checkin';
type WorkshopType = 'all' | 'swot' | 'motivators' | 'year-review' | 'byPerson';
type WorkshopType = 'all' | 'swot' | 'motivators' | 'year-review' | 'weekly-checkin' | 'byPerson';
const VALID_TABS: WorkshopType[] = ['all', 'swot', 'motivators', 'year-review', 'byPerson'];
const VALID_TABS: WorkshopType[] = [
'all',
'swot',
'motivators',
'year-review',
'weekly-checkin',
'byPerson',
];
interface ShareUser {
id: string;
@@ -84,12 +92,28 @@ interface YearReviewSession {
workshopType: 'year-review';
}
type AnySession = SwotSession | MotivatorSession | YearReviewSession;
interface WeeklyCheckInSession {
id: string;
title: string;
participant: string;
resolvedParticipant: ResolvedCollaborator;
date: Date;
updatedAt: Date;
isOwner: boolean;
role: 'OWNER' | 'VIEWER' | 'EDITOR';
user: { id: string; name: string | null; email: string };
shares: Share[];
_count: { items: number };
workshopType: 'weekly-checkin';
}
type AnySession = SwotSession | MotivatorSession | YearReviewSession | WeeklyCheckInSession;
interface WorkshopTabsProps {
swotSessions: SwotSession[];
motivatorSessions: MotivatorSession[];
yearReviewSessions: YearReviewSession[];
weeklyCheckInSessions: WeeklyCheckInSession[];
}
// Helper to get resolved collaborator from any session
@@ -98,6 +122,8 @@ function getResolvedCollaborator(session: AnySession): ResolvedCollaborator {
return (session as SwotSession).resolvedCollaborator;
} else if (session.workshopType === 'year-review') {
return (session as YearReviewSession).resolvedParticipant;
} else if (session.workshopType === 'weekly-checkin') {
return (session as WeeklyCheckInSession).resolvedParticipant;
} else {
return (session as MotivatorSession).resolvedParticipant;
}
@@ -141,6 +167,7 @@ export function WorkshopTabs({
swotSessions,
motivatorSessions,
yearReviewSessions,
weeklyCheckInSessions,
}: WorkshopTabsProps) {
const searchParams = useSearchParams();
const router = useRouter();
@@ -165,6 +192,7 @@ export function WorkshopTabs({
...swotSessions,
...motivatorSessions,
...yearReviewSessions,
...weeklyCheckInSessions,
].sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
// Filter based on active tab (for non-byPerson tabs)
@@ -175,7 +203,9 @@ export function WorkshopTabs({
? swotSessions
: activeTab === 'motivators'
? motivatorSessions
: yearReviewSessions;
: activeTab === 'year-review'
? yearReviewSessions
: weeklyCheckInSessions;
// Separate by ownership
const ownedSessions = filteredSessions.filter((s) => s.isOwner);
@@ -226,6 +256,13 @@ export function WorkshopTabs({
label="Year Review"
count={yearReviewSessions.length}
/>
<TabButton
active={activeTab === 'weekly-checkin'}
onClick={() => setActiveTab('weekly-checkin')}
icon="📝"
label="Weekly Check-in"
count={weeklyCheckInSessions.length}
/>
</div>
{/* Sessions */}
@@ -343,18 +380,29 @@ function SessionCard({ session }: { session: AnySession }) {
const isSwot = session.workshopType === 'swot';
const isYearReview = session.workshopType === 'year-review';
const isWeeklyCheckIn = session.workshopType === 'weekly-checkin';
const href = isSwot
? `/sessions/${session.id}`
: isYearReview
? `/year-review/${session.id}`
: `/motivators/${session.id}`;
const icon = isSwot ? '📊' : isYearReview ? '📅' : '🎯';
: isWeeklyCheckIn
? `/weekly-checkin/${session.id}`
: `/motivators/${session.id}`;
const icon = isSwot ? '📊' : isYearReview ? '📅' : isWeeklyCheckIn ? '📝' : '🎯';
const participant = isSwot
? (session as SwotSession).collaborator
: isYearReview
? (session as YearReviewSession).participant
: (session as MotivatorSession).participant;
const accentColor = isSwot ? '#06b6d4' : isYearReview ? '#f59e0b' : '#8b5cf6';
: isWeeklyCheckIn
? (session as WeeklyCheckInSession).participant
: (session as MotivatorSession).participant;
const accentColor = isSwot
? '#06b6d4'
: isYearReview
? '#f59e0b'
: isWeeklyCheckIn
? '#10b981'
: '#8b5cf6';
const handleDelete = () => {
startTransition(async () => {
@@ -362,7 +410,9 @@ function SessionCard({ session }: { session: AnySession }) {
? await deleteSwotSession(session.id)
: isYearReview
? await deleteYearReviewSession(session.id)
: await deleteMotivatorSession(session.id);
: isWeeklyCheckIn
? await deleteWeeklyCheckInSession(session.id)
: await deleteMotivatorSession(session.id);
if (result.success) {
setShowDeleteModal(false);
@@ -381,10 +431,15 @@ function SessionCard({ session }: { session: AnySession }) {
title: editTitle,
participant: editParticipant,
})
: await updateMotivatorSession(session.id, {
title: editTitle,
participant: editParticipant,
});
: isWeeklyCheckIn
? await updateWeeklyCheckInSession(session.id, {
title: editTitle,
participant: editParticipant,
})
: await updateMotivatorSession(session.id, {
title: editTitle,
participant: editParticipant,
});
if (result.success) {
setShowEditModal(false);
@@ -401,6 +456,8 @@ function SessionCard({ session }: { session: AnySession }) {
setShowEditModal(true);
};
const editParticipantLabel = isSwot ? 'Collaborateur' : 'Participant';
return (
<>
<div className="relative group">
@@ -456,6 +513,17 @@ function SessionCard({ session }: { session: AnySession }) {
<span>·</span>
<span>Année {(session as YearReviewSession).year}</span>
</>
) : isWeeklyCheckIn ? (
<>
<span>{(session as WeeklyCheckInSession)._count.items} items</span>
<span>·</span>
<span>
{new Date((session as WeeklyCheckInSession).date).toLocaleDateString('fr-FR', {
day: 'numeric',
month: 'short',
})}
</span>
</>
) : (
<span>{(session as MotivatorSession)._count.cards}/10</span>
)}
@@ -570,7 +638,7 @@ function SessionCard({ session }: { session: AnySession }) {
htmlFor="edit-participant"
className="block text-sm font-medium text-foreground mb-1"
>
{isSwot ? 'Collaborateur' : 'Participant'}
{editParticipantLabel}
</label>
<Input
id="edit-participant"