From 2dd9c6eff17d94f820d64ad9928fe4a49f54cdd4 Mon Sep 17 00:00:00 2001 From: alma Date: Tue, 6 May 2025 21:09:06 +0200 Subject: [PATCH] missions mission pages --- app/api/centrale/all/route.ts | 109 ------- app/api/centrale/route.ts | 257 --------------- app/centrale/[missionId]/edit/page.tsx | 59 ---- app/centrale/[missionId]/page.tsx | 422 ------------------------- app/centrale/layout.tsx | 47 --- app/centrale/new/page.tsx | 32 -- app/centrale/page.tsx | 311 ------------------ 7 files changed, 1237 deletions(-) delete mode 100644 app/api/centrale/all/route.ts delete mode 100644 app/api/centrale/route.ts delete mode 100644 app/centrale/[missionId]/edit/page.tsx delete mode 100644 app/centrale/[missionId]/page.tsx delete mode 100644 app/centrale/layout.tsx delete mode 100644 app/centrale/new/page.tsx delete mode 100644 app/centrale/page.tsx diff --git a/app/api/centrale/all/route.ts b/app/api/centrale/all/route.ts deleted file mode 100644 index 9f65c6a7..00000000 --- a/app/api/centrale/all/route.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { NextResponse } from 'next/server'; -import { getServerSession } from 'next-auth'; -import { authOptions } from "@/app/api/auth/options"; -import { prisma } from '@/lib/prisma'; -import { getPublicUrl } from '@/lib/s3'; -import { S3_CONFIG } from '@/lib/s3'; - -// Helper function to check authentication -async function checkAuth(request: Request) { - const session = await getServerSession(authOptions); - if (!session?.user?.id) { - console.error('Unauthorized access attempt:', { - url: request.url, - method: request.method, - headers: Object.fromEntries(request.headers) - }); - return { authorized: false, userId: null }; - } - return { authorized: true, userId: session.user.id }; -} - -// GET endpoint to list all missions (not filtered by user) -export async function GET(request: Request) { - try { - const { authorized, userId } = await checkAuth(request); - if (!authorized || !userId) { - return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); - } - - const { searchParams } = new URL(request.url); - const limit = Number(searchParams.get('limit') || '100'); // Default to 100 for "all" - const offset = Number(searchParams.get('offset') || '0'); - const search = searchParams.get('search'); - - // Build query conditions - const where: any = {}; - - // Add search filter if provided - if (search) { - where.OR = [ - { name: { contains: search, mode: 'insensitive' } }, - { intention: { contains: search, mode: 'insensitive' } } - ]; - } - - // Get all missions with basic info (no user filtering) - const missions = await prisma.mission.findMany({ - where, - skip: offset, - take: limit, - orderBy: { createdAt: 'desc' }, - select: { - id: true, - name: true, - logo: true, - oddScope: true, - niveau: true, - missionType: true, - projection: true, - participation: true, - services: true, - intention: true, - createdAt: true, - creator: { - select: { - id: true, - email: true - } - }, - missionUsers: { - select: { - id: true, - role: true, - user: { - select: { - id: true, - email: true - } - } - } - } - } - }); - - // Get total count - const totalCount = await prisma.mission.count({ where }); - - // Transform logo paths to public URLs - const missionsWithPublicUrls = missions.map(mission => ({ - ...mission, - logo: mission.logo ? `/api/missions/image/${mission.logo}` : null - })); - - return NextResponse.json({ - missions: missionsWithPublicUrls, - pagination: { - total: totalCount, - offset, - limit - } - }); - } catch (error) { - console.error('Error listing all missions:', error); - return NextResponse.json({ - error: 'Internal server error', - details: error instanceof Error ? error.message : String(error) - }, { status: 500 }); - } -} \ No newline at end of file diff --git a/app/api/centrale/route.ts b/app/api/centrale/route.ts deleted file mode 100644 index 66c100e2..00000000 --- a/app/api/centrale/route.ts +++ /dev/null @@ -1,257 +0,0 @@ -import { NextResponse } from 'next/server'; -import { getServerSession } from 'next-auth'; -import { authOptions } from "@/app/api/auth/options"; -import { prisma } from '@/lib/prisma'; -import { getPublicUrl } from '@/lib/s3'; -import { S3_CONFIG } from '@/lib/s3'; -import { IntegrationService } from '@/lib/services/integration-service'; - -// Helper function to check authentication -async function checkAuth(request: Request) { - const session = await getServerSession(authOptions); - if (!session?.user?.id) { - console.error('Unauthorized access attempt:', { - url: request.url, - method: request.method, - headers: Object.fromEntries(request.headers) - }); - return { authorized: false, userId: null }; - } - return { authorized: true, userId: session.user.id }; -} - -// GET endpoint to list missions with filters -export async function GET(request: Request) { - try { - const { authorized, userId } = await checkAuth(request); - if (!authorized || !userId) { - return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); - } - - const { searchParams } = new URL(request.url); - const limit = Number(searchParams.get('limit') || '10'); - const offset = Number(searchParams.get('offset') || '0'); - const search = searchParams.get('search'); - - // Build query conditions - const where: any = {}; - - // Add search filter if provided - if (search) { - where.OR = [ - { name: { contains: search, mode: 'insensitive' } }, - { intention: { contains: search, mode: 'insensitive' } } - ]; - } - - // Get missions with basic info - const missions = await (prisma as any).mission.findMany({ - where, - skip: offset, - take: limit, - orderBy: { createdAt: 'desc' }, - select: { - id: true, - name: true, - logo: true, - oddScope: true, - niveau: true, - missionType: true, - projection: true, - participation: true, - services: true, - intention: true, - createdAt: true, - creator: { - select: { - id: true, - email: true - } - }, - missionUsers: { - select: { - id: true, - role: true, - user: { - select: { - id: true, - email: true - } - } - } - } - } - }); - - // Get total count - const totalCount = await (prisma as any).mission.count({ where }); - - // Transform logo paths to public URLs - const missionsWithPublicUrls = missions.map((mission: any) => ({ - ...mission, - logo: mission.logo ? `/api/missions/image/${mission.logo}` : null - })); - - return NextResponse.json({ - missions: missionsWithPublicUrls, - pagination: { - total: totalCount, - offset, - limit - } - }); - } catch (error) { - console.error('Error listing missions:', error); - return NextResponse.json({ - error: 'Internal server error', - details: error instanceof Error ? error.message : String(error) - }, { status: 500 }); - } -} - -// POST endpoint to create a new mission -export async function POST(request: Request) { - try { - const { authorized, userId } = await checkAuth(request); - if (!authorized || !userId) { - return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); - } - - // Parse the request body - const body = await request.json(); - const { - name, - logo, - oddScope, - niveau, - intention, - missionType, - donneurDOrdre, - projection, - services, - participation, - profils, - guardians, - volunteers - } = body; - - // Validate required fields - if (!name || !niveau || !intention || !missionType || !donneurDOrdre || !projection) { - return NextResponse.json({ - error: 'Missing required fields', - required: { - name: true, - niveau: true, - intention: true, - missionType: true, - donneurDOrdre: true, - projection: true - }, - received: { - name: !!name, - niveau: !!niveau, - intention: !!intention, - missionType: !!missionType, - donneurDOrdre: !!donneurDOrdre, - projection: !!projection - } - }, { status: 400 }); - } - - // Wrap the mission creation and integration in a transaction - const result = await prisma.$transaction(async (tx: any) => { - // Create the mission - const mission = await tx.mission.create({ - data: { - name, - logo, - oddScope: oddScope || [], - niveau, - intention, - missionType, - donneurDOrdre, - projection, - services: services || [], - participation, - profils: profils || [], - creatorId: userId - } - }); - - // Add guardians if provided - if (guardians) { - const guardianRoles = ['gardien-temps', 'gardien-parole', 'gardien-memoire']; - const guardianEntries = Object.entries(guardians) - .filter(([role, userId]) => guardianRoles.includes(role) && userId) - .map(([role, userId]) => ({ - role, - userId: userId as string, - missionId: mission.id - })); - - if (guardianEntries.length > 0) { - await tx.missionUser.createMany({ - data: guardianEntries - }); - } - } - - // Add volunteers if provided - if (volunteers && volunteers.length > 0) { - const volunteerEntries = volunteers.map((userId: string) => ({ - role: 'volontaire', - userId, - missionId: mission.id - })); - - await tx.missionUser.createMany({ - data: volunteerEntries - }); - } - - return mission; - }); - - try { - // Initialize external integrations after transaction completes - const integrationService = new IntegrationService(); - const integrationResult = await integrationService.setupIntegrationsForMission(result.id); - - if (!integrationResult.success) { - // If integration failed, the mission was already deleted in the integration service - return NextResponse.json({ - error: 'Failed to set up external services', - details: integrationResult.error - }, { status: 500 }); - } - - return NextResponse.json({ - success: true, - mission: { - id: result.id, - name: result.name, - createdAt: result.createdAt - }, - integrations: { - status: 'success', - data: integrationResult.data - } - }); - } catch (integrationError) { - // If there's any unhandled error, delete the mission and report failure - console.error('Integration error:', integrationError); - await (prisma as any).mission.delete({ where: { id: result.id } }); - - return NextResponse.json({ - error: 'Failed to set up external services', - details: integrationError instanceof Error ? integrationError.message : String(integrationError) - }, { status: 500 }); - } - } catch (error) { - console.error('Error creating mission:', error); - return NextResponse.json({ - error: 'Internal server error', - details: error instanceof Error ? error.message : String(error) - }, { status: 500 }); - } -} \ No newline at end of file diff --git a/app/centrale/[missionId]/edit/page.tsx b/app/centrale/[missionId]/edit/page.tsx deleted file mode 100644 index 8dd05227..00000000 --- a/app/centrale/[missionId]/edit/page.tsx +++ /dev/null @@ -1,59 +0,0 @@ -"use client"; - -import { useState, useEffect } from "react"; -import { Button } from "@/components/ui/button"; -import { useToast } from "@/components/ui/use-toast"; -import { useParams, useRouter } from "next/navigation"; - -export default function EditMissionPage() { - const [loading, setLoading] = useState(true); - const { toast } = useToast(); - const params = useParams(); - const router = useRouter(); - const missionId = params.missionId as string; - - useEffect(() => { - toast({ - title: "Fonctionnalité en développement", - description: "L'édition de mission sera bientôt disponible.", - variant: "default", - }); - - setLoading(false); - }, [toast]); - - return ( -
-
-

