Mission Refactor Members
This commit is contained in:
parent
e523c31fa0
commit
8d4c9d5e23
4
.env
4
.env
@ -107,3 +107,7 @@ MICROSOFT_TENANT_ID="cb4281a9-4a3e-4ff5-9a85-8425dd04e2b2"
|
||||
N8N_WEBHOOK_URL="https://brain.slm-lab.net/webhook/mission-created"
|
||||
N8N_DELETE_WEBHOOK_URL="https://brain.slm-lab.net/webhook/mission-delete"
|
||||
N8N_API_KEY=LwgeE1ntADD20OuWC88S3pR0EaO7FtO4
|
||||
N8N_GENERATE_PLAN_WEBHOOK_URL=https://brain.slm-lab.net/webhook/GeneratePlan
|
||||
N8N_CLOSE_MISSION_WEBHOOK_URL=https://brain.slm-lab.net/webhook/NeahMissionClose
|
||||
N8N_CHANGE_GUARDIAN_WEBHOOK_URL=https://brain.slm-lab.net/webhook/NeahMissionChangeGuardian
|
||||
N8N_REMOVE_USER_WEBHOOK_URL=https://brain.slm-lab.net/webhook/NeahMissionRemoveUser
|
||||
164
app/api/missions/[missionId]/change-guardian/route.ts
Normal file
164
app/api/missions/[missionId]/change-guardian/route.ts
Normal file
@ -0,0 +1,164 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { getServerSession } from 'next-auth';
|
||||
import { authOptions } from '@/lib/auth';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
import { logger } from '@/lib/logger';
|
||||
|
||||
export async function POST(
|
||||
request: Request,
|
||||
props: { params: Promise<{ missionId: string }> }
|
||||
) {
|
||||
const params = await props.params;
|
||||
const { missionId } = params;
|
||||
|
||||
try {
|
||||
// Check authentication
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session?.user?.id) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const body = await request.json();
|
||||
const { guardianRole, oldUserId, newUserId, oldUserEmail, newUserEmail } = body;
|
||||
|
||||
if (!guardianRole || !newUserId) {
|
||||
return NextResponse.json(
|
||||
{ error: 'guardianRole and newUserId are required' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Get mission details
|
||||
const mission = await prisma.mission.findUnique({
|
||||
where: { id: missionId },
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
creatorId: true,
|
||||
leantimeProjectId: true,
|
||||
outlineCollectionId: true,
|
||||
rocketChatChannelId: true,
|
||||
giteaRepositoryUrl: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!mission) {
|
||||
return NextResponse.json({ error: 'Mission not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
// Check if user is authorized (creator or admin)
|
||||
const isCreator = mission.creatorId === session.user.id;
|
||||
const isAdmin = session.user.role?.includes('admin');
|
||||
|
||||
if (!isCreator && !isAdmin) {
|
||||
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
||||
}
|
||||
|
||||
// Map guardian role to database role
|
||||
const roleMap: Record<string, string> = {
|
||||
'temps': 'temps',
|
||||
'parole': 'parole',
|
||||
'memoire': 'memoire',
|
||||
};
|
||||
|
||||
const dbRole = roleMap[guardianRole];
|
||||
if (!dbRole) {
|
||||
return NextResponse.json({ error: 'Invalid guardian role' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Update the guardian in database
|
||||
// First, remove old guardian if exists
|
||||
if (oldUserId) {
|
||||
await prisma.missionUser.deleteMany({
|
||||
where: {
|
||||
missionId,
|
||||
userId: oldUserId,
|
||||
role: dbRole,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Then add new guardian
|
||||
await prisma.missionUser.create({
|
||||
data: {
|
||||
missionId,
|
||||
userId: newUserId,
|
||||
role: dbRole,
|
||||
},
|
||||
});
|
||||
|
||||
// Extract repo name from Gitea URL
|
||||
let repoName = '';
|
||||
if (mission.giteaRepositoryUrl) {
|
||||
try {
|
||||
const url = new URL(mission.giteaRepositoryUrl);
|
||||
const pathParts = url.pathname.split('/').filter(Boolean);
|
||||
repoName = pathParts[pathParts.length - 1] || '';
|
||||
} catch {
|
||||
const match = mission.giteaRepositoryUrl.match(/\/([^\/]+)\/?$/);
|
||||
repoName = match ? match[1] : '';
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare data for N8N webhook
|
||||
const n8nData = {
|
||||
missionId: mission.id,
|
||||
missionName: mission.name,
|
||||
guardianRole,
|
||||
oldUserId,
|
||||
newUserId,
|
||||
oldUserEmail,
|
||||
newUserEmail,
|
||||
repoName,
|
||||
leantimeProjectId: mission.leantimeProjectId || '',
|
||||
outlineCollectionId: mission.outlineCollectionId || '',
|
||||
rocketChatChannelId: mission.rocketChatChannelId || '',
|
||||
giteaRepositoryUrl: mission.giteaRepositoryUrl || '',
|
||||
};
|
||||
|
||||
// Call N8N webhook
|
||||
const webhookUrl = process.env.N8N_CHANGE_GUARDIAN_WEBHOOK_URL || 'https://brain.slm-lab.net/webhook/NeahMissionChangeGuardian';
|
||||
const apiKey = process.env.N8N_API_KEY || '';
|
||||
|
||||
logger.debug('Calling N8N ChangeGuardian webhook', {
|
||||
missionId,
|
||||
guardianRole,
|
||||
oldUserId,
|
||||
newUserId,
|
||||
});
|
||||
|
||||
const response = await fetch(webhookUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-api-key': apiKey,
|
||||
},
|
||||
body: JSON.stringify(n8nData),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
logger.error('N8N ChangeGuardian webhook error', {
|
||||
status: response.status,
|
||||
error: errorText.substring(0, 200),
|
||||
});
|
||||
// Continue even if N8N fails, database is already updated
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: 'Guardian changed successfully',
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error('Error changing guardian', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
missionId,
|
||||
});
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to change guardian', details: error instanceof Error ? error.message : String(error) },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
136
app/api/missions/[missionId]/remove-user/route.ts
Normal file
136
app/api/missions/[missionId]/remove-user/route.ts
Normal file
@ -0,0 +1,136 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { getServerSession } from 'next-auth';
|
||||
import { authOptions } from '@/lib/auth';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
import { logger } from '@/lib/logger';
|
||||
|
||||
export async function POST(
|
||||
request: Request,
|
||||
props: { params: Promise<{ missionId: string }> }
|
||||
) {
|
||||
const params = await props.params;
|
||||
const { missionId } = params;
|
||||
|
||||
try {
|
||||
// Check authentication
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session?.user?.id) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const body = await request.json();
|
||||
const { userId, userEmail, role } = body;
|
||||
|
||||
if (!userId) {
|
||||
return NextResponse.json(
|
||||
{ error: 'userId is required' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Get mission details
|
||||
const mission = await prisma.mission.findUnique({
|
||||
where: { id: missionId },
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
creatorId: true,
|
||||
leantimeProjectId: true,
|
||||
outlineCollectionId: true,
|
||||
rocketChatChannelId: true,
|
||||
giteaRepositoryUrl: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!mission) {
|
||||
return NextResponse.json({ error: 'Mission not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
// Check if user is authorized (creator or admin)
|
||||
const isCreator = mission.creatorId === session.user.id;
|
||||
const isAdmin = session.user.role?.includes('admin');
|
||||
|
||||
if (!isCreator && !isAdmin) {
|
||||
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
||||
}
|
||||
|
||||
// Remove user from mission in database
|
||||
await prisma.missionUser.deleteMany({
|
||||
where: {
|
||||
missionId,
|
||||
userId,
|
||||
},
|
||||
});
|
||||
|
||||
// Extract repo name from Gitea URL
|
||||
let repoName = '';
|
||||
if (mission.giteaRepositoryUrl) {
|
||||
try {
|
||||
const url = new URL(mission.giteaRepositoryUrl);
|
||||
const pathParts = url.pathname.split('/').filter(Boolean);
|
||||
repoName = pathParts[pathParts.length - 1] || '';
|
||||
} catch {
|
||||
const match = mission.giteaRepositoryUrl.match(/\/([^\/]+)\/?$/);
|
||||
repoName = match ? match[1] : '';
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare data for N8N webhook
|
||||
const n8nData = {
|
||||
missionId: mission.id,
|
||||
missionName: mission.name,
|
||||
userId,
|
||||
userEmail,
|
||||
role: role || 'volontaire',
|
||||
repoName,
|
||||
leantimeProjectId: mission.leantimeProjectId || '',
|
||||
outlineCollectionId: mission.outlineCollectionId || '',
|
||||
rocketChatChannelId: mission.rocketChatChannelId || '',
|
||||
giteaRepositoryUrl: mission.giteaRepositoryUrl || '',
|
||||
};
|
||||
|
||||
// Call N8N webhook
|
||||
const webhookUrl = process.env.N8N_REMOVE_USER_WEBHOOK_URL || 'https://brain.slm-lab.net/webhook/NeahMissionRemoveUser';
|
||||
const apiKey = process.env.N8N_API_KEY || '';
|
||||
|
||||
logger.debug('Calling N8N RemoveUser webhook', {
|
||||
missionId,
|
||||
userId,
|
||||
userEmail,
|
||||
});
|
||||
|
||||
const response = await fetch(webhookUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-api-key': apiKey,
|
||||
},
|
||||
body: JSON.stringify(n8nData),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
logger.error('N8N RemoveUser webhook error', {
|
||||
status: response.status,
|
||||
error: errorText.substring(0, 200),
|
||||
});
|
||||
// Continue even if N8N fails, database is already updated
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: 'User removed successfully',
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error('Error removing user from mission', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
missionId,
|
||||
});
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to remove user', details: error instanceof Error ? error.message : String(error) },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,7 +18,7 @@ import {
|
||||
CardContent
|
||||
} from "../ui/card";
|
||||
import { Badge } from "../ui/badge";
|
||||
import { X, Search, UserPlus, Users, PlusCircle, AlertCircle, Check, UploadCloud, File } from "lucide-react";
|
||||
import { X, Search, UserPlus, Users, PlusCircle, AlertCircle, Check, UploadCloud, File, RefreshCw } from "lucide-react";
|
||||
import { toast } from "../ui/use-toast";
|
||||
import {
|
||||
DropdownMenu,
|
||||
@ -268,7 +268,7 @@ export function MissionsAdminPanel() {
|
||||
});
|
||||
};
|
||||
|
||||
// Function to remove a user from all roles
|
||||
// Function to remove a user from all roles (local state only - for new missions)
|
||||
const removeUserFromAllRoles = (userId: string) => {
|
||||
if (gardienDuTemps === userId) setGardienDuTemps(null);
|
||||
if (gardienDeLaParole === userId) setGardienDeLaParole(null);
|
||||
@ -278,6 +278,107 @@ export function MissionsAdminPanel() {
|
||||
}
|
||||
};
|
||||
|
||||
// Function to change a guardian and call N8N webhook (for existing missions)
|
||||
const handleChangeGuardian = async (
|
||||
guardianRole: 'temps' | 'parole' | 'memoire',
|
||||
oldUserId: string | null,
|
||||
newUserId: string
|
||||
) => {
|
||||
const oldUser = oldUserId ? users.find(u => u.id === oldUserId) : null;
|
||||
const newUser = users.find(u => u.id === newUserId);
|
||||
|
||||
// Update local state first
|
||||
if (guardianRole === 'temps') {
|
||||
setGardienDuTemps(newUserId);
|
||||
} else if (guardianRole === 'parole') {
|
||||
setGardienDeLaParole(newUserId);
|
||||
} else if (guardianRole === 'memoire') {
|
||||
setGardienDeLaMemoire(newUserId);
|
||||
}
|
||||
|
||||
// If this is an existing mission, call the API
|
||||
if (missionId) {
|
||||
try {
|
||||
const response = await fetch(`/api/missions/${missionId}/change-guardian`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
guardianRole,
|
||||
oldUserId,
|
||||
newUserId,
|
||||
oldUserEmail: oldUser?.email,
|
||||
newUserEmail: newUser?.email,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to change guardian');
|
||||
}
|
||||
|
||||
toast({
|
||||
title: "Gardien modifié",
|
||||
description: `Le gardien a été changé avec succès`,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Error changing guardian', { error });
|
||||
toast({
|
||||
title: "Erreur",
|
||||
description: "Erreur lors du changement de gardien",
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
} else {
|
||||
toast({
|
||||
title: "Gardien modifié",
|
||||
description: `Le gardien a été changé`,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Function to remove a volontaire and call N8N webhook (for existing missions)
|
||||
const handleRemoveVolontaire = async (userId: string) => {
|
||||
const user = users.find(u => u.id === userId);
|
||||
|
||||
// Update local state first
|
||||
setVolontaires(prev => prev.filter(id => id !== userId));
|
||||
|
||||
// If this is an existing mission, call the API
|
||||
if (missionId) {
|
||||
try {
|
||||
const response = await fetch(`/api/missions/${missionId}/remove-user`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
userId,
|
||||
userEmail: user?.email,
|
||||
role: 'volontaire',
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to remove user');
|
||||
}
|
||||
|
||||
toast({
|
||||
title: "Utilisateur supprimé",
|
||||
description: `L'utilisateur a été retiré de la mission`,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Error removing user', { error });
|
||||
toast({
|
||||
title: "Erreur",
|
||||
description: "Erreur lors de la suppression de l'utilisateur",
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
} else {
|
||||
toast({
|
||||
title: "Utilisateur supprimé",
|
||||
description: `L'utilisateur a été retiré`,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Check if all guardian roles are filled
|
||||
const areAllGuardiensFilled = (): boolean => {
|
||||
return gardienDuTemps !== null && gardienDeLaParole !== null && gardienDeLaMemoire !== null;
|
||||
@ -1121,16 +1222,38 @@ export function MissionsAdminPanel() {
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<h4 className="font-medium text-gray-800">Gardien du Temps</h4>
|
||||
{gardienDuTemps && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => removeUserFromAllRoles(gardienDuTemps)}
|
||||
className="text-red-600 hover:bg-red-50 hover:text-red-700 border-red-200 h-8 bg-white"
|
||||
disabled={loading}
|
||||
>
|
||||
<X size={16} className="mr-1" />
|
||||
Supprimer
|
||||
</Button>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="text-blue-600 hover:bg-blue-50 hover:text-blue-700 border-blue-200 h-8 bg-white"
|
||||
disabled={loading}
|
||||
>
|
||||
<RefreshCw size={16} className="mr-1" />
|
||||
Modifier
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="bg-white border border-gray-200 max-h-[300px] overflow-y-auto w-64">
|
||||
{users.filter(u => u.id !== gardienDuTemps && u.id !== gardienDeLaParole && u.id !== gardienDeLaMemoire).map(user => (
|
||||
<DropdownMenuItem
|
||||
key={user.id}
|
||||
onClick={() => handleChangeGuardian('temps', gardienDuTemps, user.id)}
|
||||
className="cursor-pointer hover:bg-gray-100"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<div className="h-6 w-6 rounded-full bg-gray-100 flex items-center justify-center text-gray-600 text-xs font-medium mr-2">
|
||||
{user.firstName?.[0] || ""}{user.lastName?.[0] || ""}
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm">{user.firstName} {user.lastName}</div>
|
||||
<div className="text-xs text-gray-500">{user.email}</div>
|
||||
</div>
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
</div>
|
||||
{loading ? (
|
||||
@ -1177,16 +1300,38 @@ export function MissionsAdminPanel() {
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<h4 className="font-medium text-gray-800">Gardien de la Parole</h4>
|
||||
{gardienDeLaParole && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => removeUserFromAllRoles(gardienDeLaParole)}
|
||||
className="text-red-600 hover:bg-red-50 hover:text-red-700 border-red-200 h-8 bg-white"
|
||||
disabled={loading}
|
||||
>
|
||||
<X size={16} className="mr-1" />
|
||||
Supprimer
|
||||
</Button>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="text-blue-600 hover:bg-blue-50 hover:text-blue-700 border-blue-200 h-8 bg-white"
|
||||
disabled={loading}
|
||||
>
|
||||
<RefreshCw size={16} className="mr-1" />
|
||||
Modifier
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="bg-white border border-gray-200 max-h-[300px] overflow-y-auto w-64">
|
||||
{users.filter(u => u.id !== gardienDuTemps && u.id !== gardienDeLaParole && u.id !== gardienDeLaMemoire).map(user => (
|
||||
<DropdownMenuItem
|
||||
key={user.id}
|
||||
onClick={() => handleChangeGuardian('parole', gardienDeLaParole, user.id)}
|
||||
className="cursor-pointer hover:bg-gray-100"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<div className="h-6 w-6 rounded-full bg-gray-100 flex items-center justify-center text-gray-600 text-xs font-medium mr-2">
|
||||
{user.firstName?.[0] || ""}{user.lastName?.[0] || ""}
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm">{user.firstName} {user.lastName}</div>
|
||||
<div className="text-xs text-gray-500">{user.email}</div>
|
||||
</div>
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
</div>
|
||||
{loading ? (
|
||||
@ -1233,16 +1378,38 @@ export function MissionsAdminPanel() {
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<h4 className="font-medium text-gray-800">Gardien de la Mémoire</h4>
|
||||
{gardienDeLaMemoire && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => removeUserFromAllRoles(gardienDeLaMemoire)}
|
||||
className="text-red-600 hover:bg-red-50 hover:text-red-700 border-red-200 h-8 bg-white"
|
||||
disabled={loading}
|
||||
>
|
||||
<X size={16} className="mr-1" />
|
||||
Supprimer
|
||||
</Button>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="text-blue-600 hover:bg-blue-50 hover:text-blue-700 border-blue-200 h-8 bg-white"
|
||||
disabled={loading}
|
||||
>
|
||||
<RefreshCw size={16} className="mr-1" />
|
||||
Modifier
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="bg-white border border-gray-200 max-h-[300px] overflow-y-auto w-64">
|
||||
{users.filter(u => u.id !== gardienDuTemps && u.id !== gardienDeLaParole && u.id !== gardienDeLaMemoire).map(user => (
|
||||
<DropdownMenuItem
|
||||
key={user.id}
|
||||
onClick={() => handleChangeGuardian('memoire', gardienDeLaMemoire, user.id)}
|
||||
className="cursor-pointer hover:bg-gray-100"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<div className="h-6 w-6 rounded-full bg-gray-100 flex items-center justify-center text-gray-600 text-xs font-medium mr-2">
|
||||
{user.firstName?.[0] || ""}{user.lastName?.[0] || ""}
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm">{user.firstName} {user.lastName}</div>
|
||||
<div className="text-xs text-gray-500">{user.email}</div>
|
||||
</div>
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
</div>
|
||||
{loading ? (
|
||||
@ -1331,8 +1498,9 @@ export function MissionsAdminPanel() {
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => removeUserFromAllRoles(userId)}
|
||||
onClick={() => handleRemoveVolontaire(userId)}
|
||||
className="ml-1 h-5 w-5 p-0 text-gray-500 hover:text-red-600 hover:bg-transparent"
|
||||
title="Supprimer le volontaire"
|
||||
>
|
||||
<X size={12} />
|
||||
</Button>
|
||||
@ -1452,11 +1620,12 @@ export function MissionsAdminPanel() {
|
||||
Volontaire
|
||||
</Button>
|
||||
|
||||
{isUserAssigned(user.id) && (
|
||||
{/* Only show delete button for volontaires (not guardians) */}
|
||||
{volontaires.includes(user.id) && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => removeUserFromAllRoles(user.id)}
|
||||
onClick={() => handleRemoveVolontaire(user.id)}
|
||||
className="ml-2 text-red-600 hover:bg-red-50 hover:text-red-700 border-red-200 h-8 bg-white"
|
||||
disabled={loading}
|
||||
>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user