first commit
This commit is contained in:
467
src/pages/finance/ui/FinanceDetailTour.tsx
Normal file
467
src/pages/finance/ui/FinanceDetailTour.tsx
Normal file
@@ -0,0 +1,467 @@
|
||||
"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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user