import React, { useState, useEffect, useMemo } from 'react'; import { X, Search, UserPlus, Loader2, Shield, ShieldAlert } 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; } export const AddMemberModal: React.FC = ({ isOpen, onClose, tourId, participants = [], onRemoveMember }) => { 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 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 (!window.confirm(`Xóa ${memberName} khỏi tour này?`) || !onRemoveMember) return; try { await onRemoveMember(userId); } catch (err: any) { alert(err.message || 'Không thể xóa thành viên'); } }; 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'); } 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.

Đã gán ({participants.length})

{participants.map((p) => ( {p.user?.name || p.userId} {onRemoveMember && ( )} ))} {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
)}
)}
); };