fix: lỗi hiển thị trong itineraryTimeline
This commit is contained in:
@@ -75,6 +75,14 @@ export const ItineraryTimeline = ({
|
||||
const [isCommentModalOpen, setIsCommentModalOpen] = useState(false);
|
||||
const [commentLocationId, setCommentLocationId] = useState('');
|
||||
const [commentLocationName, setCommentLocationName] = useState('');
|
||||
|
||||
// State to track single expanded stage (exclusive single-expansion mode)
|
||||
const [expandedStageId, setExpandedStageId] = useState<string | null>(legs.length > 0 ? legs[0]?.id : null);
|
||||
|
||||
const toggleStageExpanded = (legId: string) => {
|
||||
// Exclusive mode: if clicking the same stage, close it. Otherwise, open only the clicked one.
|
||||
setExpandedStageId(prevId => prevId === legId ? null : legId);
|
||||
};
|
||||
|
||||
// Tối ưu hóa: Cập nhật UI ngay lập tức bằng cách can thiệp vào State của Store
|
||||
const handleCommentIncrement = (locationId: string) => {
|
||||
@@ -197,11 +205,14 @@ export const ItineraryTimeline = ({
|
||||
|
||||
useEffect(() => {
|
||||
console.log("ItineraryTimeline: Legs updated", legs);
|
||||
// Initialize first leg as expanded when legs change
|
||||
if (legs.length > 0 && !expandedStageId) {
|
||||
setExpandedStageId(legs[0].id);
|
||||
}
|
||||
}, [legs]);
|
||||
|
||||
return (
|
||||
<div id="itinerary-timeline-print-zone" className="max-w-2xl mx-auto p-0 bg-gray-50 min-h-screen">
|
||||
<div className="px-2 pt-4">
|
||||
<div id="itinerary-timeline-print-zone" className="timeline-scroll-container itinerary-timeline-container">
|
||||
{legs.length === 0 ? (
|
||||
<div className="text-center py-20 bg-white rounded-3xl border-2 border-dashed border-gray-200">
|
||||
<List className="w-12 h-12 text-gray-300 mx-auto mb-4" />
|
||||
@@ -220,9 +231,12 @@ export const ItineraryTimeline = ({
|
||||
const prevLegLastLoc = legIdx > 0 ? legs[legIdx - 1]?.locations.slice(-1)[0] : null;
|
||||
|
||||
return (
|
||||
<div key={leg.id} className="relative mb-12 animate-in fade-in slide-in-from-bottom-4 duration-300">
|
||||
{/* Leg Header */}
|
||||
<div className="sticky top-[136px] z-20 bg-gray-50/90 backdrop-blur-sm flex items-center mb-6 py-2 px-2">
|
||||
<section key={leg.id} className={`folder-node-wrapper ${expandedStageId === leg.id ? 'expanded' : 'collapsed'}`}>
|
||||
{/* Folder header row - clickable to toggle exclusive expansion */}
|
||||
<div
|
||||
onClick={() => toggleStageExpanded(leg.id)}
|
||||
className="folder-header-row animate-in fade-in slide-in-from-bottom-4 duration-300 hover:bg-gray-50/50 transition-colors"
|
||||
>
|
||||
<div className="font-black text-blue-600 text-xl truncate flex-1 flex flex-col gap-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="bg-blue-600 text-white w-8 h-8 rounded-lg flex items-center justify-center text-sm shrink-0">
|
||||
@@ -245,24 +259,24 @@ export const ItineraryTimeline = ({
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 ml-4">
|
||||
<div className="flex items-center gap-2 ml-4" onClick={(e) => e.stopPropagation()}>
|
||||
{canEdit && (
|
||||
<>
|
||||
<button
|
||||
onClick={() => onAddLocation?.(leg.id, legIdx === 0 && leg.locations.length === 0)}
|
||||
onClick={(e) => { e.stopPropagation(); onAddLocation?.(leg.id, legIdx === 0 && leg.locations.length === 0); }}
|
||||
className="p-2 text-gray-400 hover:text-blue-600 hover:bg-blue-50 rounded-xl transition-all"
|
||||
title="Thêm địa điểm vào chặng này"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleEditLeg(leg)}
|
||||
onClick={(e) => { e.stopPropagation(); handleEditLeg(leg); }}
|
||||
className="p-2 text-gray-400 hover:text-blue-600 hover:bg-blue-50 rounded-xl transition-all"
|
||||
>
|
||||
<Edit2 className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDeleteLeg(leg.id)}
|
||||
onClick={(e) => { e.stopPropagation(); handleDeleteLeg(leg.id); }}
|
||||
className="p-2 text-gray-400 hover:text-red-600 hover:bg-red-50 rounded-xl transition-all"
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
@@ -298,12 +312,16 @@ export const ItineraryTimeline = ({
|
||||
)} {/* Only show optimize button if canEdit */}
|
||||
</div>
|
||||
|
||||
{/* Vertical Line for the whole leg */}
|
||||
{/* Mở rộng đường kẻ xuống dưới (bottom-[-3rem]) để nối liền với chặng tiếp theo */}
|
||||
<div className={`absolute left-6 top-16 ${legIdx === legs.length - 1 ? 'bottom-0' : 'bottom-[-3.5rem]'} w-0.5 bg-blue-100 -z-0`} />
|
||||
{/* Scrollable content body with proper z-index layering */}
|
||||
<div className="child-nodes-list-wrapper">
|
||||
{/* Folder child content box - Grid accordion for exclusive expansion */}
|
||||
<div className={`folder-child-content-box ${expandedStageId === leg.id ? 'expanded' : ''}`}>
|
||||
<div className="child-nodes-list relative">
|
||||
{/* Vertical Line for the whole leg - Dynamic height */}
|
||||
<div className="absolute left-6 top-16 w-0.5 bg-blue-100 -z-0 h-full" />
|
||||
|
||||
<div className="ml-2">
|
||||
{/* Nút thêm nhanh "Điểm xuất phát" cho Chặng 1 nếu chưa có */}
|
||||
<div className="ml-2">
|
||||
{/* Nút thêm nhanh "Điểm xuất phát" cho Chặng 1 nếu chưa có */}
|
||||
{legIdx === 0 && !leg.locations.some((loc: any) => loc.plannedStart && new Date(loc.plannedStart).getTime() === 0) && (
|
||||
<div className="relative flex group mb-6 opacity-80 hover:opacity-100 transition-opacity">
|
||||
<div className="z-10 mt-1.5 mr-4">
|
||||
@@ -522,8 +540,11 @@ export const ItineraryTimeline = ({
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div> {/* End of ml-2 wrapper */}
|
||||
</div> {/* End of child-nodes-list */}
|
||||
</div> {/* End of folder-child-content-box */}
|
||||
</div> {/* End of child-nodes-list-wrapper */}
|
||||
</section>
|
||||
);
|
||||
})
|
||||
)}
|
||||
@@ -546,7 +567,6 @@ export const ItineraryTimeline = ({
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Modal Khai báo số chặng (Popover) */}
|
||||
{isLegCountModalOpen && (
|
||||
|
||||
@@ -5,9 +5,10 @@ import { useNotification } from '@/hooks/useNotification';
|
||||
|
||||
interface TourChatProps {
|
||||
tourId: string;
|
||||
embedded?: boolean;
|
||||
}
|
||||
|
||||
export const TourChat: React.FC<TourChatProps> = ({ tourId }) => {
|
||||
export const TourChat: React.FC<TourChatProps> = ({ tourId, embedded = false }) => {
|
||||
const notify = useNotification();
|
||||
const [messages, setMessages] = useState<any[]>([]);
|
||||
const [newMessage, setNewMessage] = useState('');
|
||||
@@ -416,21 +417,24 @@ export const TourChat: React.FC<TourChatProps> = ({ tourId }) => {
|
||||
}
|
||||
};
|
||||
|
||||
// currentUserId is defined at the top
|
||||
|
||||
return (
|
||||
<div className="bg-white border border-gray-150 rounded-2xl shadow-lg overflow-hidden flex flex-col h-[500px]">
|
||||
{/* Chat Header */}
|
||||
<div className="p-4 border-b border-gray-150 flex items-center gap-2 bg-gray-50/50">
|
||||
<MessageSquare className="w-5 h-5 text-blue-500" />
|
||||
<div>
|
||||
<h3 className="text-sm font-bold text-gray-800">Trò chuyện nhóm hành trình</h3>
|
||||
<p className="text-[10px] text-gray-400 font-medium">Nơi trao đổi thông tin, hình ảnh và định vị giữa các thành viên</p>
|
||||
</div>
|
||||
<div className={`chat-viewport-wrapper flex flex-col !overflow-hidden !w-full ${embedded ? 'h-full flex-1' : 'h-[100dvh]'} bg-white`}>
|
||||
{/* Fixed Top Layout Block - Header and Tabs Container */}
|
||||
<div className="fixed-top-layout-block flex-shrink-0 !w-full z-50 bg-white border-b border-gray-200">
|
||||
{/* Chat Header - Only show when not embedded */}
|
||||
{!embedded && (
|
||||
<div className="p-4 border-b border-gray-200 bg-white flex items-center gap-2">
|
||||
<MessageSquare className="w-5 h-5 text-blue-500" />
|
||||
<div>
|
||||
<h3 className="text-sm font-bold text-gray-800">Trò chuyện nhóm hành trình</h3>
|
||||
<p className="text-[10px] text-gray-400 font-medium">Nơi trao đổi thông tin, hình ảnh và định vị giữa các thành viên</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Messages list */}
|
||||
<div className="flex-1 p-4 overflow-y-auto flex flex-col gap-3 min-h-0 bg-slate-50/20">
|
||||
{/* Chat History Scroll Viewport - ONLY scrollable area */}
|
||||
<div className="chat-history-scroll-viewport flex-1 min-h-0 !overflow-y-auto !overflow-x-hidden p-4 flex flex-col gap-3 bg-slate-50/20">
|
||||
{loading ? (
|
||||
<div className="flex-1 flex items-center justify-center text-gray-400 text-xs gap-1.5">
|
||||
<Loader2 className="w-4 h-4 animate-spin text-blue-500" /> Đang tải tin nhắn...
|
||||
@@ -468,7 +472,7 @@ export const TourChat: React.FC<TourChatProps> = ({ tourId }) => {
|
||||
<div className={`p-3 rounded-2xl text-xs font-medium leading-relaxed flex flex-col gap-1.5 relative group ${
|
||||
isMe
|
||||
? 'bg-blue-600 text-white rounded-tr-none'
|
||||
: 'bg-white text-gray-700 rounded-tl-none border border-gray-150 shadow-sm'
|
||||
: 'bg-white text-gray-700 rounded-tl-none border border-gray-200 shadow-sm'
|
||||
}`}>
|
||||
{/* Attachment Image */}
|
||||
{msg.attachmentUrl && (
|
||||
@@ -498,7 +502,7 @@ export const TourChat: React.FC<TourChatProps> = ({ tourId }) => {
|
||||
className={`flex items-center gap-1.5 px-3 py-2 rounded-xl text-[11px] font-bold transition-all border ${
|
||||
isMe
|
||||
? 'bg-blue-700 border-blue-600 text-blue-100 hover:bg-blue-800'
|
||||
: 'bg-gray-100 border-gray-200 text-gray-750 hover:bg-gray-200'
|
||||
: 'bg-gray-100 border-gray-200 text-gray-700 hover:bg-gray-200'
|
||||
}`}
|
||||
>
|
||||
<MapPin className="w-3.5 h-3.5 text-rose-500 shrink-0 animate-bounce" />
|
||||
@@ -525,7 +529,7 @@ export const TourChat: React.FC<TourChatProps> = ({ tourId }) => {
|
||||
|
||||
{/* Previews (Image & GPS Location) */}
|
||||
{(imagePreview || attachedLocation) && (
|
||||
<div className="px-4 py-2 border-t border-gray-150 bg-gray-50/80 flex flex-wrap gap-2">
|
||||
<div className="px-4 py-2 border-t border-gray-200 bg-gray-50/80 flex flex-wrap gap-2 !flex-shrink-0">
|
||||
{imagePreview && (
|
||||
<div className="relative w-16 h-16 rounded-lg overflow-hidden border border-gray-200 shadow-sm">
|
||||
<img src={imagePreview} alt="Preview" className="w-full h-full object-cover" />
|
||||
@@ -557,8 +561,8 @@ export const TourChat: React.FC<TourChatProps> = ({ tourId }) => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Chat Input wrapper */}
|
||||
<div className="relative">
|
||||
{/* Locked Chat Input Footer */}
|
||||
<div className="locked-chat-input-footer !flex-shrink-0 !w-full !border-t border-gray-200 bg-white !z-40" style={{ paddingBottom: 'env(safe-area-inset-bottom, 12px)' }}>
|
||||
{/* Mention list dropdown */}
|
||||
{showMentionList && filteredParticipants.length > 0 && (
|
||||
<div
|
||||
@@ -589,7 +593,7 @@ export const TourChat: React.FC<TourChatProps> = ({ tourId }) => {
|
||||
{/* Chat Input form */}
|
||||
<form
|
||||
onSubmit={handleSendMessage}
|
||||
className="p-3 border-t border-gray-150 bg-gray-50 flex gap-2 items-center"
|
||||
className="p-3 flex gap-2 items-center !w-full relative"
|
||||
>
|
||||
<input
|
||||
type="file"
|
||||
|
||||
@@ -1,10 +1,299 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--main-header-height: 56px;
|
||||
--sub-nav-height: 48px;
|
||||
--combined-top-height: 104px;
|
||||
}
|
||||
|
||||
html, body, #root, .app-container {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
max-width: 100vw !important;
|
||||
overflow-x: hidden !important;
|
||||
}
|
||||
|
||||
/* Fix viewport overflow on all container levels */
|
||||
body, html {
|
||||
width: 100%;
|
||||
max-width: 100vw;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.itinerary-timeline-page {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.main-top-bar {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
height: var(--main-header-height);
|
||||
z-index: 50;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.sub-nav-menu {
|
||||
position: sticky;
|
||||
top: var(--main-header-height);
|
||||
height: var(--sub-nav-height);
|
||||
z-index: 40;
|
||||
width: 100% !important;
|
||||
margin: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
box-shadow: none !important;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
/* Master viewport wrapper - contains entire itinerary */
|
||||
.itinerary-viewport {
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
height: 100dvh !important;
|
||||
width: 100vw !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
.itinerary-viewport-wrapper {
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
height: 100dvh !important;
|
||||
width: 100vw !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
/* Global sticky header zone - locks main header + sub-nav at top */
|
||||
.global-sticky-header-zone {
|
||||
position: sticky !important;
|
||||
top: 0;
|
||||
z-index: 100 !important;
|
||||
background-color: #ffffff;
|
||||
flex-shrink: 0 !important;
|
||||
}
|
||||
|
||||
/* Fixed header block (handled at parent TourDetailPage level) */
|
||||
.fixed-top-header-block {
|
||||
flex-shrink: 0 !important;
|
||||
width: 100% !important;
|
||||
background-color: #ffffff;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
/* Timeline scroll container - main scrollable region */
|
||||
.directory-scroll-viewport {
|
||||
flex: 1 !important;
|
||||
overflow-y: auto !important;
|
||||
position: relative !important;
|
||||
z-index: 10 !important;
|
||||
width: 100% !important;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
background-color: #f8fafc;
|
||||
}
|
||||
|
||||
.timeline-scroll-container {
|
||||
flex: 1 !important;
|
||||
overflow-y: auto !important;
|
||||
position: relative !important;
|
||||
z-index: 10 !important;
|
||||
width: 100% !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
/* Scrollable timeline area - removes all padding gaps */
|
||||
.timeline-scroll-area {
|
||||
flex: 1 1 0% !important;
|
||||
overflow-y: auto !important;
|
||||
width: 100% !important;
|
||||
padding: 0 !important;
|
||||
margin-top: 0px !important;
|
||||
background-color: #f8fafc;
|
||||
}
|
||||
|
||||
.timeline-scroll-viewport {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Main timeline container with dynamic height support */
|
||||
.itinerary-timeline-container {
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
width: 100% !important;
|
||||
max-width: 100vw !important;
|
||||
height: auto !important;
|
||||
overflow-x: hidden !important;
|
||||
position: relative !important;
|
||||
}
|
||||
|
||||
/* Individual folder node wrapper - base stage container */
|
||||
.folder-node-wrapper {
|
||||
width: 100% !important;
|
||||
background-color: #ffffff;
|
||||
margin-bottom: 1px !important;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Individual stage card block - backwards compatibility */
|
||||
.stage-card-block {
|
||||
width: 100% !important;
|
||||
background-color: #ffffff;
|
||||
margin-bottom: 1px !important;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Stage section - semantic wrapper */
|
||||
.stage-section {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
display: block !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
/* Folder header row - sticky positioning for pinned effect */
|
||||
.folder-header-row {
|
||||
position: sticky !important;
|
||||
top: 0px !important;
|
||||
z-index: 30 !important;
|
||||
width: 100% !important;
|
||||
padding: 12px 16px !important;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #ffffff !important;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid #f3f4f6;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.folder-header-row:hover {
|
||||
background-color: #f9fafb !important;
|
||||
}
|
||||
|
||||
/* Stage header row - sticky positioning for pinned effect */
|
||||
.stage-header-row {
|
||||
position: sticky !important;
|
||||
top: 0px !important;
|
||||
z-index: 40 !important;
|
||||
width: 100% !important;
|
||||
padding: 12px 16px !important;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #ffffff !important;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid #f3f4f6;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.stage-header-row:hover {
|
||||
background-color: #f9fafb !important;
|
||||
}
|
||||
|
||||
/* Alternate sticky header styling (kept for backwards compatibility) */
|
||||
.stage-sticky-header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 30;
|
||||
background-color: #ffffff;
|
||||
width: 100%;
|
||||
padding: 8px 16px;
|
||||
border-bottom: 1px solid #f3f4f6;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Folder child content box - Grid accordion for exclusive expansion */
|
||||
.folder-child-content-box {
|
||||
display: grid !important;
|
||||
grid-template-rows: 0fr;
|
||||
transition: grid-template-rows 0.25s cubic-bezier(0.4, 0, 0.2, 1) !important;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Expanded folder state - children visible */
|
||||
.folder-child-content-box.expanded {
|
||||
grid-template-rows: 1fr !important;
|
||||
}
|
||||
|
||||
/* Wrapper for child nodes list - proper z-index layering */
|
||||
.child-nodes-list-wrapper {
|
||||
position: relative;
|
||||
z-index: 20 !important;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Child nodes list - content container within expanded folder */
|
||||
.child-nodes-list {
|
||||
overflow: hidden;
|
||||
min-height: 0px;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
/* CSS Grid Accordion Engine - Smooth expand/collapse without layout shattering */
|
||||
.stage-accordion-content {
|
||||
display: grid !important;
|
||||
grid-template-rows: 0fr;
|
||||
transition: grid-template-rows 0.25s ease-out !important;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Expanded state - opens to full content height */
|
||||
.stage-accordion-content.expanded {
|
||||
grid-template-rows: 1fr !important;
|
||||
}
|
||||
|
||||
/* Scrollable content body - lower z-index so it scrolls behind header */
|
||||
.stage-scrollable-content-body {
|
||||
position: relative;
|
||||
z-index: 20 !important;
|
||||
width: 100%;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
/* Inner content wrapper - structural compliance */
|
||||
.stage-points-list {
|
||||
overflow: hidden;
|
||||
min-height: 0px;
|
||||
padding: 0 16px 16px 16px;
|
||||
}
|
||||
|
||||
/* Legacy accordion container classes (for backwards compatibility) */
|
||||
.stage-accordion-transition-container {
|
||||
display: grid !important;
|
||||
grid-template-rows: 0fr;
|
||||
transition: grid-template-rows 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.stage-accordion-transition-container.expanded {
|
||||
grid-template-rows: 1fr !important;
|
||||
}
|
||||
|
||||
.stage-accordion-inner-content {
|
||||
overflow: hidden !important;
|
||||
min-height: 0px !important;
|
||||
width: 100% !important;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.stage-content-body {
|
||||
width: 100%;
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1681,9 +1681,9 @@ const renderInitialsAvatar = (name: string, sizeClass = 'w-10 h-10 text-sm') =>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* TAB 4: REALTIME CHAT */}
|
||||
{activeTab === 'chats' && (
|
||||
<div className="flex-1 w-full bg-slate-900/40 border-0 md:border border-slate-800/80 md:rounded-3xl overflow-hidden flex animate-in fade-in slide-in-from-bottom-2 duration-300">
|
||||
{/* TAB 4: REALTIME CHAT */}
|
||||
{activeTab === 'chats' && (
|
||||
<div className="flex flex-col h-[100dvh] md:h-auto w-full bg-slate-900/40 border-0 md:border border-slate-800/80 md:rounded-3xl overflow-hidden flex animate-in fade-in slide-in-from-bottom-2 duration-300">
|
||||
|
||||
{/* Chats List sidebar: show if not mobile OR if mobile and no active chat user */}
|
||||
{(!isMobile || !activeChatUser) && (
|
||||
|
||||
@@ -2003,9 +2003,9 @@ export const TourDetailPage = ({
|
||||
</div>
|
||||
|
||||
{/* Main Content Area - Điều chỉnh max-width khi ở chế độ bản đồ */}
|
||||
<div className={`${activeTab === 'plan' && viewMode === 'map' ? 'w-full' : 'max-w-2xl mx-auto'} mt-6 px-4 pb-24`}>
|
||||
<div className={`${activeTab === 'plan' && viewMode === 'map' ? 'w-full' : 'max-w-2xl mx-auto'} px-4 pb-24`}>
|
||||
{/* Tab Switcher */}
|
||||
<div className="bg-white rounded-2xl shadow-lg border border-gray-100 p-1 flex mb-6 sticky top-20 z-20">
|
||||
<div className="bg-white rounded-2xl shadow-lg border border-gray-100 p-1 flex mb-6 sticky top-[60px] z-40">
|
||||
{tabs.map((tab) => (
|
||||
<button
|
||||
key={tab.id}
|
||||
@@ -2013,7 +2013,7 @@ export const TourDetailPage = ({
|
||||
setActiveTab(tab.id as any);
|
||||
if (tab.id === 'chat') setUnreadChatCount(0);
|
||||
}}
|
||||
className={`flex-1 flex items-center justify-center py-3 rounded-xl text-sm font-semibold transition-all relative ${tab.id === 'settings' && isPublicView ? 'hidden' : ''} ${ // Hide settings tab in public view
|
||||
className={`flex-1 flex items-center justify-center py-2 rounded-xl text-xs font-semibold transition-all relative ${tab.id === 'settings' && isPublicView ? 'hidden' : ''} ${ // Hide settings tab in public view
|
||||
activeTab === tab.id
|
||||
? 'bg-blue-50 text-blue-600 shadow-sm'
|
||||
: 'text-gray-400 hover:text-gray-500 hover:bg-gray-50'
|
||||
@@ -3004,8 +3004,17 @@ export const TourDetailPage = ({
|
||||
/>
|
||||
)}
|
||||
{activeTab === 'chat' && currentTour && (
|
||||
<div className="animate-in fade-in slide-in-from-bottom-2">
|
||||
<TourChat tourId={currentTour.id} />
|
||||
<div className="fixed left-0 right-0 bottom-0 flex flex-col bg-white z-[15]" style={{top: 'calc(60px + 5px)'}}>
|
||||
{/* Chat Group Header Banner - Fixed below nav + tabs, sticky to title */}
|
||||
<div className="flex-shrink-0 p-3 border-b border-gray-200 bg-white flex items-center gap-2">
|
||||
<MessageSquare className="w-5 h-5 text-blue-500" />
|
||||
<div>
|
||||
<h3 className="text-sm font-bold text-gray-800">Trò chuyện trong nhóm</h3>
|
||||
<p className="text-[10px] text-gray-400 font-medium">Nơi trao đổi thông tin, hình ảnh và định vị giữa các thành viên</p>
|
||||
</div>
|
||||
</div>
|
||||
{/* TourChat with proper scrollable area */}
|
||||
<TourChat tourId={currentTour.id} embedded={true} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user