Modifier la mission

- - {loading ? ( -
-
-
- ) : ( -
-

- La fonctionnalité d'édition de mission est en cours de développement et sera disponible prochainement. -

- -
- - - -
-
- )} -
-
- ); -} \ No newline at end of file diff --git a/app/centrale/[missionId]/page.tsx b/app/centrale/[missionId]/page.tsx deleted file mode 100644 index 79f907e9..00000000 --- a/app/centrale/[missionId]/page.tsx +++ /dev/null @@ -1,422 +0,0 @@ -"use client"; - -import { useState, useEffect } from "react"; -import { Button } from "@/components/ui/button"; -import { FileIcon, Calendar, Eye, MapPin, Users, Clock, ThumbsUp, Languages, BarChart, Edit, Trash2 } from "lucide-react"; -import { useToast } from "@/components/ui/use-toast"; -import { useParams, useRouter } from "next/navigation"; - -// Define types for mission details -interface User { - id: string; - email: string; -} - -interface Attachment { - id: string; - filename: string; - filePath: string; - fileType: string; - fileSize: number; - publicUrl: string; - createdAt: string; -} - -interface Mission { - id: string; - name: string; - logo?: string | null; - logoUrl?: string | null; - oddScope: string[]; - niveau: string; - missionType: string; - projection: string; - intention?: string; - donneurDOrdre?: string; - participation?: string; - services?: string[]; - profils?: string[]; - attachments?: Attachment[]; - createdAt: string; - creator: User; - missionUsers: any[]; -} - -export default function MissionDetailPage() { - const [mission, setMission] = useState(null); - const [loading, setLoading] = useState(true); - const [deleting, setDeleting] = useState(false); - const { toast } = useToast(); - const params = useParams(); - const router = useRouter(); - const missionId = params.missionId as string; - - // Fetch mission details - useEffect(() => { - const fetchMissionDetails = async () => { - try { - setLoading(true); - const response = await fetch(`/api/missions/${missionId}`); - if (!response.ok) { - throw new Error('Failed to fetch mission details'); - } - const data = await response.json(); - console.log("Mission details:", data); - setMission(data); - } catch (error) { - console.error('Error fetching mission details:', error); - toast({ - title: "Erreur", - description: "Impossible de charger les détails de la mission", - variant: "destructive", - }); - } finally { - setLoading(false); - } - }; - - if (missionId) { - fetchMissionDetails(); - } - }, [missionId, toast]); - - // Helper function to format date - const formatDate = (dateString: string) => { - const date = new Date(dateString); - return date.toLocaleDateString('fr-FR', { - day: '2-digit', - month: 'long', - year: 'numeric' - }); - }; - - // Helper functions to get labels - const getMissionTypeLabel = (type: string) => { - switch(type) { - case 'remote': return 'À distance'; - case 'onsite': return 'Sur site'; - case 'hybrid': return 'Hybride'; - default: return type; - } - }; - - const getDurationLabel = (projection: string) => { - switch(projection) { - case 'short': return '< 1 mois'; - case 'medium': return '1-3 mois'; - case 'long': return '> 3 mois'; - default: return projection; - } - }; - - const getNiveauLabel = (niveau: string) => { - switch(niveau) { - case 'a': return 'Apprentissage'; - case 'b': return 'Basique'; - case 'c': return 'Complexe'; - case 's': return 'Spécial'; - default: return niveau; - } - }; - - // Function to get odd info - const getODDInfo = (oddScope: string[]) => { - const oddCode = oddScope && oddScope.length > 0 - ? oddScope[0] - : null; - - // Extract number from odd code (e.g., "odd-3" -> "3") - const oddNumber = oddCode ? oddCode.replace('odd-', '') : null; - - return { - number: oddNumber, - label: oddNumber ? `ODD ${oddNumber}` : "Non catégorisé", - iconPath: oddNumber ? `/F SDG Icons 2019 WEB/F-WEB-Goal-${oddNumber.padStart(2, '0')}.png` : "" - }; - }; - - // Handle edit mission - const handleEditMission = () => { - router.push(`/missions/${missionId}/edit`); - }; - - // Handle delete mission - const handleDeleteMission = async () => { - if (!confirm("Êtes-vous sûr de vouloir supprimer cette mission ? Cette action est irréversible.")) { - return; - } - - try { - setDeleting(true); - const response = await fetch(`/api/missions/${missionId}`, { - method: 'DELETE', - }); - - if (!response.ok) { - throw new Error('Failed to delete mission'); - } - - toast({ - title: "Mission supprimée", - description: "La mission a été supprimée avec succès", - }); - - // Redirect back to missions list - router.push('/missions'); - } catch (error) { - console.error('Error deleting mission:', error); - toast({ - title: "Erreur", - description: "Impossible de supprimer la mission", - variant: "destructive", - }); - } finally { - setDeleting(false); - } - }; - - // Loading state - if (loading) { - return ( -
-
-
- ); - } - - // Error state if mission not found - if (!mission) { - return ( -
-
-

