Files
workshop-manager/src/components/teams/MembersList.tsx
Julien Froidefond 5f661c8bfd
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 12m53s
feat: introduce Teams & OKRs feature with models, types, and UI components for team management and objective tracking
2026-01-07 10:11:59 +01:00

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>
);
}