fix: cách các pages hiển thị trên browser điện thoại

This commit is contained in:
2026-06-26 10:21:01 +07:00
parent 6d90a47c24
commit cfb97d976f
5 changed files with 64 additions and 64 deletions
+12 -12
View File
@@ -259,11 +259,11 @@ export const ItineraryTimeline = ({
}, [legs, expandedStageId]);
return (
<div id="itinerary-timeline-print-zone" className="timeline-scroll-container itinerary-timeline-container">
<div id="itinerary-timeline-print-zone" className="timeline-scroll-container itinerary-timeline-container overflow-x-hidden">
{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" />
<p className="text-gray-500 font-medium">Chưa chặng nào trong lộ trình.</p>
<div className="text-center py-16 sm:py-20 bg-white rounded-3xl border-2 border-dashed border-gray-200">
<List className="w-10 h-10 sm:w-12 sm:h-12 text-gray-300 mx-auto mb-3 sm:mb-4" />
<p className="text-gray-500 font-medium text-xs sm:text-sm">Chưa chặng nào trong lộ trình.</p>
</div>
) : (
legs.map((leg, legIdx) => {
@@ -278,15 +278,15 @@ export const ItineraryTimeline = ({
const prevLegLastLoc = legIdx > 0 ? legs[legIdx - 1]?.locations.slice(-1)[0] : null;
return (
<section key={leg.id} id={`leg-anchor-node-${leg.id}`} className={`folder-node-wrapper scroll-mt-[70px] ${expandedStageId === leg.id ? 'expanded' : 'collapsed'}`}>
<section key={leg.id} id={`leg-anchor-node-${leg.id}`} className={`folder-node-wrapper scroll-mt-[60px] sm:scroll-mt-[70px] ${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="font-black text-blue-600 text-lg sm: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">
<span className="bg-blue-600 text-white w-7 h-7 sm:w-8 sm:h-8 rounded-lg flex items-center justify-center text-xs sm:text-sm shrink-0">
{leg.sequence}
</span>
<div className="flex flex-col overflow-hidden">
@@ -474,21 +474,21 @@ export const ItineraryTimeline = ({
e.stopPropagation();
onNavigate?.(location);
}}
className={`font-semibold text-lg cursor-pointer hover:text-blue-600 transition-colors select-none ${location.status === 'COMPLETED' ? 'text-gray-500 line-through' : 'text-gray-800'}`}
className={`font-semibold text-base sm:text-lg cursor-pointer hover:text-blue-600 transition-colors select-none ${location.status === 'COMPLETED' ? 'text-gray-500 line-through' : 'text-gray-800'}`}
>
{location.name}
</h3>
<div className="flex items-center text-sm text-gray-500 mt-1">
<div className="flex items-center text-xs sm:text-sm text-gray-500 mt-1">
<MapPin className="w-3 h-3 mr-1" />
<span className="truncate max-w-[200px] sm:max-w-md">{location.address}</span>
<span className="truncate max-w-[120px] xs:max-w-[180px] sm:max-w-md">{location.address}</span>
</div>
{location.note && (
<div className="mt-2 text-xs text-gray-600 bg-gray-50 p-2 rounded-lg border border-gray-100 italic">
<div className="mt-1.5 sm:mt-2 text-[10px] sm:text-xs text-gray-600 bg-gray-50 p-1.5 sm:p-2 rounded-lg border border-gray-100 italic">
{location.note}
</div>
)}
{dwellMinutes !== null && (
<div className="flex items-center text-xs text-amber-600 font-medium mt-1">
<div className="flex items-center text-[10px] sm:text-xs text-amber-600 font-medium mt-1">
<Clock className="w-3 h-3 mr-1" />
<span>Thời gian dừng: {formatTravelTime(dwellMinutes)}</span>
</div>
+2 -2
View File
@@ -1121,7 +1121,7 @@ const renderInitialsAvatar = (name: string, sizeClass = 'w-10 h-10 text-sm') =>
}
return (
<div className="h-screen w-full bg-gradient-to-br from-slate-950 via-slate-900 to-slate-950 text-white flex items-center justify-center font-sans overflow-hidden p-0 md:p-6">
<div className="h-auto min-h-dvh w-full bg-gradient-to-br from-slate-950 via-slate-900 to-slate-950 text-white flex flex-col md:flex-row p-3 sm:p-6 gap-4 sm:gap-6 overflow-x-hidden">
{/* Background Glow */}
<div className="hidden md:block absolute top-0 right-0 w-[500px] h-[500px] bg-indigo-500/5 rounded-full blur-[120px] pointer-events-none z-0"></div>
@@ -1129,7 +1129,7 @@ const renderInitialsAvatar = (name: string, sizeClass = 'w-10 h-10 text-sm') =>
<div className={`w-full relative z-10 flex overflow-hidden transition-all duration-350 ${
isMobile
? 'h-full bg-slate-950 flex-col'
? 'flex-col h-full bg-slate-950'
: 'max-w-7xl h-[90vh] bg-slate-900/30 border border-slate-800/80 shadow-2xl rounded-3xl backdrop-blur-md'
}`}>
+26 -26
View File
@@ -230,27 +230,27 @@ export const MyPhotosPage = ({ onBack }: { onBack: () => void }) => {
};
return (
<div className="w-full flex flex-col text-slate-100 bg-transparent font-sans">
<div className="w-full flex flex-col text-slate-100 bg-transparent font-sans min-h-dvh overflow-x-hidden">
{/* Header */}
<div className="bg-slate-900/60 backdrop-blur-md border-b border-slate-800/80 px-6 py-4 flex items-center gap-4 rounded-t-3xl">
<div className="bg-slate-900/60 backdrop-blur-md border-b border-slate-800/80 px-4 py-3 flex items-center gap-3 rounded-t-3xl">
<button onClick={onBack} className="p-2 hover:bg-slate-800 rounded-full transition-colors">
<ChevronLeft className="w-6 h-6 text-slate-400 hover:text-white" />
<ChevronLeft className="w-5 h-5 text-slate-400 hover:text-white" />
</button>
<div>
<h1 className="text-xl font-black text-white">nh của tôi</h1>
<h1 className="text-lg font-black text-white">nh của tôi</h1>
<p className="text-[10px] font-bold text-slate-400 uppercase tracking-widest">Kho lưu trữ nh gốc nhân</p>
</div>
</div>
{/* Filter Bar */}
<div className="bg-slate-900/40 border-b border-slate-800/60 px-6 py-4 flex flex-wrap items-center gap-4 shadow-sm">
<div className="bg-slate-900/40 border-b border-slate-800/60 px-4 py-3 flex flex-wrap items-center gap-3 shadow-sm">
<div className="flex items-center gap-2 text-slate-400">
<Filter className="w-4 h-4" />
<span className="text-xs font-bold uppercase tracking-wider">Bộ lọc:</span>
</div>
<div className="relative min-w-[150px]">
<div className="px-3 py-2 bg-slate-800 text-indigo-300 rounded-xl text-xs font-bold border border-slate-700/60 truncate max-w-[200px]">
<div className="relative min-w-[120px] sm:min-w-[150px]">
<div className="px-3 py-2 bg-slate-800 text-indigo-300 rounded-xl text-xs font-bold border border-slate-700/60 truncate max-w-[180px] sm:max-w-[200px]">
{selectedTourIdForPhoto === 'all' ? 'Tất cả hành trình' : toursWithPhotos.find(t => t.id === selectedTourIdForPhoto)?.title || 'Tour đã chọn'}
</div>
</div>
@@ -295,28 +295,28 @@ export const MyPhotosPage = ({ onBack }: { onBack: () => void }) => {
</div>
</div>
<div className="flex-1 p-6 w-full max-w-7xl mx-auto">
<div className="flex-1 p-4 sm:p-6 w-full max-w-7xl mx-auto">
{isLoading ? (
<div className="flex flex-col items-center justify-center py-20 text-slate-400">
<Loader2 className="w-10 h-10 animate-spin mb-4 text-indigo-500" />
<p className="font-bold text-sm">Đang tải kho nh...</p>
<div className="flex flex-col items-center justify-center py-16 sm:py-20 text-slate-400">
<Loader2 className="w-8 h-8 sm:w-10 sm:h-10 animate-spin mb-3 sm:mb-4 text-indigo-500" />
<p className="font-bold text-xs sm:text-sm">Đang tải kho nh...</p>
</div>
) : photos.length === 0 ? (
<div className="py-24 text-center bg-slate-900/30 rounded-[40px] border-2 border-dashed border-slate-800/80">
<ImageIcon className="w-16 h-16 text-slate-700 mx-auto mb-4" />
<h3 className="text-xl font-bold text-slate-400">Chưa nh nào</h3>
<p className="text-sm text-slate-500">Hãy tham gia các chuyến đi lưu lại khoảnh khắc nhé!</p>
<div className="py-16 sm:py-24 text-center bg-slate-900/30 rounded-[32px] sm:rounded-[40px] border-2 border-dashed border-slate-800/80">
<ImageIcon className="w-12 h-12 sm:w-16 sm:h-16 text-slate-700 mx-auto mb-3 sm:mb-4" />
<h3 className="text-lg sm:text-xl font-bold text-slate-400">Chưa nh nào</h3>
<p className="text-xs sm:text-sm text-slate-500">Hãy tham gia các chuyến đi lưu lại khoảnh khắc nhé!</p>
</div>
) : (
<div className="animate-in fade-in">
<div className="flex flex-col md:flex-row gap-6">
<div className="flex flex-col md:flex-row gap-4 sm:gap-6">
{/* Left Column: Tour List */}
<div className="md:w-1/4 bg-slate-900/50 rounded-2xl border border-slate-800/60 p-4 flex-shrink-0">
<h3 className="text-xs font-black uppercase text-slate-400 tracking-wider mb-3">Hành trình của bạn</h3>
<div className="space-y-2">
<div className="w-full md:w-1/4 bg-slate-900/50 rounded-2xl border border-slate-800/60 p-3 sm:p-4 flex-shrink-0">
<h3 className="text-[10px] sm:text-xs font-black uppercase text-slate-400 tracking-wider mb-2 sm:mb-3">Hành trình của bạn</h3>
<div className="space-y-1.5 sm:space-y-2">
<button
onClick={() => { setSelectedTourIdForPhoto('all'); setSelectedPhotoForDisplay(null); }}
className={`w-full text-left px-3.5 py-2.5 rounded-xl text-xs font-bold transition-all ${
className={`w-full text-left px-3 py-2 sm:px-3.5 sm:py-2.5 rounded-xl text-[10px] sm:text-xs font-bold transition-all ${
selectedTourIdForPhoto === 'all' ? 'bg-indigo-650 text-white shadow-md' : 'bg-slate-800/60 text-slate-300 hover:bg-slate-800'
}`}
>
@@ -326,7 +326,7 @@ export const MyPhotosPage = ({ onBack }: { onBack: () => void }) => {
<button
key={tour.id}
onClick={() => { setSelectedTourIdForPhoto(tour.id); setSelectedPhotoForDisplay(null); }}
className={`w-full text-left px-3.5 py-2.5 rounded-xl text-xs font-bold transition-all truncate ${
className={`w-full text-left px-3 py-2 sm:px-3.5 sm:py-2.5 rounded-xl text-[10px] sm:text-xs font-bold transition-all truncate ${
selectedTourIdForPhoto === tour.id ? 'bg-indigo-650 text-white shadow-md' : 'bg-slate-800/60 text-slate-300 hover:bg-slate-800'
}`}
title={tour.title}
@@ -338,14 +338,14 @@ export const MyPhotosPage = ({ onBack }: { onBack: () => void }) => {
</div>
{/* Right Column: Large Photo Display */}
<div className="md:flex-1 bg-slate-900/50 rounded-2xl border border-slate-800/60 p-4 flex flex-col items-center justify-center min-h-[350px]">
<div className="w-full md:flex-1 bg-slate-900/50 rounded-2xl border border-slate-800/60 p-3 sm:p-4 flex flex-col items-center justify-center min-h-[250px] sm:min-h-[350px]">
{selectedPhotoForDisplay ? (
<div className="relative w-full h-full flex flex-col items-center justify-center">
<div className="relative overflow-hidden rounded-xl shadow-lg max-w-full max-h-[calc(100vh-380px)] group">
<div className="relative overflow-hidden rounded-xl shadow-lg max-w-full max-h-[calc(100vh-280px)] sm:max-h-[calc(100vh-320px)] group">
<img
src={selectedPhotoForDisplay.imageUrl || selectedPhotoForDisplay.originalUrl}
alt="Selected"
className="max-w-full max-h-[calc(100vh-380px)] object-contain cursor-zoom-in"
className="max-w-full max-h-[calc(100vh-280px)] sm:max-h-[calc(100vh-320px)] object-contain cursor-zoom-in"
onClick={() => setIsFullscreen(true)}
/>
@@ -542,13 +542,13 @@ export const MyPhotosPage = ({ onBack }: { onBack: () => void }) => {
<div className="flex items-center justify-between mb-4 px-1">
<h3 className="text-xs font-black text-slate-400 uppercase tracking-widest">Kho nh ({filteredPhotos.length})</h3>
</div>
<div className="grid grid-cols-4 sm:grid-cols-6 md:grid-cols-8 lg:grid-cols-10 gap-3 max-h-[250px] overflow-y-auto pr-2 custom-scrollbar">
<div className="grid grid-cols-3 xs:grid-cols-4 sm:grid-cols-5 md:grid-cols-7 lg:grid-cols-10 gap-2 sm:gap-3 max-h-[200px] sm:max-h-[250px] overflow-y-auto pr-1 sm:pr-2 custom-scrollbar">
{filteredPhotos.map((photo: any) => (
<div
key={photo.id}
onClick={() => setSelectedPhotoForDisplay(photo)}
className={`aspect-square bg-slate-950 rounded-xl overflow-hidden relative group border-2 transition-all cursor-pointer ${
selectedPhotoForDisplay?.id === photo.id ? 'border-indigo-505 border-indigo-500 scale-[0.98]' : 'border-transparent hover:border-slate-700'
selectedPhotoForDisplay?.id === photo.id ? 'border-indigo-500 scale-[0.98]' : 'border-transparent hover:border-slate-700'
}`}
>
<img
+9 -9
View File
@@ -1900,13 +1900,13 @@ export const TourDetailPage = ({
};
return (
<div className="min-h-screen bg-[var(--background)] pb-20">
<div className="min-h-dvh bg-[var(--background)] pb-20 overflow-x-hidden">
{/* Top Navigation Bar */}
<div className="sticky top-0 z-30 bg-[var(--surface)]/80 backdrop-blur-md border-b border-[var(--border)] px-4 py-3 flex items-center justify-between">
<button onClick={onBack} className="p-2 hover:bg-[var(--background)] rounded-full transition-colors">
<ChevronLeft className="w-6 h-6 text-[var(--text-secondary)]" />
<ChevronLeft className="w-5 h-5 sm:w-6 sm:h-6 text-[var(--text-secondary)]" />
</button>
<h1 className="text-lg font-bold text-[var(--text-primary)] truncate px-4 flex-1 text-center">
<h1 className="text-base sm:text-lg font-bold text-[var(--text-primary)] truncate px-4 flex-1 text-center">
{tourInfo.title}
</h1>
<div className="flex items-center gap-1">
@@ -1935,7 +1935,7 @@ export const TourDetailPage = ({
{!(activeTab === 'plan' && viewMode === 'map') && (
<>
{/* Tour Header Info */}
<div className="relative min-h-[480px] w-full bg-blue-900 flex flex-col justify-end">
<div className="relative min-h-[300px] sm:min-h-[400px] md:min-h-[480px] w-full bg-blue-900 flex flex-col justify-end">
<img
src={tourInfo.coverImage}
className="absolute inset-0 w-full h-full object-cover opacity-60"
@@ -1943,9 +1943,9 @@ export const TourDetailPage = ({
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/80 via-black/20 to-transparent" />
<div className="relative z-10 p-6 text-white pt-28 pb-20">
<div className="max-w-2xl mx-auto space-y-4">
<h2 className="text-3xl font-black tracking-tight drop-shadow-md">{tourInfo.title}</h2>
<div className="relative z-10 p-4 sm:p-6 pt-16 sm:pt-28 pb-8 sm:pb-20">
<div className="max-w-2xl mx-auto space-y-3 sm:space-y-4">
<h2 className="text-xl sm:text-2xl md:text-3xl font-black tracking-tight drop-shadow-md">{tourInfo.title}</h2>
{/* Nhãn hiển thị ngay dưới Tiêu đề */}
{currentTour?.tags && currentTour.tags.length > 0 && (
@@ -2296,10 +2296,10 @@ export const TourDetailPage = ({
onOpenNavigationPage={onOpenNavigationPage}
/>
) : (
<div className="h-[calc(100vh-53px)] w-full md:rounded-3xl overflow-hidden md:shadow-xl md:border-4 md:border-white relative animate-in fade-in duration-500">
<div className="h-auto min-h-[calc(100vh-100px)] sm:min-h-[calc(100vh-140px)] md:h-[calc(100vh-53px)] w-full md:rounded-3xl overflow-hidden md:shadow-xl md:border-4 md:border-white relative animate-in fade-in duration-500">
{/* Search Bar Overlay - Thanh tìm kiếm địa điểm trên bản đồ lộ trình */}
{!isPublicView && (
<div className="absolute top-3 right-3 z-[1001] w-48 md:w-64">
<div className="absolute top-2 sm:top-3 right-2 sm:right-3 z-[1001] w-40 sm:w-48 md:w-64">
<div className="relative group">
<input
type="text"
+2 -2
View File
@@ -363,7 +363,7 @@ export const TourNavigationPage: React.FC<TourNavigationPageProps> = ({ tourId,
};
return (
<div className="fixed inset-0 bg-slate-950 flex flex-col overflow-hidden select-none text-white antialiased">
<div className="fixed inset-0 bg-slate-950 flex flex-col overflow-hidden select-none text-white antialiased overflow-x-hidden">
<div className="w-full bg-[#1e293b]/95 backdrop-blur-md border-b border-slate-800 px-4 py-3.5 flex items-center gap-3 z-50 shrink-0"
style={{ paddingTop: 'calc(0.75rem + env(safe-area-inset-top, 0px))' }}>
<button
@@ -371,7 +371,7 @@ export const TourNavigationPage: React.FC<TourNavigationPageProps> = ({ tourId,
className="text-slate-300 hover:text-white p-1 rounded-lg hover:bg-slate-800 transition-colors"
title="Quay lại danh sách lộ trình"
>
<ChevronLeft className="w-6 h-6" />
<ChevronLeft className="w-5 h-5" />
</button>
<h2 className="text-sm font-bold truncate tracking-wide text-slate-100">
{routeData.tourTitle || "Bản đồ chỉ đường"}