Mission non trouvée

-

Cette mission n'existe pas ou a été supprimée.

- -
-
- ); - } - - const oddInfo = getODDInfo(mission.oddScope); - - return ( -
- {/* Header */} -
-
-
-

{mission.name}

-
-
- - {formatDate(mission.createdAt)} -
-
- - {Math.floor(Math.random() * 100) + 1} Views -
-
-
- - {/* Display logo instead of Participate button */} -
- {mission.logoUrl ? ( - {mission.name} { - console.log("Logo failed to load:", mission.logoUrl); - // Show placeholder on error - (e.currentTarget as HTMLImageElement).style.display = 'none'; - const parent = e.currentTarget.parentElement; - if (parent) { - parent.classList.add('bg-gray-100'); - parent.classList.add('flex'); - parent.classList.add('items-center'); - parent.classList.add('justify-center'); - parent.innerHTML = `${mission.name.slice(0, 2).toUpperCase()}`; - } - }} - /> - ) : ( -
- {mission.name.slice(0, 2).toUpperCase()} -
- )} -
-
-
- - {/* Info Grid */} -
-
-
- -
-
-

Type de mission

-

{getMissionTypeLabel(mission.missionType)}

-
-
- -
-
- -
-
-

Donneur d'ordre

-

{mission.donneurDOrdre || "Non spécifié"}

-
-
- -
-
- -
-
-

Durée

-

{getDurationLabel(mission.projection)}

-
-
- -
-
- -
-
-

Niveau

-

{getNiveauLabel(mission.niveau)}

-
-
- -
-
- -
-
-

Participation

-

{mission.participation || "Non spécifié"}

-
-
- - {oddInfo.number && ( -
-
- {oddInfo.label} -
-
-

Objectif

-

Développement durable

-
-
- )} -
- - {/* Project Description */} -
-

Description de la mission

-
- {mission.intention || "Aucune description disponible pour cette mission."} -
-
- - {/* Attachments Section */} - {mission.attachments && mission.attachments.length > 0 && ( -
-

Documents

-
- {mission.attachments.map((attachment) => ( - -
- -
-
-

{attachment.filename}

-

- {attachment.fileType.split('/')[1]?.toUpperCase() || 'Fichier'} -

-
-
- ))} -
-
- )} - - {/* Skills Required Section */} - {mission.profils && mission.profils.length > 0 && ( -
-

Profils recherchés

-
- {mission.profils.map((profil, index) => ( - - {profil} - - ))} -
-
- )} - - {/* Services Section */} - {mission.services && mission.services.length > 0 && ( -
-

Services

-
- {mission.services.map((service, index) => ( - - {service} - - ))} -
-
- )} - - {/* Action Buttons */} -
- - - -
-
- ); -} \ No newline at end of file diff --git a/app/centrale/layout.tsx b/app/centrale/layout.tsx deleted file mode 100644 index aac9d26b..00000000 --- a/app/centrale/layout.tsx +++ /dev/null @@ -1,47 +0,0 @@ -"use client"; - -import React from "react"; -import Link from "next/link"; -import { usePathname } from "next/navigation"; - -export default function MissionsLayout({ - children, -}: { - children: React.ReactNode; -}) { - const pathname = usePathname(); - - return ( -
-
- {/* Sidebar with light pink background */} -
- {/* Title section */} -
-

CAP

-

Centre d'Administration et de Pilotage

-
- - {/* Navigation links */} - -
- - {/* Main content - white background */} -
- {children} -
-
-
- ); -} \ No newline at end of file diff --git a/app/centrale/new/page.tsx b/app/centrale/new/page.tsx deleted file mode 100644 index 28340e08..00000000 --- a/app/centrale/new/page.tsx +++ /dev/null @@ -1,32 +0,0 @@ -"use client"; - -import { useState } from "react"; -import { MissionsAdminPanel } from "@/components/missions/missions-admin-panel"; -import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbSeparator } from "@/components/ui/breadcrumb"; -import Link from "next/link"; - -export default function NewMissionPage() { - return ( -
-
- - - - - Missions - - - - - Poster une Mission - - - -
- -
- -
-
- ); -} \ No newline at end of file diff --git a/app/centrale/page.tsx b/app/centrale/page.tsx deleted file mode 100644 index c4a2e185..00000000 --- a/app/centrale/page.tsx +++ /dev/null @@ -1,311 +0,0 @@ -"use client"; - -import { useState, useEffect } from "react"; -import { Search } from "lucide-react"; -import { Input } from "@/components/ui/input"; -import Link from "next/link"; -import { Button } from "@/components/ui/button"; -import { useToast } from "@/components/ui/use-toast"; -import { getPublicUrl } from "@/lib/s3"; - -// Define Mission interface -interface User { - id: string; - email: string; -} - -interface MissionUser { - id: string; - role: string; - user: User; -} - -interface Mission { - id: string; - name: string; - logo?: string; - oddScope: string[]; - niveau: string; - missionType: string; - projection: string; - participation?: string; - services?: string[]; - createdAt: string; - creator: User; - missionUsers: MissionUser[]; - intention?: string; -} - -export default function MissionsPage() { - const [searchTerm, setSearchTerm] = useState(""); - const [missions, setMissions] = useState([]); - const [loading, setLoading] = useState(true); - const { toast } = useToast(); - - // Fetch missions from API - useEffect(() => { - const fetchMissions = async () => { - try { - setLoading(true); - const response = await fetch('/api/missions'); - if (!response.ok) { - throw new Error('Failed to fetch missions'); - } - const data = await response.json(); - // Debug log to check mission data structure including intention - console.log("Mission data with intention:", data.missions); - setMissions(data.missions || []); - } catch (error) { - console.error('Error fetching missions:', error); - toast({ - title: "Erreur", - description: "Impossible de charger les missions", - variant: "destructive", - }); - } finally { - setLoading(false); - } - }; - - fetchMissions(); - }, []); - - // Filter missions based on search term - const filteredMissions = missions.filter(mission => - mission.name.toLowerCase().includes(searchTerm.toLowerCase()) || - mission.niveau.toLowerCase().includes(searchTerm.toLowerCase()) || - mission.missionType.toLowerCase().includes(searchTerm.toLowerCase()) || - mission.oddScope.some(scope => scope.toLowerCase().includes(searchTerm.toLowerCase())) - ); - - // Function to format date - const formatDate = (dateString: string) => { - const date = new Date(dateString); - return date.toLocaleDateString('fr-FR', { - day: '2-digit', - month: '2-digit', - year: 'numeric' - }); - }; - - // Function to get mission category and icon - const getODDInfo = (mission: Mission) => { - const oddCode = mission.oddScope && mission.oddScope.length > 0 - ? mission.oddScope[0] - : null; - - // Extract number from odd code (e.g., "odd-3" -> "3") - const oddNumber = oddCode ? oddCode.replace('odd-', '') : null; - - return { - number: oddNumber, - label: oddNumber ? `ODD ${oddNumber}` : "Non catégorisé", - iconPath: oddNumber ? `/F SDG Icons 2019 WEB/F-WEB-Goal-${oddNumber.padStart(2, '0')}.png` : "" - }; - }; - - // Function to get appropriate badge color based on niveau - const getNiveauBadgeColor = (niveau: string) => { - switch(niveau) { - case 'a': return 'bg-green-100 text-green-800'; - case 'b': return 'bg-blue-100 text-blue-800'; - case 'c': return 'bg-purple-100 text-purple-800'; - case 's': return 'bg-amber-100 text-amber-800'; - default: return 'bg-gray-100 text-gray-800'; - } - }; - - // Function to get full niveau label - const getNiveauLabel = (niveau: string) => { - switch(niveau) { - case 'a': return 'A'; - case 'b': return 'B'; - case 'c': return 'C'; - case 's': return 'S'; - default: return niveau.toUpperCase(); - } - }; - - // Function to get mission type label - const getMissionTypeLabel = (type: string) => { - switch(type) { - case 'remote': return 'À distance'; - case 'onsite': return 'Sur site'; - case 'hybrid': return 'Hybride'; - default: return type; - } - }; - - // Function to get participation label - const getParticipationLabel = (participation: string | null | undefined) => { - console.log("Participation value:", participation); // Debug log - if (!participation) return 'Non spécifié'; - switch(participation) { - case 'volontaire': return 'Volontaire'; - case 'cooptation': return 'Cooptation'; - default: return participation; - } - }; - - // Function to get mission duration - const getDuration = (projection: string) => { - switch(projection) { - case 'short': return '< 1 mois'; - case 'medium': return '1-3 mois'; - case 'long': return '> 3 mois'; - default: return projection; - } - }; - - return ( -
-
-
-

