Thêm tính năng sửa tính năng pending bị lỗi

This commit is contained in:
2026-06-14 21:07:21 +07:00
parent 8ff09eeeaf
commit 7cc724a133
15 changed files with 439 additions and 110 deletions
+47 -2
View File
@@ -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>