diff --git a/frontend/src/components/CommentModal.tsx b/frontend/src/components/CommentModal.tsx index 662c6ca..503a07b 100644 --- a/frontend/src/components/CommentModal.tsx +++ b/frontend/src/components/CommentModal.tsx @@ -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 = ({ isOpen, onClose, locationId, locationName, onCommentAdded, isPublicView = false }) => { +export const CommentModal: React.FC = ({ isOpen, onClose, locationId, locationName, onCommentAdded, onCommentDeleted, isPublicView = false }) => { const [comments, setComments] = useState([]); 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 = ({ 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 = ({ 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 = ({ isOpen, onClose, loc
-

{c.userName}

+
+

{c.userName}

+ {(userRole === 'OWNER' || userRole === 'MANAGER' || c.userId === currentUserId) && ( + + )} +

{c.content}

@@ -160,6 +201,17 @@ export const CommentModal: React.FC = ({ isOpen, onClose, loc

+ + { + handleDelete(confirmState.commentId); + setConfirmState({ open: false, commentId: '' }); + }} + onCancel={() => setConfirmState({ open: false, commentId: '' })} + /> ); }; \ No newline at end of file diff --git a/frontend/src/components/ConfirmModal.tsx b/frontend/src/components/ConfirmModal.tsx index ad38fdd..e52e311 100644 --- a/frontend/src/components/ConfirmModal.tsx +++ b/frontend/src/components/ConfirmModal.tsx @@ -1,42 +1,53 @@ -import React, { useState } from 'react'; -import { X } from 'lucide-react'; +import React from 'react'; +import { AlertTriangle, X } from 'lucide-react'; interface ConfirmModalProps { isOpen: boolean; title?: string; - message: string; - confirmText?: string; - cancelText?: string; + message?: string; onConfirm: () => void; onCancel: () => void; } -export const ConfirmModal: React.FC = ({ - isOpen, - title = 'Xác nhận', - message, - confirmText = 'Xác nhận', - cancelText = 'Hủy', - onConfirm, - onCancel, -}) => { +export const ConfirmModal: React.FC = ({ isOpen, title, message, onConfirm, onCancel }) => { if (!isOpen) return null; return ( -
-
-
-

{title}

-

{message}

-
- -
+ +

{title || 'Xác nhận'}

+

+ {message || 'Bạn có chắc chắn muốn thực hiện hành động này không?'} +

+ +
+ +
); -}; +}; \ No newline at end of file diff --git a/frontend/src/components/ItineraryTimeline.tsx b/frontend/src/components/ItineraryTimeline.tsx index 66863b1..40274d2 100644 --- a/frontend/src/components/ItineraryTimeline.tsx +++ b/frontend/src/components/ItineraryTimeline.tsx @@ -80,6 +80,19 @@ export const ItineraryTimeline = ({ useTourStore.setState({ legs: updatedLegs }); }; + const handleCommentDecrement = (locationId: string) => { + const currentLegs = useTourStore.getState().legs; + const updatedLegs = currentLegs.map(leg => ({ + ...leg, + locations: leg.locations.map(loc => + loc.id === locationId + ? { ...loc, _count: { ...loc._count, comments: Math.max(0, (loc._count?.comments || 1) - 1) } } + : loc + ) + })); + useTourStore.setState({ legs: updatedLegs }); + }; + // State cho Modal sửa chặng const [isEditModalOpen, setIsEditModalOpen] = useState(false); const [editingLegData, setEditingLegData] = useState({ @@ -575,6 +588,7 @@ export const ItineraryTimeline = ({ locationName={commentLocationName} isPublicView={isPublicView} onCommentAdded={() => handleCommentIncrement(commentLocationId)} + onCommentDeleted={() => handleCommentDecrement(commentLocationId)} />
); diff --git a/frontend/src/pages/TourDetailPage.tsx b/frontend/src/pages/TourDetailPage.tsx index 213a1d2..9444f0f 100644 --- a/frontend/src/pages/TourDetailPage.tsx +++ b/frontend/src/pages/TourDetailPage.tsx @@ -223,6 +223,19 @@ export const TourDetailPage = ({ onBack, tourId, isPublicView = false }: { onBac useTourStore.setState({ legs: updatedLegs }); }; + const handleCommentDecrement = (locationId: string) => { + const currentLegs = useTourStore.getState().legs; + const updatedLegs = currentLegs.map(leg => ({ + ...leg, + locations: leg.locations.map(loc => + loc.id === locationId + ? { ...loc, _count: { ...loc._count, comments: Math.max(0, (loc._count?.comments || 1) - 1) } } + : loc + ) + })); + useTourStore.setState({ legs: updatedLegs }); + }; + const fetchPublicTours = useTourStore(state => state.fetchPublicTours); const setMapCenter = useTourStore(state => state.setMapCenter); const updateTourStartPoint = useTourStore(state => state.updateTourStartPoint); @@ -1199,6 +1212,7 @@ export const TourDetailPage = ({ onBack, tourId, isPublicView = false }: { onBac locationName={commentLocationName} isPublicView={isPublicView} // Pass isPublicView onCommentAdded={() => handleCommentIncrement(commentLocationId)} + onCommentDeleted={() => handleCommentDecrement(commentLocationId)} /> );