468 lines
16 KiB
TypeScript
468 lines
16 KiB
TypeScript
"use client";
|
|
|
|
import {
|
|
ArrowLeft,
|
|
Calendar,
|
|
DollarSign,
|
|
Download,
|
|
Eye,
|
|
Hotel,
|
|
MapPin,
|
|
Plane,
|
|
Share2,
|
|
Star,
|
|
TrendingUp,
|
|
Users,
|
|
} from "lucide-react";
|
|
import { useState } from "react";
|
|
import { Link } from "react-router-dom";
|
|
|
|
type TourPurchase = {
|
|
id: number;
|
|
userName: string;
|
|
userPhone: string;
|
|
tourName: string;
|
|
tourId: number;
|
|
agencyName: string;
|
|
agencyId: number;
|
|
destination: string;
|
|
travelDate: string;
|
|
amount: number;
|
|
paymentStatus: "paid" | "pending" | "cancelled" | "refunded";
|
|
purchaseDate: string;
|
|
rating: number;
|
|
review: string;
|
|
};
|
|
|
|
const mockTourData = {
|
|
id: 1,
|
|
name: "Dubai Luxury Tour",
|
|
destination: "Dubai, UAE",
|
|
duration: "7 days",
|
|
price: 1500000,
|
|
totalBookings: 45,
|
|
totalRevenue: 67500000,
|
|
averageRating: 4.8,
|
|
agency: "Silk Road Travel",
|
|
description:
|
|
"Experience the ultimate luxury in Dubai with 5-star accommodations, private tours, and exclusive experiences.",
|
|
inclusions: [
|
|
"5-star hotel accommodation",
|
|
"Private city tours",
|
|
"Desert safari experience",
|
|
"Burj Khalifa tickets",
|
|
"Airport transfers",
|
|
],
|
|
};
|
|
|
|
const mockTourPurchases: TourPurchase[] = [
|
|
{
|
|
id: 1,
|
|
userName: "Aziza Karimova",
|
|
userPhone: "+998 90 123 45 67",
|
|
tourName: "Dubai Luxury Tour",
|
|
tourId: 1,
|
|
agencyName: "Silk Road Travel",
|
|
agencyId: 1,
|
|
destination: "Dubai, UAE",
|
|
travelDate: "2025-11-10",
|
|
amount: 1500000,
|
|
paymentStatus: "paid",
|
|
purchaseDate: "2025-10-10",
|
|
rating: 5,
|
|
review:
|
|
"Amazing experience! The hotel was luxurious and the tours were well organized.",
|
|
},
|
|
{
|
|
id: 2,
|
|
userName: "Sardor Rahimov",
|
|
userPhone: "+998 91 234 56 78",
|
|
tourName: "Dubai Luxury Tour",
|
|
tourId: 1,
|
|
agencyName: "Silk Road Travel",
|
|
agencyId: 1,
|
|
destination: "Dubai, UAE",
|
|
travelDate: "2025-11-15",
|
|
amount: 1500000,
|
|
paymentStatus: "paid",
|
|
purchaseDate: "2025-10-12",
|
|
rating: 4,
|
|
review:
|
|
"Great tour overall. The desert safari was the highlight of our trip.",
|
|
},
|
|
{
|
|
id: 3,
|
|
userName: "Nilufar Toshmatova",
|
|
userPhone: "+998 93 345 67 89",
|
|
tourName: "Dubai Luxury Tour",
|
|
tourId: 1,
|
|
agencyName: "Silk Road Travel",
|
|
agencyId: 1,
|
|
destination: "Dubai, UAE",
|
|
travelDate: "2025-11-20",
|
|
amount: 1500000,
|
|
paymentStatus: "pending",
|
|
purchaseDate: "2025-10-14",
|
|
rating: 0,
|
|
review: "",
|
|
},
|
|
];
|
|
|
|
export default function FinanceDetailTour() {
|
|
const [activeTab, setActiveTab] = useState<
|
|
"overview" | "bookings" | "reviews"
|
|
>("overview");
|
|
|
|
const getStatusBadge = (status: TourPurchase["paymentStatus"]) => {
|
|
const base =
|
|
"px-3 py-1 rounded-full text-sm font-medium inline-flex items-center gap-2";
|
|
switch (status) {
|
|
case "paid":
|
|
return (
|
|
<span
|
|
className={`${base} bg-green-900 text-green-400 border border-green-700`}
|
|
>
|
|
<div className="w-2 h-2 rounded-full bg-green-400"></div>
|
|
Paid
|
|
</span>
|
|
);
|
|
case "pending":
|
|
return (
|
|
<span
|
|
className={`${base} bg-yellow-900 text-yellow-400 border border-yellow-700`}
|
|
>
|
|
<div className="w-2 h-2 rounded-full bg-yellow-400"></div>
|
|
Pending
|
|
</span>
|
|
);
|
|
case "cancelled":
|
|
return (
|
|
<span
|
|
className={`${base} bg-red-900 text-red-400 border border-red-700`}
|
|
>
|
|
<div className="w-2 h-2 rounded-full bg-red-400"></div>
|
|
Cancelled
|
|
</span>
|
|
);
|
|
case "refunded":
|
|
return (
|
|
<span
|
|
className={`${base} bg-blue-900 text-blue-400 border border-blue-700`}
|
|
>
|
|
<div className="w-2 h-2 rounded-full bg-blue-400"></div>
|
|
Refunded
|
|
</span>
|
|
);
|
|
}
|
|
};
|
|
|
|
const renderStars = (rating: number) => {
|
|
return (
|
|
<div className="flex items-center gap-1">
|
|
{[1, 2, 3, 4, 5].map((star) => (
|
|
<Star
|
|
key={star}
|
|
className={`w-4 h-4 ${
|
|
star <= rating
|
|
? "text-yellow-400 fill-yellow-400"
|
|
: "text-gray-600"
|
|
}`}
|
|
/>
|
|
))}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const paidBookings = mockTourPurchases.filter(
|
|
(p) => p.paymentStatus === "paid",
|
|
);
|
|
const totalRevenue = paidBookings.reduce((sum, p) => sum + p.amount, 0);
|
|
const pendingRevenue = mockTourPurchases
|
|
.filter((p) => p.paymentStatus === "pending")
|
|
.reduce((sum, p) => sum + p.amount, 0);
|
|
|
|
return (
|
|
<div className="min-h-screen w-full bg-gray-900 text-gray-100">
|
|
<div className="w-[90%] mx-auto py-6">
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between mb-8">
|
|
<div className="flex items-center gap-4">
|
|
<Link
|
|
to="/finance"
|
|
className="bg-gray-800 p-2 rounded-lg hover:bg-gray-700 transition-colors"
|
|
>
|
|
<ArrowLeft className="w-5 h-5" />
|
|
</Link>
|
|
<div>
|
|
<h1 className="text-3xl font-bold">Tour Financial Details</h1>
|
|
<p className="text-gray-400 mt-1">
|
|
Financial performance for {mockTourData.name}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex gap-3">
|
|
<button className="bg-gray-800 px-4 py-2 rounded-lg hover:bg-gray-700 transition-colors flex items-center gap-2">
|
|
<Download className="w-4 h-4" />
|
|
Export Report
|
|
</button>
|
|
<button className="bg-blue-600 px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors flex items-center gap-2">
|
|
<Share2 className="w-4 h-4" />
|
|
Share
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Tour Summary Cards */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
|
<div className="bg-gray-800 p-6 rounded-xl shadow">
|
|
<div className="flex items-center justify-between">
|
|
<p className="text-gray-400 font-medium">Total Revenue</p>
|
|
<DollarSign className="text-green-400 w-6 h-6" />
|
|
</div>
|
|
<p className="text-2xl font-bold text-green-400 mt-3">
|
|
${(totalRevenue / 1000000).toFixed(1)}M
|
|
</p>
|
|
<p className="text-sm text-gray-500 mt-1">
|
|
From completed bookings
|
|
</p>
|
|
</div>
|
|
|
|
<div className="bg-gray-800 p-6 rounded-xl shadow">
|
|
<div className="flex items-center justify-between">
|
|
<p className="text-gray-400 font-medium">Pending Revenue</p>
|
|
<TrendingUp className="text-yellow-400 w-6 h-6" />
|
|
</div>
|
|
<p className="text-2xl font-bold text-yellow-400 mt-3">
|
|
${(pendingRevenue / 1000000).toFixed(1)}M
|
|
</p>
|
|
<p className="text-sm text-gray-500 mt-1">Awaiting payment</p>
|
|
</div>
|
|
|
|
<div className="bg-gray-800 p-6 rounded-xl shadow">
|
|
<div className="flex items-center justify-between">
|
|
<p className="text-gray-400 font-medium">Total Bookings</p>
|
|
<Users className="text-blue-400 w-6 h-6" />
|
|
</div>
|
|
<p className="text-2xl font-bold text-blue-400 mt-3">
|
|
{mockTourPurchases.length}
|
|
</p>
|
|
<p className="text-sm text-gray-500 mt-1">All bookings</p>
|
|
</div>
|
|
|
|
<div className="bg-gray-800 p-6 rounded-xl shadow">
|
|
<div className="flex items-center justify-between">
|
|
<p className="text-gray-400 font-medium">Average Rating</p>
|
|
<Star className="text-yellow-400 w-6 h-6" />
|
|
</div>
|
|
<p className="text-2xl font-bold text-yellow-400 mt-3">
|
|
{mockTourData.averageRating}/5
|
|
</p>
|
|
<p className="text-sm text-gray-500 mt-1">Customer satisfaction</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Main Content */}
|
|
<div className="bg-gray-800 rounded-xl shadow">
|
|
{/* Tabs */}
|
|
<div className="flex border-b border-gray-700">
|
|
<button
|
|
className={`px-6 py-4 font-medium flex items-center gap-2 transition-colors ${
|
|
activeTab === "overview"
|
|
? "text-blue-400 border-b-2 border-blue-400"
|
|
: "text-gray-400 hover:text-gray-300"
|
|
}`}
|
|
onClick={() => setActiveTab("overview")}
|
|
>
|
|
<Eye className="w-4 h-4" />
|
|
Tour Overview
|
|
</button>
|
|
<button
|
|
className={`px-6 py-4 font-medium flex items-center gap-2 transition-colors ${
|
|
activeTab === "bookings"
|
|
? "text-blue-400 border-b-2 border-blue-400"
|
|
: "text-gray-400 hover:text-gray-300"
|
|
}`}
|
|
onClick={() => setActiveTab("bookings")}
|
|
>
|
|
<Users className="w-4 h-4" />
|
|
Bookings ({mockTourPurchases.length})
|
|
</button>
|
|
<button
|
|
className={`px-6 py-4 font-medium flex items-center gap-2 transition-colors ${
|
|
activeTab === "reviews"
|
|
? "text-blue-400 border-b-2 border-blue-400"
|
|
: "text-gray-400 hover:text-gray-300"
|
|
}`}
|
|
onClick={() => setActiveTab("reviews")}
|
|
>
|
|
<Star className="w-4 h-4" />
|
|
Reviews
|
|
</button>
|
|
</div>
|
|
|
|
<div className="p-6">
|
|
{activeTab === "overview" && (
|
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
|
{/* Tour Information */}
|
|
<div className="lg:col-span-2">
|
|
<h3 className="text-lg font-bold mb-4">Tour Information</h3>
|
|
<div className="space-y-4">
|
|
<div className="flex items-center gap-3 p-3 bg-gray-700 rounded-lg">
|
|
<Plane className="w-5 h-5 text-blue-400" />
|
|
<div>
|
|
<p className="text-sm text-gray-400">Tour Name</p>
|
|
<p className="text-gray-100 font-medium">
|
|
{mockTourData.name}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-3 p-3 bg-gray-700 rounded-lg">
|
|
<MapPin className="w-5 h-5 text-green-400" />
|
|
<div>
|
|
<p className="text-sm text-gray-400">Destination</p>
|
|
<p className="text-gray-100">
|
|
{mockTourData.destination}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-3 p-3 bg-gray-700 rounded-lg">
|
|
<Calendar className="w-5 h-5 text-purple-400" />
|
|
<div>
|
|
<p className="text-sm text-gray-400">Duration</p>
|
|
<p className="text-gray-100">{mockTourData.duration}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-3 p-3 bg-gray-700 rounded-lg">
|
|
<Hotel className="w-5 h-5 text-yellow-400" />
|
|
<div>
|
|
<p className="text-sm text-gray-400">Agency</p>
|
|
<p className="text-gray-100">{mockTourData.agency}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="p-3 bg-gray-700 rounded-lg">
|
|
<p className="text-sm text-gray-400 mb-2">Description</p>
|
|
<p className="text-gray-100">
|
|
{mockTourData.description}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<h3 className="text-lg font-bold mb-4">Tour Inclusions</h3>
|
|
<div className="mt-6 p-4 bg-gray-700 rounded-lg">
|
|
<p className="text-sm text-gray-400 mb-2">Base Price</p>
|
|
<p className="text-2xl font-bold text-green-400">
|
|
${(mockTourData.price / 1000000).toFixed(1)}M
|
|
</p>
|
|
<p className="text-sm text-gray-400 mt-1">per person</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{activeTab === "bookings" && (
|
|
<div className="space-y-6">
|
|
<h2 className="text-xl font-bold mb-4">Recent Bookings</h2>
|
|
{mockTourPurchases.map((purchase) => (
|
|
<div
|
|
key={purchase.id}
|
|
className="bg-gray-700 p-6 rounded-lg hover:bg-gray-600 transition-colors"
|
|
>
|
|
<div className="flex justify-between items-start mb-4">
|
|
<div>
|
|
<h3 className="text-lg font-bold text-gray-100">
|
|
{purchase.userName}
|
|
</h3>
|
|
<p className="text-gray-400 text-sm">
|
|
{purchase.userPhone}
|
|
</p>
|
|
</div>
|
|
{getStatusBadge(purchase.paymentStatus)}
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-4">
|
|
<div>
|
|
<p className="text-sm text-gray-400">Travel Date</p>
|
|
<p className="text-gray-100 font-medium">
|
|
{purchase.travelDate}
|
|
</p>
|
|
</div>
|
|
|
|
<div>
|
|
<p className="text-sm text-gray-400">Booking Date</p>
|
|
<p className="text-gray-100">{purchase.purchaseDate}</p>
|
|
</div>
|
|
|
|
<div>
|
|
<p className="text-sm text-gray-400">Amount</p>
|
|
<p className="text-green-400 font-bold">
|
|
${(purchase.amount / 1000000).toFixed(1)}M
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex justify-between items-center pt-4 border-t border-gray-600">
|
|
<div className="text-sm text-gray-400">
|
|
Agency: {purchase.agencyName}
|
|
</div>
|
|
<Link
|
|
to={`/bookings/${purchase.id}`}
|
|
className="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors"
|
|
>
|
|
View Details
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
{activeTab === "reviews" && (
|
|
<div className="space-y-6">
|
|
<h2 className="text-xl font-bold mb-4">Customer Reviews</h2>
|
|
{mockTourPurchases
|
|
.filter((purchase) => purchase.rating > 0)
|
|
.map((purchase) => (
|
|
<div
|
|
key={purchase.id}
|
|
className="bg-gray-700 p-6 rounded-lg hover:bg-gray-600 transition-colors"
|
|
>
|
|
<div className="flex justify-between items-start mb-4">
|
|
<div>
|
|
<h3 className="text-lg font-bold text-gray-100">
|
|
{purchase.userName}
|
|
</h3>
|
|
<p className="text-gray-400 text-sm">
|
|
{purchase.travelDate}
|
|
</p>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
{renderStars(purchase.rating)}
|
|
<span className="text-gray-300">
|
|
{purchase.rating}.0
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<p className="text-gray-100 mb-4">{purchase.review}</p>
|
|
|
|
<div className="flex justify-between items-center pt-4 border-t border-gray-600">
|
|
<div className="text-sm text-gray-400">
|
|
Booked on {purchase.purchaseDate}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|