Sửa lỗi OWNER, MANAGER không thể chia sẻ link

This commit is contained in:
2026-06-16 09:33:15 +07:00
parent 02c493f7e0
commit 63da413b3c
9 changed files with 138 additions and 80 deletions
+2 -2
View File
@@ -59,7 +59,7 @@ const MapPicker = ({ onPick, center }: { onPick: (latlng: L.LatLng) => void, cen
);
};
export const AddLocationModal = ({ isOpen, onClose, tourId, initialLegId, editingLocation }: { isOpen: boolean, onClose: () => void, tourId: string, initialLegId?: string, editingLocation?: any }) => {
export const AddLocationModal = ({ isOpen, onClose, tourId, initialLegId, editingLocation, isPublicView = false }: { isOpen: boolean, onClose: () => void, tourId: string, initialLegId?: string, editingLocation?: any, isPublicView?: boolean }) => {
const [formData, setFormData] = useState({
name: '',
address: '',
@@ -147,7 +147,7 @@ export const AddLocationModal = ({ isOpen, onClose, tourId, initialLegId, editin
};
// 3. Early return phải nằm SAU tất cả các khai báo Hook
if (!isOpen) return null;
if (!isOpen || isPublicView) return null; // Do not render if public view
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
+4 -3
View File
@@ -9,7 +9,8 @@ interface AddMemberModalProps {
joinRequests?: Array<{ id: string; userId: string; user?: { id: string; name: string; email: string }; status: string; requestedById: string }>;
onRemoveMember?: (userId: string) => Promise<void>;
onMemberAdded?: () => void;
userRole?: string;
userRole?: string; // User's role in the tour
isPublicView?: boolean; // New prop to indicate public view
}
export const AddMemberModal: React.FC<AddMemberModalProps> = ({ isOpen, onClose, tourId, participants = [], joinRequests = [], onRemoveMember, onMemberAdded, userRole }) => {
@@ -28,7 +29,7 @@ export const AddMemberModal: React.FC<AddMemberModalProps> = ({ isOpen, onClose,
const participantIds = useMemo(() => new Set(participants.map((p) => p.userId)), [participants]);
const requestUserIds = useMemo(() => new Set(joinRequests.map((r) => (r as any).userId)), [joinRequests]);
const visibleUsers = useMemo(() => users.filter((u) => !participantIds.has(u.id)), [users, participantIds]);
const canCreateDirectly = !userRole || userRole === 'OWNER' || userRole === 'MANAGER';
const fetchUsers = async () => {
@@ -134,7 +135,7 @@ export const AddMemberModal: React.FC<AddMemberModalProps> = ({ isOpen, onClose,
}
};
if (!isOpen) return null;
if (!isOpen || isPublicView) return null; // Do not render if public view
return (
<div className="fixed inset-0 z-[2000] flex items-center justify-center p-4">
+4 -3
View File
@@ -14,7 +14,8 @@ interface CommentModalProps {
onClose: () => void;
locationId: string;
locationName: string;
onCommentAdded?: () => void;
onCommentAdded?: () => void; // Callback to update comment count on parent
isPublicView?: boolean; // New prop to indicate public view
}
export const CommentModal: React.FC<CommentModalProps> = ({ isOpen, onClose, locationId, locationName }) => {
@@ -146,10 +147,10 @@ export const CommentModal: React.FC<CommentModalProps> = ({ isOpen, onClose, loc
value={newComment}
onChange={(e) => setNewComment(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleSend()}
placeholder="Viết bình luận..."
placeholder={isPublicView ? 'Đăng nhập để bình luận...' : 'Viết bình luận...'}
className="flex-1 bg-gray-50 border border-gray-200 rounded-2xl px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:bg-white transition-all"
/>
<button onClick={handleSend} disabled={!newComment.trim()} className="p-3 bg-blue-600 text-white rounded-2xl hover:bg-blue-700 disabled:opacity-50 disabled:bg-gray-300 transition-all active:scale-95 shadow-lg shadow-blue-100">
<button onClick={handleSend} disabled={!newComment.trim() || isPublicView} className="p-3 bg-blue-600 text-white rounded-2xl hover:bg-blue-700 disabled:opacity-50 disabled:bg-gray-300 transition-all active:scale-95 shadow-lg shadow-blue-100">
<Send className="w-4 h-4" />
</button>
</div>
+13 -8
View File
@@ -37,10 +37,11 @@ const formatTravelTime = (minutes: number) => {
return mins > 0 ? `${hours} giờ ${mins} phút` : `${hours} giờ`;
};
export const ItineraryTimeline = ({
export const ItineraryTimeline = ({
onAddLocation,
onEditLocation
}: { onAddLocation?: (legId: string) => void, onEditLocation?: (location: any) => void }) => {
onEditLocation,
isPublicView = false
}: { onAddLocation?: (legId: string) => void, onEditLocation?: (location: any) => void, isPublicView?: boolean }) => {
// Tối ưu hóa selectors để chỉ lắng nghe những thay đổi cần thiết
const currentTour = useTourStore(state => state.currentTour);
const legs = useTourStore(state => state.legs);
@@ -48,11 +49,15 @@ export const ItineraryTimeline = ({
const optimizeRouting = useTourStore(state => state.optimizeRouting);
const addLeg = useTourStore(state => state.addLeg);
const updateLeg = useTourStore(state => state.updateLeg);
// Removed: const { isPublicView } = useTourStore(state => state); // isPublicView is passed as a prop
const deleteLeg = useTourStore(state => state.deleteLeg);
const initializeLegs = useTourStore(state => state.initializeLegs);
const fetchTour = useTourStore(state => state.fetchTour);
const deleteLocation = useTourStore(state => state.deleteLocation);
// Khai báo logic canEdit để sử dụng trong toàn bộ component
const canEdit = isPublicView ? false : ['OWNER', 'MANAGER'].includes(userRole || '');
const [confirmState, setConfirmState] = useState<{ open: boolean; title?: string; message?: string; onConfirm?: () => void }>({ open: false });
const [isLegCountModalOpen, setIsLegCountModalOpen] = useState(false);
const [tempLegCount, setTempLegCount] = useState(3);
@@ -202,7 +207,7 @@ export const ItineraryTimeline = ({
)}
</div>
<div className="flex items-center gap-2 ml-4">
{['OWNER', 'MANAGER'].includes(userRole || '') && (
{canEdit && (
<>
<button
onClick={() => onAddLocation?.(leg.id)}
@@ -236,7 +241,7 @@ export const ItineraryTimeline = ({
</div>
</div>
)}
{totalDwellMinutes > 0 && (
{totalDwellMinutes > 0 && ( // Always show dwell time
<div className="hidden md:flex text-xs font-bold text-amber-600 bg-amber-50 px-2 py-1 rounded-lg border border-amber-100 items-center gap-1">
<Clock className="w-3 h-3" />
Dừng: {formatTravelTime(totalDwellMinutes)}
@@ -251,7 +256,7 @@ export const ItineraryTimeline = ({
<Zap className="w-3 h-3" />
Tối ưu
</button>
)}
)} {/* Only show optimize button if canEdit */}
</div>
{/* Vertical Line for the whole leg */}
@@ -365,7 +370,7 @@ export const ItineraryTimeline = ({
Thực tế: {format(parseISO(location.actualStart), 'HH:mm')}
</div>
)}
{['OWNER', 'MANAGER'].includes(userRole || '') && !isStartPoint && !isEndPoint && (
{canEdit && !isStartPoint && !isEndPoint && ( // Only show edit/delete if canEdit
<div className="flex gap-1 mt-2">
<button onClick={() => onEditLocation?.(location)} className="p-1 text-gray-400 hover:text-blue-600 transition-colors">
<Edit2 className="w-3.5 h-3.5" />
@@ -409,7 +414,7 @@ export const ItineraryTimeline = ({
)}
{/* Actions at the bottom of the list */}
{['OWNER', 'MANAGER'].includes(userRole || '') && (
{canEdit && ( // Only show these buttons if canEdit
<div className="flex flex-col gap-3 pb-20 mt-8">
<button
onClick={handleDeclareLegs}