All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 12m53s
176 lines
5.7 KiB
TypeScript
176 lines
5.7 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import { getGravatarUrl } from '@/lib/gravatar';
|
|
import { Badge } from '@/components/ui';
|
|
import { Button } from '@/components/ui';
|
|
import { AddMemberModal } from './AddMemberModal';
|
|
import type { TeamMember, TeamRole } from '@/lib/types';
|
|
import { TEAM_ROLE_LABELS } from '@/lib/types';
|
|
|
|
interface MembersListProps {
|
|
members: TeamMember[];
|
|
teamId: string;
|
|
isAdmin: boolean;
|
|
onMemberUpdate: () => void;
|
|
}
|
|
|
|
export function MembersList({ members, teamId, isAdmin, onMemberUpdate }: MembersListProps) {
|
|
const [addMemberOpen, setAddMemberOpen] = useState(false);
|
|
const [updatingRole, setUpdatingRole] = useState<string | null>(null);
|
|
const [removingMember, setRemovingMember] = useState<string | null>(null);
|
|
|
|
const handleRoleChange = async (userId: string, newRole: TeamRole) => {
|
|
setUpdatingRole(userId);
|
|
try {
|
|
const response = await fetch(`/api/teams/${teamId}/members`, {
|
|
method: 'PATCH',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ userId, role: newRole }),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json();
|
|
alert(error.error || 'Erreur lors de la mise à jour du rôle');
|
|
return;
|
|
}
|
|
|
|
onMemberUpdate();
|
|
} catch (error) {
|
|
console.error('Error updating role:', error);
|
|
alert('Erreur lors de la mise à jour du rôle');
|
|
} finally {
|
|
setUpdatingRole(null);
|
|
}
|
|
};
|
|
|
|
const handleRemoveMember = async (userId: string) => {
|
|
if (!confirm('Êtes-vous sûr de vouloir retirer ce membre de l\'équipe ?')) {
|
|
return;
|
|
}
|
|
|
|
setRemovingMember(userId);
|
|
try {
|
|
const response = await fetch(`/api/teams/${teamId}/members?userId=${userId}`, {
|
|
method: 'DELETE',
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json();
|
|
alert(error.error || 'Erreur lors de la suppression du membre');
|
|
return;
|
|
}
|
|
|
|
onMemberUpdate();
|
|
} catch (error) {
|
|
console.error('Error removing member:', error);
|
|
alert('Erreur lors de la suppression du membre');
|
|
} finally {
|
|
setRemovingMember(null);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div>
|
|
<div className="mb-4 flex items-center justify-between">
|
|
<h3 className="text-lg font-semibold text-foreground">Membres ({members.length})</h3>
|
|
{isAdmin && (
|
|
<Button
|
|
onClick={() => setAddMemberOpen(true)}
|
|
className="bg-[var(--purple)] text-white hover:opacity-90 border-transparent"
|
|
>
|
|
Ajouter un membre
|
|
</Button>
|
|
)}
|
|
</div>
|
|
|
|
<div className="space-y-3">
|
|
{members.map((member) => (
|
|
<div
|
|
key={member.id}
|
|
className="flex items-center gap-4 rounded-xl border border-border bg-card p-4"
|
|
>
|
|
{/* Avatar */}
|
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
<img
|
|
src={getGravatarUrl(member.user.email, 96)}
|
|
alt={member.user.name || member.user.email}
|
|
width={48}
|
|
height={48}
|
|
className="rounded-full border-2 border-border"
|
|
/>
|
|
|
|
{/* User Info */}
|
|
<div className="min-w-0 flex-1">
|
|
<div className="flex items-center gap-2">
|
|
<span className="font-medium text-foreground truncate">
|
|
{member.user.name || 'Sans nom'}
|
|
</span>
|
|
<Badge
|
|
style={{
|
|
backgroundColor:
|
|
member.role === 'ADMIN'
|
|
? 'color-mix(in srgb, var(--purple) 15%, transparent)'
|
|
: 'color-mix(in srgb, var(--gray) 15%, transparent)',
|
|
color: member.role === 'ADMIN' ? 'var(--purple)' : 'var(--gray)',
|
|
}}
|
|
>
|
|
{TEAM_ROLE_LABELS[member.role]}
|
|
</Badge>
|
|
</div>
|
|
<div className="text-sm text-muted truncate">{member.user.email}</div>
|
|
</div>
|
|
|
|
{/* Actions */}
|
|
{isAdmin && (
|
|
<div className="flex items-center gap-2">
|
|
{member.role === 'MEMBER' ? (
|
|
<Button
|
|
onClick={() => handleRoleChange(member.userId, 'ADMIN')}
|
|
disabled={updatingRole === member.userId}
|
|
variant="outline"
|
|
size="sm"
|
|
>
|
|
{updatingRole === member.userId ? '...' : 'Promouvoir Admin'}
|
|
</Button>
|
|
) : (
|
|
<Button
|
|
onClick={() => handleRoleChange(member.userId, 'MEMBER')}
|
|
disabled={updatingRole === member.userId}
|
|
variant="outline"
|
|
size="sm"
|
|
>
|
|
{updatingRole === member.userId ? '...' : 'Rétrograder'}
|
|
</Button>
|
|
)}
|
|
<Button
|
|
onClick={() => handleRemoveMember(member.userId)}
|
|
disabled={removingMember === member.userId}
|
|
variant="outline"
|
|
size="sm"
|
|
style={{
|
|
color: 'var(--destructive)',
|
|
borderColor: 'var(--destructive)',
|
|
}}
|
|
>
|
|
{removingMember === member.userId ? '...' : 'Retirer'}
|
|
</Button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{addMemberOpen && (
|
|
<AddMemberModal
|
|
teamId={teamId}
|
|
existingMemberIds={members.map((m) => m.userId)}
|
|
onClose={() => setAddMemberOpen(false)}
|
|
onSuccess={onMemberUpdate}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|