import React, { useState, useEffect, useMemo } from 'react'; import { X, Search, UserPlus, Loader2, Shield, Trash2 } from 'lucide-react'; interface AddMemberModalProps { isOpen: boolean; onClose: () => void; tourId: string; participants?: Array<{ userId: string; role: string; user?: { id: string; name: string; email: string } }>; onRemoveMember?: (userId: string) => Promise; onMemberAdded?: () => void; } export const AddMemberModal: React.FC = ({ isOpen, onClose, tourId, participants = [], onRemoveMember, onMemberAdded }) => { const [query, setQuery] = useState(''); const [users, setUsers] = useState([]); const [loading, setLoading] = useState(false); const [selectedUser, setSelectedUser] = useState(null); const [role, setRole] = useState<'OWNER' | 'MANAGER' | 'MEMBER' | 'MEMBER_NO_FINANCE' | 'VIEWER_ONLY'>('MEMBER'); const [submitting, setSubmitting] = useState(false); const [fetchError, setFetchError] = useState(''); const [submitError, setSubmitError] = useState(''); const [isConfirmOpen, setIsConfirmOpen] = useState(false); const [confirmTarget, setConfirmTarget] = useState<{ userId: string; name: string } | null>(null); const participantIds = useMemo(() => new Set(participants.map((p) => p.userId)), [participants]); const visibleUsers = useMemo(() => users.filter((u) => !participantIds.has(u.id)), [users, participantIds]); const fetchUsers = async () => { setLoading(true); setFetchError(''); try { const API_BASE = `http://${window.location.hostname}:3001`; const res = await fetch(`${API_BASE}/api/v1/users?q=${encodeURIComponent(query)}`, { headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` } }); if (!res.ok) throw new Error('Không thể tải danh sách người dùng'); const data = await res.json(); setUsers(Array.isArray(data) ? data : []); } catch (err: any) { setFetchError(err.message || 'Không thể tải danh sách người dùng'); } finally { setLoading(false); } }; useEffect(() => { if (!isOpen) return; fetchUsers(); }, [isOpen]); useEffect(() => { if (!isOpen) { setQuery(''); setSelectedUser(null); setRole('MEMBER'); setFetchError(''); setSubmitError(''); } }, [isOpen]); const handleRemove = async (userId: string, memberName: string) => { if (!onRemoveMember) return; setConfirmTarget({ userId, name: memberName }); setIsConfirmOpen(true); }; const confirmRemove = async () => { if (!confirmTarget || !onRemoveMember) return; try { await onRemoveMember(confirmTarget.userId); } catch (err: any) { setSubmitError(err.message || 'Không thể xóa thành viên'); } finally { setIsConfirmOpen(false); setConfirmTarget(null); } }; const handleAdd = async () => { if (!selectedUser) return; setSubmitting(true); setSubmitError(''); try { const API_BASE = `http://${window.location.hostname}:3001`; const res = await fetch(`${API_BASE}/api/v1/tours/${tourId}/members`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('token')}`, }, body: JSON.stringify({ userId: selectedUser, role }), }); if (!res.ok) { const data = await res.json(); throw new Error(data.message || 'Thêm thành viên thất bại'); } onMemberAdded?.(); onClose(); } catch (err: any) { setSubmitError(err.message || 'Thêm thành viên thất bại'); } finally { setSubmitting(false); } }; if (!isOpen) return null; return (

Thêm thành viên

Chọn người dùng và phân quyền cho tour này.

Thành viên của tour ({participants.length})

{participants.map((p) => { const rawToken = localStorage.getItem('token'); let currentUserId: string | null = null; try { const payload = JSON.parse(atob((rawToken || '').split('.')[1])); currentUserId = payload.sub; } catch { currentUserId = null; } const isCurrentUser = currentUserId && p.userId === currentUserId; const isOwner = p.role === 'OWNER'; const canRemove = onRemoveMember && !isCurrentUser && !isOwner; return (
{p.user?.name?.charAt(0) || '?'}
{canRemove && ( )}
{p.user?.name || p.userId}
); })} {participants.length === 0 && ( Chưa có thành viên nào )}
setQuery(e.target.value)} onBlur={fetchUsers} />
{fetchError && (
{fetchError}
)} {submitError && (
{submitError}
)} {loading ? (
) : (
{visibleUsers.map((u) => { const isSelected = selectedUser === u.id; return ( ); })} {!loading && visibleUsers.length === 0 && (
Không tìm thấy người dùng phù hợp
)}
)}
{isConfirmOpen && (
setIsConfirmOpen(false)} />

Xác nhận xóa thành viên

Bạn có chắc muốn xóa {confirmTarget?.name} khỏi tour này?

)}
); };