chore: clean up code formatting and remove unnecessary whitespace across multiple files for improved readability
This commit is contained in:
@@ -109,4 +109,3 @@ export default function LoginPage() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -170,4 +170,3 @@ export default function RegisterPage() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { handlers } from '@/lib/auth';
|
||||
|
||||
export const { GET, POST } = handlers;
|
||||
|
||||
|
||||
@@ -22,4 +22,3 @@ export async function POST(request: Request) {
|
||||
return NextResponse.json({ error: 'Erreur lors de la création du compte' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,12 @@
|
||||
import { auth } from '@/lib/auth';
|
||||
import {
|
||||
canAccessMotivatorSession,
|
||||
getMotivatorSessionEvents,
|
||||
} from '@/services/moving-motivators';
|
||||
import { canAccessMotivatorSession, getMotivatorSessionEvents } from '@/services/moving-motivators';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
// Store active connections per session
|
||||
const connections = new Map<string, Set<ReadableStreamDefaultController>>();
|
||||
|
||||
export async function GET(
|
||||
request: Request,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
export async function GET(request: Request, { params }: { params: Promise<{ id: string }> }) {
|
||||
const { id: sessionId } = await params;
|
||||
const session = await auth();
|
||||
|
||||
@@ -115,4 +109,3 @@ export function broadcastToMotivatorSession(sessionId: string, event: object) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,10 +6,7 @@ export const dynamic = 'force-dynamic';
|
||||
// Store active connections per session
|
||||
const connections = new Map<string, Set<ReadableStreamDefaultController>>();
|
||||
|
||||
export async function GET(
|
||||
request: Request,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
export async function GET(request: Request, { params }: { params: Promise<{ id: string }> }) {
|
||||
const { id: sessionId } = await params;
|
||||
const session = await auth();
|
||||
|
||||
@@ -112,4 +109,3 @@ export function broadcastToSession(sessionId: string, event: object) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,10 +45,7 @@ export async function POST(request: Request) {
|
||||
const { title, collaborator } = body;
|
||||
|
||||
if (!title || !collaborator) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Titre et collaborateur requis' },
|
||||
{ status: 400 }
|
||||
);
|
||||
return NextResponse.json({ error: 'Titre et collaborateur requis' }, { status: 400 });
|
||||
}
|
||||
|
||||
const newSession = await prisma.session.create({
|
||||
@@ -68,4 +65,3 @@ export async function POST(request: Request) {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -107,4 +107,3 @@ export function EditableMotivatorTitle({
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -49,11 +49,7 @@ export default async function MotivatorSessionPage({ params }: MotivatorSessionP
|
||||
isOwner={session.isOwner}
|
||||
/>
|
||||
<div className="mt-2">
|
||||
<CollaboratorDisplay
|
||||
collaborator={session.resolvedParticipant}
|
||||
size="lg"
|
||||
showEmail
|
||||
/>
|
||||
<CollaboratorDisplay collaborator={session.resolvedParticipant} size="lg" showEmail />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
@@ -80,13 +76,8 @@ export default async function MotivatorSessionPage({ params }: MotivatorSessionP
|
||||
isOwner={session.isOwner}
|
||||
canEdit={session.canEdit}
|
||||
>
|
||||
<MotivatorBoard
|
||||
sessionId={session.id}
|
||||
cards={session.cards}
|
||||
canEdit={session.canEdit}
|
||||
/>
|
||||
<MotivatorBoard sessionId={session.id} cards={session.cards} canEdit={session.canEdit} />
|
||||
</MotivatorLiveWrapper>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,15 @@
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { Card, CardHeader, CardTitle, CardDescription, CardContent, Button, Input } from '@/components/ui';
|
||||
import {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
CardDescription,
|
||||
CardContent,
|
||||
Button,
|
||||
Input,
|
||||
} from '@/components/ui';
|
||||
import { createMotivatorSession } from '@/actions/moving-motivators';
|
||||
|
||||
export default function NewMotivatorSessionPage() {
|
||||
@@ -99,4 +107,3 @@ export default function NewMotivatorSessionPage() {
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
101
src/app/page.tsx
101
src/app/page.tsx
@@ -10,8 +10,8 @@ export default function Home() {
|
||||
Vos ateliers, <span className="text-primary">réinventés</span>
|
||||
</h1>
|
||||
<p className="mx-auto mb-8 max-w-2xl text-lg text-muted">
|
||||
Des outils interactifs et collaboratifs pour accompagner vos équipes.
|
||||
Analysez, comprenez et faites progresser vos collaborateurs avec des ateliers modernes.
|
||||
Des outils interactifs et collaboratifs pour accompagner vos équipes. Analysez,
|
||||
comprenez et faites progresser vos collaborateurs avec des ateliers modernes.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
@@ -46,7 +46,7 @@ export default function Home() {
|
||||
description="Explorez les 10 motivations intrinsèques de vos collaborateurs. Comprenez leur impact et alignez aspirations et missions."
|
||||
features={[
|
||||
'10 cartes de motivation à classer',
|
||||
'Évaluation de l\'influence positive/négative',
|
||||
"Évaluation de l'influence positive/négative",
|
||||
'Récapitulatif personnalisé des motivations',
|
||||
]}
|
||||
accentColor="#8b5cf6"
|
||||
@@ -73,8 +73,9 @@ export default function Home() {
|
||||
Pourquoi faire un SWOT ?
|
||||
</h3>
|
||||
<p className="text-muted mb-4">
|
||||
L'analyse SWOT est un outil puissant pour prendre du recul sur une situation professionnelle.
|
||||
Elle permet de dresser un portrait objectif et structuré, base indispensable pour définir des actions pertinentes.
|
||||
L'analyse SWOT est un outil puissant pour prendre du recul sur une situation
|
||||
professionnelle. Elle permet de dresser un portrait objectif et structuré, base
|
||||
indispensable pour définir des actions pertinentes.
|
||||
</p>
|
||||
<ul className="space-y-2 text-sm text-muted">
|
||||
<li className="flex items-start gap-2">
|
||||
@@ -105,15 +106,21 @@ export default function Home() {
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="rounded-lg bg-green-500/10 p-3 border border-green-500/20">
|
||||
<p className="font-semibold text-green-600 text-sm mb-1">💪 Forces</p>
|
||||
<p className="text-xs text-muted">Compétences, talents, réussites, qualités distinctives</p>
|
||||
<p className="text-xs text-muted">
|
||||
Compétences, talents, réussites, qualités distinctives
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-lg bg-orange-500/10 p-3 border border-orange-500/20">
|
||||
<p className="font-semibold text-orange-600 text-sm mb-1">⚠️ Faiblesses</p>
|
||||
<p className="text-xs text-muted">Lacunes, difficultés récurrentes, axes de progression</p>
|
||||
<p className="text-xs text-muted">
|
||||
Lacunes, difficultés récurrentes, axes de progression
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-lg bg-blue-500/10 p-3 border border-blue-500/20">
|
||||
<p className="font-semibold text-blue-600 text-sm mb-1">🚀 Opportunités</p>
|
||||
<p className="text-xs text-muted">Projets, formations, évolutions, nouveaux défis</p>
|
||||
<p className="text-xs text-muted">
|
||||
Projets, formations, évolutions, nouveaux défis
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-lg bg-red-500/10 p-3 border border-red-500/20">
|
||||
<p className="font-semibold text-red-600 text-sm mb-1">🛡️ Menaces</p>
|
||||
@@ -129,10 +136,26 @@ export default function Home() {
|
||||
Comment ça marche ?
|
||||
</h3>
|
||||
<div className="grid md:grid-cols-4 gap-4">
|
||||
<StepCard number={1} title="Remplir la matrice" description="Identifiez ensemble les éléments de chaque quadrant lors d'un échange constructif" />
|
||||
<StepCard number={2} title="Prioriser" description="Classez les éléments par importance et impact pour concentrer les efforts" />
|
||||
<StepCard number={3} title="Croiser" description="Reliez les forces aux opportunités, anticipez les menaces avec les atouts" />
|
||||
<StepCard number={4} title="Agir" description="Définissez des actions concrètes avec des échéances et des responsables" />
|
||||
<StepCard
|
||||
number={1}
|
||||
title="Remplir la matrice"
|
||||
description="Identifiez ensemble les éléments de chaque quadrant lors d'un échange constructif"
|
||||
/>
|
||||
<StepCard
|
||||
number={2}
|
||||
title="Prioriser"
|
||||
description="Classez les éléments par importance et impact pour concentrer les efforts"
|
||||
/>
|
||||
<StepCard
|
||||
number={3}
|
||||
title="Croiser"
|
||||
description="Reliez les forces aux opportunités, anticipez les menaces avec les atouts"
|
||||
/>
|
||||
<StepCard
|
||||
number={4}
|
||||
title="Agir"
|
||||
description="Définissez des actions concrètes avec des échéances et des responsables"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -156,8 +179,9 @@ export default function Home() {
|
||||
Pourquoi explorer ses motivations ?
|
||||
</h3>
|
||||
<p className="text-muted mb-4">
|
||||
Créé par Jurgen Appelo (Management 3.0), cet exercice révèle les motivations intrinsèques qui nous animent.
|
||||
Comprendre ce qui nous motive permet de mieux s'épanouir et d'aligner nos missions avec nos aspirations profondes.
|
||||
Créé par Jurgen Appelo (Management 3.0), cet exercice révèle les motivations
|
||||
intrinsèques qui nous animent. Comprendre ce qui nous motive permet de mieux
|
||||
s'épanouir et d'aligner nos missions avec nos aspirations profondes.
|
||||
</p>
|
||||
<ul className="space-y-2 text-sm text-muted">
|
||||
<li className="flex items-start gap-2">
|
||||
@@ -206,20 +230,20 @@ export default function Home() {
|
||||
Comment ça marche ?
|
||||
</h3>
|
||||
<div className="grid md:grid-cols-3 gap-4">
|
||||
<StepCard
|
||||
number={1}
|
||||
title="Classer par importance"
|
||||
description="Ordonnez les 10 cartes de la moins importante (gauche) à la plus importante (droite) pour vous"
|
||||
<StepCard
|
||||
number={1}
|
||||
title="Classer par importance"
|
||||
description="Ordonnez les 10 cartes de la moins importante (gauche) à la plus importante (droite) pour vous"
|
||||
/>
|
||||
<StepCard
|
||||
number={2}
|
||||
title="Évaluer l'influence"
|
||||
description="Pour chaque motivation, indiquez si votre situation actuelle l'impacte positivement ou négativement"
|
||||
<StepCard
|
||||
number={2}
|
||||
title="Évaluer l'influence"
|
||||
description="Pour chaque motivation, indiquez si votre situation actuelle l'impacte positivement ou négativement"
|
||||
/>
|
||||
<StepCard
|
||||
number={3}
|
||||
title="Analyser et discuter"
|
||||
description="Le récapitulatif révèle les motivations clés et les points de vigilance pour un échange constructif"
|
||||
<StepCard
|
||||
number={3}
|
||||
title="Analyser et discuter"
|
||||
description="Le récapitulatif révèle les motivations clés et les points de vigilance pour un échange constructif"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -279,9 +303,7 @@ function WorkshopCard({
|
||||
newHref: string;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className="group relative overflow-hidden rounded-2xl border-2 border-border bg-card p-8 transition-all hover:border-primary/50 hover:shadow-xl"
|
||||
>
|
||||
<div className="group relative overflow-hidden rounded-2xl border-2 border-border bg-card p-8 transition-all hover:border-primary/50 hover:shadow-xl">
|
||||
{/* Accent gradient */}
|
||||
<div
|
||||
className="absolute inset-x-0 top-0 h-1 opacity-80"
|
||||
@@ -313,7 +335,12 @@ function WorkshopCard({
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
{feature}
|
||||
</li>
|
||||
@@ -380,22 +407,16 @@ function StepCard({
|
||||
);
|
||||
}
|
||||
|
||||
function MotivatorPill({
|
||||
icon,
|
||||
name,
|
||||
color,
|
||||
}: {
|
||||
icon: string;
|
||||
name: string;
|
||||
color: string;
|
||||
}) {
|
||||
function MotivatorPill({ icon, name, color }: { icon: string; name: string; color: string }) {
|
||||
return (
|
||||
<div
|
||||
className="flex items-center gap-2 px-3 py-1.5 rounded-full"
|
||||
style={{ backgroundColor: `${color}15`, border: `1px solid ${color}30` }}
|
||||
>
|
||||
<span>{icon}</span>
|
||||
<span className="font-medium" style={{ color }}>{name}</span>
|
||||
<span className="font-medium" style={{ color }}>
|
||||
{name}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,9 +12,7 @@ export function PasswordForm() {
|
||||
const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null);
|
||||
|
||||
const canSubmit =
|
||||
currentPassword.length > 0 &&
|
||||
newPassword.length >= 6 &&
|
||||
newPassword === confirmPassword;
|
||||
currentPassword.length > 0 && newPassword.length >= 6 && newPassword === confirmPassword;
|
||||
|
||||
async function handleSubmit(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
@@ -58,10 +56,7 @@ export function PasswordForm() {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
htmlFor="newPassword"
|
||||
className="mb-1.5 block text-sm font-medium text-foreground"
|
||||
>
|
||||
<label htmlFor="newPassword" className="mb-1.5 block text-sm font-medium text-foreground">
|
||||
Nouveau mot de passe
|
||||
</label>
|
||||
<Input
|
||||
@@ -90,17 +85,13 @@ export function PasswordForm() {
|
||||
required
|
||||
/>
|
||||
{confirmPassword && newPassword !== confirmPassword && (
|
||||
<p className="mt-1 text-xs text-destructive">
|
||||
Les mots de passe ne correspondent pas
|
||||
</p>
|
||||
<p className="mt-1 text-xs text-destructive">Les mots de passe ne correspondent pas</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{message && (
|
||||
<p
|
||||
className={`text-sm ${
|
||||
message.type === 'success' ? 'text-success' : 'text-destructive'
|
||||
}`}
|
||||
className={`text-sm ${message.type === 'success' ? 'text-success' : 'text-destructive'}`}
|
||||
>
|
||||
{message.text}
|
||||
</p>
|
||||
@@ -112,5 +103,3 @@ export function PasswordForm() {
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -67,9 +67,7 @@ export function ProfileForm({ initialData }: ProfileFormProps) {
|
||||
|
||||
{message && (
|
||||
<p
|
||||
className={`text-sm ${
|
||||
message.type === 'success' ? 'text-success' : 'text-destructive'
|
||||
}`}
|
||||
className={`text-sm ${message.type === 'success' ? 'text-success' : 'text-destructive'}`}
|
||||
>
|
||||
{message.text}
|
||||
</p>
|
||||
@@ -81,5 +79,3 @@ export function ProfileForm({ initialData }: ProfileFormProps) {
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -38,9 +38,7 @@ export default async function ProfilePage() {
|
||||
{/* Profile Info */}
|
||||
<section className="rounded-xl border border-border bg-card p-6">
|
||||
<div className="mb-6 flex items-start justify-between">
|
||||
<h2 className="text-xl font-semibold text-foreground">
|
||||
Informations personnelles
|
||||
</h2>
|
||||
<h2 className="text-xl font-semibold text-foreground">Informations personnelles</h2>
|
||||
<a
|
||||
href="https://gravatar.com"
|
||||
target="_blank"
|
||||
@@ -60,17 +58,13 @@ export default async function ProfilePage() {
|
||||
|
||||
{/* Password */}
|
||||
<section className="rounded-xl border border-border bg-card p-6">
|
||||
<h2 className="mb-6 text-xl font-semibold text-foreground">
|
||||
Changer le mot de passe
|
||||
</h2>
|
||||
<h2 className="mb-6 text-xl font-semibold text-foreground">Changer le mot de passe</h2>
|
||||
<PasswordForm />
|
||||
</section>
|
||||
|
||||
{/* Account Info */}
|
||||
<section className="rounded-xl border border-border bg-card p-6">
|
||||
<h2 className="mb-4 text-xl font-semibold text-foreground">
|
||||
Informations du compte
|
||||
</h2>
|
||||
<h2 className="mb-4 text-xl font-semibold text-foreground">Informations du compte</h2>
|
||||
<div className="space-y-3 text-sm">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted">ID du compte</span>
|
||||
@@ -92,4 +86,3 @@ export default async function ProfilePage() {
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,15 @@
|
||||
import { useState, useTransition } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { useSearchParams, useRouter } from 'next/navigation';
|
||||
import { Card, Badge, Button, Modal, ModalFooter, Input, CollaboratorDisplay } from '@/components/ui';
|
||||
import {
|
||||
Card,
|
||||
Badge,
|
||||
Button,
|
||||
Modal,
|
||||
ModalFooter,
|
||||
Input,
|
||||
CollaboratorDisplay,
|
||||
} from '@/components/ui';
|
||||
import { deleteSwotSession, updateSwotSession } from '@/actions/session';
|
||||
import { deleteMotivatorSession, updateMotivatorSession } from '@/actions/moving-motivators';
|
||||
|
||||
@@ -104,10 +112,10 @@ function getGroupKey(session: AnySession): string {
|
||||
// Group sessions by participant (using matched user ID when available)
|
||||
function groupByPerson(sessions: AnySession[]): Map<string, AnySession[]> {
|
||||
const grouped = new Map<string, AnySession[]>();
|
||||
|
||||
|
||||
sessions.forEach((session) => {
|
||||
const key = getGroupKey(session);
|
||||
|
||||
|
||||
const existing = grouped.get(key);
|
||||
if (existing) {
|
||||
existing.push(session);
|
||||
@@ -115,24 +123,23 @@ function groupByPerson(sessions: AnySession[]): Map<string, AnySession[]> {
|
||||
grouped.set(key, [session]);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Sort sessions within each group by date
|
||||
grouped.forEach((sessions) => {
|
||||
sessions.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
|
||||
});
|
||||
|
||||
|
||||
return grouped;
|
||||
}
|
||||
|
||||
export function WorkshopTabs({ swotSessions, motivatorSessions }: WorkshopTabsProps) {
|
||||
const searchParams = useSearchParams();
|
||||
const router = useRouter();
|
||||
|
||||
|
||||
// Get tab from URL or default to 'all'
|
||||
const tabParam = searchParams.get('tab');
|
||||
const activeTab: WorkshopType = tabParam && VALID_TABS.includes(tabParam as WorkshopType)
|
||||
? (tabParam as WorkshopType)
|
||||
: 'all';
|
||||
const activeTab: WorkshopType =
|
||||
tabParam && VALID_TABS.includes(tabParam as WorkshopType) ? (tabParam as WorkshopType) : 'all';
|
||||
|
||||
const setActiveTab = (tab: WorkshopType) => {
|
||||
const params = new URLSearchParams(searchParams.toString());
|
||||
@@ -205,9 +212,7 @@ export function WorkshopTabs({ swotSessions, motivatorSessions }: WorkshopTabsPr
|
||||
{activeTab === 'byPerson' ? (
|
||||
// By Person View
|
||||
sortedPersons.length === 0 ? (
|
||||
<div className="text-center py-12 text-muted">
|
||||
Aucun atelier pour le moment
|
||||
</div>
|
||||
<div className="text-center py-12 text-muted">Aucun atelier pour le moment</div>
|
||||
) : (
|
||||
<div className="space-y-8">
|
||||
{sortedPersons.map(([personKey, sessions]) => {
|
||||
@@ -231,9 +236,7 @@ export function WorkshopTabs({ swotSessions, motivatorSessions }: WorkshopTabsPr
|
||||
</div>
|
||||
)
|
||||
) : filteredSessions.length === 0 ? (
|
||||
<div className="text-center py-12 text-muted">
|
||||
Aucun atelier de ce type pour le moment
|
||||
</div>
|
||||
<div className="text-center py-12 text-muted">Aucun atelier de ce type pour le moment</div>
|
||||
) : (
|
||||
<div className="space-y-8">
|
||||
{/* My Sessions */}
|
||||
@@ -287,9 +290,10 @@ function TabButton({
|
||||
onClick={onClick}
|
||||
className={`
|
||||
flex items-center gap-2 px-4 py-2 rounded-lg font-medium transition-colors
|
||||
${active
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: 'text-muted hover:bg-card-hover hover:text-foreground'
|
||||
${
|
||||
active
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: 'text-muted hover:bg-card-hover hover:text-foreground'
|
||||
}
|
||||
`}
|
||||
>
|
||||
@@ -306,7 +310,7 @@ function SessionCard({ session }: { session: AnySession }) {
|
||||
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||
const [showEditModal, setShowEditModal] = useState(false);
|
||||
const [isPending, startTransition] = useTransition();
|
||||
|
||||
|
||||
// Edit form state
|
||||
const [editTitle, setEditTitle] = useState(session.title);
|
||||
const [editParticipant, setEditParticipant] = useState(
|
||||
@@ -328,7 +332,7 @@ function SessionCard({ session }: { session: AnySession }) {
|
||||
const result = isSwot
|
||||
? await deleteSwotSession(session.id)
|
||||
: await deleteMotivatorSession(session.id);
|
||||
|
||||
|
||||
if (result.success) {
|
||||
setShowDeleteModal(false);
|
||||
} else {
|
||||
@@ -341,8 +345,11 @@ function SessionCard({ session }: { session: AnySession }) {
|
||||
startTransition(async () => {
|
||||
const result = isSwot
|
||||
? await updateSwotSession(session.id, { title: editTitle, collaborator: editParticipant })
|
||||
: await updateMotivatorSession(session.id, { title: editTitle, participant: editParticipant });
|
||||
|
||||
: await updateMotivatorSession(session.id, {
|
||||
title: editTitle,
|
||||
participant: editParticipant,
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
setShowEditModal(false);
|
||||
} else {
|
||||
@@ -372,14 +379,13 @@ function SessionCard({ session }: { session: AnySession }) {
|
||||
{/* Header: Icon + Title + Role badge */}
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<span className="text-xl">{icon}</span>
|
||||
<h3 className="font-semibold text-foreground line-clamp-1 flex-1">
|
||||
{session.title}
|
||||
</h3>
|
||||
<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)',
|
||||
backgroundColor:
|
||||
session.role === 'EDITOR' ? 'rgba(6,182,212,0.1)' : 'rgba(234,179,8,0.1)',
|
||||
color: session.role === 'EDITOR' ? '#06b6d4' : '#eab308',
|
||||
}}
|
||||
>
|
||||
@@ -390,12 +396,11 @@ function SessionCard({ session }: { session: AnySession }) {
|
||||
|
||||
{/* Participant + Owner info */}
|
||||
<div className="mb-3 flex items-center gap-2">
|
||||
<CollaboratorDisplay
|
||||
collaborator={getResolvedCollaborator(session)}
|
||||
size="sm"
|
||||
/>
|
||||
<CollaboratorDisplay collaborator={getResolvedCollaborator(session)} size="sm" />
|
||||
{!session.isOwner && (
|
||||
<span className="text-xs text-muted">· par {session.user.name || session.user.email}</span>
|
||||
<span className="text-xs text-muted">
|
||||
· par {session.user.name || session.user.email}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -441,9 +446,7 @@ function SessionCard({ session }: { session: AnySession }) {
|
||||
</div>
|
||||
))}
|
||||
{session.shares.length > 3 && (
|
||||
<span className="text-[10px] text-muted">
|
||||
+{session.shares.length - 3}
|
||||
</span>
|
||||
<span className="text-[10px] text-muted">+{session.shares.length - 3}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -464,7 +467,12 @@ function SessionCard({ session }: { session: AnySession }) {
|
||||
title="Modifier"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
@@ -477,7 +485,12 @@ function SessionCard({ session }: { session: AnySession }) {
|
||||
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" />
|
||||
<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>
|
||||
@@ -511,7 +524,10 @@ function SessionCard({ session }: { session: AnySession }) {
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="edit-participant" className="block text-sm font-medium text-foreground mb-1">
|
||||
<label
|
||||
htmlFor="edit-participant"
|
||||
className="block text-sm font-medium text-foreground mb-1"
|
||||
>
|
||||
{isSwot ? 'Collaborateur' : 'Participant'}
|
||||
</label>
|
||||
<Input
|
||||
@@ -550,24 +566,17 @@ function SessionCard({ session }: { session: AnySession }) {
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<p className="text-muted">
|
||||
Êtes-vous sûr de vouloir supprimer l'atelier <strong className="text-foreground">"{session.title}"</strong> ?
|
||||
Êtes-vous sûr de vouloir supprimer l'atelier{' '}
|
||||
<strong className="text-foreground">"{session.title}"</strong> ?
|
||||
</p>
|
||||
<p className="text-sm text-destructive">
|
||||
Cette action est irréversible. Toutes les données seront perdues.
|
||||
</p>
|
||||
<ModalFooter>
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => setShowDeleteModal(false)}
|
||||
disabled={isPending}
|
||||
>
|
||||
<Button variant="ghost" onClick={() => setShowDeleteModal(false)} disabled={isPending}>
|
||||
Annuler
|
||||
</Button>
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={handleDelete}
|
||||
disabled={isPending}
|
||||
>
|
||||
<Button variant="destructive" onClick={handleDelete} disabled={isPending}>
|
||||
{isPending ? 'Suppression...' : 'Supprimer'}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
@@ -576,4 +585,3 @@ function SessionCard({ session }: { session: AnySession }) {
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -80,13 +80,8 @@ export default async function SessionPage({ params }: SessionPageProps) {
|
||||
isOwner={session.isOwner}
|
||||
canEdit={session.canEdit}
|
||||
>
|
||||
<SwotBoard
|
||||
sessionId={session.id}
|
||||
items={session.items}
|
||||
actions={session.actions}
|
||||
/>
|
||||
<SwotBoard sessionId={session.id} items={session.items} actions={session.actions} />
|
||||
</SessionLiveWrapper>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,15 @@
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { Card, CardHeader, CardTitle, CardDescription, CardContent, Button, Input } from '@/components/ui';
|
||||
import {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
CardDescription,
|
||||
CardContent,
|
||||
Button,
|
||||
Input,
|
||||
} from '@/components/ui';
|
||||
|
||||
export default function NewSessionPage() {
|
||||
const router = useRouter();
|
||||
@@ -100,4 +108,3 @@ export default function NewSessionPage() {
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -62,9 +62,7 @@ export default async function SessionsPage() {
|
||||
<div className="mb-8 flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-foreground">Mes Ateliers</h1>
|
||||
<p className="mt-1 text-muted">
|
||||
Tous vos ateliers en un seul endroit
|
||||
</p>
|
||||
<p className="mt-1 text-muted">Tous vos ateliers en un seul endroit</p>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Link href="/sessions/new">
|
||||
@@ -90,7 +88,8 @@ export default async function SessionsPage() {
|
||||
Commencez votre premier atelier
|
||||
</h2>
|
||||
<p className="text-muted mb-6 max-w-md mx-auto">
|
||||
Créez un atelier SWOT pour analyser les forces et faiblesses, ou un Moving Motivators pour découvrir les motivations de vos collaborateurs.
|
||||
Créez un atelier SWOT pour analyser les forces et faiblesses, ou un Moving Motivators
|
||||
pour découvrir les motivations de vos collaborateurs.
|
||||
</p>
|
||||
<div className="flex gap-3 justify-center">
|
||||
<Link href="/sessions/new">
|
||||
@@ -109,10 +108,7 @@ export default async function SessionsPage() {
|
||||
</Card>
|
||||
) : (
|
||||
<Suspense fallback={<WorkshopTabsSkeleton />}>
|
||||
<WorkshopTabs
|
||||
swotSessions={allSwotSessions}
|
||||
motivatorSessions={allMotivatorSessions}
|
||||
/>
|
||||
<WorkshopTabs swotSessions={allSwotSessions} motivatorSessions={allMotivatorSessions} />
|
||||
</Suspense>
|
||||
)}
|
||||
</main>
|
||||
|
||||
@@ -54,16 +54,13 @@ export default async function UsersPage() {
|
||||
<div className="text-sm text-muted">Sessions totales</div>
|
||||
</div>
|
||||
<div className="rounded-xl border border-border bg-card p-4">
|
||||
<div className="text-2xl font-bold text-opportunity">
|
||||
{avgSessionsPerUser.toFixed(1)}
|
||||
</div>
|
||||
<div className="text-2xl font-bold text-opportunity">{avgSessionsPerUser.toFixed(1)}</div>
|
||||
<div className="text-sm text-muted">Moy. par user</div>
|
||||
</div>
|
||||
<div className="rounded-xl border border-border bg-card p-4">
|
||||
<div className="text-2xl font-bold text-accent">
|
||||
{users.reduce(
|
||||
(acc, u) =>
|
||||
acc + u._count.sharedSessions + u._count.sharedMotivatorSessions,
|
||||
(acc, u) => acc + u._count.sharedSessions + u._count.sharedMotivatorSessions,
|
||||
0
|
||||
)}
|
||||
</div>
|
||||
@@ -74,10 +71,8 @@ export default async function UsersPage() {
|
||||
{/* Users List */}
|
||||
<div className="space-y-3">
|
||||
{users.map((user) => {
|
||||
const totalUserSessions =
|
||||
user._count.sessions + user._count.motivatorSessions;
|
||||
const totalShares =
|
||||
user._count.sharedSessions + user._count.sharedMotivatorSessions;
|
||||
const totalUserSessions = user._count.sessions + user._count.motivatorSessions;
|
||||
const totalShares = user._count.sharedSessions + user._count.sharedMotivatorSessions;
|
||||
const isCurrentUser = user.id === session.user?.id;
|
||||
|
||||
return (
|
||||
@@ -194,16 +189,12 @@ export default async function UsersPage() {
|
||||
<div className="text-sm font-medium text-foreground">
|
||||
{totalUserSessions} session{totalUserSessions !== 1 ? 's' : ''}
|
||||
</div>
|
||||
<div className="text-xs text-muted">
|
||||
{formatRelativeTime(user.createdAt)}
|
||||
</div>
|
||||
<div className="text-xs text-muted">{formatRelativeTime(user.createdAt)}</div>
|
||||
</div>
|
||||
|
||||
{/* Date Info */}
|
||||
<div className="hidden flex-col items-end sm:flex">
|
||||
<div className="text-sm text-foreground">
|
||||
{formatRelativeTime(user.createdAt)}
|
||||
</div>
|
||||
<div className="text-sm text-foreground">{formatRelativeTime(user.createdAt)}</div>
|
||||
<div className="text-xs text-muted">
|
||||
{new Date(user.createdAt).toLocaleDateString('fr-FR')}
|
||||
</div>
|
||||
@@ -217,9 +208,7 @@ export default async function UsersPage() {
|
||||
{users.length === 0 && (
|
||||
<div className="flex flex-col items-center justify-center rounded-xl border border-border bg-card py-16">
|
||||
<div className="text-4xl">👥</div>
|
||||
<div className="mt-4 text-lg font-medium text-foreground">
|
||||
Aucun utilisateur
|
||||
</div>
|
||||
<div className="mt-4 text-lg font-medium text-foreground">Aucun utilisateur</div>
|
||||
<div className="mt-1 text-sm text-muted">
|
||||
Les utilisateurs apparaîtront ici une fois inscrits
|
||||
</div>
|
||||
@@ -228,4 +217,3 @@ export default async function UsersPage() {
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user