feat: tạo hook useNotification.tsx để dùng chung toàn hệ thống
This commit is contained in:
@@ -5,8 +5,8 @@ import { ExpenseManager } from '../components/ExpenseManager';
|
||||
import { useTourStore } from '@/store/useTourStore';
|
||||
import { AddLocationModal } from '@/components/AddLocationModal';
|
||||
import { AddMemberModal } from '../components/AddMemberModal';
|
||||
import { ConfirmModal } from '../components/ConfirmModal';
|
||||
import { NotificationModal, useNotificationModal } from '@/components/NotificationModal';
|
||||
import { useConfirm } from '@/hooks/useConfirm';
|
||||
import { useNotification } from '@/hooks/useNotification';
|
||||
import { CommentModal } from '@/components/CommentModal';
|
||||
import { MapContainer, TileLayer, Marker, Popup, useMapEvents, Polyline, useMap } from 'react-leaflet';
|
||||
import _MarkerClusterGroup from 'react-leaflet-cluster';
|
||||
@@ -197,7 +197,6 @@ export const TourDetailPage = ({ onBack, tourId, isPublicView = false }: { onBac
|
||||
const [commentLocationId, setCommentLocationId] = useState('');
|
||||
const [commentLocationName, setCommentLocationName] = useState('');
|
||||
const [joinRequestActionId, setJoinRequestActionId] = useState<string | null>(null);
|
||||
const [confirmState, setConfirmState] = useState<{ open: boolean; title?: string; message?: string; onConfirm?: () => void }>({ open: false });
|
||||
|
||||
const fetchTour = useTourStore(state => state.fetchTour);
|
||||
const fetchPublicTourDetails = useTourStore(state => state.fetchPublicTourDetails); // New action
|
||||
@@ -248,7 +247,8 @@ export const TourDetailPage = ({ onBack, tourId, isPublicView = false }: { onBac
|
||||
const acceptJoinRequest = useTourStore(state => state.acceptJoinRequest);
|
||||
const rejectJoinRequest = useTourStore(state => state.rejectJoinRequest);
|
||||
|
||||
const notificationModal = useNotificationModal();
|
||||
const confirm = useConfirm();
|
||||
const notify = useNotification();
|
||||
|
||||
// Khôi phục vị trí và mức zoom từ localStorage
|
||||
const [initialViewState] = useState(() => {
|
||||
@@ -304,7 +304,11 @@ export const TourDetailPage = ({ onBack, tourId, isPublicView = false }: { onBac
|
||||
}).catch(() => {});
|
||||
} else if (navigator.clipboard && window.isSecureContext) {
|
||||
navigator.clipboard.writeText(shareUrl).then(() => {
|
||||
notificationModal.openModal('Thành công', 'Đã sao chép liên kết chia sẻ chuyến đi!', 'success');
|
||||
notify({
|
||||
title: 'Thành công',
|
||||
message: 'Đã sao chép liên kết chia sẻ chuyến đi!',
|
||||
type: 'success'
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// Giải pháp dự phòng cho môi trường không có HTTPS (truy cập qua IP)
|
||||
@@ -314,7 +318,11 @@ export const TourDetailPage = ({ onBack, tourId, isPublicView = false }: { onBac
|
||||
textArea.select();
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
notificationModal.openModal('Thành công', 'Đã sao chép liên kết chia sẻ chuyến đi!', 'success');
|
||||
notify({
|
||||
title: 'Thành công',
|
||||
message: 'Đã sao chép liên kết chia sẻ chuyến đi!',
|
||||
type: 'success'
|
||||
});
|
||||
} catch (err) {}
|
||||
document.body.removeChild(textArea);
|
||||
}
|
||||
@@ -450,10 +458,14 @@ export const TourDetailPage = ({ onBack, tourId, isPublicView = false }: { onBac
|
||||
childDiscount: childDiscountInput,
|
||||
tags: tagsInput
|
||||
});
|
||||
notificationModal.openModal('Thành công', 'Đã cập nhật thông tin chuyến đi.', 'success');
|
||||
notify({
|
||||
title: 'Thành công',
|
||||
message: 'Đã cập nhật thông tin chuyến đi.',
|
||||
type: 'success'
|
||||
});
|
||||
fetchTour(currentTour.id); // Re-fetch tour để cập nhật lại các tính toán liên quan
|
||||
} catch (error: any) {
|
||||
notificationModal.openModal('Lỗi', error.message || 'Không thể cập nhật thông tin.', 'error');
|
||||
notify({ title: 'Lỗi', message: error.message || 'Không thể cập nhật thông tin.', type: 'error' });
|
||||
}
|
||||
};
|
||||
// Xác định Điểm xuất phát và Điểm kết thúc hiển thị dưới widget tài chính
|
||||
@@ -609,23 +621,21 @@ export const TourDetailPage = ({ onBack, tourId, isPublicView = false }: { onBac
|
||||
onClick={async (e) => {
|
||||
e.stopPropagation();
|
||||
if (!currentTour) return;
|
||||
setConfirmState({
|
||||
open: true,
|
||||
const isConfirmed = await confirm({
|
||||
title: 'Chấp nhận yêu cầu',
|
||||
message: `Chấp nhận ${req.user?.name || req.userId} vào tour?`,
|
||||
onConfirm: async () => {
|
||||
setJoinRequestActionId(req.id);
|
||||
try {
|
||||
await acceptJoinRequest(currentTour.id, req.id);
|
||||
setJoinRequests((prev) => prev.filter((r) => r.id !== req.id));
|
||||
} catch (e: any) {
|
||||
notificationModal.openModal('Thông báo', e.message || 'Không thể chấp nhận yêu cầu', 'error');
|
||||
} finally {
|
||||
setJoinRequestActionId(null);
|
||||
setConfirmState({ open: false });
|
||||
}
|
||||
},
|
||||
message: `Chấp nhận ${req.user?.name || req.userId} vào tour?`
|
||||
});
|
||||
if (isConfirmed) {
|
||||
setJoinRequestActionId(req.id);
|
||||
try {
|
||||
await acceptJoinRequest(currentTour.id, req.id);
|
||||
setJoinRequests((prev) => prev.filter((r) => r.id !== req.id));
|
||||
} catch (e: any) {
|
||||
notify({ title: 'Thông báo', message: e.message || 'Không thể chấp nhận yêu cầu', type: 'error' });
|
||||
} finally {
|
||||
setJoinRequestActionId(null);
|
||||
}
|
||||
}
|
||||
}}
|
||||
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"
|
||||
@@ -638,23 +648,21 @@ export const TourDetailPage = ({ onBack, tourId, isPublicView = false }: { onBac
|
||||
onClick={async (e) => {
|
||||
e.stopPropagation();
|
||||
if (!currentTour) return;
|
||||
setConfirmState({
|
||||
open: true,
|
||||
const isConfirmed = await confirm({
|
||||
title: 'Từ chối yêu cầu',
|
||||
message: `Từ chối ${req.user?.name || req.userId} tham gia tour?`,
|
||||
onConfirm: async () => {
|
||||
setJoinRequestActionId(req.id);
|
||||
try {
|
||||
await rejectJoinRequest(currentTour.id, req.id);
|
||||
setJoinRequests((prev) => prev.filter((r) => r.id !== req.id));
|
||||
} catch (e: any) {
|
||||
notificationModal.openModal('Thông báo', e.message || 'Không thể từ chối yêu cầu', 'error');
|
||||
} finally {
|
||||
setJoinRequestActionId(null);
|
||||
setConfirmState({ open: false });
|
||||
}
|
||||
},
|
||||
message: `Từ chối ${req.user?.name || req.userId} tham gia tour?`
|
||||
});
|
||||
if (isConfirmed) {
|
||||
setJoinRequestActionId(req.id);
|
||||
try {
|
||||
await rejectJoinRequest(currentTour.id, req.id);
|
||||
setJoinRequests((prev) => prev.filter((r) => r.id !== req.id));
|
||||
} catch (e: any) {
|
||||
notify({ title: 'Thông báo', message: e.message || 'Không thể từ chối yêu cầu', type: 'error' });
|
||||
} finally {
|
||||
setJoinRequestActionId(null);
|
||||
}
|
||||
}
|
||||
}}
|
||||
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"
|
||||
@@ -905,23 +913,21 @@ export const TourDetailPage = ({ onBack, tourId, isPublicView = false }: { onBac
|
||||
disabled={joinRequestActionId === req.id}
|
||||
onClick={async () => {
|
||||
if (!currentTour) return;
|
||||
setConfirmState({
|
||||
open: true,
|
||||
const isConfirmed = await confirm({
|
||||
title: 'Chấp nhận yêu cầu',
|
||||
message: `Chấp nhận ${req.user?.name || req.userId} vào tour?`,
|
||||
onConfirm: async () => {
|
||||
setJoinRequestActionId(req.id);
|
||||
try {
|
||||
await acceptJoinRequest(currentTour.id, req.id);
|
||||
setJoinRequests((prev) => prev.filter((r) => r.id !== req.id));
|
||||
} catch (e: any) {
|
||||
notificationModal.openModal('Thông báo', e.message || 'Không thể chấp nhận yêu cầu', 'error');
|
||||
} finally {
|
||||
setJoinRequestActionId(null);
|
||||
setConfirmState({ open: false });
|
||||
}
|
||||
},
|
||||
message: `Chấp nhận ${req.user?.name || req.userId} vào tour?`
|
||||
});
|
||||
if (isConfirmed) {
|
||||
setJoinRequestActionId(req.id);
|
||||
try {
|
||||
await acceptJoinRequest(currentTour.id, req.id);
|
||||
setJoinRequests((prev) => prev.filter((r) => r.id !== req.id));
|
||||
} catch (e: any) {
|
||||
notify({ title: 'Thông báo', message: e.message || 'Không thể chấp nhận yêu cầu', type: 'error' });
|
||||
} finally {
|
||||
setJoinRequestActionId(null);
|
||||
}
|
||||
}
|
||||
}}
|
||||
className="p-2 rounded-xl bg-green-50 text-green-700 hover:bg-green-100 disabled:opacity-50"
|
||||
aria-label="Accept"
|
||||
@@ -932,23 +938,21 @@ export const TourDetailPage = ({ onBack, tourId, isPublicView = false }: { onBac
|
||||
disabled={joinRequestActionId === req.id}
|
||||
onClick={async () => {
|
||||
if (!currentTour) return;
|
||||
setConfirmState({
|
||||
open: true,
|
||||
const isConfirmed = await confirm({
|
||||
title: 'Từ chối yêu cầu',
|
||||
message: `Từ chối ${req.user?.name || req.userId} tham gia tour?`,
|
||||
onConfirm: async () => {
|
||||
setJoinRequestActionId(req.id);
|
||||
try {
|
||||
await rejectJoinRequest(currentTour.id, req.id);
|
||||
setJoinRequests((prev) => prev.filter((r) => r.id !== req.id));
|
||||
} catch (e: any) {
|
||||
notificationModal.openModal('Thông báo', e.message || 'Không thể từ chối yêu cầu', 'error');
|
||||
} finally {
|
||||
setJoinRequestActionId(null);
|
||||
setConfirmState({ open: false });
|
||||
}
|
||||
},
|
||||
message: `Từ chối ${req.user?.name || req.userId} tham gia tour?`
|
||||
});
|
||||
if (isConfirmed) {
|
||||
setJoinRequestActionId(req.id);
|
||||
try {
|
||||
await rejectJoinRequest(currentTour.id, req.id);
|
||||
setJoinRequests((prev) => prev.filter((r) => r.id !== req.id));
|
||||
} catch (e: any) {
|
||||
notify({ title: 'Thông báo', message: e.message || 'Không thể từ chối yêu cầu', type: 'error' });
|
||||
} finally {
|
||||
setJoinRequestActionId(null);
|
||||
}
|
||||
}
|
||||
}}
|
||||
className="p-2 rounded-xl bg-red-50 text-red-700 hover:bg-red-100 disabled:opacity-50"
|
||||
aria-label="Reject"
|
||||
@@ -1168,7 +1172,7 @@ export const TourDetailPage = ({ onBack, tourId, isPublicView = false }: { onBac
|
||||
await removeMember(currentTour.id, selectedMember.userId);
|
||||
setIsMemberDetailOpen(false);
|
||||
} catch (e) {
|
||||
notificationModal.openModal('Thông báo', 'Không thể xóa thành viên', 'error');
|
||||
notify({ title: 'Thông báo', message: 'Không thể xóa thành viên', type: 'error' });
|
||||
}
|
||||
}}
|
||||
className="px-3 py-2 bg-red-600 hover:bg-red-700 text-white rounded-xl text-sm font-bold"
|
||||
@@ -1191,20 +1195,6 @@ export const TourDetailPage = ({ onBack, tourId, isPublicView = false }: { onBac
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<ConfirmModal
|
||||
isOpen={confirmState.open}
|
||||
title={confirmState.title}
|
||||
message={confirmState.message}
|
||||
onConfirm={() => confirmState.onConfirm?.()}
|
||||
onCancel={() => setConfirmState({ open: false })}
|
||||
/>
|
||||
<NotificationModal
|
||||
isOpen={notificationModal.modalState?.isOpen ?? false}
|
||||
title={notificationModal.modalState?.title}
|
||||
message={notificationModal.modalState?.message}
|
||||
type={notificationModal.modalState?.type}
|
||||
onConfirm={() => notificationModal.closeModal()}
|
||||
/>
|
||||
<CommentModal
|
||||
isOpen={isCommentModalOpen}
|
||||
onClose={() => setIsCommentModalOpen(false)}
|
||||
|
||||
Reference in New Issue
Block a user