Files
travelplanning/TourDetailPage.tsx
T

137 lines
5.1 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import { ItineraryTimeline } from './ItineraryTimeline.js';
import { ExpenseManager } from './ExpenseManager.js';
import { useTourStore } from './useTourStore.js';
import {
Map as MapIcon,
Wallet,
Image as ImageIcon,
Calendar,
Users,
ChevronLeft
} from 'lucide-react';
export const TourDetailPage = () => {
const [activeTab, setActiveTab] = useState<'plan' | 'expense' | 'photo'>('plan');
const { currentTour, fetchTour, userRole } = useTourStore();
useEffect(() => {
// Gọi ID 1 (Dữ liệu từ file seed) để kiểm tra
fetchTour(1);
}, [fetchTour]);
// Định nghĩa các tab dựa trên quyền hạn (RBAC) từ store
const tabs = [
{ id: 'plan', label: 'Lộ trình', icon: MapIcon, visible: ['OWNER', 'EDITOR', 'MEMBER_PLAN_ONLY'].includes(userRole || '') },
{ id: 'expense', label: 'Chi phí', icon: Wallet, visible: ['OWNER', 'EDITOR'].includes(userRole || '') },
{ id: 'photo', label: 'Ảnh', icon: ImageIcon, visible: true },
].filter(t => t.visible);
// Tự động chuyển tab nếu người dùng không có quyền xem 'plan'
useEffect(() => {
if (userRole === 'MEMBER_PHOTO_ONLY') {
setActiveTab('photo');
}
}, [userRole]);
// Mock dữ liệu header (Trong thực tế sẽ lấy từ currentTour)
const tourInfo = {
title: currentTour?.title || "Hành trình khám phá TP.HCM",
date: "20 - 21 Tháng 11, 2023",
members: 5,
budget: "2.500.000 VND"
};
return (
<div className="min-h-screen bg-white">
{/* Top Navigation Bar */}
<div className="sticky top-0 z-30 bg-white border-b border-gray-100 px-4 py-3 flex items-center justify-between">
<button className="p-2 hover:bg-gray-100 rounded-full transition-colors">
<ChevronLeft className="w-6 h-6 text-gray-600" />
</button>
<h1 className="text-lg font-bold text-gray-800 truncate px-4">
{tourInfo.title}
</h1>
<div className="w-10" /> {/* Spacer */}
</div>
{/* Tour Header Info */}
<div className="bg-blue-600 text-white p-6 pb-20">
<div className="max-w-2xl mx-auto space-y-4">
<div className="flex flex-wrap gap-4 text-sm opacity-90">
<div className="flex items-center">
<Calendar className="w-4 h-4 mr-1.5" />
{tourInfo.date}
</div>
<div className="flex items-center">
<Users className="w-4 h-4 mr-1.5" />
{tourInfo.members} thành viên
</div>
</div>
<div className="flex items-baseline gap-2">
<span className="text-3xl font-bold">{tourInfo.budget}</span>
<span className="text-blue-100 text-sm">dự kiến</span>
</div>
</div>
</div>
{/* Main Content Area */}
<div className="max-w-2xl mx-auto -mt-12 px-4 pb-24">
{/* Tab Switcher */}
<div className="bg-white rounded-2xl shadow-xl border border-gray-100 p-1 flex mb-6">
{tabs.map((tab) => (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id as any)}
className={`flex-1 flex items-center justify-center py-3 rounded-xl text-sm font-semibold transition-all ${
activeTab === tab.id
? 'bg-blue-50 text-blue-600 shadow-sm'
: 'text-gray-400 hover:text-gray-600'
}`}
>
<tab.icon className={`w-4 h-4 mr-2 ${activeTab === tab.id ? 'animate-pulse' : ''}`} />
{tab.label}
</button>
))}
</div>
{/* Tab Panels */}
<div className="transition-opacity duration-300">
{activeTab === 'plan' && (
<div className="animate-in fade-in slide-in-from-bottom-4">
<ItineraryTimeline />
</div>
)}
{activeTab === 'expense' && (
<div className="animate-in fade-in slide-in-from-bottom-4">
<ExpenseManager />
</div>
)}
{activeTab === 'photo' && (
<div className="grid grid-cols-3 gap-2 animate-in fade-in">
{[1, 2, 3, 4, 5, 6].map((i) => (
<div key={i} className="aspect-square bg-gray-100 rounded-lg overflow-hidden relative group">
<div className="absolute inset-0 bg-black/0 group-hover:bg-black/20 transition-colors" />
<img
src={`https://picsum.photos/seed/${i + 10}/400/400`}
alt="Tour photo"
className="w-full h-full object-cover"
/>
</div>
))}
</div>
)}
</div>
</div>
{/* Floating Action Button (Mobile) */}
<div className="fixed bottom-6 left-1/2 -translate-x-1/2 z-40">
<button className="bg-blue-600 text-white px-6 py-3 rounded-full shadow-lg hover:bg-blue-700 transition-all flex items-center font-bold">
{activeTab === 'plan' ? 'Thêm địa điểm' : activeTab === 'expense' ? 'Thêm chi phí' : 'Đăng ảnh'}
</button>
</div>
</div>
);
};