chore: clean up code formatting and remove unnecessary whitespace across multiple files for improved readability
This commit is contained in:
@@ -44,3 +44,4 @@ README.md
|
||||
devbook.md
|
||||
TODO.md
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ services:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- "3011:3000"
|
||||
- '3011:3000'
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- DATABASE_URL=file:/app/data/dev.db
|
||||
@@ -14,4 +14,3 @@ services:
|
||||
volumes:
|
||||
- ./data:/app/data
|
||||
restart: unless-stopped
|
||||
|
||||
|
||||
@@ -15,10 +15,7 @@ export async function createMotivatorSession(data: { title: string; participant:
|
||||
}
|
||||
|
||||
try {
|
||||
const motivatorSession = await motivatorsService.createMotivatorSession(
|
||||
session.user.id,
|
||||
data
|
||||
);
|
||||
const motivatorSession = await motivatorsService.createMotivatorSession(session.user.id, data);
|
||||
revalidatePath('/motivators');
|
||||
return { success: true, data: motivatorSession };
|
||||
} catch (error) {
|
||||
@@ -89,10 +86,7 @@ export async function updateMotivatorCard(
|
||||
}
|
||||
|
||||
// Check edit permission
|
||||
const canEdit = await motivatorsService.canEditMotivatorSession(
|
||||
sessionId,
|
||||
authSession.user.id
|
||||
);
|
||||
const canEdit = await motivatorsService.canEditMotivatorSession(sessionId, authSession.user.id);
|
||||
if (!canEdit) {
|
||||
return { success: false, error: 'Permission refusée' };
|
||||
}
|
||||
@@ -132,10 +126,7 @@ export async function reorderMotivatorCards(sessionId: string, cardIds: string[]
|
||||
}
|
||||
|
||||
// Check edit permission
|
||||
const canEdit = await motivatorsService.canEditMotivatorSession(
|
||||
sessionId,
|
||||
authSession.user.id
|
||||
);
|
||||
const canEdit = await motivatorsService.canEditMotivatorSession(sessionId, authSession.user.id);
|
||||
if (!canEdit) {
|
||||
return { success: false, error: 'Permission refusée' };
|
||||
}
|
||||
@@ -159,11 +150,7 @@ export async function reorderMotivatorCards(sessionId: string, cardIds: string[]
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateCardInfluence(
|
||||
cardId: string,
|
||||
sessionId: string,
|
||||
influence: number
|
||||
) {
|
||||
export async function updateCardInfluence(cardId: string, sessionId: string, influence: number) {
|
||||
return updateMotivatorCard(cardId, sessionId, { influence });
|
||||
}
|
||||
|
||||
@@ -192,8 +179,7 @@ export async function shareMotivatorSession(
|
||||
return { success: true, data: share };
|
||||
} catch (error) {
|
||||
console.error('Error sharing motivator session:', error);
|
||||
const message =
|
||||
error instanceof Error ? error.message : 'Erreur lors du partage';
|
||||
const message = error instanceof Error ? error.message : 'Erreur lors du partage';
|
||||
return { success: false, error: message };
|
||||
}
|
||||
}
|
||||
@@ -205,11 +191,7 @@ export async function removeMotivatorShare(sessionId: string, shareUserId: strin
|
||||
}
|
||||
|
||||
try {
|
||||
await motivatorsService.removeMotivatorShare(
|
||||
sessionId,
|
||||
authSession.user.id,
|
||||
shareUserId
|
||||
);
|
||||
await motivatorsService.removeMotivatorShare(sessionId, authSession.user.id, shareUserId);
|
||||
revalidatePath(`/motivators/${sessionId}`);
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
@@ -217,4 +199,3 @@ export async function removeMotivatorShare(sessionId: string, shareUserId: strin
|
||||
return { success: false, error: 'Erreur lors de la suppression du partage' };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,10 +35,7 @@ export async function updateProfileAction(data: { name?: string; email?: string
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function updatePasswordAction(data: {
|
||||
currentPassword: string;
|
||||
newPassword: string;
|
||||
}) {
|
||||
export async function updatePasswordAction(data: { currentPassword: string; newPassword: string }) {
|
||||
const session = await auth();
|
||||
if (!session?.user?.id) {
|
||||
return { success: false, error: 'Non authentifié' };
|
||||
@@ -48,12 +45,6 @@ export async function updatePasswordAction(data: {
|
||||
return { success: false, error: 'Le nouveau mot de passe doit faire au moins 6 caractères' };
|
||||
}
|
||||
|
||||
const result = await updateUserPassword(
|
||||
session.user.id,
|
||||
data.currentPassword,
|
||||
data.newPassword
|
||||
);
|
||||
const result = await updateUserPassword(session.user.id, data.currentPassword, data.newPassword);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -99,7 +99,12 @@ export async function updateSwotSession(
|
||||
}
|
||||
|
||||
// Emit event for real-time sync
|
||||
await sessionsService.createSessionEvent(sessionId, session.user.id, 'SESSION_UPDATED', updateData);
|
||||
await sessionsService.createSessionEvent(
|
||||
sessionId,
|
||||
session.user.id,
|
||||
'SESSION_UPDATED',
|
||||
updateData
|
||||
);
|
||||
|
||||
revalidatePath(`/sessions/${sessionId}`);
|
||||
revalidatePath('/sessions');
|
||||
@@ -130,4 +135,3 @@ export async function deleteSwotSession(sessionId: string) {
|
||||
return { success: false, error: 'Erreur lors de la suppression' };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,11 +2,7 @@
|
||||
|
||||
import { revalidatePath } from 'next/cache';
|
||||
import { auth } from '@/lib/auth';
|
||||
import {
|
||||
shareSession,
|
||||
removeShare,
|
||||
getSessionShares,
|
||||
} from '@/services/sessions';
|
||||
import { shareSession, removeShare, getSessionShares } from '@/services/sessions';
|
||||
import type { ShareRole } from '@prisma/client';
|
||||
|
||||
export async function shareSessionAction(
|
||||
@@ -26,10 +22,10 @@ export async function shareSessionAction(
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Erreur inconnue';
|
||||
if (message === 'User not found') {
|
||||
return { success: false, error: "Aucun utilisateur trouvé avec cet email" };
|
||||
return { success: false, error: 'Aucun utilisateur trouvé avec cet email' };
|
||||
}
|
||||
if (message === 'Cannot share session with yourself') {
|
||||
return { success: false, error: "Vous ne pouvez pas partager avec vous-même" };
|
||||
return { success: false, error: 'Vous ne pouvez pas partager avec vous-même' };
|
||||
}
|
||||
return { success: false, error: message };
|
||||
}
|
||||
@@ -65,5 +61,3 @@ export async function getSharesAction(sessionId: string) {
|
||||
return { success: false, error: message, data: [] };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -228,4 +228,3 @@ export async function deleteAction(actionId: string, sessionId: string) {
|
||||
return { success: false, error: 'Erreur lors de la suppression' };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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">
|
||||
@@ -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';
|
||||
|
||||
@@ -130,9 +138,8 @@ export function WorkshopTabs({ swotSessions, motivatorSessions }: WorkshopTabsPr
|
||||
|
||||
// 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'
|
||||
}
|
||||
`}
|
||||
>
|
||||
@@ -341,7 +345,10 @@ 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);
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,19 +18,13 @@ export function LiveIndicator({ isConnected, error }: LiveIndicatorProps) {
|
||||
return (
|
||||
<div
|
||||
className={`flex items-center gap-2 rounded-full px-3 py-1.5 text-sm transition-colors ${
|
||||
isConnected
|
||||
? 'bg-success/10 text-success'
|
||||
: 'bg-yellow/10 text-yellow'
|
||||
isConnected ? 'bg-success/10 text-success' : 'bg-yellow/10 text-yellow'
|
||||
}`}
|
||||
>
|
||||
<span
|
||||
className={`h-2 w-2 rounded-full ${
|
||||
isConnected ? 'bg-success animate-pulse' : 'bg-yellow'
|
||||
}`}
|
||||
className={`h-2 w-2 rounded-full ${isConnected ? 'bg-success animate-pulse' : 'bg-yellow'}`}
|
||||
/>
|
||||
<span>{isConnected ? 'Live' : 'Connexion...'}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -101,11 +101,7 @@ export function SessionLiveWrapper({
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setShareModalOpen(true)}
|
||||
>
|
||||
<Button variant="outline" size="sm" onClick={() => setShareModalOpen(true)}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
@@ -120,9 +116,7 @@ export function SessionLiveWrapper({
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className={!canEdit ? 'pointer-events-none opacity-90' : ''}>
|
||||
{children}
|
||||
</div>
|
||||
<div className={!canEdit ? 'pointer-events-none opacity-90' : ''}>{children}</div>
|
||||
|
||||
{/* Share Modal */}
|
||||
<ShareModal
|
||||
@@ -136,5 +130,3 @@ export function SessionLiveWrapper({
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -105,14 +105,10 @@ export function ShareModal({
|
||||
|
||||
{/* Current shares */}
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm font-medium text-foreground">
|
||||
Collaborateurs ({shares.length})
|
||||
</p>
|
||||
<p className="text-sm font-medium text-foreground">Collaborateurs ({shares.length})</p>
|
||||
|
||||
{shares.length === 0 ? (
|
||||
<p className="text-sm text-muted">
|
||||
Aucun collaborateur pour le moment
|
||||
</p>
|
||||
<p className="text-sm text-muted">Aucun collaborateur pour le moment</p>
|
||||
) : (
|
||||
<ul className="space-y-2">
|
||||
{shares.map((share) => (
|
||||
@@ -126,9 +122,7 @@ export function ShareModal({
|
||||
<p className="text-sm font-medium text-foreground">
|
||||
{share.user.name || share.user.email}
|
||||
</p>
|
||||
{share.user.name && (
|
||||
<p className="text-xs text-muted">{share.user.email}</p>
|
||||
)}
|
||||
{share.user.name && <p className="text-xs text-muted">{share.user.email}</p>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -176,5 +170,3 @@ export function ShareModal({
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
export { LiveIndicator } from './LiveIndicator';
|
||||
export { ShareModal } from './ShareModal';
|
||||
export { SessionLiveWrapper } from './SessionLiveWrapper';
|
||||
|
||||
|
||||
@@ -112,11 +112,7 @@ export function Header() {
|
||||
onClick={() => setMenuOpen(!menuOpen)}
|
||||
className="flex h-9 items-center gap-2 rounded-lg border border-border bg-card pl-1.5 pr-3 transition-colors hover:bg-card-hover"
|
||||
>
|
||||
<Avatar
|
||||
email={session.user.email!}
|
||||
name={session.user.name}
|
||||
size={24}
|
||||
/>
|
||||
<Avatar email={session.user.email!} name={session.user.name} size={24} />
|
||||
<span className="text-sm font-medium text-foreground">
|
||||
{session.user.name || session.user.email?.split('@')[0]}
|
||||
</span>
|
||||
@@ -137,10 +133,7 @@ export function Header() {
|
||||
|
||||
{menuOpen && (
|
||||
<>
|
||||
<div
|
||||
className="fixed inset-0 z-10"
|
||||
onClick={() => setMenuOpen(false)}
|
||||
/>
|
||||
<div className="fixed inset-0 z-10" onClick={() => setMenuOpen(false)} />
|
||||
<div className="absolute right-0 z-20 mt-2 w-48 rounded-lg border border-border bg-card py-1 shadow-lg">
|
||||
<div className="border-b border-border px-4 py-2">
|
||||
<p className="text-xs text-muted">Connecté en tant que</p>
|
||||
|
||||
@@ -140,4 +140,3 @@ function InfluenceSlider({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -66,14 +66,15 @@ export function MotivatorBoard({ sessionId, cards: initialCards, canEdit }: Moti
|
||||
|
||||
// Persist to server
|
||||
startTransition(async () => {
|
||||
await reorderMotivatorCards(sessionId, newCards.map((c) => c.id));
|
||||
await reorderMotivatorCards(
|
||||
sessionId,
|
||||
newCards.map((c) => c.id)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function handleInfluenceChange(cardId: string, influence: number) {
|
||||
setCards((prev) =>
|
||||
prev.map((c) => (c.id === cardId ? { ...c, influence } : c))
|
||||
);
|
||||
setCards((prev) => prev.map((c) => (c.id === cardId ? { ...c, influence } : c)));
|
||||
|
||||
startTransition(async () => {
|
||||
await updateCardInfluence(cardId, sessionId, influence);
|
||||
@@ -151,11 +152,7 @@ export function MotivatorBoard({ sessionId, cards: initialCards, canEdit }: Moti
|
||||
>
|
||||
<div className="flex gap-2 min-w-max px-2">
|
||||
{sortedCards.map((card) => (
|
||||
<MotivatorCard
|
||||
key={card.id}
|
||||
card={card}
|
||||
disabled={!canEdit}
|
||||
/>
|
||||
<MotivatorCard key={card.id} card={card} disabled={!canEdit} />
|
||||
))}
|
||||
</div>
|
||||
</SortableContext>
|
||||
@@ -182,7 +179,8 @@ export function MotivatorBoard({ sessionId, cards: initialCards, canEdit }: Moti
|
||||
Évaluez l'influence de chaque motivation
|
||||
</h2>
|
||||
<p className="text-muted">
|
||||
Pour chaque carte, indiquez si cette motivation a une influence positive ou négative sur votre situation actuelle
|
||||
Pour chaque carte, indiquez si cette motivation a une influence positive ou négative
|
||||
sur votre situation actuelle
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -216,9 +214,7 @@ export function MotivatorBoard({ sessionId, cards: initialCards, canEdit }: Moti
|
||||
<h2 className="text-xl font-semibold text-foreground mb-2">
|
||||
Récapitulatif de vos Moving Motivators
|
||||
</h2>
|
||||
<p className="text-muted">
|
||||
Voici l'analyse de vos motivations et leur impact
|
||||
</p>
|
||||
<p className="text-muted">Voici l'analyse de vos motivations et leur impact</p>
|
||||
</div>
|
||||
|
||||
<MotivatorSummary cards={sortedCards} />
|
||||
@@ -273,4 +269,3 @@ function StepIndicator({
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,14 +19,7 @@ export function MotivatorCard({
|
||||
}: MotivatorCardProps) {
|
||||
const config = MOTIVATOR_BY_TYPE[card.type];
|
||||
|
||||
const {
|
||||
attributes,
|
||||
listeners,
|
||||
setNodeRef,
|
||||
transform,
|
||||
transition,
|
||||
isDragging,
|
||||
} = useSortable({
|
||||
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
||||
id: card.id,
|
||||
disabled,
|
||||
});
|
||||
@@ -62,10 +55,7 @@ export function MotivatorCard({
|
||||
<div className="text-3xl mb-1 mt-2">{config.icon}</div>
|
||||
|
||||
{/* Name */}
|
||||
<div
|
||||
className="font-semibold text-sm text-center px-2"
|
||||
style={{ color: config.color }}
|
||||
>
|
||||
<div className="font-semibold text-sm text-center px-2" style={{ color: config.color }}>
|
||||
{config.name}
|
||||
</div>
|
||||
|
||||
@@ -129,9 +119,7 @@ export function MotivatorCardStatic({
|
||||
/>
|
||||
|
||||
{/* Icon */}
|
||||
<div className={`mb-1 mt-2 ${size === 'small' ? 'text-xl' : 'text-3xl'}`}>
|
||||
{config.icon}
|
||||
</div>
|
||||
<div className={`mb-1 mt-2 ${size === 'small' ? 'text-xl' : 'text-3xl'}`}>{config.icon}</div>
|
||||
|
||||
{/* Name */}
|
||||
<div
|
||||
@@ -169,4 +157,3 @@ export function MotivatorCardStatic({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -101,11 +101,7 @@ export function MotivatorLiveWrapper({
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setShareModalOpen(true)}
|
||||
>
|
||||
<Button variant="outline" size="sm" onClick={() => setShareModalOpen(true)}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
@@ -120,9 +116,7 @@ export function MotivatorLiveWrapper({
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className={!canEdit ? 'pointer-events-none opacity-90' : ''}>
|
||||
{children}
|
||||
</div>
|
||||
<div className={!canEdit ? 'pointer-events-none opacity-90' : ''}>{children}</div>
|
||||
|
||||
{/* Share Modal */}
|
||||
<MotivatorShareModal
|
||||
@@ -136,4 +130,3 @@ export function MotivatorLiveWrapper({
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -105,14 +105,10 @@ export function MotivatorShareModal({
|
||||
|
||||
{/* Current shares */}
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm font-medium text-foreground">
|
||||
Collaborateurs ({shares.length})
|
||||
</p>
|
||||
<p className="text-sm font-medium text-foreground">Collaborateurs ({shares.length})</p>
|
||||
|
||||
{shares.length === 0 ? (
|
||||
<p className="text-sm text-muted">
|
||||
Aucun collaborateur pour le moment
|
||||
</p>
|
||||
<p className="text-sm text-muted">Aucun collaborateur pour le moment</p>
|
||||
) : (
|
||||
<ul className="space-y-2">
|
||||
{shares.map((share) => (
|
||||
@@ -126,9 +122,7 @@ export function MotivatorShareModal({
|
||||
<p className="text-sm font-medium text-foreground">
|
||||
{share.user.name || share.user.email}
|
||||
</p>
|
||||
{share.user.name && (
|
||||
<p className="text-xs text-muted">{share.user.email}</p>
|
||||
)}
|
||||
{share.user.name && <p className="text-xs text-muted">{share.user.email}</p>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -176,4 +170,3 @@ export function MotivatorShareModal({
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,10 +18,14 @@ export function MotivatorSummary({ cards }: MotivatorSummaryProps) {
|
||||
const bottom3 = sortedByImportance.slice(0, 3);
|
||||
|
||||
// Cards with positive influence
|
||||
const positiveInfluence = cards.filter((c) => c.influence > 0).sort((a, b) => b.influence - a.influence);
|
||||
const positiveInfluence = cards
|
||||
.filter((c) => c.influence > 0)
|
||||
.sort((a, b) => b.influence - a.influence);
|
||||
|
||||
// Cards with negative influence
|
||||
const negativeInfluence = cards.filter((c) => c.influence < 0).sort((a, b) => a.influence - b.influence);
|
||||
const negativeInfluence = cards
|
||||
.filter((c) => c.influence < 0)
|
||||
.sort((a, b) => a.influence - b.influence);
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
@@ -100,4 +104,3 @@ function SummarySection({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,4 +4,3 @@ export { MotivatorSummary } from './MotivatorSummary';
|
||||
export { InfluenceZone } from './InfluenceZone';
|
||||
export { MotivatorLiveWrapper } from './MotivatorLiveWrapper';
|
||||
export { MotivatorShareModal } from './MotivatorShareModal';
|
||||
|
||||
|
||||
@@ -103,4 +103,3 @@ export function EditableTitle({ sessionId, initialTitle, isOwner }: EditableTitl
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
export { EditableTitle } from './EditableTitle';
|
||||
|
||||
|
||||
@@ -22,7 +22,10 @@ interface ActionPanelProps {
|
||||
onActionLeave: () => void;
|
||||
}
|
||||
|
||||
const categoryBadgeVariant: Record<SwotCategory, 'strength' | 'weakness' | 'opportunity' | 'threat'> = {
|
||||
const categoryBadgeVariant: Record<
|
||||
SwotCategory,
|
||||
'strength' | 'weakness' | 'opportunity' | 'threat'
|
||||
> = {
|
||||
STRENGTH: 'strength',
|
||||
WEAKNESS: 'weakness',
|
||||
OPPORTUNITY: 'opportunity',
|
||||
@@ -189,7 +192,12 @@ export function ActionPanel({
|
||||
className="rounded p-1 text-muted opacity-0 transition-opacity hover:bg-card-hover hover:text-foreground group-hover:opacity-100"
|
||||
aria-label="Modifier"
|
||||
>
|
||||
<svg className="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<svg
|
||||
className="h-3.5 w-3.5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
@@ -203,7 +211,12 @@ export function ActionPanel({
|
||||
className="rounded p-1 text-muted opacity-0 transition-opacity hover:bg-destructive/10 hover:text-destructive group-hover:opacity-100"
|
||||
aria-label="Supprimer"
|
||||
>
|
||||
<svg className="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<svg
|
||||
className="h-3.5 w-3.5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
@@ -271,7 +284,7 @@ export function ActionPanel({
|
||||
<Modal
|
||||
isOpen={showModal}
|
||||
onClose={closeModal}
|
||||
title={editingAction ? 'Modifier l\'action' : 'Nouvelle action croisée'}
|
||||
title={editingAction ? "Modifier l'action" : 'Nouvelle action croisée'}
|
||||
size="lg"
|
||||
>
|
||||
<form onSubmit={handleSubmit}>
|
||||
@@ -339,7 +352,7 @@ export function ActionPanel({
|
||||
Annuler
|
||||
</Button>
|
||||
<Button type="submit" loading={isPending}>
|
||||
{editingAction ? 'Enregistrer' : 'Créer l\'action'}
|
||||
{editingAction ? 'Enregistrer' : "Créer l'action"}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</form>
|
||||
@@ -347,4 +360,3 @@ export function ActionPanel({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,22 +11,17 @@ interface HelpContent {
|
||||
|
||||
const HELP_CONTENT: Record<SwotCategory, HelpContent> = {
|
||||
STRENGTH: {
|
||||
description:
|
||||
'Les atouts internes et qualités qui distinguent positivement.',
|
||||
description: 'Les atouts internes et qualités qui distinguent positivement.',
|
||||
examples: [
|
||||
'Expertise technique solide',
|
||||
'Excellentes capacités de communication',
|
||||
'Leadership naturel',
|
||||
'Rigueur et organisation',
|
||||
],
|
||||
questions: [
|
||||
'Qu\'est-ce qui le/la distingue ?',
|
||||
'Quels retours positifs reçoit-il/elle ?',
|
||||
],
|
||||
questions: ["Qu'est-ce qui le/la distingue ?", 'Quels retours positifs reçoit-il/elle ?'],
|
||||
},
|
||||
WEAKNESS: {
|
||||
description:
|
||||
'Les axes d\'amélioration et points à travailler.',
|
||||
description: "Les axes d'amélioration et points à travailler.",
|
||||
examples: [
|
||||
'Difficulté à déléguer',
|
||||
'Gestion du stress à améliorer',
|
||||
@@ -39,22 +34,17 @@ const HELP_CONTENT: Record<SwotCategory, HelpContent> = {
|
||||
],
|
||||
},
|
||||
OPPORTUNITY: {
|
||||
description:
|
||||
'Les facteurs externes favorables à saisir.',
|
||||
description: 'Les facteurs externes favorables à saisir.',
|
||||
examples: [
|
||||
'Nouveau projet stratégique',
|
||||
'Formation disponible',
|
||||
'Poste ouvert en interne',
|
||||
'Mentor potentiel identifié',
|
||||
],
|
||||
questions: [
|
||||
'Quelles évolutions pourraient l\'aider ?',
|
||||
'Quelles ressources sont disponibles ?',
|
||||
],
|
||||
questions: ["Quelles évolutions pourraient l'aider ?", 'Quelles ressources sont disponibles ?'],
|
||||
},
|
||||
THREAT: {
|
||||
description:
|
||||
'Les risques externes à anticiper.',
|
||||
description: 'Les risques externes à anticiper.',
|
||||
examples: [
|
||||
'Réorganisation menaçant le poste',
|
||||
'Compétences devenant obsolètes',
|
||||
@@ -82,9 +72,10 @@ export function QuadrantHelp({ category }: QuadrantHelpProps) {
|
||||
className={`
|
||||
flex h-5 w-5 items-center justify-center rounded-full
|
||||
text-xs font-medium transition-all
|
||||
${isOpen
|
||||
? 'bg-foreground/20 text-foreground rotate-45'
|
||||
: 'bg-foreground/5 text-muted hover:bg-foreground/10 hover:text-foreground'
|
||||
${
|
||||
isOpen
|
||||
? 'bg-foreground/20 text-foreground rotate-45'
|
||||
: 'bg-foreground/5 text-muted hover:bg-foreground/10 hover:text-foreground'
|
||||
}
|
||||
`}
|
||||
aria-label="Aide"
|
||||
@@ -113,9 +104,7 @@ export function QuadrantHelpPanel({ category, isOpen }: QuadrantHelpPanelProps)
|
||||
<div className="overflow-hidden">
|
||||
<div className="rounded-lg bg-white/40 dark:bg-black/20 p-3 mb-3">
|
||||
{/* Description */}
|
||||
<p className="text-xs text-foreground/80 leading-relaxed">
|
||||
{content.description}
|
||||
</p>
|
||||
<p className="text-xs text-foreground/80 leading-relaxed">{content.description}</p>
|
||||
|
||||
<div className="mt-3 flex gap-4">
|
||||
{/* Examples */}
|
||||
@@ -125,10 +114,7 @@ export function QuadrantHelpPanel({ category, isOpen }: QuadrantHelpPanelProps)
|
||||
</h4>
|
||||
<ul className="space-y-0.5">
|
||||
{content.examples.map((example, i) => (
|
||||
<li
|
||||
key={i}
|
||||
className="flex items-start gap-1.5 text-xs text-foreground/70"
|
||||
>
|
||||
<li key={i} className="flex items-start gap-1.5 text-xs text-foreground/70">
|
||||
<span className="mt-1.5 h-1 w-1 flex-shrink-0 rounded-full bg-current opacity-50" />
|
||||
{example}
|
||||
</li>
|
||||
@@ -143,10 +129,7 @@ export function QuadrantHelpPanel({ category, isOpen }: QuadrantHelpPanelProps)
|
||||
</h4>
|
||||
<ul className="space-y-1">
|
||||
{content.questions.map((question, i) => (
|
||||
<li
|
||||
key={i}
|
||||
className="text-xs italic text-foreground/60"
|
||||
>
|
||||
<li key={i} className="text-xs italic text-foreground/60">
|
||||
{question}
|
||||
</li>
|
||||
))}
|
||||
@@ -158,4 +141,3 @@ export function QuadrantHelpPanel({ category, isOpen }: QuadrantHelpPanelProps)
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useTransition } from 'react';
|
||||
import {
|
||||
DragDropContext,
|
||||
Droppable,
|
||||
Draggable,
|
||||
DropResult,
|
||||
} from '@hello-pangea/dnd';
|
||||
import { DragDropContext, Droppable, Draggable, DropResult } from '@hello-pangea/dnd';
|
||||
import type { SwotItem, Action, ActionLink, SwotCategory } from '@prisma/client';
|
||||
import { SwotQuadrant } from './SwotQuadrant';
|
||||
import { SwotCard } from './SwotCard';
|
||||
@@ -67,9 +62,7 @@ export function SwotBoard({ sessionId, items, actions }: SwotBoardProps) {
|
||||
if (!linkMode) return;
|
||||
|
||||
setSelectedItems((prev) =>
|
||||
prev.includes(itemId)
|
||||
? prev.filter((id) => id !== itemId)
|
||||
: [...prev, itemId]
|
||||
prev.includes(itemId) ? prev.filter((id) => id !== itemId) : [...prev, itemId]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -167,4 +160,3 @@ export function SwotBoard({ sessionId, items, actions }: SwotBoardProps) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -120,7 +120,12 @@ export const SwotCard = forwardRef<HTMLDivElement, SwotCardProps>(
|
||||
className="rounded p-1 text-muted hover:bg-card-hover hover:text-foreground"
|
||||
aria-label="Modifier"
|
||||
>
|
||||
<svg className="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<svg
|
||||
className="h-3.5 w-3.5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
@@ -137,7 +142,12 @@ export const SwotCard = forwardRef<HTMLDivElement, SwotCardProps>(
|
||||
className="rounded p-1 text-muted hover:bg-primary/10 hover:text-primary"
|
||||
aria-label="Dupliquer"
|
||||
>
|
||||
<svg className="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<svg
|
||||
className="h-3.5 w-3.5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
@@ -154,7 +164,12 @@ export const SwotCard = forwardRef<HTMLDivElement, SwotCardProps>(
|
||||
className="rounded p-1 text-muted hover:bg-destructive/10 hover:text-destructive"
|
||||
aria-label="Supprimer"
|
||||
>
|
||||
<svg className="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<svg
|
||||
className="h-3.5 w-3.5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
@@ -168,7 +183,9 @@ export const SwotCard = forwardRef<HTMLDivElement, SwotCardProps>(
|
||||
|
||||
{/* Selection indicator in link mode */}
|
||||
{linkMode && isSelected && (
|
||||
<div className={`absolute -right-1 -top-1 rounded-full bg-card p-0.5 shadow ${styles.text}`}>
|
||||
<div
|
||||
className={`absolute -right-1 -top-1 rounded-full bg-card p-0.5 shadow ${styles.text}`}
|
||||
>
|
||||
<svg className="h-4 w-4" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z" />
|
||||
</svg>
|
||||
@@ -182,4 +199,3 @@ export const SwotCard = forwardRef<HTMLDivElement, SwotCardProps>(
|
||||
);
|
||||
|
||||
SwotCard.displayName = 'SwotCard';
|
||||
|
||||
|
||||
@@ -92,9 +92,10 @@ export const SwotQuadrant = forwardRef<HTMLDivElement, SwotQuadrantProps>(
|
||||
className={`
|
||||
flex h-5 w-5 items-center justify-center rounded-full
|
||||
text-xs font-medium transition-all
|
||||
${showHelp
|
||||
? 'bg-foreground/20 text-foreground'
|
||||
: 'bg-foreground/5 text-muted hover:bg-foreground/10 hover:text-foreground'
|
||||
${
|
||||
showHelp
|
||||
? 'bg-foreground/20 text-foreground'
|
||||
: 'bg-foreground/5 text-muted hover:bg-foreground/10 hover:text-foreground'
|
||||
}
|
||||
`}
|
||||
aria-label="Aide"
|
||||
@@ -112,7 +113,12 @@ export const SwotQuadrant = forwardRef<HTMLDivElement, SwotQuadrantProps>(
|
||||
aria-label={`Ajouter un item ${title}`}
|
||||
>
|
||||
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M12 4v16m8-8H4"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
@@ -166,4 +172,3 @@ export const SwotQuadrant = forwardRef<HTMLDivElement, SwotQuadrantProps>(
|
||||
);
|
||||
|
||||
SwotQuadrant.displayName = 'SwotQuadrant';
|
||||
|
||||
|
||||
@@ -3,4 +3,3 @@ export { SwotQuadrant } from './SwotQuadrant';
|
||||
export { SwotCard } from './SwotCard';
|
||||
export { ActionPanel } from './ActionPanel';
|
||||
export { QuadrantHelp } from './QuadrantHelp';
|
||||
|
||||
|
||||
@@ -28,5 +28,3 @@ export function Avatar({
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -49,4 +49,3 @@ export const Badge = forwardRef<HTMLSpanElement, BadgeProps>(
|
||||
);
|
||||
|
||||
Badge.displayName = 'Badge';
|
||||
|
||||
|
||||
@@ -10,16 +10,11 @@ interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
}
|
||||
|
||||
const variantStyles: Record<ButtonVariant, string> = {
|
||||
primary:
|
||||
'bg-primary text-primary-foreground hover:bg-primary-hover border-transparent',
|
||||
secondary:
|
||||
'bg-card text-foreground hover:bg-card-hover border-border',
|
||||
outline:
|
||||
'bg-transparent text-foreground hover:bg-card-hover border-border',
|
||||
ghost:
|
||||
'bg-transparent text-foreground hover:bg-card-hover border-transparent',
|
||||
destructive:
|
||||
'bg-destructive text-white hover:bg-destructive/90 border-transparent',
|
||||
primary: 'bg-primary text-primary-foreground hover:bg-primary-hover border-transparent',
|
||||
secondary: 'bg-card text-foreground hover:bg-card-hover border-border',
|
||||
outline: 'bg-transparent text-foreground hover:bg-card-hover border-border',
|
||||
ghost: 'bg-transparent text-foreground hover:bg-card-hover border-transparent',
|
||||
destructive: 'bg-destructive text-white hover:bg-destructive/90 border-transparent',
|
||||
};
|
||||
|
||||
const sizeStyles: Record<ButtonSize, string> = {
|
||||
@@ -29,7 +24,10 @@ const sizeStyles: Record<ButtonSize, string> = {
|
||||
};
|
||||
|
||||
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className = '', variant = 'primary', size = 'md', loading, disabled, children, ...props }, ref) => {
|
||||
(
|
||||
{ className = '', variant = 'primary', size = 'md', loading, disabled, children, ...props },
|
||||
ref
|
||||
) => {
|
||||
return (
|
||||
<button
|
||||
ref={ref}
|
||||
@@ -74,4 +72,3 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
);
|
||||
|
||||
Button.displayName = 'Button';
|
||||
|
||||
|
||||
@@ -40,11 +40,12 @@ export const CardTitle = forwardRef<HTMLHeadingElement, HTMLAttributes<HTMLHeadi
|
||||
|
||||
CardTitle.displayName = 'CardTitle';
|
||||
|
||||
export const CardDescription = forwardRef<HTMLParagraphElement, HTMLAttributes<HTMLParagraphElement>>(
|
||||
({ className = '', ...props }, ref) => (
|
||||
<p ref={ref} className={`mt-1 text-sm text-muted ${className}`} {...props} />
|
||||
)
|
||||
);
|
||||
export const CardDescription = forwardRef<
|
||||
HTMLParagraphElement,
|
||||
HTMLAttributes<HTMLParagraphElement>
|
||||
>(({ className = '', ...props }, ref) => (
|
||||
<p ref={ref} className={`mt-1 text-sm text-muted ${className}`} {...props} />
|
||||
));
|
||||
|
||||
CardDescription.displayName = 'CardDescription';
|
||||
|
||||
@@ -63,4 +64,3 @@ export const CardFooter = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivEleme
|
||||
);
|
||||
|
||||
CardFooter.displayName = 'CardFooter';
|
||||
|
||||
|
||||
@@ -79,5 +79,3 @@ export function CollaboratorDisplay({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -12,10 +12,7 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
return (
|
||||
<div className="w-full">
|
||||
{label && (
|
||||
<label
|
||||
htmlFor={inputId}
|
||||
className="mb-2 block text-sm font-medium text-foreground"
|
||||
>
|
||||
<label htmlFor={inputId} className="mb-2 block text-sm font-medium text-foreground">
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
@@ -32,13 +29,10 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
`}
|
||||
{...props}
|
||||
/>
|
||||
{error && (
|
||||
<p className="mt-1.5 text-sm text-destructive">{error}</p>
|
||||
)}
|
||||
{error && <p className="mt-1.5 text-sm text-destructive">{error}</p>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
Input.displayName = 'Input';
|
||||
|
||||
|
||||
@@ -84,12 +84,7 @@ export function Modal({ isOpen, onClose, title, children, size = 'md' }: ModalPr
|
||||
className="rounded-lg p-1 text-muted hover:bg-card-hover hover:text-foreground transition-colors"
|
||||
aria-label="Fermer"
|
||||
>
|
||||
<svg
|
||||
className="h-5 w-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
|
||||
@@ -12,10 +12,7 @@ export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
|
||||
return (
|
||||
<div className="w-full">
|
||||
{label && (
|
||||
<label
|
||||
htmlFor={textareaId}
|
||||
className="mb-2 block text-sm font-medium text-foreground"
|
||||
>
|
||||
<label htmlFor={textareaId} className="mb-2 block text-sm font-medium text-foreground">
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
@@ -33,13 +30,10 @@ export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
|
||||
`}
|
||||
{...props}
|
||||
/>
|
||||
{error && (
|
||||
<p className="mt-1.5 text-sm text-destructive">{error}</p>
|
||||
)}
|
||||
{error && <p className="mt-1.5 text-sm text-destructive">{error}</p>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
Textarea.displayName = 'Textarea';
|
||||
|
||||
|
||||
@@ -6,4 +6,3 @@ export { CollaboratorDisplay } from './CollaboratorDisplay';
|
||||
export { Input } from './Input';
|
||||
export { Modal, ModalFooter } from './Modal';
|
||||
export { Textarea } from './Textarea';
|
||||
|
||||
|
||||
@@ -128,4 +128,3 @@ export function useMotivatorLive({
|
||||
|
||||
return { isConnected, lastEvent, error };
|
||||
}
|
||||
|
||||
|
||||
@@ -129,4 +129,3 @@ export function useSessionLive({
|
||||
|
||||
return { isConnected, lastEvent, error };
|
||||
}
|
||||
|
||||
|
||||
@@ -29,4 +29,3 @@ export const authConfig: NextAuthConfig = {
|
||||
},
|
||||
providers: [], // Configured in auth.ts
|
||||
};
|
||||
|
||||
|
||||
@@ -59,4 +59,3 @@ export const { handlers, signIn, signOut, auth } = NextAuth({
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -14,11 +14,7 @@ export function getGravatarUrl(
|
||||
size: number = 40,
|
||||
fallback: GravatarDefault = 'identicon'
|
||||
): string {
|
||||
const hash = createHash('md5')
|
||||
.update(email.toLowerCase().trim())
|
||||
.digest('hex');
|
||||
const hash = createHash('md5').update(email.toLowerCase().trim()).digest('hex');
|
||||
|
||||
return `https://www.gravatar.com/avatar/${hash}?d=${fallback}&s=${size}`;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -230,7 +230,7 @@ export const MOTIVATORS_CONFIG: MotivatorConfig[] = [
|
||||
type: 'POWER',
|
||||
name: 'Pouvoir',
|
||||
icon: '⚡',
|
||||
description: 'Avoir de l\'influence et du contrôle sur les décisions',
|
||||
description: "Avoir de l'influence et du contrôle sur les décisions",
|
||||
color: '#ef4444', // red
|
||||
},
|
||||
{
|
||||
@@ -291,12 +291,10 @@ export const MOTIVATORS_CONFIG: MotivatorConfig[] = [
|
||||
},
|
||||
];
|
||||
|
||||
export const MOTIVATOR_BY_TYPE: Record<MotivatorType, MotivatorConfig> =
|
||||
MOTIVATORS_CONFIG.reduce(
|
||||
(acc, config) => {
|
||||
acc[config.type] = config;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<MotivatorType, MotivatorConfig>
|
||||
);
|
||||
|
||||
export const MOTIVATOR_BY_TYPE: Record<MotivatorType, MotivatorConfig> = MOTIVATORS_CONFIG.reduce(
|
||||
(acc, config) => {
|
||||
acc[config.type] = config;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<MotivatorType, MotivatorConfig>
|
||||
);
|
||||
|
||||
@@ -7,4 +7,3 @@ export const config = {
|
||||
// Match all paths except static files and api routes that don't need auth
|
||||
matcher: ['/((?!api/auth|_next/static|_next/image|favicon.ico).*)'],
|
||||
};
|
||||
|
||||
|
||||
@@ -100,9 +100,7 @@ export async function resolveCollaborator(collaborator: string): Promise<Resolve
|
||||
});
|
||||
|
||||
const normalizedSearch = trimmed.toLowerCase();
|
||||
const userByName = users.find(
|
||||
(u) => u.name?.toLowerCase() === normalizedSearch
|
||||
) || null;
|
||||
const userByName = users.find((u) => u.name?.toLowerCase() === normalizedSearch) || null;
|
||||
|
||||
return { raw: collaborator, matchedUser: userByName };
|
||||
}
|
||||
@@ -248,4 +246,3 @@ export async function getAllUsersWithStats(): Promise<UserWithStats[]> {
|
||||
|
||||
return usersWithMotivators;
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,11 @@ export async function getMotivatorSessionsByUserId(userId: string) {
|
||||
]);
|
||||
|
||||
// Mark owned sessions and merge with shared
|
||||
const ownedWithRole = owned.map((s) => ({ ...s, isOwner: true as const, role: 'OWNER' as const }));
|
||||
const ownedWithRole = owned.map((s) => ({
|
||||
...s,
|
||||
isOwner: true as const,
|
||||
role: 'OWNER' as const,
|
||||
}));
|
||||
const sharedWithRole = shared.map((s) => ({
|
||||
...s.session,
|
||||
isOwner: false as const,
|
||||
@@ -200,10 +204,7 @@ export async function updateMotivatorCard(
|
||||
});
|
||||
}
|
||||
|
||||
export async function reorderMotivatorCards(
|
||||
sessionId: string,
|
||||
cardIds: string[]
|
||||
) {
|
||||
export async function reorderMotivatorCards(sessionId: string, cardIds: string[]) {
|
||||
const updates = cardIds.map((id, index) =>
|
||||
prisma.motivatorCard.update({
|
||||
where: { id },
|
||||
@@ -350,4 +351,3 @@ export async function getLatestMotivatorEventTimestamp(sessionId: string) {
|
||||
});
|
||||
return event?.createdAt;
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,11 @@ export async function getSessionsByUserId(userId: string) {
|
||||
]);
|
||||
|
||||
// Mark owned sessions and merge with shared
|
||||
const ownedWithRole = owned.map((s) => ({ ...s, isOwner: true as const, role: 'OWNER' as const }));
|
||||
const ownedWithRole = owned.map((s) => ({
|
||||
...s,
|
||||
isOwner: true as const,
|
||||
role: 'OWNER' as const,
|
||||
}));
|
||||
const sharedWithRole = shared.map((s) => ({
|
||||
...s.session,
|
||||
isOwner: false as const,
|
||||
@@ -248,11 +252,7 @@ export async function reorderSwotItems(
|
||||
return prisma.$transaction(updates);
|
||||
}
|
||||
|
||||
export async function moveSwotItem(
|
||||
itemId: string,
|
||||
newCategory: SwotCategory,
|
||||
newOrder: number
|
||||
) {
|
||||
export async function moveSwotItem(itemId: string, newCategory: SwotCategory, newOrder: number) {
|
||||
return prisma.swotItem.update({
|
||||
where: { id: itemId },
|
||||
data: {
|
||||
@@ -464,4 +464,3 @@ export async function getLatestEventTimestamp(sessionId: string) {
|
||||
});
|
||||
return event?.createdAt;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user