Thêm tính năng sửa tính năng pending bị lỗi
This commit is contained in:
+47
-2
@@ -1,5 +1,5 @@
|
||||
import React, { useState, useEffect, useMemo } from 'react';
|
||||
import { X, Search, UserPlus, Loader2, Shield, Trash2, Clock } from 'lucide-react';
|
||||
import { X, Search, UserPlus, Loader2, Shield, Trash2, Clock, Check } from 'lucide-react';
|
||||
|
||||
interface AddMemberModalProps {
|
||||
isOpen: boolean;
|
||||
@@ -23,6 +23,7 @@ export const AddMemberModal: React.FC<AddMemberModalProps> = ({ isOpen, onClose,
|
||||
const [submitError, setSubmitError] = useState('');
|
||||
const [isConfirmOpen, setIsConfirmOpen] = useState(false);
|
||||
const [confirmTarget, setConfirmTarget] = useState<{ userId: string; name: string } | null>(null);
|
||||
const [actionLoading, setActionLoading] = useState<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]);
|
||||
@@ -81,6 +82,30 @@ export const AddMemberModal: React.FC<AddMemberModalProps> = ({ isOpen, onClose,
|
||||
}
|
||||
};
|
||||
|
||||
const handleRequestAction = async (reqId: string, action: 'accept' | 'reject', userName: string) => {
|
||||
if (!onMemberAdded) return;
|
||||
setActionLoading(reqId);
|
||||
try {
|
||||
const API_BASE = `http://${window.location.hostname}:3001`;
|
||||
const endpoint = action === 'accept'
|
||||
? `${API_BASE}/api/v1/tours/${tourId}/join-requests/${reqId}/accept`
|
||||
: `${API_BASE}/api/v1/tours/${tourId}/join-requests/${reqId}/reject`;
|
||||
const res = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` },
|
||||
});
|
||||
if (!res.ok) {
|
||||
const data = await res.json().catch(() => ({}));
|
||||
throw new Error(data.message || data.error || (action === 'accept' ? 'Không thể chấp nhận' : 'Không thể từ chối'));
|
||||
}
|
||||
await onMemberAdded();
|
||||
} catch (err: any) {
|
||||
alert(err.message || 'Thao tác thất bại');
|
||||
} finally {
|
||||
setActionLoading(null);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAdd = async () => {
|
||||
if (!selectedUser) return;
|
||||
setSubmitting(true);
|
||||
@@ -181,10 +206,30 @@ export const AddMemberModal: React.FC<AddMemberModalProps> = ({ isOpen, onClose,
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
{(joinRequests as any[]).map((req) => (
|
||||
<div key={req.id} className="flex flex-col items-center gap-1">
|
||||
<div key={req.id} className="flex flex-col items-center gap-1 relative">
|
||||
<div className="w-10 h-10 rounded-full bg-amber-50 border border-amber-200 flex items-center justify-center text-amber-600 font-bold text-sm overflow-hidden">
|
||||
{req.user?.name?.charAt(0) || '?'}
|
||||
</div>
|
||||
<div className="absolute -top-1 -right-1 flex">
|
||||
<button
|
||||
type="button"
|
||||
disabled={actionLoading === req.id}
|
||||
onClick={() => handleRequestAction(req.id, 'accept', req.user?.name || req.userId)}
|
||||
className="w-4 h-4 bg-green-500 hover:bg-green-600 rounded-full flex items-center justify-center text-white text-[10px] leading-none disabled:opacity-50"
|
||||
aria-label="Accept"
|
||||
>
|
||||
+
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
disabled={actionLoading === req.id}
|
||||
onClick={() => handleRequestAction(req.id, 'reject', req.user?.name || req.userId)}
|
||||
className="w-4 h-4 bg-red-500 hover:bg-red-600 rounded-full flex items-center justify-center text-white text-[10px] leading-none disabled:opacity-50 -ml-1"
|
||||
aria-label="Reject"
|
||||
>
|
||||
x
|
||||
</button>
|
||||
</div>
|
||||
<span className="text-[10px] font-semibold text-gray-700 max-w-[72px] truncate">{req.user?.name || req.userId}</span>
|
||||
<span className="text-[9px] font-bold text-amber-600 bg-amber-100 px-1.5 py-0.5 rounded-full border border-amber-200">PENDING</span>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user