import React, { useState, useEffect, useMemo } from 'react'; import { X, Search, UserPlus, Loader2, Shield, Trash2, Clock } from 'lucide-react'; interface AddMemberModalProps { isOpen: boolean; onClose: () => void; tourId: string; participants?: Array<{ userId: string; role: string; user?: { id: string; name: string; email: string } }>; joinRequests?: Array<{ id: string; userId: string; user?: { id: string; name: string; email: string }; status: string; requestedById: string }>; onRemoveMember?: (userId: string) => Promise; onMemberAdded?: () => void; userRole?: string; } export const AddMemberModal: React.FC = ({ isOpen, onClose, tourId, participants = [], joinRequests = [], onRemoveMember, onMemberAdded, userRole }) => { 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 requestUserIds = useMemo(() => new Set(joinRequests.map((r) => (r as any).userId)), [joinRequests]); const visibleUsers = useMemo(() => users.filter((u) => !participantIds.has(u.id)), [users, participantIds]); const canCreateDirectly = !userRole || userRole === 'OWNER' || userRole === 'MANAGER'; 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 endpoint = canCreateDirectly ? `/api/v1/tours/${tourId}/members` : `/api/v1/tours/${tourId}/join-requests`; const body = canCreateDirectly ? { userId: selectedUser, role } : { userId: selectedUser }; const res = await fetch(`${API_BASE}${endpoint}`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('token')}`, }, body: JSON.stringify(body), }); if (!res.ok) { const data = await res.json(); throw new Error(data.message || data.error || 'Thao tác thất bại'); } await onMemberAdded?.(); onClose(); } catch (err: any) { setSubmitError(err.message || 'Thao tác thất bại'); } finally { setSubmitting(false); } }; if (!isOpen) return null; return (

{canCreateDirectly ? 'Thêm thành viên' : 'Mời tham gia tour'}

{canCreateDirectly ? 'Chọn người dùng và phân quyền cho tour này.' : 'Mời sẽ được gửi và chờ người quản lý phê duyệt.'}

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 )}
{joinRequests.length > 0 && (

Đang chờ phê duyệt ({joinRequests.length})

{(joinRequests as any[]).map((req) => (
{req.user?.name?.charAt(0) || '?'}
{req.user?.name || req.userId} PENDING
))}
)} {canCreateDirectly && (
)}
{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?

)}
); };