main create
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { ItineraryTimeline } from './ItineraryTimeline';
|
||||
import { ExpenseManager } from './ExpenseManager';
|
||||
import { useTourStore } from './useTourStore';
|
||||
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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user