bugs: lỗi khi người dùng link để tham gia tour nhưng không xuất hiện trong danh sách thành viên

This commit is contained in:
2026-06-20 20:59:01 +07:00
parent 52706cab7d
commit 36157bd53b
18 changed files with 1419 additions and 188 deletions
+60
View File
@@ -844,6 +844,27 @@ export const TourDetailPage = ({
const canUploadPhoto = isPublicView ? false : ['OWNER', 'MANAGER', 'MEMBER', 'MEMBER_NO_FINANCE'].includes(userRole || '');
const isOwner = isPublicView ? false : userRole === 'OWNER';
const canShare = isPublicView || ['OWNER', 'MANAGER', 'MEMBER'].includes(userRole || '');
const canManage = isOwner || (!isPublicView && userRole === 'MANAGER');
const duplicateMatches = useMemo(() => {
if (!canManage || !currentTour?.participants) return [];
const manualMembers = currentTour.participants.filter((p: any) => !p.userId && p.displayName);
const systemMembers = currentTour.participants.filter((p: any) => p.userId && p.user?.name);
const matches: Array<{ manual: any; system: any }> = [];
manualMembers.forEach((m: any) => {
const match = systemMembers.find((s: any) => {
return s.user.name.trim().toLowerCase() === m.displayName.trim().toLowerCase();
});
if (match) {
matches.push({ manual: m, system: match });
}
});
return matches;
}, [canManage, currentTour?.participants]);
useEffect(() => {
if (isPublicView) {
@@ -1609,6 +1630,45 @@ export const TourDetailPage = ({
</button>
)}
</div>
{duplicateMatches.map((match) => (
<div key={match.manual.id} className="mt-3 p-3 bg-amber-500/20 backdrop-blur-md border border-amber-500/30 rounded-2xl flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3 text-xs text-amber-100 animate-in fade-in slide-in-from-top-1 shadow-lg">
<div className="flex items-center gap-2">
<span className="w-2.5 h-2.5 rounded-full bg-amber-400 animate-pulse shrink-0"></span>
<span>
Phát hiện thành viên ngoài hệ thống <strong>"{match.manual.displayName}"</strong> trùng tên với tài khoản <strong>"{match.system.user.name}"</strong> vừa tham gia.
</span>
</div>
<button
onClick={async () => {
try {
const res = await fetch(`/api/v1/tours/${currentTour.id}/members/merge`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('token')}`
},
body: JSON.stringify({
manualParticipantId: match.manual.id,
systemUserId: match.system.userId
})
});
const data = await res.json();
if (!res.ok) {
throw new Error(data.message || 'Hợp nhất thất bại.');
}
notify({ title: 'Thành công', message: 'Đã gán thành viên ngoài hệ thống thành công!', type: 'success' });
fetchTour(currentTour.id);
} catch (e: any) {
notify({ title: 'Lỗi', message: e.message || 'Không thể hợp nhất', type: 'error' });
}
}}
className="px-3.5 py-2 bg-amber-500 hover:bg-amber-600 active:scale-95 text-white font-black rounded-xl transition-all shrink-0 shadow-md text-[11px]"
>
Gán & Hợp nhất
</button>
</div>
))}
</div>
</div>
</div>