feat: cho phép người dùng và người quản lí xóa bình luận

This commit is contained in:
2026-06-16 12:21:44 +07:00
parent fc96ee9eb8
commit 047170d4be
4 changed files with 120 additions and 29 deletions
+56 -4
View File
@@ -1,12 +1,15 @@
import React, { useState, useEffect } from 'react';
import { X, Send, MessageSquare, User, Loader2 } from 'lucide-react';
import { X, Send, MessageSquare, User, Loader2, Trash2 } from 'lucide-react';
import { io } from 'socket.io-client';
import { useTourStore } from '@/store/useTourStore';
import { ConfirmModal } from './ConfirmModal';
interface Comment {
id: string;
userName: string;
content: string;
createdAt: string;
userId: string;
}
interface CommentModalProps {
@@ -15,13 +18,23 @@ interface CommentModalProps {
locationId: string;
locationName: string;
onCommentAdded?: () => void; // Callback to update comment count on parent
onCommentDeleted?: () => void;
isPublicView?: boolean; // New prop to indicate public view
}
export const CommentModal: React.FC<CommentModalProps> = ({ isOpen, onClose, locationId, locationName, onCommentAdded, isPublicView = false }) => {
export const CommentModal: React.FC<CommentModalProps> = ({ isOpen, onClose, locationId, locationName, onCommentAdded, onCommentDeleted, isPublicView = false }) => {
const [comments, setComments] = useState<Comment[]>([]);
const [newComment, setNewComment] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [confirmState, setConfirmState] = useState<{ open: boolean; commentId: string }>({ open: false, commentId: '' });
const userRole = useTourStore(state => state.userRole);
const currentUserId = React.useMemo(() => {
try {
const user = JSON.parse(localStorage.getItem('user') || '{}');
return user.id;
} catch { return null; }
}, []);
const fetchComments = async () => {
setIsLoading(true);
@@ -39,7 +52,8 @@ export const CommentModal: React.FC<CommentModalProps> = ({ isOpen, onClose, loc
id: c.id,
userName: c.user?.name || 'Ẩn danh',
content: c.content,
createdAt: c.createdAt
createdAt: c.createdAt,
userId: c.userId
})));
}
} catch (error) {
@@ -97,6 +111,23 @@ export const CommentModal: React.FC<CommentModalProps> = ({ isOpen, onClose, loc
}
};
const handleDelete = async (commentId: string) => {
try {
const res = await fetch(`/api/v1/locations/comments/${commentId}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}`
}
});
if (res.ok) {
setComments(prev => prev.filter(c => c.id !== commentId));
onCommentDeleted?.();
}
} catch (error) {
console.error('Lỗi khi xóa bình luận:', error);
}
};
if (!isOpen) return null;
return (
@@ -131,7 +162,17 @@ export const CommentModal: React.FC<CommentModalProps> = ({ isOpen, onClose, loc
</div>
<div className="flex-1">
<div className="bg-white p-3 rounded-2xl rounded-tl-none border border-gray-100 shadow-sm">
<p className="text-xs font-black text-gray-900 mb-1">{c.userName}</p>
<div className="flex justify-between items-start mb-1">
<p className="text-xs font-black text-gray-900">{c.userName}</p>
{(userRole === 'OWNER' || userRole === 'MANAGER' || c.userId === currentUserId) && (
<button
onClick={() => setConfirmState({ open: true, commentId: c.id })}
className="text-gray-400 hover:text-red-500 transition-colors"
>
<Trash2 className="w-3.5 h-3.5" />
</button>
)}
</div>
<p className="text-sm text-gray-600 leading-relaxed">{c.content}</p>
</div>
<p className="text-[10px] text-gray-400 mt-1 ml-1 font-medium">
@@ -160,6 +201,17 @@ export const CommentModal: React.FC<CommentModalProps> = ({ isOpen, onClose, loc
</div>
</div>
</div>
<ConfirmModal
isOpen={confirmState.open}
title="Xóa bình luận"
message="Bạn có chắc chắn muốn xóa bình luận này không? Hành động này sẽ không thể hoàn tác."
onConfirm={() => {
handleDelete(confirmState.commentId);
setConfirmState({ open: false, commentId: '' });
}}
onCancel={() => setConfirmState({ open: false, commentId: '' })}
/>
</div>
);
};