refactor: improve team management, OKRs, and session components
This commit is contained in:
@@ -34,7 +34,10 @@ export async function PATCH(
|
||||
|
||||
if (!isAdmin && !isConcernedMember) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Seuls les administrateurs et le membre concerné peuvent mettre à jour les Key Results' },
|
||||
{
|
||||
error:
|
||||
'Seuls les administrateurs et le membre concerné peuvent mettre à jour les Key Results',
|
||||
},
|
||||
{ status: 403 }
|
||||
);
|
||||
}
|
||||
@@ -51,10 +54,8 @@ export async function PATCH(
|
||||
return NextResponse.json(updated);
|
||||
} catch (error) {
|
||||
console.error('Error updating key result:', error);
|
||||
const errorMessage = error instanceof Error ? error.message : 'Erreur lors de la mise à jour du Key Result';
|
||||
return NextResponse.json(
|
||||
{ error: errorMessage },
|
||||
{ status: 500 }
|
||||
);
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : 'Erreur lors de la mise à jour du Key Result';
|
||||
return NextResponse.json({ error: errorMessage }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,10 +40,7 @@ export async function GET(request: Request, { params }: { params: Promise<{ id:
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error fetching OKR:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Erreur lors de la récupération de l\'OKR' },
|
||||
{ status: 500 }
|
||||
);
|
||||
return NextResponse.json({ error: "Erreur lors de la récupération de l'OKR" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,19 +62,28 @@ export async function PATCH(request: Request, { params }: { params: Promise<{ id
|
||||
const isAdmin = await isTeamAdmin(okr.teamMember.team.id, session.user.id);
|
||||
const isConcernedMember = okr.teamMember.userId === session.user.id;
|
||||
if (!isAdmin && !isConcernedMember) {
|
||||
return NextResponse.json({ error: 'Seuls les administrateurs et le membre concerné peuvent modifier les OKRs' }, { status: 403 });
|
||||
return NextResponse.json(
|
||||
{ error: 'Seuls les administrateurs et le membre concerné peuvent modifier les OKRs' },
|
||||
{ status: 403 }
|
||||
);
|
||||
}
|
||||
|
||||
const body: UpdateOKRInput & {
|
||||
startDate?: string;
|
||||
const body: UpdateOKRInput & {
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
keyResultsUpdates?: {
|
||||
create?: Array<{ title: string; targetValue: number; unit: string; order: number }>;
|
||||
update?: Array<{ id: string; title?: string; targetValue?: number; unit?: string; order?: number }>;
|
||||
update?: Array<{
|
||||
id: string;
|
||||
title?: string;
|
||||
targetValue?: number;
|
||||
unit?: string;
|
||||
order?: number;
|
||||
}>;
|
||||
delete?: string[];
|
||||
};
|
||||
} = await request.json();
|
||||
|
||||
|
||||
// Convert date strings to Date objects if provided
|
||||
const updateData: UpdateOKRInput = { ...body };
|
||||
if (body.startDate) {
|
||||
@@ -102,11 +108,9 @@ export async function PATCH(request: Request, { params }: { params: Promise<{ id
|
||||
return NextResponse.json(updated);
|
||||
} catch (error) {
|
||||
console.error('Error updating OKR:', error);
|
||||
const errorMessage = error instanceof Error ? error.message : 'Erreur lors de la mise à jour de l\'OKR';
|
||||
return NextResponse.json(
|
||||
{ error: errorMessage },
|
||||
{ status: 500 }
|
||||
);
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : "Erreur lors de la mise à jour de l'OKR";
|
||||
return NextResponse.json({ error: errorMessage }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,7 +131,10 @@ export async function DELETE(request: Request, { params }: { params: Promise<{ i
|
||||
// Check if user is admin of the team
|
||||
const isAdmin = await isTeamAdmin(okr.teamMember.team.id, session.user.id);
|
||||
if (!isAdmin) {
|
||||
return NextResponse.json({ error: 'Seuls les administrateurs peuvent supprimer les OKRs' }, { status: 403 });
|
||||
return NextResponse.json(
|
||||
{ error: 'Seuls les administrateurs peuvent supprimer les OKRs' },
|
||||
{ status: 403 }
|
||||
);
|
||||
}
|
||||
|
||||
await deleteOKR(id);
|
||||
@@ -135,10 +142,8 @@ export async function DELETE(request: Request, { params }: { params: Promise<{ i
|
||||
return NextResponse.json({ success: true });
|
||||
} catch (error) {
|
||||
console.error('Error deleting OKR:', error);
|
||||
const errorMessage = error instanceof Error ? error.message : 'Erreur lors de la suppression de l\'OKR';
|
||||
return NextResponse.json(
|
||||
{ error: errorMessage },
|
||||
{ status: 500 }
|
||||
);
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : "Erreur lors de la suppression de l'OKR";
|
||||
return NextResponse.json({ error: errorMessage }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,10 @@ export async function POST(request: Request, { params }: { params: Promise<{ id:
|
||||
// Check if user is admin
|
||||
const isAdmin = await isTeamAdmin(id, session.user.id);
|
||||
if (!isAdmin) {
|
||||
return NextResponse.json({ error: 'Seuls les administrateurs peuvent ajouter des membres' }, { status: 403 });
|
||||
return NextResponse.json(
|
||||
{ error: 'Seuls les administrateurs peuvent ajouter des membres' },
|
||||
{ status: 403 }
|
||||
);
|
||||
}
|
||||
|
||||
const body: AddTeamMemberInput = await request.json();
|
||||
@@ -30,11 +33,9 @@ export async function POST(request: Request, { params }: { params: Promise<{ id:
|
||||
return NextResponse.json(member, { status: 201 });
|
||||
} catch (error) {
|
||||
console.error('Error adding team member:', error);
|
||||
const errorMessage = error instanceof Error ? error.message : 'Erreur lors de l\'ajout du membre';
|
||||
return NextResponse.json(
|
||||
{ error: errorMessage },
|
||||
{ status: 500 }
|
||||
);
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : "Erreur lors de l'ajout du membre";
|
||||
return NextResponse.json({ error: errorMessage }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +51,10 @@ export async function PATCH(request: Request, { params }: { params: Promise<{ id
|
||||
// Check if user is admin
|
||||
const isAdmin = await isTeamAdmin(id, session.user.id);
|
||||
if (!isAdmin) {
|
||||
return NextResponse.json({ error: 'Seuls les administrateurs peuvent modifier les rôles' }, { status: 403 });
|
||||
return NextResponse.json(
|
||||
{ error: 'Seuls les administrateurs peuvent modifier les rôles' },
|
||||
{ status: 403 }
|
||||
);
|
||||
}
|
||||
|
||||
const body: UpdateMemberRoleInput & { userId: string } = await request.json();
|
||||
@@ -65,10 +69,7 @@ export async function PATCH(request: Request, { params }: { params: Promise<{ id
|
||||
return NextResponse.json(member);
|
||||
} catch (error) {
|
||||
console.error('Error updating member role:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Erreur lors de la mise à jour du rôle' },
|
||||
{ status: 500 }
|
||||
);
|
||||
return NextResponse.json({ error: 'Erreur lors de la mise à jour du rôle' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +85,10 @@ export async function DELETE(request: Request, { params }: { params: Promise<{ i
|
||||
// Check if user is admin
|
||||
const isAdmin = await isTeamAdmin(id, session.user.id);
|
||||
if (!isAdmin) {
|
||||
return NextResponse.json({ error: 'Seuls les administrateurs peuvent retirer des membres' }, { status: 403 });
|
||||
return NextResponse.json(
|
||||
{ error: 'Seuls les administrateurs peuvent retirer des membres' },
|
||||
{ status: 403 }
|
||||
);
|
||||
}
|
||||
|
||||
const { searchParams } = new URL(request.url);
|
||||
@@ -99,10 +103,6 @@ export async function DELETE(request: Request, { params }: { params: Promise<{ i
|
||||
return NextResponse.json({ success: true });
|
||||
} catch (error) {
|
||||
console.error('Error removing team member:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Erreur lors de la suppression du membre' },
|
||||
{ status: 500 }
|
||||
);
|
||||
return NextResponse.json({ error: 'Erreur lors de la suppression du membre' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ export async function GET(request: Request, { params }: { params: Promise<{ id:
|
||||
} catch (error) {
|
||||
console.error('Error fetching team:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Erreur lors de la récupération de l\'équipe' },
|
||||
{ error: "Erreur lors de la récupération de l'équipe" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
@@ -46,7 +46,10 @@ export async function PATCH(request: Request, { params }: { params: Promise<{ id
|
||||
// Check if user is admin
|
||||
const isAdmin = await isTeamAdmin(id, session.user.id);
|
||||
if (!isAdmin) {
|
||||
return NextResponse.json({ error: 'Seuls les administrateurs peuvent modifier l\'équipe' }, { status: 403 });
|
||||
return NextResponse.json(
|
||||
{ error: "Seuls les administrateurs peuvent modifier l'équipe" },
|
||||
{ status: 403 }
|
||||
);
|
||||
}
|
||||
|
||||
const body: UpdateTeamInput = await request.json();
|
||||
@@ -56,7 +59,7 @@ export async function PATCH(request: Request, { params }: { params: Promise<{ id
|
||||
} catch (error) {
|
||||
console.error('Error updating team:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Erreur lors de la mise à jour de l\'équipe' },
|
||||
{ error: "Erreur lors de la mise à jour de l'équipe" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
@@ -74,7 +77,10 @@ export async function DELETE(request: Request, { params }: { params: Promise<{ i
|
||||
// Check if user is admin
|
||||
const isAdmin = await isTeamAdmin(id, session.user.id);
|
||||
if (!isAdmin) {
|
||||
return NextResponse.json({ error: 'Seuls les administrateurs peuvent supprimer l\'équipe' }, { status: 403 });
|
||||
return NextResponse.json(
|
||||
{ error: "Seuls les administrateurs peuvent supprimer l'équipe" },
|
||||
{ status: 403 }
|
||||
);
|
||||
}
|
||||
|
||||
await deleteTeam(id);
|
||||
@@ -83,9 +89,8 @@ export async function DELETE(request: Request, { params }: { params: Promise<{ i
|
||||
} catch (error) {
|
||||
console.error('Error deleting team:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Erreur lors de la suppression de l\'équipe' },
|
||||
{ error: "Erreur lors de la suppression de l'équipe" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ export async function POST(request: Request) {
|
||||
const { name, description } = body;
|
||||
|
||||
if (!name) {
|
||||
return NextResponse.json({ error: 'Le nom de l\'équipe est requis' }, { status: 400 });
|
||||
return NextResponse.json({ error: "Le nom de l'équipe est requis" }, { status: 400 });
|
||||
}
|
||||
|
||||
const team = await createTeam(name, description || null, session.user.id);
|
||||
@@ -43,10 +43,6 @@ export async function POST(request: Request) {
|
||||
return NextResponse.json(team, { status: 201 });
|
||||
} catch (error) {
|
||||
console.error('Error creating team:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Erreur lors de la création de l\'équipe' },
|
||||
{ status: 500 }
|
||||
);
|
||||
return NextResponse.json({ error: "Erreur lors de la création de l'équipe" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,4 +30,3 @@ export async function GET() {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import { auth } from '@/lib/auth';
|
||||
import {
|
||||
canAccessWeatherSession,
|
||||
getWeatherSessionEvents,
|
||||
} from '@/services/weather';
|
||||
import { canAccessWeatherSession, getWeatherSessionEvents } from '@/services/weather';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
@@ -102,11 +99,15 @@ export function broadcastToWeatherSession(sessionId: string, event: object) {
|
||||
const sessionConnections = connections.get(sessionId);
|
||||
if (!sessionConnections || sessionConnections.size === 0) {
|
||||
// No active connections, event will be picked up by polling
|
||||
console.log(`[SSE Broadcast] No connections for session ${sessionId}, will be picked up by polling`);
|
||||
console.log(
|
||||
`[SSE Broadcast] No connections for session ${sessionId}, will be picked up by polling`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`[SSE Broadcast] Broadcasting to ${sessionConnections.size} connections for session ${sessionId}`);
|
||||
console.log(
|
||||
`[SSE Broadcast] Broadcasting to ${sessionConnections.size} connections for session ${sessionId}`
|
||||
);
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
const message = encoder.encode(`data: ${JSON.stringify(event)}\n\n`);
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import { auth } from '@/lib/auth';
|
||||
import {
|
||||
canAccessYearReviewSession,
|
||||
getYearReviewSessionEvents,
|
||||
} from '@/services/year-review';
|
||||
import { canAccessYearReviewSession, getYearReviewSessionEvents } from '@/services/year-review';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
@@ -120,4 +117,3 @@ export function broadcastToYearReviewSession(sessionId: string, event: object) {
|
||||
connections.delete(sessionId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -56,10 +56,10 @@ export default async function MotivatorSessionPage({ params }: MotivatorSessionP
|
||||
/>
|
||||
<div className="mt-2">
|
||||
<CollaboratorDisplay
|
||||
collaborator={session.resolvedParticipant as ResolvedCollaborator}
|
||||
size="lg"
|
||||
showEmail
|
||||
/>
|
||||
collaborator={session.resolvedParticipant as ResolvedCollaborator}
|
||||
size="lg"
|
||||
showEmail
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
|
||||
158
src/app/page.tsx
158
src/app/page.tsx
@@ -282,11 +282,36 @@ export default function Home() {
|
||||
Les 5 catégories du bilan
|
||||
</h3>
|
||||
<div className="space-y-3">
|
||||
<CategoryPill icon="🏆" name="Réalisations" color="#22c55e" description="Ce que vous avez accompli" />
|
||||
<CategoryPill icon="⚔️" name="Défis" color="#ef4444" description="Les difficultés rencontrées" />
|
||||
<CategoryPill icon="📚" name="Apprentissages" color="#3b82f6" description="Ce que vous avez appris" />
|
||||
<CategoryPill icon="🎯" name="Objectifs" color="#8b5cf6" description="Vos ambitions pour l'année prochaine" />
|
||||
<CategoryPill icon="⭐" name="Moments" color="#f59e0b" description="Les moments forts et marquants" />
|
||||
<CategoryPill
|
||||
icon="🏆"
|
||||
name="Réalisations"
|
||||
color="#22c55e"
|
||||
description="Ce que vous avez accompli"
|
||||
/>
|
||||
<CategoryPill
|
||||
icon="⚔️"
|
||||
name="Défis"
|
||||
color="#ef4444"
|
||||
description="Les difficultés rencontrées"
|
||||
/>
|
||||
<CategoryPill
|
||||
icon="📚"
|
||||
name="Apprentissages"
|
||||
color="#3b82f6"
|
||||
description="Ce que vous avez appris"
|
||||
/>
|
||||
<CategoryPill
|
||||
icon="🎯"
|
||||
name="Objectifs"
|
||||
color="#8b5cf6"
|
||||
description="Vos ambitions pour l'année prochaine"
|
||||
/>
|
||||
<CategoryPill
|
||||
icon="⭐"
|
||||
name="Moments"
|
||||
color="#f59e0b"
|
||||
description="Les moments forts et marquants"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -328,7 +353,9 @@ export default function Home() {
|
||||
<span className="text-4xl">📝</span>
|
||||
<div>
|
||||
<h2 className="text-3xl font-bold text-foreground">Weekly Check-in</h2>
|
||||
<p className="text-green-500 font-medium">Le point hebdomadaire avec vos collaborateurs</p>
|
||||
<p className="text-green-500 font-medium">
|
||||
Le point hebdomadaire avec vos collaborateurs
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -340,9 +367,9 @@ export default function Home() {
|
||||
Pourquoi faire un check-in hebdomadaire ?
|
||||
</h3>
|
||||
<p className="text-muted mb-4">
|
||||
Le Weekly Check-in est un rituel de management qui permet de maintenir un lien régulier
|
||||
avec vos collaborateurs. Il favorise la communication, l'alignement et la détection
|
||||
précoce des problèmes ou opportunités.
|
||||
Le Weekly Check-in est un rituel de management qui permet de maintenir un lien
|
||||
régulier avec vos collaborateurs. Il favorise la communication, l'alignement et
|
||||
la détection précoce des problèmes ou opportunités.
|
||||
</p>
|
||||
<ul className="space-y-2 text-sm text-muted">
|
||||
<li className="flex items-start gap-2">
|
||||
@@ -371,10 +398,30 @@ export default function Home() {
|
||||
Les 4 catégories du check-in
|
||||
</h3>
|
||||
<div className="space-y-3">
|
||||
<CategoryPill icon="✅" name="Ce qui s'est bien passé" color="#22c55e" description="Les réussites et points positifs" />
|
||||
<CategoryPill icon="⚠️" name="Ce qui s'est mal passé" color="#ef4444" description="Les difficultés et points d'amélioration" />
|
||||
<CategoryPill icon="🎯" name="Enjeux du moment" color="#3b82f6" description="Sur quoi je me concentre actuellement" />
|
||||
<CategoryPill icon="🚀" name="Prochains enjeux" color="#8b5cf6" description="Ce sur quoi je vais me concentrer prochainement" />
|
||||
<CategoryPill
|
||||
icon="✅"
|
||||
name="Ce qui s'est bien passé"
|
||||
color="#22c55e"
|
||||
description="Les réussites et points positifs"
|
||||
/>
|
||||
<CategoryPill
|
||||
icon="⚠️"
|
||||
name="Ce qui s'est mal passé"
|
||||
color="#ef4444"
|
||||
description="Les difficultés et points d'amélioration"
|
||||
/>
|
||||
<CategoryPill
|
||||
icon="🎯"
|
||||
name="Enjeux du moment"
|
||||
color="#3b82f6"
|
||||
description="Sur quoi je me concentre actuellement"
|
||||
/>
|
||||
<CategoryPill
|
||||
icon="🚀"
|
||||
name="Prochains enjeux"
|
||||
color="#8b5cf6"
|
||||
description="Ce sur quoi je vais me concentrer prochainement"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -428,9 +475,9 @@ export default function Home() {
|
||||
Pourquoi créer une météo personnelle ?
|
||||
</h3>
|
||||
<p className="text-muted mb-4">
|
||||
La météo est un outil simple et visuel pour exprimer rapidement votre état sur 4 axes clés.
|
||||
En la partageant avec votre équipe, vous créez de la transparence et facilitez la communication
|
||||
sur votre bien-être et votre performance.
|
||||
La météo est un outil simple et visuel pour exprimer rapidement votre état sur 4
|
||||
axes clés. En la partageant avec votre équipe, vous créez de la transparence et
|
||||
facilitez la communication sur votre bien-être et votre performance.
|
||||
</p>
|
||||
<ul className="space-y-2 text-sm text-muted">
|
||||
<li className="flex items-start gap-2">
|
||||
@@ -459,10 +506,30 @@ export default function Home() {
|
||||
Les 4 axes de la météo
|
||||
</h3>
|
||||
<div className="space-y-3">
|
||||
<CategoryPill icon="☀️" name="Performance" color="#f59e0b" description="Votre performance personnelle et l'atteinte de vos objectifs" />
|
||||
<CategoryPill icon="😊" name="Moral" color="#22c55e" description="Votre moral actuel et votre ressenti" />
|
||||
<CategoryPill icon="🌊" name="Flux" color="#3b82f6" description="Votre flux de travail personnel et les blocages éventuels" />
|
||||
<CategoryPill icon="💎" name="Création de valeur" color="#8b5cf6" description="Votre création de valeur et votre apport" />
|
||||
<CategoryPill
|
||||
icon="☀️"
|
||||
name="Performance"
|
||||
color="#f59e0b"
|
||||
description="Votre performance personnelle et l'atteinte de vos objectifs"
|
||||
/>
|
||||
<CategoryPill
|
||||
icon="😊"
|
||||
name="Moral"
|
||||
color="#22c55e"
|
||||
description="Votre moral actuel et votre ressenti"
|
||||
/>
|
||||
<CategoryPill
|
||||
icon="🌊"
|
||||
name="Flux"
|
||||
color="#3b82f6"
|
||||
description="Votre flux de travail personnel et les blocages éventuels"
|
||||
/>
|
||||
<CategoryPill
|
||||
icon="💎"
|
||||
name="Création de valeur"
|
||||
color="#8b5cf6"
|
||||
description="Votre création de valeur et votre apport"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -504,7 +571,9 @@ export default function Home() {
|
||||
<span className="text-4xl">🎯</span>
|
||||
<div>
|
||||
<h2 className="text-3xl font-bold text-foreground">OKRs & Équipes</h2>
|
||||
<p className="text-purple-500 font-medium">Définissez et suivez les objectifs de votre équipe</p>
|
||||
<p className="text-purple-500 font-medium">
|
||||
Définissez et suivez les objectifs de votre équipe
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -516,9 +585,10 @@ export default function Home() {
|
||||
Pourquoi utiliser les OKRs ?
|
||||
</h3>
|
||||
<p className="text-muted mb-4">
|
||||
Les OKRs (Objectives and Key Results) sont un cadre de gestion d'objectifs qui permet
|
||||
d'aligner les efforts de l'équipe autour d'objectifs communs et mesurables.
|
||||
Cette méthode favorise la transparence, la responsabilisation et la performance collective.
|
||||
Les OKRs (Objectives and Key Results) sont un cadre de gestion d'objectifs qui
|
||||
permet d'aligner les efforts de l'équipe autour d'objectifs communs
|
||||
et mesurables. Cette méthode favorise la transparence, la responsabilisation et la
|
||||
performance collective.
|
||||
</p>
|
||||
<ul className="space-y-2 text-sm text-muted">
|
||||
<li className="flex items-start gap-2">
|
||||
@@ -547,29 +617,29 @@ export default function Home() {
|
||||
Fonctionnalités principales
|
||||
</h3>
|
||||
<div className="space-y-3">
|
||||
<FeaturePill
|
||||
icon="👥"
|
||||
name="Gestion d'équipes"
|
||||
color="#8b5cf6"
|
||||
description="Créez des équipes et gérez les membres avec des rôles admin/membre"
|
||||
<FeaturePill
|
||||
icon="👥"
|
||||
name="Gestion d'équipes"
|
||||
color="#8b5cf6"
|
||||
description="Créez des équipes et gérez les membres avec des rôles admin/membre"
|
||||
/>
|
||||
<FeaturePill
|
||||
icon="🎯"
|
||||
name="OKRs par période"
|
||||
color="#3b82f6"
|
||||
description="Définissez des OKRs pour des trimestres ou périodes personnalisées"
|
||||
<FeaturePill
|
||||
icon="🎯"
|
||||
name="OKRs par période"
|
||||
color="#3b82f6"
|
||||
description="Définissez des OKRs pour des trimestres ou périodes personnalisées"
|
||||
/>
|
||||
<FeaturePill
|
||||
icon="📊"
|
||||
name="Key Results mesurables"
|
||||
color="#10b981"
|
||||
description="Suivez la progression de chaque Key Result avec des valeurs et pourcentages"
|
||||
<FeaturePill
|
||||
icon="📊"
|
||||
name="Key Results mesurables"
|
||||
color="#10b981"
|
||||
description="Suivez la progression de chaque Key Result avec des valeurs et pourcentages"
|
||||
/>
|
||||
<FeaturePill
|
||||
icon="👁️"
|
||||
name="Visibilité transparente"
|
||||
color="#f59e0b"
|
||||
description="Tous les membres de l'équipe peuvent voir les OKRs de chacun"
|
||||
<FeaturePill
|
||||
icon="👁️"
|
||||
name="Visibilité transparente"
|
||||
color="#f59e0b"
|
||||
description="Tous les membres de l'équipe peuvent voir les OKRs de chacun"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -125,7 +125,12 @@ interface WeatherSession {
|
||||
canEdit?: boolean;
|
||||
}
|
||||
|
||||
type AnySession = SwotSession | MotivatorSession | YearReviewSession | WeeklyCheckInSession | WeatherSession;
|
||||
type AnySession =
|
||||
| SwotSession
|
||||
| MotivatorSession
|
||||
| YearReviewSession
|
||||
| WeeklyCheckInSession
|
||||
| WeatherSession;
|
||||
|
||||
interface WorkshopTabsProps {
|
||||
swotSessions: SwotSession[];
|
||||
@@ -239,18 +244,20 @@ export function WorkshopTabs({
|
||||
: activeTab === 'team'
|
||||
? teamCollabSessions
|
||||
: activeTab === 'swot'
|
||||
? swotSessions
|
||||
: activeTab === 'motivators'
|
||||
? motivatorSessions
|
||||
: activeTab === 'year-review'
|
||||
? yearReviewSessions
|
||||
: activeTab === 'weekly-checkin'
|
||||
? weeklyCheckInSessions
|
||||
: weatherSessions;
|
||||
? swotSessions
|
||||
: activeTab === 'motivators'
|
||||
? motivatorSessions
|
||||
: activeTab === 'year-review'
|
||||
? yearReviewSessions
|
||||
: activeTab === 'weekly-checkin'
|
||||
? weeklyCheckInSessions
|
||||
: weatherSessions;
|
||||
|
||||
// Separate by ownership (for non-team tab: owned, shared, teamCollab)
|
||||
const ownedSessions = filteredSessions.filter((s) => s.isOwner);
|
||||
const sharedSessions = filteredSessions.filter((s) => !s.isOwner && !(s as AnySession & { isTeamCollab?: boolean }).isTeamCollab);
|
||||
const sharedSessions = filteredSessions.filter(
|
||||
(s) => !s.isOwner && !(s as AnySession & { isTeamCollab?: boolean }).isTeamCollab
|
||||
);
|
||||
const teamCollabFiltered =
|
||||
activeTab === 'all' ? teamCollabSessions : activeTab === 'team' ? teamCollabSessions : [];
|
||||
|
||||
@@ -342,7 +349,8 @@ export function WorkshopTabs({
|
||||
🏢 Ateliers de l'équipe – non partagés ({teamCollabSessions.length})
|
||||
</h2>
|
||||
<p className="text-sm text-muted mb-4">
|
||||
En tant qu'admin d'équipe, vous voyez les ateliers de vos collaborateurs qui ne vous sont pas encore partagés.
|
||||
En tant qu'admin d'équipe, vous voyez les ateliers de vos collaborateurs
|
||||
qui ne vous sont pas encore partagés.
|
||||
</p>
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
{teamCollabSessions.map((s) => (
|
||||
@@ -436,7 +444,7 @@ function TypeFilterDropdown({
|
||||
<span>{current.icon}</span>
|
||||
<span>{current.label}</span>
|
||||
<Badge variant={isTypeSelected ? 'default' : 'primary'} className="ml-1 text-xs">
|
||||
{isTypeSelected ? counts[activeTab] ?? 0 : totalCount}
|
||||
{isTypeSelected ? (counts[activeTab] ?? 0) : totalCount}
|
||||
</Badge>
|
||||
<svg
|
||||
className={`h-4 w-4 transition-transform ${open ? 'rotate-180' : ''}`}
|
||||
@@ -525,7 +533,13 @@ function TabButton({
|
||||
);
|
||||
}
|
||||
|
||||
function SessionCard({ session, isTeamCollab = false }: { session: AnySession; isTeamCollab?: boolean }) {
|
||||
function SessionCard({
|
||||
session,
|
||||
isTeamCollab = false,
|
||||
}: {
|
||||
session: AnySession;
|
||||
isTeamCollab?: boolean;
|
||||
}) {
|
||||
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||
const [showEditModal, setShowEditModal] = useState(false);
|
||||
const [isPending, startTransition] = useTransition();
|
||||
@@ -618,117 +632,117 @@ function SessionCard({ session, isTeamCollab = false }: { session: AnySession; i
|
||||
const editParticipantLabel = workshop.participantLabel;
|
||||
|
||||
const cardContent = (
|
||||
<Card hover={!isTeamCollab} className={`h-full p-4 relative overflow-hidden ${isTeamCollab ? 'opacity-60' : ''}`}>
|
||||
{/* Accent bar */}
|
||||
<div
|
||||
className="absolute top-0 left-0 right-0 h-1"
|
||||
style={{ backgroundColor: accentColor }}
|
||||
/>
|
||||
<Card
|
||||
hover={!isTeamCollab}
|
||||
className={`h-full p-4 relative overflow-hidden ${isTeamCollab ? 'opacity-60' : ''}`}
|
||||
>
|
||||
{/* Accent bar */}
|
||||
<div className="absolute top-0 left-0 right-0 h-1" style={{ backgroundColor: accentColor }} />
|
||||
|
||||
{/* Header: Icon + Title + Role badge */}
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<span className="text-xl">{workshop.icon}</span>
|
||||
<h3 className="font-semibold text-foreground line-clamp-1 flex-1">{session.title}</h3>
|
||||
{!session.isOwner && (
|
||||
<span
|
||||
className="text-xs px-1.5 py-0.5 rounded"
|
||||
style={{
|
||||
backgroundColor:
|
||||
session.role === 'EDITOR' ? 'rgba(6,182,212,0.1)' : 'rgba(234,179,8,0.1)',
|
||||
color: session.role === 'EDITOR' ? '#06b6d4' : '#eab308',
|
||||
}}
|
||||
>
|
||||
{session.role === 'EDITOR' ? '✏️' : '👁️'}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{/* Header: Icon + Title + Role badge */}
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<span className="text-xl">{workshop.icon}</span>
|
||||
<h3 className="font-semibold text-foreground line-clamp-1 flex-1">{session.title}</h3>
|
||||
{!session.isOwner && (
|
||||
<span
|
||||
className="text-xs px-1.5 py-0.5 rounded"
|
||||
style={{
|
||||
backgroundColor:
|
||||
session.role === 'EDITOR' ? 'rgba(6,182,212,0.1)' : 'rgba(234,179,8,0.1)',
|
||||
color: session.role === 'EDITOR' ? '#06b6d4' : '#eab308',
|
||||
}}
|
||||
>
|
||||
{session.role === 'EDITOR' ? '✏️' : '👁️'}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Participant + Owner info */}
|
||||
<div className="mb-3 flex items-center gap-2">
|
||||
<CollaboratorDisplay collaborator={getResolvedCollaborator(session)} size="sm" />
|
||||
{!session.isOwner && (
|
||||
<span className="text-xs text-muted">
|
||||
· par {session.user.name || session.user.email}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{/* Participant + Owner info */}
|
||||
<div className="mb-3 flex items-center gap-2">
|
||||
<CollaboratorDisplay collaborator={getResolvedCollaborator(session)} size="sm" />
|
||||
{!session.isOwner && (
|
||||
<span className="text-xs text-muted">
|
||||
· par {session.user.name || session.user.email}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Footer: Stats + Avatars + Date */}
|
||||
<div className="flex items-center justify-between text-xs">
|
||||
{/* Stats */}
|
||||
<div className="flex items-center gap-2 text-muted">
|
||||
{isSwot ? (
|
||||
<>
|
||||
<span>{(session as SwotSession)._count.items} items</span>
|
||||
<span>·</span>
|
||||
<span>{(session as SwotSession)._count.actions} actions</span>
|
||||
</>
|
||||
) : isYearReview ? (
|
||||
<>
|
||||
<span>{(session as YearReviewSession)._count.items} items</span>
|
||||
<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>
|
||||
</>
|
||||
) : isWeather ? (
|
||||
<>
|
||||
<span>{(session as WeatherSession)._count.entries} membres</span>
|
||||
<span>·</span>
|
||||
<span>
|
||||
{new Date((session as WeatherSession).date).toLocaleDateString('fr-FR', {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
})}
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<span>{(session as MotivatorSession)._count.cards}/10</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Date */}
|
||||
<span className="text-muted">
|
||||
{new Date(session.updatedAt).toLocaleDateString('fr-FR', {
|
||||
{/* Footer: Stats + Avatars + Date */}
|
||||
<div className="flex items-center justify-between text-xs">
|
||||
{/* Stats */}
|
||||
<div className="flex items-center gap-2 text-muted">
|
||||
{isSwot ? (
|
||||
<>
|
||||
<span>{(session as SwotSession)._count.items} items</span>
|
||||
<span>·</span>
|
||||
<span>{(session as SwotSession)._count.actions} actions</span>
|
||||
</>
|
||||
) : isYearReview ? (
|
||||
<>
|
||||
<span>{(session as YearReviewSession)._count.items} items</span>
|
||||
<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>
|
||||
</div>
|
||||
</>
|
||||
) : isWeather ? (
|
||||
<>
|
||||
<span>{(session as WeatherSession)._count.entries} membres</span>
|
||||
<span>·</span>
|
||||
<span>
|
||||
{new Date((session as WeatherSession).date).toLocaleDateString('fr-FR', {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
})}
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<span>{(session as MotivatorSession)._count.cards}/10</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Shared with */}
|
||||
{session.isOwner && session.shares.length > 0 && (
|
||||
<div className="flex items-center gap-2 mt-3 pt-3 border-t border-border">
|
||||
<span className="text-[10px] text-muted uppercase tracking-wide">Partagé</span>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{session.shares.slice(0, 3).map((share) => (
|
||||
<div
|
||||
key={share.id}
|
||||
className="flex items-center gap-1 px-1.5 py-0.5 rounded-full bg-primary/10 text-[10px] text-primary"
|
||||
title={share.role === 'EDITOR' ? 'Éditeur' : 'Lecteur'}
|
||||
>
|
||||
<span className="font-medium">
|
||||
{share.user.name?.split(' ')[0] || share.user.email.split('@')[0]}
|
||||
</span>
|
||||
<span>{share.role === 'EDITOR' ? '✏️' : '👁️'}</span>
|
||||
</div>
|
||||
))}
|
||||
{session.shares.length > 3 && (
|
||||
<span className="text-[10px] text-muted">+{session.shares.length - 3}</span>
|
||||
)}
|
||||
</div>
|
||||
{/* Date */}
|
||||
<span className="text-muted">
|
||||
{new Date(session.updatedAt).toLocaleDateString('fr-FR', {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Shared with */}
|
||||
{session.isOwner && session.shares.length > 0 && (
|
||||
<div className="flex items-center gap-2 mt-3 pt-3 border-t border-border">
|
||||
<span className="text-[10px] text-muted uppercase tracking-wide">Partagé</span>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{session.shares.slice(0, 3).map((share) => (
|
||||
<div
|
||||
key={share.id}
|
||||
className="flex items-center gap-1 px-1.5 py-0.5 rounded-full bg-primary/10 text-[10px] text-primary"
|
||||
title={share.role === 'EDITOR' ? 'Éditeur' : 'Lecteur'}
|
||||
>
|
||||
<span className="font-medium">
|
||||
{share.user.name?.split(' ')[0] || share.user.email.split('@')[0]}
|
||||
</span>
|
||||
<span>{share.role === 'EDITOR' ? '✏️' : '👁️'}</span>
|
||||
</div>
|
||||
))}
|
||||
{session.shares.length > 3 && (
|
||||
<span className="text-[10px] text-muted">+{session.shares.length - 3}</span>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -764,24 +778,24 @@ function SessionCard({ session, isTeamCollab = false }: { session: AnySession; i
|
||||
</svg>
|
||||
</button>
|
||||
{(session.isOwner || session.isTeamCollab) && (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setShowDeleteModal(true);
|
||||
}}
|
||||
className="p-1.5 rounded-lg bg-destructive/10 text-destructive hover:bg-destructive/20"
|
||||
title="Supprimer"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setShowDeleteModal(true);
|
||||
}}
|
||||
className="p-1.5 rounded-lg bg-destructive/10 text-destructive hover:bg-destructive/20"
|
||||
title="Supprimer"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -62,15 +62,17 @@ export default function EditOKRPage() {
|
||||
order?: number;
|
||||
};
|
||||
|
||||
const handleSubmit = async (data: CreateOKRInput & {
|
||||
startDate: Date | string;
|
||||
endDate: Date | string;
|
||||
keyResultsUpdates?: {
|
||||
create?: CreateKeyResultInput[];
|
||||
update?: KeyResultUpdate[];
|
||||
delete?: string[]
|
||||
}
|
||||
}) => {
|
||||
const handleSubmit = async (
|
||||
data: CreateOKRInput & {
|
||||
startDate: Date | string;
|
||||
endDate: Date | string;
|
||||
keyResultsUpdates?: {
|
||||
create?: CreateKeyResultInput[];
|
||||
update?: KeyResultUpdate[];
|
||||
delete?: string[];
|
||||
};
|
||||
}
|
||||
) => {
|
||||
// Convert to UpdateOKRInput format
|
||||
const updateData = {
|
||||
objective: data.objective,
|
||||
@@ -164,4 +166,3 @@ export default function EditOKRPage() {
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -186,9 +186,7 @@ export default function OKRDetailPage() {
|
||||
>
|
||||
{okr.period}
|
||||
</Badge>
|
||||
<Badge style={getOKRStatusColor(okr.status)}>
|
||||
{OKR_STATUS_LABELS[okr.status]}
|
||||
</Badge>
|
||||
<Badge style={getOKRStatusColor(okr.status)}>{OKR_STATUS_LABELS[okr.status]}</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
@@ -269,13 +267,10 @@ export default function OKRDetailPage() {
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<Card className="p-8 text-center text-muted">
|
||||
Aucun Key Result défini
|
||||
</Card>
|
||||
<Card className="p-8 text-center text-muted">Aucun Key Result défini</Card>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -79,4 +79,3 @@ export default function NewOKRPage() {
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -70,10 +70,10 @@ export default async function TeamDetailPage({ params }: TeamDetailPageProps) {
|
||||
|
||||
{/* Members Section */}
|
||||
<Card className="mb-8 p-6">
|
||||
<TeamDetailClient
|
||||
members={team.members as unknown as TeamMember[]}
|
||||
teamId={id}
|
||||
isAdmin={isAdmin}
|
||||
<TeamDetailClient
|
||||
members={team.members as unknown as TeamMember[]}
|
||||
teamId={id}
|
||||
isAdmin={isAdmin}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
@@ -82,4 +82,3 @@ export default async function TeamDetailPage({ params }: TeamDetailPageProps) {
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ export default function NewTeamPage() {
|
||||
e.preventDefault();
|
||||
|
||||
if (!name.trim()) {
|
||||
alert('Le nom de l\'équipe est requis');
|
||||
alert("Le nom de l'équipe est requis");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ export default function NewTeamPage() {
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
alert(error.error || 'Erreur lors de la création de l\'équipe');
|
||||
alert(error.error || "Erreur lors de la création de l'équipe");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ export default function NewTeamPage() {
|
||||
router.refresh();
|
||||
} catch (error) {
|
||||
console.error('Error creating team:', error);
|
||||
alert('Erreur lors de la création de l\'équipe');
|
||||
alert("Erreur lors de la création de l'équipe");
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
@@ -81,7 +81,7 @@ export default function NewTeamPage() {
|
||||
disabled={submitting}
|
||||
className="bg-[var(--purple)] text-white hover:opacity-90 border-transparent"
|
||||
>
|
||||
{submitting ? 'Création...' : 'Créer l\'équipe'}
|
||||
{submitting ? 'Création...' : "Créer l'équipe"}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -89,4 +89,3 @@ export default function NewTeamPage() {
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,12 @@ import { auth } from '@/lib/auth';
|
||||
import { getWorkshop, getSessionsTabUrl } from '@/lib/workshops';
|
||||
import { getWeatherSessionById, getPreviousWeatherEntriesForUsers } from '@/services/weather';
|
||||
import { getUserTeams } from '@/services/teams';
|
||||
import { WeatherBoard, WeatherLiveWrapper, WeatherInfoPanel, WeatherAverageBar } from '@/components/weather';
|
||||
import {
|
||||
WeatherBoard,
|
||||
WeatherLiveWrapper,
|
||||
WeatherInfoPanel,
|
||||
WeatherAverageBar,
|
||||
} from '@/components/weather';
|
||||
import { Badge } from '@/components/ui';
|
||||
import { EditableWeatherTitle } from '@/components/ui/EditableWeatherTitle';
|
||||
|
||||
@@ -26,10 +31,7 @@ export default async function WeatherSessionPage({ params }: WeatherSessionPageP
|
||||
notFound();
|
||||
}
|
||||
|
||||
const allUserIds = [
|
||||
session.user.id,
|
||||
...session.shares.map((s: { userId: string }) => s.userId),
|
||||
];
|
||||
const allUserIds = [session.user.id, ...session.shares.map((s: { userId: string }) => s.userId)];
|
||||
|
||||
const [previousEntries, userTeams] = await Promise.all([
|
||||
getPreviousWeatherEntriesForUsers(session.id, session.date, allUserIds),
|
||||
|
||||
@@ -19,7 +19,9 @@ export default function NewWeatherPage() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [selectedDate, setSelectedDate] = useState(new Date().toISOString().split('T')[0]);
|
||||
const [title, setTitle] = useState(() => getWeekYearLabel(new Date(new Date().toISOString().split('T')[0])));
|
||||
const [title, setTitle] = useState(() =>
|
||||
getWeekYearLabel(new Date(new Date().toISOString().split('T')[0]))
|
||||
);
|
||||
const [isTitleManuallyEdited, setIsTitleManuallyEdited] = useState(false);
|
||||
|
||||
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
|
||||
@@ -69,7 +71,8 @@ export default function NewWeatherPage() {
|
||||
Nouvelle Météo
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Créez une météo personnelle pour faire le point sur 4 axes clés et partagez-la avec votre équipe
|
||||
Créez une météo personnelle pour faire le point sur 4 axes clés et partagez-la avec
|
||||
votre équipe
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
|
||||
@@ -109,7 +112,8 @@ export default function NewWeatherPage() {
|
||||
<h3 className="font-medium text-foreground mb-2">Comment ça marche ?</h3>
|
||||
<ol className="text-sm text-muted space-y-1 list-decimal list-inside">
|
||||
<li>
|
||||
<strong>Performance</strong> : Comment évaluez-vous votre performance personnelle ?
|
||||
<strong>Performance</strong> : Comment évaluez-vous votre performance personnelle
|
||||
?
|
||||
</li>
|
||||
<li>
|
||||
<strong>Moral</strong> : Quel est votre moral actuel ?
|
||||
@@ -118,11 +122,13 @@ export default function NewWeatherPage() {
|
||||
<strong>Flux</strong> : Comment se passe votre flux de travail personnel ?
|
||||
</li>
|
||||
<li>
|
||||
<strong>Création de valeur</strong> : Comment évaluez-vous votre création de valeur ?
|
||||
<strong>Création de valeur</strong> : Comment évaluez-vous votre création de
|
||||
valeur ?
|
||||
</li>
|
||||
</ol>
|
||||
<p className="text-sm text-muted mt-2">
|
||||
💡 <strong>Astuce</strong> : Partagez votre météo avec votre équipe pour qu'ils puissent voir votre état. Chaque membre peut créer sa propre météo et la partager !
|
||||
💡 <strong>Astuce</strong> : Partagez votre météo avec votre équipe pour qu'ils
|
||||
puissent voir votre état. Chaque membre peut créer sa propre météo et la partager !
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ export default async function WeeklyCheckInSessionPage({ params }: WeeklyCheckIn
|
||||
// We use session.resolvedParticipant.matchedUser.id which is the participant's user ID
|
||||
const currentQuarterPeriod = getCurrentQuarterPeriod(session.date);
|
||||
let currentQuarterOKRs: Awaited<ReturnType<typeof getUserOKRsForPeriod>> = [];
|
||||
|
||||
|
||||
// Only fetch OKRs if the participant is a recognized user (has matchedUser)
|
||||
const resolvedParticipant = session.resolvedParticipant as ResolvedCollaborator;
|
||||
if (resolvedParticipant.matchedUser) {
|
||||
@@ -99,9 +99,7 @@ export default async function WeeklyCheckInSessionPage({ params }: WeeklyCheckIn
|
||||
const participantTeamIds = new Set(
|
||||
currentQuarterOKRs.map((okr) => okr.team?.id).filter(Boolean) as string[]
|
||||
);
|
||||
const adminTeamIds = userTeams
|
||||
.filter((t) => t.userRole === 'ADMIN')
|
||||
.map((t) => t.id);
|
||||
const adminTeamIds = userTeams.filter((t) => t.userRole === 'ADMIN').map((t) => t.id);
|
||||
return adminTeamIds.some((tid) => participantTeamIds.has(tid));
|
||||
})()
|
||||
}
|
||||
|
||||
@@ -20,7 +20,9 @@ export default function NewWeeklyCheckInPage() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [selectedDate, setSelectedDate] = useState(new Date().toISOString().split('T')[0]);
|
||||
const [title, setTitle] = useState(() => getWeekYearLabel(new Date(new Date().toISOString().split('T')[0])));
|
||||
const [title, setTitle] = useState(() =>
|
||||
getWeekYearLabel(new Date(new Date().toISOString().split('T')[0]))
|
||||
);
|
||||
const [isTitleManuallyEdited, setIsTitleManuallyEdited] = useState(false);
|
||||
|
||||
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
|
||||
|
||||
@@ -56,10 +56,10 @@ export default async function YearReviewSessionPage({ params }: YearReviewSessio
|
||||
/>
|
||||
<div className="mt-2">
|
||||
<CollaboratorDisplay
|
||||
collaborator={session.resolvedParticipant as ResolvedCollaborator}
|
||||
size="lg"
|
||||
showEmail
|
||||
/>
|
||||
collaborator={session.resolvedParticipant as ResolvedCollaborator}
|
||||
size="lg"
|
||||
showEmail
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
|
||||
@@ -135,4 +135,3 @@ export default function NewYearReviewPage() {
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user