Gérez vos missions et opportunités de bénévolat

-
- - setSearchTerm(e.target.value)} - /> -
-
-
- -
- {loading ? ( -
-
-
- ) : filteredMissions.length > 0 ? ( -
- {/* @ts-ignore */} - {(() => { - // Debug: Log all mission logos to see what URLs are being used - console.log("All mission logos:", filteredMissions.map(m => ({ - id: m.id, - name: m.name, - logo: m.logo - }))); - return filteredMissions.map((mission) => { - const oddInfo = getODDInfo(mission); - const niveauColor = getNiveauBadgeColor(mission.niveau); - - return ( -
- {/* Card Header with Name and Level */} -
-

{mission.name}

-
- {/* ODD scope icon moved next to level badge */} - {oddInfo.number && ( -
- {oddInfo.label} { - // Fallback if image fails to load - (e.target as HTMLImageElement).style.display = 'none'; - }} - /> -
- )} - - {getNiveauLabel(mission.niveau)} - -
-
- - {/* Centered Logo */} -
-
- {mission.logo ? ( - {mission.name} { - console.log("Logo failed to load:", mission.logo); - console.log("Full URL attempted:", mission.logo); - // If the image fails to load, show the fallback - (e.currentTarget as HTMLImageElement).style.display = 'none'; - // Show the fallback div - const fallbackDiv = e.currentTarget.parentElement?.querySelector('.logo-fallback'); - if (fallbackDiv) { - (fallbackDiv as HTMLElement).style.display = 'flex'; - } - }} - /> - ) : null} -
- {mission.name.slice(0, 2).toUpperCase()} -
-
-
- - {/* Card Content - Services and Description */} -
- {/* Services section */} - {mission.services && mission.services.length > 0 && ( -
- Services: -
- {mission.services.map(service => ( - - {service} - - ))} -
-
- )} - - {/* Description text (can be added from mission data) */} -
- {mission.intention ? - (mission.intention.substring(0, 100) + (mission.intention.length > 100 ? '...' : '')) : - 'Pas de description disponible.'} -
-
- - {/* Card Footer */} -
- - Créée le {formatDate(mission.createdAt)} - - - - - -
-
- ); - }); - })()} -
- ) : ( -
-
- -
-

Aucune mission trouvée

-

- Créez votre première mission pour commencer à organiser vos projets et inviter des participants. -

- - - -
- )} -
-
- ); -} \ No newline at end of file