api ulandi

This commit is contained in:
Samandar Turgunboyev
2025-10-25 18:42:01 +05:00
parent 1a08775451
commit 05b752daf2
84 changed files with 11179 additions and 3724 deletions

View File

@@ -1,5 +1,7 @@
"use client";
import { deleteTours, getAllTours } from "@/pages/tours/lib/api";
import formatPrice from "@/shared/lib/formatPrice";
import { Button } from "@/shared/ui/button";
import {
Dialog,
@@ -16,65 +18,79 @@ import {
TableHeader,
TableRow,
} from "@/shared/ui/table";
import { Edit, Plane, PlusCircle, Trash2 } from "lucide-react";
import { useEffect, useState } from "react";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import {
AlertTriangle,
ChevronLeft,
ChevronRight,
Edit,
Loader2,
Plane,
PlusCircle,
Trash2,
} from "lucide-react";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
type Tour = {
id: number;
image?: string;
tickets: string;
min_price: string;
max_price: string;
top_duration: string;
top_destinations: string;
hotel_features_by_type: string;
hotel_types: string;
hotel_amenities: string;
};
const Tours = () => {
const [tours, setTours] = useState<Tour[]>([]);
const { t } = useTranslation();
const [page, setPage] = useState(1);
const [totalPages, setTotalPages] = useState(3);
const [deleteId, setDeleteId] = useState<number | null>(null);
const navigate = useNavigate();
const queryClient = useQueryClient();
useEffect(() => {
const mockData: Tour[] = Array.from({ length: 10 }, (_, i) => ({
id: i + 1,
image: `/dubai-marina.jpg`,
tickets: `Bilet turi ${i + 1}`,
min_price: `${200 + i * 50}$`,
max_price: `${400 + i * 70}$`,
top_duration: `${3 + i} kun`,
top_destinations: `Shahar ${i + 1}`,
hotel_features_by_type: "Spa, Wi-Fi, Pool",
hotel_types: "5 yulduzli mehmonxona",
hotel_amenities: "Nonushta, Parking, Bar",
}));
const { data, isLoading, isError, refetch } = useQuery({
queryKey: ["all_tours", page],
queryFn: () => getAllTours({ page: page, page_size: 10 }),
});
const itemsPerPage = 6;
const start = (page - 1) * itemsPerPage;
const end = start + itemsPerPage;
setTotalPages(Math.ceil(mockData.length / itemsPerPage));
setTours(mockData.slice(start, end));
}, [page]);
const confirmDelete = () => {
if (deleteId !== null) {
setTours((prev) => prev.filter((t) => t.id !== deleteId));
const { mutate } = useMutation({
mutationFn: (id: number) => deleteTours({ id }),
onSuccess: () => {
queryClient.refetchQueries({ queryKey: ["all_tours"] });
setDeleteId(null);
}
},
});
const confirmDelete = (id: number) => {
mutate(id);
};
if (isLoading) {
return (
<div className="flex flex-col items-center justify-center min-h-screen bg-slate-900 text-white gap-4 w-full">
<Loader2 className="w-10 h-10 animate-spin text-cyan-400" />
<p className="text-slate-400">{t("Ma'lumotlar yuklanmoqda...")}</p>
</div>
);
}
if (isError) {
return (
<div className="flex flex-col items-center justify-center min-h-screen bg-slate-900 w-full text-center text-white gap-4">
<AlertTriangle className="w-10 h-10 text-red-500" />
<p className="text-lg">
{t("Ma'lumotlarni yuklashda xatolik yuz berdi.")}
</p>
<Button
onClick={() => {
refetch();
}}
className="bg-gradient-to-r from-blue-600 to-cyan-600 text-white rounded-lg px-5 py-2 hover:opacity-90"
>
{t("Qayta urinish")}
</Button>
</div>
);
}
return (
<div className="min-h-screen bg-gray-900 text-foreground py-10 px-5 w-full">
<div className="flex justify-between items-center mb-8">
<h1 className="text-3xl font-semibold">Turlar ro'yxati</h1>
<h1 className="text-3xl font-semibold">{t("Turlar ro'yxati")}</h1>
<Button onClick={() => navigate("/tours/create")} variant="default">
<PlusCircle className="w-5 h-5 mr-2" /> Yangi tur qo'shish
<PlusCircle className="w-5 h-5 mr-2" /> {t("Yangi tur qo'shish")}
</Button>
</div>
@@ -83,18 +99,19 @@ const Tours = () => {
<TableHeader>
<TableRow className="bg-muted/50">
<TableHead className="w-[50px] text-center">#</TableHead>
<TableHead className="min-w-[150px]">Manzil</TableHead>
<TableHead className="min-w-[120px]">Davomiyligi</TableHead>
<TableHead className="min-w-[180px]">Mehmonxona</TableHead>
<TableHead className="min-w-[200px]">Narx Oralig'i</TableHead>
<TableHead className="min-w-[200px]">Imkoniyatlar</TableHead>
<TableHead className="min-w-[150px]">{t("Manzil")}</TableHead>
<TableHead className="min-w-[120px]">
{t("Davomiyligi")}
</TableHead>
<TableHead className="min-w-[180px]">{t("Mehmonxona")}</TableHead>
<TableHead className="min-w-[200px]">{t("Narxi")}</TableHead>
<TableHead className="min-w-[150px] text-center">
Amallar
{t("Операции")}
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{tours.map((tour, idx) => (
{data?.data.data.results.map((tour, idx) => (
<TableRow key={tour.id}>
<TableCell className="font-medium text-center">
{(page - 1) * 6 + idx + 1}
@@ -102,28 +119,25 @@ const Tours = () => {
<TableCell>
<div className="flex items-center gap-2 font-semibold">
<Plane className="w-4 h-4 text-primary" />
{tour.top_destinations}
{tour.destination}
</div>
</TableCell>
<TableCell className="text-sm text-primary font-medium">
{tour.top_duration}
{tour.duration_days} kun
</TableCell>
<TableCell>
<div className="flex flex-col">
<span className="font-medium">{tour.hotel_types}</span>
<span className="font-medium">{tour.hotel_name}</span>
<span className="text-xs text-muted-foreground">
{tour.tickets}
{tour.hotel_rating} {t("yulduzli mehmonxona")}
</span>
</div>
</TableCell>
<TableCell>
<span className="font-bold text-base text-green-600">
{tour.min_price} {tour.max_price}
{formatPrice(tour.price, true)}
</span>
</TableCell>
<TableCell className="text-sm">
{tour.hotel_amenities}
</TableCell>
<TableCell className="text-center">
<div className="flex gap-2 justify-center">
@@ -148,7 +162,7 @@ const Tours = () => {
size="sm"
onClick={() => navigate(`/tours/${tour.id}`)}
>
Batafsil
{t("Batafsil")}
</Button>
</div>
</TableCell>
@@ -158,50 +172,67 @@ const Tours = () => {
</Table>
</div>
{/* Delete Confirmation Dialog - Faqat bitta */}
<Dialog open={deleteId !== null} onOpenChange={() => setDeleteId(null)}>
<DialogContent className="sm:max-w-[425px] bg-gray-900">
<DialogHeader>
<DialogTitle className="text-xl">
Turni o'chirishni tasdiqlang
{t("Turni o'chirishni tasdiqlang")}
</DialogTitle>
</DialogHeader>
<div className="py-4">
<p className="text-muted-foreground">
Haqiqatan ham bu turni o'chirib tashlamoqchimisiz? Bu amalni ortga
qaytarib bo'lmaydi.
{t(
"Haqiqatan ham bu turni o'chirib tashlamoqchimisiz? Bu amalni ortga qaytarib bo'lmaydi.",
)}
</p>
</div>
<DialogFooter className="gap-4 flex">
<Button variant="outline" onClick={() => setDeleteId(null)}>
Bekor qilish
{t("Bekor qilish")}
</Button>
<Button variant="destructive" onClick={confirmDelete}>
<Button
variant="destructive"
onClick={() => confirmDelete(deleteId!)}
>
<Trash2 className="w-4 h-4 mr-2" />
O'chirish
{t("O'chirish")}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
<div className="flex justify-center mt-10 gap-3">
<Button
variant="outline"
onClick={() => setPage((p) => Math.max(1, p - 1))}
<div className="flex justify-end mt-10 gap-3">
<button
disabled={page === 1}
onClick={() => setPage((p) => Math.max(p - 1, 1))}
className="p-2 rounded-lg border border-slate-600 text-slate-300 hover:bg-slate-700/50 disabled:opacity-50 disabled:cursor-not-allowed transition-all"
>
Oldingi
</Button>
<span className="text-sm flex items-center">
Sahifa {page} / {totalPages}
</span>
<Button
variant="outline"
onClick={() => setPage((p) => Math.min(totalPages, p + 1))}
disabled={page === totalPages}
<ChevronLeft className="w-5 h-5" />
</button>
{[...Array(data?.data.data.total_pages)].map((_, i) => (
<button
key={i}
onClick={() => setPage(i + 1)}
className={`px-4 py-2 rounded-lg border transition-all font-medium ${
page === i + 1
? "bg-gradient-to-r from-blue-600 to-cyan-600 border-blue-500 text-white shadow-lg shadow-cyan-500/50"
: "border-slate-600 text-slate-300 hover:bg-slate-700/50 hover:border-slate-500"
}`}
>
{i + 1}
</button>
))}
<button
disabled={page === data?.data.data.total_pages}
onClick={() =>
setPage((p) =>
Math.min(p + 1, data ? data.data.data.total_pages : 1),
)
}
className="p-2 rounded-lg border border-slate-600 text-slate-300 hover:bg-slate-700/50 disabled:opacity-50 disabled:cursor-not-allowed transition-all"
>
Keyingi
</Button>
<ChevronRight className="w-5 h-5" />
</button>
</div>
</div>
);