added pagination params page

This commit is contained in:
Samandar Turgunboyev
2025-11-17 13:09:24 +05:00
parent 41fe8725c7
commit 0f650ad15a
13 changed files with 544 additions and 346 deletions

View File

@@ -26,16 +26,23 @@ import {
} from "lucide-react"; } from "lucide-react";
import { useState } from "react"; import { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom"; import { useNavigate, useSearchParams } from "react-router-dom";
import { toast } from "sonner"; import { toast } from "sonner";
export default function TourAgenciesPage() { export default function TourAgenciesPage() {
const { t } = useTranslation(); const { t } = useTranslation();
const [currentPage, setCurrentPage] = useState(1); const [searchParams, setSearchParams] = useSearchParams();
const itemsPerPage = 4; const initialPage = Number(searchParams.get("page")) || 1;
const [currentPage, setCurrentPage] = useState(initialPage);
const itemsPerPage = 6;
const navigate = useNavigate(); const navigate = useNavigate();
const [isDialogOpen, setIsDialogOpen] = useState(false); const [isDialogOpen, setIsDialogOpen] = useState(false);
const updatePage = (newPage: number) => {
setCurrentPage(newPage);
setSearchParams({ page: newPage.toString() });
};
const { data, refetch, isLoading, isError } = useQuery({ const { data, refetch, isLoading, isError } = useQuery({
queryKey: ["all_agency", currentPage], queryKey: ["all_agency", currentPage],
queryFn: () => getAllAgency({ page: currentPage, page_size: itemsPerPage }), queryFn: () => getAllAgency({ page: currentPage, page_size: itemsPerPage }),
@@ -309,37 +316,40 @@ export default function TourAgenciesPage() {
</div> </div>
{/* Pagination */} {/* Pagination */}
<div className="flex justify-end gap-2"> <div className="flex justify-end mt-10 gap-3">
<button <button
disabled={currentPage === 1} disabled={currentPage === 1}
onClick={() => setCurrentPage((p) => Math.max(p - 1, 1))} onClick={() => updatePage(Math.max(currentPage - 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 hover:border-slate-500" className="p-2 rounded-lg border border-slate-600 text-slate-300 hover:bg-slate-700/50 disabled:opacity-50"
> >
<ChevronLeft className="w-5 h-5" /> <ChevronLeft className="w-5 h-5" />
</button> </button>
{[...Array(data?.data.data.total_pages)].map((_, i) => ( {[...Array(data?.data.data.total_pages)].map((_, i) => {
const pageNum = i + 1;
return (
<button <button
key={i} key={i}
onClick={() => setCurrentPage(i + 1)} onClick={() => updatePage(pageNum)}
className={`px-4 py-2 rounded-lg border transition-all font-medium ${ className={`px-4 py-2 rounded-lg border font-medium transition-all ${
currentPage === i + 1 currentPage === pageNum
? "bg-gradient-to-r from-blue-600 to-cyan-600 border-blue-500 text-white shadow-lg shadow-cyan-500/50" ? "bg-gradient-to-r from-blue-600 to-cyan-600 text-white border-blue-500"
: "border-slate-600 text-slate-300 hover:bg-slate-700/50 hover:border-slate-500" : "border-slate-600 text-slate-300 hover:bg-slate-700/50"
}`} }`}
> >
{i + 1} {pageNum}
</button> </button>
))} );
})}
<button <button
disabled={currentPage === data?.data.data.total_pages} disabled={currentPage === data?.data.data.total_pages}
onClick={() => onClick={() =>
setCurrentPage((p) => updatePage(
Math.min(p + 1, data ? data?.data.data.total_pages : 0), Math.min(currentPage + 1, 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 hover:border-slate-500" className="p-2 rounded-lg border border-slate-600 text-slate-300 hover:bg-slate-700/50 disabled:opacity-50"
> >
<ChevronRight className="w-5 h-5" /> <ChevronRight className="w-5 h-5" />
</button> </button>

View File

@@ -25,17 +25,26 @@ import {
} from "lucide-react"; } from "lucide-react";
import { useState } from "react"; import { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useSearchParams } from "react-router-dom";
import { toast } from "sonner"; import { toast } from "sonner";
const EmployeesManagement = () => { const EmployeesManagement = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const [currentPage, setCurrentPage] = useState(1); const [searchParams, setSearchParams] = useSearchParams();
const initialPage = Number(searchParams.get("page")) || 1;
const [page, setPage] = useState(initialPage);
const [isDialogOpen, setIsDialogOpen] = useState(false); const [isDialogOpen, setIsDialogOpen] = useState(false);
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const updatePage = (newPage: number) => {
setPage(newPage);
setSearchParams({ page: newPage.toString() });
};
const { data, isLoading } = useQuery({ const { data, isLoading } = useQuery({
queryKey: ["employees", currentPage], queryKey: ["employees", page],
queryFn: () => getAllEmployees({ page: currentPage, page_size: 10 }), queryFn: () => getAllEmployees({ page: page, page_size: 12 }),
select: (data) => data.data.data, select: (data) => data.data.data,
}); });
@@ -193,37 +202,38 @@ const EmployeesManagement = () => {
</div> </div>
{/* Pagination */} {/* Pagination */}
<div className="flex justify-end gap-2"> <div className="flex justify-end mt-10 gap-3">
<button <button
disabled={currentPage === 1} disabled={page === 1}
onClick={() => setCurrentPage((p) => Math.max(p - 1, 1))} onClick={() => updatePage(Math.max(page - 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 hover:border-slate-500" className="p-2 rounded-lg border border-slate-600 text-slate-300 hover:bg-slate-700/50 disabled:opacity-50"
> >
<ChevronLeft className="w-5 h-5" /> <ChevronLeft className="w-5 h-5" />
</button> </button>
{[...Array(data?.total_pages || 0)].map((_, i) => ( {[...Array(data?.total_pages)].map((_, i) => {
const pageNum = i + 1;
return (
<button <button
key={i} key={i}
onClick={() => setCurrentPage(i + 1)} onClick={() => updatePage(pageNum)}
className={`px-4 py-2 rounded-lg border transition-all font-medium ${ className={`px-4 py-2 rounded-lg border font-medium transition-all ${
currentPage === i + 1 page === pageNum
? "bg-gradient-to-r from-blue-600 to-cyan-600 border-blue-500 text-white shadow-lg shadow-cyan-500/50" ? "bg-gradient-to-r from-blue-600 to-cyan-600 text-white border-blue-500"
: "border-slate-600 text-slate-300 hover:bg-slate-700/50 hover:border-slate-500" : "border-slate-600 text-slate-300 hover:bg-slate-700/50"
}`} }`}
> >
{i + 1} {pageNum}
</button> </button>
))} );
})}
<button <button
disabled={currentPage === data?.total_pages} disabled={page === data?.total_pages}
onClick={() => onClick={() =>
setCurrentPage((p) => updatePage(Math.min(page + 1, data?.total_pages ?? 1))
Math.min(p + 1, data ? data?.total_pages : 0),
)
} }
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 hover:border-slate-500" className="p-2 rounded-lg border border-slate-600 text-slate-300 hover:bg-slate-700/50 disabled:opacity-50"
> >
<ChevronRight className="w-5 h-5" /> <ChevronRight className="w-5 h-5" />
</button> </button>

View File

@@ -54,6 +54,7 @@ import {
import { useEffect, useMemo, useRef, useState } from "react"; import { useEffect, useMemo, useRef, useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useSearchParams } from "react-router-dom";
import { toast } from "sonner"; import { toast } from "sonner";
import z from "zod"; import z from "zod";
@@ -67,7 +68,10 @@ const faqForm = z.object({
const Faq = () => { const Faq = () => {
const [activeTab, setActiveTab] = useState<string>(""); const [activeTab, setActiveTab] = useState<string>("");
const [currentPage, setCurrentPage] = useState(1); const [searchParams, setSearchParams] = useSearchParams();
const initialPage = Number(searchParams.get("page")) || 1;
const [page, setPage] = useState(initialPage);
const { t } = useTranslation(); const { t } = useTranslation();
const [openModal, setOpenModal] = useState(false); const [openModal, setOpenModal] = useState(false);
const [editFaq, setEditFaq] = useState<number | null>(null); const [editFaq, setEditFaq] = useState<number | null>(null);
@@ -75,6 +79,11 @@ const Faq = () => {
const scrollRef = useRef<HTMLDivElement>(null); const scrollRef = useRef<HTMLDivElement>(null);
const loaderRef = useRef<HTMLDivElement>(null); const loaderRef = useRef<HTMLDivElement>(null);
const updatePage = (newPage: number) => {
setPage(newPage);
setSearchParams({ page: newPage.toString() });
};
// Infinite scroll uchun useInfiniteQuery // Infinite scroll uchun useInfiniteQuery
const { const {
data: categoryData, data: categoryData,
@@ -96,20 +105,16 @@ const Faq = () => {
initialPageParam: 1, initialPageParam: 1,
}); });
useEffect(() => {
setCurrentPage(1);
}, [activeTab]);
const category = useMemo(() => { const category = useMemo(() => {
return categoryData?.pages.flatMap((page) => page.data.data.results) ?? []; return categoryData?.pages.flatMap((page) => page.data.data.results) ?? [];
}, [categoryData]); }, [categoryData]);
const { data: faq } = useQuery({ const { data: faq } = useQuery({
queryKey: ["all_faq", activeTab, currentPage], queryKey: ["all_faq", activeTab, page],
queryFn: () => { queryFn: () => {
return getAllFaq({ return getAllFaq({
page: currentPage, page,
page_size: 10, page_size: 1,
category: Number(activeTab), category: Number(activeTab),
}); });
}, },
@@ -301,7 +306,14 @@ const Faq = () => {
</div> </div>
{/* Tabs */} {/* Tabs */}
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full"> <Tabs
value={activeTab}
onValueChange={(value) => {
setActiveTab(value);
updatePage(1);
}}
className="w-full"
>
<div className="relative"> <div className="relative">
<TabsList <TabsList
ref={scrollRef} ref={scrollRef}
@@ -390,35 +402,36 @@ const Faq = () => {
))} ))}
</Tabs> </Tabs>
<div className="flex justify-end gap-2"> <div className="flex justify-end mt-10 gap-3">
<button <button
disabled={currentPage === 1} disabled={page === 1}
onClick={() => setCurrentPage((p) => Math.max(p - 1, 1))} onClick={() => updatePage(Math.max(page - 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 hover:border-slate-500" className="p-2 rounded-lg border border-slate-600 text-slate-300 hover:bg-slate-700/50 disabled:opacity-50"
> >
<ChevronLeft className="w-5 h-5" /> <ChevronLeft className="w-5 h-5" />
</button> </button>
{[...Array(faq?.total_pages)].map((_, i) => ( {[...Array(faq?.total_pages)].map((_, i) => {
const pageNum = i + 1;
return (
<button <button
key={i} key={i}
onClick={() => setCurrentPage(i + 1)} onClick={() => updatePage(pageNum)}
className={`px-4 py-2 rounded-lg border transition-all font-medium ${ className={`px-4 py-2 rounded-lg border font-medium transition-all ${
currentPage === i + 1 page === pageNum
? "bg-gradient-to-r from-blue-600 to-cyan-600 border-blue-500 text-white shadow-lg shadow-cyan-500/50" ? "bg-gradient-to-r from-blue-600 to-cyan-600 text-white border-blue-500"
: "border-slate-600 text-slate-300 hover:bg-slate-700/50 hover:border-slate-500" : "border-slate-600 text-slate-300 hover:bg-slate-700/50"
}`} }`}
> >
{i + 1} {pageNum}
</button> </button>
))} );
})}
<button <button
disabled={currentPage === faq?.total_pages} disabled={page === faq?.total_pages}
onClick={() => onClick={() => updatePage(Math.min(page + 1, faq?.total_pages ?? 1))}
setCurrentPage((p) => Math.min(p + 1, faq ? faq.total_pages : 0)) className="p-2 rounded-lg border border-slate-600 text-slate-300 hover:bg-slate-700/50 disabled:opacity-50"
}
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 hover:border-slate-500"
> >
<ChevronRight className="w-5 h-5" /> <ChevronRight className="w-5 h-5" />
</button> </button>

View File

@@ -45,6 +45,7 @@ import {
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useSearchParams } from "react-router-dom";
import { toast } from "sonner"; import { toast } from "sonner";
import z from "zod"; import z from "zod";
@@ -55,13 +56,22 @@ const categoryFormSchema = z.object({
const FaqCategory = () => { const FaqCategory = () => {
const [deleteId, setDeleteId] = useState<number | null>(null); const [deleteId, setDeleteId] = useState<number | null>(null);
const [currentPage, setCurrentPage] = useState(1); const [searchParams, setSearchParams] = useSearchParams();
const initialPage = Number(searchParams.get("page")) || 1;
const [page, setPage] = useState(initialPage);
const [categories, setCategories] = useState<number | null>(null); const [categories, setCategories] = useState<number | null>(null);
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const updatePage = (newPage: number) => {
setPage(newPage);
setSearchParams({ page: newPage.toString() });
};
const { data: category } = useQuery({ const { data: category } = useQuery({
queryKey: ["all_faqcategory", currentPage], queryKey: ["all_faqcategory", page],
queryFn: () => { queryFn: () => {
return getAllFaqCategory({ page: currentPage, page_size: 10 }); return getAllFaqCategory({ page: page, page_size: 20 });
}, },
select(data) { select(data) {
return data.data.data; return data.data.data;
@@ -255,37 +265,38 @@ const FaqCategory = () => {
</Table> </Table>
</div> </div>
<div className="flex justify-end gap-2"> <div className="flex justify-end mt-10 gap-3">
<button <button
disabled={currentPage === 1} disabled={page === 1}
onClick={() => setCurrentPage((p) => Math.max(p - 1, 1))} onClick={() => updatePage(Math.max(page - 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 hover:border-slate-500" className="p-2 rounded-lg border border-slate-600 text-slate-300 hover:bg-slate-700/50 disabled:opacity-50"
> >
<ChevronLeft className="w-5 h-5" /> <ChevronLeft className="w-5 h-5" />
</button> </button>
{[...Array(category?.total_pages)].map((_, i) => ( {[...Array(category?.total_pages)].map((_, i) => {
const pageNum = i + 1;
return (
<button <button
key={i} key={i}
onClick={() => setCurrentPage(i + 1)} onClick={() => updatePage(pageNum)}
className={`px-4 py-2 rounded-lg border transition-all font-medium ${ className={`px-4 py-2 rounded-lg border font-medium transition-all ${
currentPage === i + 1 page === pageNum
? "bg-gradient-to-r from-blue-600 to-cyan-600 border-blue-500 text-white shadow-lg shadow-cyan-500/50" ? "bg-gradient-to-r from-blue-600 to-cyan-600 text-white border-blue-500"
: "border-slate-600 text-slate-300 hover:bg-slate-700/50 hover:border-slate-500" : "border-slate-600 text-slate-300 hover:bg-slate-700/50"
}`} }`}
> >
{i + 1} {pageNum}
</button> </button>
))} );
})}
<button <button
disabled={currentPage === category?.total_pages} disabled={page === category?.total_pages}
onClick={() => onClick={() =>
setCurrentPage((p) => updatePage(Math.min(page + 1, category?.total_pages ?? 1))
Math.min(p + 1, category ? category.total_pages : 0),
)
} }
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 hover:border-slate-500" className="p-2 rounded-lg border border-slate-600 text-slate-300 hover:bg-slate-700/50 disabled:opacity-50"
> >
<ChevronRight className="w-5 h-5" /> <ChevronRight className="w-5 h-5" />
</button> </button>

View File

@@ -40,28 +40,23 @@ export default function FinancePage({ user }: { user: Role }) {
const [searchParams, setSearchParams] = useSearchParams(); const [searchParams, setSearchParams] = useSearchParams();
const tabParam = searchParams.get("tab") as "bookings" | "agencies" | null; const tabParam = searchParams.get("tab") as "bookings" | "agencies" | null;
const pageParam = Number(searchParams.get("page")) || 1; const pageParam = Number(searchParams.get("page")) || 1;
const filterParam = String(searchParams.get("filter")) || "";
const pageAgencyParam = Number(searchParams.get("page_agency")) || 1; const pageAgencyParam = Number(searchParams.get("page_agency")) || 1;
const [currentPage, setCurrentPage] = useState(pageParam); const [currentPage, setCurrentPage] = useState(pageParam);
const [currentPageAgency, setCurrentPageAgency] = useState(pageAgencyParam); const [currentPageAgency, setCurrentPageAgency] = useState(pageAgencyParam);
const [tab, setTab] = useState<"bookings" | "agencies">( const [tab, setTab] = useState<"bookings" | "agencies">(
tabParam ?? "bookings", tabParam ?? "bookings",
); );
const [filterStatus, setFilterStatus] = useState< const [filterStatus, setFilterStatus] = useState(filterParam);
| ""
| "pending_payment"
| "pending_confirmation"
| "confirmed"
| "completed"
| "cancelled"
>("");
useEffect(() => { useEffect(() => {
setSearchParams({ setSearchParams({
tab, tab,
page: String(currentPage), page: String(currentPage),
page_agency: String(currentPageAgency), page_agency: String(currentPageAgency),
filter: String(filterStatus),
}); });
}, [tab, currentPage, currentPageAgency, setSearchParams]); }, [tab, currentPage, currentPageAgency, setSearchParams, filterStatus]);
useEffect(() => { useEffect(() => {
if (tabParam && tabParam !== tab) { if (tabParam && tabParam !== tab) {
@@ -69,18 +64,19 @@ export default function FinancePage({ user }: { user: Role }) {
} }
}, [tabParam]); }, [tabParam]);
useEffect(() => {
setCurrentPage(1);
setCurrentPageAgency(1);
}, [filterStatus, tab]);
const { data, isLoading, isError, refetch } = useQuery({ const { data, isLoading, isError, refetch } = useQuery({
queryKey: ["list_order_user", currentPage, filterStatus], queryKey: ["list_order_user", currentPage, filterStatus],
queryFn: () => queryFn: () =>
getAllOrder({ getAllOrder({
page: currentPage, page: currentPage,
page_size: 10, page_size: 12,
order_status: filterStatus, order_status: filterStatus as
| ""
| "pending_payment"
| "pending_confirmation"
| "confirmed"
| "completed"
| "cancelled",
}), }),
}); });
@@ -94,7 +90,7 @@ export default function FinancePage({ user }: { user: Role }) {
queryFn: () => queryFn: () =>
getAllOrderAgecy({ getAllOrderAgecy({
page: currentPageAgency, page: currentPageAgency,
page_size: 10, page_size: 12,
}), }),
}); });
@@ -277,7 +273,7 @@ export default function FinancePage({ user }: { user: Role }) {
? "bg-blue-600 text-white shadow-md" ? "bg-blue-600 text-white shadow-md"
: "bg-gray-800 text-gray-300 hover:bg-gray-700" : "bg-gray-800 text-gray-300 hover:bg-gray-700"
}`} }`}
onClick={() => onClick={() => {
setFilterStatus( setFilterStatus(
s as s as
| "" | ""
@@ -286,8 +282,9 @@ export default function FinancePage({ user }: { user: Role }) {
| "confirmed" | "confirmed"
| "completed" | "completed"
| "cancelled", | "cancelled",
) );
} setCurrentPage(1);
}}
> >
{s === "" {s === ""
? t("Barcha bandlovlar") ? t("Barcha bandlovlar")

View File

@@ -26,22 +26,31 @@ import {
} from "lucide-react"; } from "lucide-react";
import { useState } from "react"; import { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom"; import { useNavigate, useSearchParams } from "react-router-dom";
import { toast } from "sonner"; import { toast } from "sonner";
const News = () => { const News = () => {
const [currentPage, setCurrentPage] = useState(1); const [searchParams, setSearchParams] = useSearchParams();
const initialPage = Number(searchParams.get("page")) || 1;
const [page, setPage] = useState(initialPage);
const { t } = useTranslation(); const { t } = useTranslation();
const [deleteId, setDeleteId] = useState<number | null>(null); const [deleteId, setDeleteId] = useState<number | null>(null);
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const navigate = useNavigate(); const navigate = useNavigate();
const updatePage = (newPage: number) => {
setPage(newPage);
setSearchParams({ page: newPage.toString() });
};
const { const {
data: allNews, data: allNews,
isLoading, isLoading,
isError, isError,
} = useQuery({ } = useQuery({
queryKey: ["all_news", currentPage], queryKey: ["all_news", page],
queryFn: () => getAllNews({ page: currentPage, page_size: 10 }), queryFn: () => getAllNews({ page, page_size: 12 }),
}); });
const { mutate: deleteMutate, isPending } = useMutation({ const { mutate: deleteMutate, isPending } = useMutation({
@@ -303,37 +312,38 @@ const News = () => {
</Dialog> </Dialog>
{/* Pagination */} {/* Pagination */}
<div className="flex justify-end gap-2 w-[90%] mx-auto mt-8"> <div className="flex justify-end mt-10 gap-3">
<button <button
disabled={currentPage === 1} disabled={page === 1}
onClick={() => setCurrentPage((p) => Math.max(p - 1, 1))} onClick={() => updatePage(Math.max(page - 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 hover:border-slate-500" className="p-2 rounded-lg border border-slate-600 text-slate-300 hover:bg-slate-700/50 disabled:opacity-50"
> >
<ChevronLeft className="w-5 h-5" /> <ChevronLeft className="w-5 h-5" />
</button> </button>
{[...Array(allNews?.data.data.total_pages)].map((_, i) => ( {[...Array(allNews?.data.data.total_pages)].map((_, i) => {
const pageNum = i + 1;
return (
<button <button
key={i} key={i}
onClick={() => setCurrentPage(i + 1)} onClick={() => updatePage(pageNum)}
className={`px-4 py-2 rounded-lg border transition-all font-medium ${ className={`px-4 py-2 rounded-lg border font-medium transition-all ${
currentPage === i + 1 page === pageNum
? "bg-gradient-to-r from-blue-600 to-cyan-600 border-blue-500 text-white shadow-lg shadow-cyan-500/50" ? "bg-gradient-to-r from-blue-600 to-cyan-600 text-white border-blue-500"
: "border-slate-600 text-slate-300 hover:bg-slate-700/50 hover:border-slate-500" : "border-slate-600 text-slate-300 hover:bg-slate-700/50"
}`} }`}
> >
{i + 1} {pageNum}
</button> </button>
))} );
})}
<button <button
disabled={currentPage === allNews?.data.data.total_pages} disabled={page === allNews?.data.data.total_pages}
onClick={() => onClick={() =>
setCurrentPage((p) => updatePage(Math.min(page + 1, allNews?.data.data.total_pages ?? 1))
Math.min(p + 1, allNews ? allNews?.data.data.total_pages : 0),
)
} }
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 hover:border-slate-500" className="p-2 rounded-lg border border-slate-600 text-slate-300 hover:bg-slate-700/50 disabled:opacity-50"
> >
<ChevronRight className="w-5 h-5" /> <ChevronRight className="w-5 h-5" />
</button> </button>

View File

@@ -38,6 +38,7 @@ import {
} from "lucide-react"; } from "lucide-react";
import { useState } from "react"; import { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useSearchParams } from "react-router-dom";
import { toast } from "sonner"; import { toast } from "sonner";
export default function WithdrawRequests({ export default function WithdrawRequests({
@@ -53,7 +54,16 @@ export default function WithdrawRequests({
| "user"; | "user";
}) { }) {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const [currentPage, setCurrentPage] = useState(1); const [searchParams, setSearchParams] = useSearchParams();
const initialPage = Number(searchParams.get("page")) || 1;
const [page, setPage] = useState(initialPage);
const updatePage = (newPage: number) => {
setPage(newPage);
setSearchParams({ page: newPage.toString() });
};
const [statusFilter, setStatusFilter] = useState< const [statusFilter, setStatusFilter] = useState<
"pending" | "approved" | "cancelled" | "" "pending" | "approved" | "cancelled" | ""
>(""); >("");
@@ -70,11 +80,11 @@ export default function WithdrawRequests({
const [closeId, setCloseId] = useState<number | null>(null); const [closeId, setCloseId] = useState<number | null>(null);
const { data, isLoading, isError, refetch } = useQuery({ const { data, isLoading, isError, refetch } = useQuery({
queryKey: ["withdraw-requests", currentPage, statusFilter], queryKey: ["withdraw-requests", page, statusFilter],
queryFn: () => queryFn: () =>
getPayoutList({ getPayoutList({
page: currentPage, page,
page_size: 10, page_size: 20,
status: statusFilter, status: statusFilter,
}), }),
}); });
@@ -178,7 +188,7 @@ export default function WithdrawRequests({
key={tab.value} key={tab.value}
onClick={() => { onClick={() => {
setStatusFilter(tab.value); setStatusFilter(tab.value);
setCurrentPage(1); updatePage(1);
}} }}
className={`rounded-xl px-4 py-2 font-medium transition-all ${ className={`rounded-xl px-4 py-2 font-medium transition-all ${
statusFilter === tab.value statusFilter === tab.value
@@ -360,37 +370,38 @@ export default function WithdrawRequests({
</Card> </Card>
{/* 🔹 PAGINATION */} {/* 🔹 PAGINATION */}
<div className="flex justify-end gap-2 mt-5"> <div className="flex justify-end mt-10 gap-3">
<button <button
disabled={currentPage === 1} disabled={page === 1}
onClick={() => setCurrentPage((p) => Math.max(p - 1, 1))} onClick={() => updatePage(Math.max(page - 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 hover:border-slate-500" className="p-2 rounded-lg border border-slate-600 text-slate-300 hover:bg-slate-700/50 disabled:opacity-50"
> >
<ChevronLeft className="w-5 h-5" /> <ChevronLeft className="w-5 h-5" />
</button> </button>
{[...Array(data?.data.data.total_pages)].map((_, i) => ( {[...Array(data?.data.data.total_pages)].map((_, i) => {
const pageNum = i + 1;
return (
<button <button
key={i} key={i}
onClick={() => setCurrentPage(i + 1)} onClick={() => updatePage(pageNum)}
className={`px-4 py-2 rounded-lg border transition-all font-medium ${ className={`px-4 py-2 rounded-lg border font-medium transition-all ${
currentPage === i + 1 page === pageNum
? "bg-gradient-to-r from-blue-600 to-cyan-600 border-blue-500 text-white shadow-lg shadow-cyan-500/50" ? "bg-gradient-to-r from-blue-600 to-cyan-600 text-white border-blue-500"
: "border-slate-600 text-slate-300 hover:bg-slate-700/50 hover:border-slate-500" : "border-slate-600 text-slate-300 hover:bg-slate-700/50"
}`} }`}
> >
{i + 1} {pageNum}
</button> </button>
))} );
})}
<button <button
disabled={currentPage === data?.data.data.total_pages} disabled={page === data?.data.data.total_pages}
onClick={() => onClick={() =>
setCurrentPage((p) => updatePage(Math.min(page + 1, data?.data.data.total_pages ?? 1))
Math.min(p + 1, data ? data?.data.data.total_pages : 0),
)
} }
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 hover:border-slate-500" className="p-2 rounded-lg border border-slate-600 text-slate-300 hover:bg-slate-700/50 disabled:opacity-50"
> >
<ChevronRight className="w-5 h-5" /> <ChevronRight className="w-5 h-5" />
</button> </button>

View File

@@ -56,6 +56,7 @@ import {
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useSearchParams } from "react-router-dom";
import { toast } from "sonner"; import { toast } from "sonner";
import { z } from "zod"; import { z } from "zod";
@@ -92,15 +93,24 @@ const positions = [
]; ];
const SiteBannerAdmin = () => { const SiteBannerAdmin = () => {
const [currentPage, setCurrentPage] = useState(1); const [searchParams, setSearchParams] = useSearchParams();
const initialPage = Number(searchParams.get("page")) || 1;
const [page, setPage] = useState(initialPage);
const updatePage = (newPage: number) => {
setPage(newPage);
setSearchParams({ page: newPage.toString() });
};
const { const {
data: banner, data: banner,
isLoading, isLoading,
isError, isError,
refetch, refetch,
} = useQuery({ } = useQuery({
queryKey: ["all_banner", currentPage], queryKey: ["all_banner", page],
queryFn: () => getBanner({ page: currentPage, page_size: 10 }), queryFn: () => getBanner({ page, page_size: 20 }),
select(data) { select(data) {
return data.data.data; return data.data.data;
}, },
@@ -385,37 +395,38 @@ const SiteBannerAdmin = () => {
</Table> </Table>
</div> </div>
<div className="flex justify-end gap-2 mt-5"> <div className="flex justify-end mt-10 gap-3">
<button <button
disabled={currentPage === 1} disabled={page === 1}
onClick={() => setCurrentPage((p) => Math.max(p - 1, 1))} onClick={() => updatePage(Math.max(page - 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 hover:border-slate-500" className="p-2 rounded-lg border border-slate-600 text-slate-300 hover:bg-slate-700/50 disabled:opacity-50"
> >
<ChevronLeft className="w-5 h-5" /> <ChevronLeft className="w-5 h-5" />
</button> </button>
{[...Array(banner?.total_pages)].map((_, i) => ( {[...Array(banner?.total_pages)].map((_, i) => {
const pageNum = i + 1;
return (
<button <button
key={i} key={i}
onClick={() => setCurrentPage(i + 1)} onClick={() => updatePage(pageNum)}
className={`px-4 py-2 rounded-lg border transition-all font-medium ${ className={`px-4 py-2 rounded-lg border font-medium transition-all ${
currentPage === i + 1 page === pageNum
? "bg-gradient-to-r from-blue-600 to-cyan-600 border-blue-500 text-white shadow-lg shadow-cyan-500/50" ? "bg-gradient-to-r from-blue-600 to-cyan-600 text-white border-blue-500"
: "border-slate-600 text-slate-300 hover:bg-slate-700/50 hover:border-slate-500" : "border-slate-600 text-slate-300 hover:bg-slate-700/50"
}`} }`}
> >
{i + 1} {pageNum}
</button> </button>
))} );
})}
<button <button
disabled={currentPage === banner?.total_pages} disabled={page === banner?.total_pages}
onClick={() => onClick={() =>
setCurrentPage((p) => updatePage(Math.min(page + 1, banner?.total_pages ?? 1))
Math.min(p + 1, banner ? banner?.total_pages : 0),
)
} }
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 hover:border-slate-500" className="p-2 rounded-lg border border-slate-600 text-slate-300 hover:bg-slate-700/50 disabled:opacity-50"
> >
<ChevronRight className="w-5 h-5" /> <ChevronRight className="w-5 h-5" />
</button> </button>

View File

@@ -15,15 +15,25 @@ import {
DialogTitle, DialogTitle,
} from "@/shared/ui/dialog"; } from "@/shared/ui/dialog";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { AlertTriangle, Loader2, XIcon } from "lucide-react"; import {
AlertTriangle,
ChevronLeft,
ChevronRight,
Loader2,
XIcon,
} from "lucide-react";
import { useState } from "react"; import { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom"; import { Link, useSearchParams } from "react-router-dom";
import { toast } from "sonner"; import { toast } from "sonner";
const SupportAgency = () => { const SupportAgency = () => {
const [query, setQuery] = useState(""); const [query, setQuery] = useState("");
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const [searchParams, setSearchParams] = useSearchParams();
const initialPage = Number(searchParams.get("page")) || 1;
const [page, setPage] = useState(initialPage);
const [openUser, setOpenUser] = useState<boolean>(false); const [openUser, setOpenUser] = useState<boolean>(false);
const [user, setUser] = useState<{ const [user, setUser] = useState<{
status: boolean; status: boolean;
@@ -38,10 +48,15 @@ const SupportAgency = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const [selected, setSelected] = useState<GetSupportAgencyRes | null>(null); const [selected, setSelected] = useState<GetSupportAgencyRes | null>(null);
const updatePage = (newPage: number) => {
setPage(newPage);
setSearchParams({ page: newPage.toString() });
};
const { data, isLoading, isError, refetch } = useQuery({ const { data, isLoading, isError, refetch } = useQuery({
queryKey: ["support_agency"], queryKey: ["support_agency", page],
queryFn: () => queryFn: () =>
getSupportAgency({ page: 1, page_size: 10, search: "", status: "" }), getSupportAgency({ page: page, page_size: 12, search: "", status: "" }),
}); });
const { mutate: updateTours } = useMutation({ const { mutate: updateTours } = useMutation({
@@ -117,7 +132,10 @@ const SupportAgency = () => {
<div className="flex gap-3 mb-6"> <div className="flex gap-3 mb-6">
<input <input
value={query} value={query}
onChange={(e) => setQuery(e.target.value)} onChange={(e) => {
setQuery(e.target.value);
updatePage(1);
}}
placeholder={t("Qidiruv (ism, email yoki telefon)...")} placeholder={t("Qidiruv (ism, email yoki telefon)...")}
className="flex-1 p-2 border rounded-md focus:outline-none focus:ring" className="flex-1 p-2 border rounded-md focus:outline-none focus:ring"
/> />
@@ -188,6 +206,43 @@ const SupportAgency = () => {
</div> </div>
)} )}
<div className="flex justify-end mt-10 gap-3">
<button
disabled={page === 1}
onClick={() => updatePage(Math.max(page - 1, 1))}
className="p-2 rounded-lg border border-slate-600 text-slate-300 hover:bg-slate-700/50 disabled:opacity-50"
>
<ChevronLeft className="w-5 h-5" />
</button>
{[...Array(data?.data.data.total_pages)].map((_, i) => {
const pageNum = i + 1;
return (
<button
key={i}
onClick={() => updatePage(pageNum)}
className={`px-4 py-2 rounded-lg border font-medium transition-all ${
page === pageNum
? "bg-gradient-to-r from-blue-600 to-cyan-600 text-white border-blue-500"
: "border-slate-600 text-slate-300 hover:bg-slate-700/50"
}`}
>
{pageNum}
</button>
);
})}
<button
disabled={page === data?.data.data.total_pages}
onClick={() =>
updatePage(Math.min(page + 1, 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"
>
<ChevronRight className="w-5 h-5" />
</button>
</div>
{selected && ( {selected && (
<div <div
className="fixed inset-0 z-50 flex items-center justify-center bg-black/70 p-4" className="fixed inset-0 z-50 flex items-center justify-center bg-black/70 p-4"

View File

@@ -272,7 +272,7 @@ const StepOne = ({
function onSubmit(value: z.infer<typeof TourformSchema>) { function onSubmit(value: z.infer<typeof TourformSchema>) {
const formData = new FormData(); const formData = new FormData();
const tour = data ? data.data : null;
// Asosiy ma'lumotlar // Asosiy ma'lumotlar
formData.append("title", value.title); formData.append("title", value.title);
formData.append("location_name", value.location_name); formData.append("location_name", value.location_name);
@@ -315,7 +315,7 @@ const StepOne = ({
formData.append("hotel_meals_ru", value.hotel_meals_info_ru); formData.append("hotel_meals_ru", value.hotel_meals_info_ru);
} }
formData.append("duration_days", String(value.duration)); formData.append("duration_days", String(value.duration));
formData.append("rating", String("0.0")); formData.append("rating", tour ? String(tour.rating) : "0.0");
if (value.banner instanceof File) { if (value.banner instanceof File) {
formData.append("image_banner", value.banner); formData.append("image_banner", value.banner);

View File

@@ -37,7 +37,7 @@ import {
} from "lucide-react"; } from "lucide-react";
import { useState } from "react"; import { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom"; import { useNavigate, useSearchParams } from "react-router-dom";
import { toast } from "sonner"; import { toast } from "sonner";
type Role = type Role =
@@ -51,15 +51,17 @@ type Role =
const Tours = ({ user }: { user: Role }) => { const Tours = ({ user }: { user: Role }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [page, setPage] = useState(1);
const [deleteId, setDeleteId] = useState<number | null>(null); const [deleteId, setDeleteId] = useState<number | null>(null);
const [showPopularDialog, setShowPopularDialog] = useState(false); const [showPopularDialog, setShowPopularDialog] = useState(false);
const navigate = useNavigate(); const navigate = useNavigate();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const [searchParams, setSearchParams] = useSearchParams();
const initialPage = Number(searchParams.get("page")) || 1;
const [page, setPage] = useState(initialPage);
const { data, isLoading, isError, refetch } = useQuery({ const { data, isLoading, isError, refetch } = useQuery({
queryKey: ["all_tours", page], queryKey: ["all_tours", page],
queryFn: () => getAllTours({ page: page, page_size: 10 }), queryFn: () => getAllTours({ page, page_size: 10 }),
}); });
const { data: popularTour } = useQuery({ const { data: popularTour } = useQuery({
@@ -110,6 +112,11 @@ const Tours = ({ user }: { user: Role }) => {
setShowPopularDialog(false); setShowPopularDialog(false);
}; };
const updatePage = (newPage: number) => {
setPage(newPage);
setSearchParams({ page: newPage.toString() });
};
if (isLoading) { if (isLoading) {
return ( return (
<div className="flex flex-col items-center justify-center min-h-screen bg-slate-900 text-white gap-4 w-full"> <div className="flex flex-col items-center justify-center min-h-screen bg-slate-900 text-white gap-4 w-full">
@@ -343,32 +350,35 @@ const Tours = ({ user }: { user: Role }) => {
<div className="flex justify-end mt-10 gap-3"> <div className="flex justify-end mt-10 gap-3">
<button <button
disabled={page === 1} disabled={page === 1}
onClick={() => setPage((p) => Math.max(p - 1, 1))} onClick={() => updatePage(Math.max(page - 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" className="p-2 rounded-lg border border-slate-600 text-slate-300 hover:bg-slate-700/50 disabled:opacity-50"
> >
<ChevronLeft className="w-5 h-5" /> <ChevronLeft className="w-5 h-5" />
</button> </button>
{[...Array(data?.data.data.total_pages)].map((_, i) => (
{[...Array(data?.data.data.total_pages)].map((_, i) => {
const pageNum = i + 1;
return (
<button <button
key={i} key={i}
onClick={() => setPage(i + 1)} onClick={() => updatePage(pageNum)}
className={`px-4 py-2 rounded-lg border transition-all font-medium ${ className={`px-4 py-2 rounded-lg border font-medium transition-all ${
page === i + 1 page === pageNum
? "bg-gradient-to-r from-blue-600 to-cyan-600 border-blue-500 text-white shadow-lg shadow-cyan-500/50" ? "bg-gradient-to-r from-blue-600 to-cyan-600 text-white border-blue-500"
: "border-slate-600 text-slate-300 hover:bg-slate-700/50 hover:border-slate-500" : "border-slate-600 text-slate-300 hover:bg-slate-700/50"
}`} }`}
> >
{i + 1} {pageNum}
</button> </button>
))} );
})}
<button <button
disabled={page === data?.data.data.total_pages} disabled={page === data?.data.data.total_pages}
onClick={() => onClick={() =>
setPage((p) => updatePage(Math.min(page + 1, data?.data.data.total_pages ?? 1))
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" className="p-2 rounded-lg border border-slate-600 text-slate-300 hover:bg-slate-700/50 disabled:opacity-50"
> >
<ChevronRight className="w-5 h-5" /> <ChevronRight className="w-5 h-5" />
</button> </button>

View File

@@ -25,6 +25,39 @@ import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useNavigate, useSearchParams } from "react-router-dom"; import { useNavigate, useSearchParams } from "react-router-dom";
// Error Component
const ErrorDisplay: React.FC<{ message: string; onRetry: () => void }> = ({
message,
onRetry,
}) => {
const { t } = useTranslation();
return (
<div className="flex flex-col items-center justify-center min-h-[300px] bg-slate-800/50 rounded-lg text-center text-white gap-4 p-6">
<AlertTriangle className="w-10 h-10 text-red-500" />
<p className="text-lg">{message}</p>
<Button
onClick={onRetry}
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>
);
};
// Loading Component
const LoadingDisplay: React.FC<{ message?: string }> = ({ message }) => {
const { t } = useTranslation();
return (
<div className="flex flex-col items-center justify-center min-h-[300px] bg-slate-800/50 rounded-lg text-white gap-4">
<Loader2 className="w-10 h-10 animate-spin text-cyan-400" />
<p className="text-slate-400">
{message || t("Ma'lumotlar yuklanmoqda...")}
</p>
</div>
);
};
const ToursSetting: React.FC = () => { const ToursSetting: React.FC = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
@@ -134,61 +167,12 @@ const ToursSetting: React.FC = () => {
isError: amenitiesError, isError: amenitiesError,
refetch: amenitiesRef, refetch: amenitiesRef,
} = useQuery({ } = useQuery({
queryKey: ["all_amenities", page, pageSize], queryKey: ["all_amenities", pageAmenities, pageSizeAmenities],
queryFn: () => getAllAmenities({ page, page_size: pageSize }), queryFn: () =>
getAllAmenities({ page: pageAmenities, page_size: pageSizeAmenities }),
select: (res) => res.data.data, select: (res) => res.data.data,
}); });
if (
isLoading ||
tarifLoad ||
transportLoad ||
typeLoad ||
featureLoad ||
featureTypeLoad ||
amenitiesLoad
) {
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 ||
tarifError ||
transportError ||
typeError ||
featureError ||
featureTypeError ||
amenitiesError
) {
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();
tarifRef();
transportRef();
typeRef();
featureRef();
featureTypeRef();
amenitiesRef();
}}
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>
);
}
const handleTabChange = (value: string) => { const handleTabChange = (value: string) => {
setActiveTab(value); setActiveTab(value);
navigate({ navigate({
@@ -217,37 +201,95 @@ const ToursSetting: React.FC = () => {
</TabsList> </TabsList>
<TabsContent value="badge" className="space-y-4"> <TabsContent value="badge" className="space-y-4">
{isLoading ? (
<LoadingDisplay />
) : isError ? (
<ErrorDisplay
message={t("Belgilarni yuklashda xatolik yuz berdi.")}
onRetry={refetch}
/>
) : (
<BadgeTable data={data} page={page} pageSize={pageSize} /> <BadgeTable data={data} page={page} pageSize={pageSize} />
)}
</TabsContent> </TabsContent>
<TabsContent value="tarif" className="space-y-4"> <TabsContent value="tarif" className="space-y-4">
{tarifLoad ? (
<LoadingDisplay />
) : tarifError ? (
<ErrorDisplay
message={t("Tariflarni yuklashda xatolik yuz berdi.")}
onRetry={tarifRef}
/>
) : (
<TarifTable <TarifTable
data={tarifData} data={tarifData}
page={pageTarif} page={pageTarif}
pageSize={pageSizeTarif} pageSize={pageSizeTarif}
/> />
)}
</TabsContent> </TabsContent>
<TabsContent value="transport" className="space-y-4"> <TabsContent value="transport" className="space-y-4">
{transportLoad ? (
<LoadingDisplay />
) : transportError ? (
<ErrorDisplay
message={t("Transportlarni yuklashda xatolik yuz berdi.")}
onRetry={transportRef}
/>
) : (
<TransportTable <TransportTable
data={transportData} data={transportData}
page={pageTransport} page={pageTransport}
pageSize={pageSizeTransport} pageSize={pageSizeTransport}
/> />
)}
</TabsContent> </TabsContent>
<TabsContent value="meal" className="space-y-4"> <TabsContent value="meal" className="space-y-4">
{amenitiesLoad ? (
<LoadingDisplay />
) : amenitiesError ? (
<ErrorDisplay
message={t("Qulayliklarni yuklashda xatolik yuz berdi.")}
onRetry={amenitiesRef}
/>
) : (
<Amenities <Amenities
data={amenitiesData} data={amenitiesData}
page={pageAmenities} page={pageAmenities}
pageSize={pageSizeAmenities} pageSize={pageSizeAmenities}
/> />
)}
</TabsContent> </TabsContent>
<TabsContent value="hotel_type" className="space-y-4"> <TabsContent value="hotel_type" className="space-y-4">
{typeLoad ? (
<LoadingDisplay />
) : typeError ? (
<ErrorDisplay
message={t("Otel turlarini yuklashda xatolik yuz berdi.")}
onRetry={typeRef}
/>
) : (
<MealTable <MealTable
data={typeData} data={typeData}
page={pageTransport} page={pageType}
pageSize={pageSizeTransport} pageSize={pageSizeType}
/> />
)}
</TabsContent> </TabsContent>
<TabsContent value="hotel_features" className="space-y-4"> <TabsContent value="hotel_features" className="space-y-4">
{featureLoad ? (
<LoadingDisplay />
) : featureError ? (
<ErrorDisplay
message={t("Otel sharoitlarini yuklashda xatolik yuz berdi.")}
onRetry={featureRef}
/>
) : (
<FeaturesTable <FeaturesTable
data={featureData} data={featureData}
page={pageFeature} page={pageFeature}
@@ -255,14 +297,25 @@ const ToursSetting: React.FC = () => {
setActiveTab={setActiveTab} setActiveTab={setActiveTab}
setFeatureId={setFeatureId} setFeatureId={setFeatureId}
/> />
)}
</TabsContent> </TabsContent>
<TabsContent value="feature_type" className="space-y-4"> <TabsContent value="feature_type" className="space-y-4">
{featureTypeLoad ? (
<LoadingDisplay />
) : featureTypeError ? (
<ErrorDisplay
message={t("Sharoit turlarini yuklashda xatolik yuz berdi.")}
onRetry={featureTypeRef}
/>
) : (
<FeaturesTableType <FeaturesTableType
data={featureTypeData} data={featureTypeData}
page={pageFeature} page={pageFeature}
featureId={featureId} featureId={featureId}
pageSize={pageSizeFeature} pageSize={pageSizeFeature}
/> />
)}
</TabsContent> </TabsContent>
</Tabs> </Tabs>
</div> </div>

View File

@@ -18,20 +18,28 @@ import {
} from "lucide-react"; } from "lucide-react";
import { useState } from "react"; import { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom"; import { useNavigate, useSearchParams } from "react-router-dom";
export default function UserList() { export default function UserList() {
const [searchQuery, setSearchQuery] = useState(""); const [searchQuery, setSearchQuery] = useState("");
const [currentPage, setCurrentPage] = useState(1); const [searchParams, setSearchParams] = useSearchParams();
const initialPage = Number(searchParams.get("page")) || 1;
const [page, setPage] = useState(initialPage);
const usersPerPage = 6; const usersPerPage = 6;
const { t } = useTranslation(); const { t } = useTranslation();
const navigate = useNavigate(); const navigate = useNavigate();
const updatePage = (newPage: number) => {
setPage(newPage);
setSearchParams({ page: newPage.toString() });
};
const { data, isLoading, isError, refetch } = useQuery({ const { data, isLoading, isError, refetch } = useQuery({
queryKey: ["user_all", currentPage, searchQuery], queryKey: ["user_all", page, searchQuery],
queryFn: () => queryFn: () =>
getAllUsers({ getAllUsers({
page: currentPage, page,
page_size: usersPerPage, page_size: usersPerPage,
search: searchQuery, search: searchQuery,
}), }),
@@ -119,7 +127,7 @@ export default function UserList() {
value={searchQuery} value={searchQuery}
onChange={(e) => { onChange={(e) => {
setSearchQuery(e.target.value); setSearchQuery(e.target.value);
setCurrentPage(1); updatePage(1);
}} }}
className="w-full pl-14 pr-4 py-3 bg-slate-700/30 border border-slate-600/50 text-white placeholder-slate-400 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all" className="w-full pl-14 pr-4 py-3 bg-slate-700/30 border border-slate-600/50 text-white placeholder-slate-400 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all"
/> />
@@ -214,41 +222,40 @@ export default function UserList() {
))} ))}
</div> </div>
<div className="flex justify-end gap-2"> <div className="flex justify-end mt-10 gap-3">
<button <button
disabled={currentPage === 1} disabled={page === 1}
onClick={() => setCurrentPage((p) => Math.max(p - 1, 1))} onClick={() => updatePage(Math.max(page - 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" className="p-2 rounded-lg border border-slate-600 text-slate-300 hover:bg-slate-700/50 disabled:opacity-50"
> >
<ChevronLeft className="w-5 h-5" /> <ChevronLeft className="w-5 h-5" />
</button> </button>
{[...Array(data?.data.data.total_pages)].map((_, i) => ( {[...Array(data?.data.data.total_pages)].map((_, i) => {
const pageNum = i + 1;
return (
<button <button
key={i} key={i}
onClick={() => setCurrentPage(i + 1)} onClick={() => updatePage(pageNum)}
className={`px-4 py-2 rounded-lg border transition-all font-medium ${ className={`px-4 py-2 rounded-lg border font-medium transition-all ${
currentPage === i + 1 page === pageNum
? "bg-gradient-to-r from-blue-600 to-cyan-600 border-blue-500 text-white shadow-lg shadow-cyan-500/50" ? "bg-gradient-to-r from-blue-600 to-cyan-600 text-white border-blue-500"
: "border-slate-600 text-slate-300 hover:bg-slate-700/50 hover:border-slate-500" : "border-slate-600 text-slate-300 hover:bg-slate-700/50"
}`} }`}
> >
{i + 1} {pageNum}
</button> </button>
))} );
})}
<button <button
disabled={ disabled={page === data?.data.data.total_pages}
data
? currentPage === data.data.data.total_pages
: currentPage === 1
}
onClick={() => onClick={() =>
setCurrentPage((p) => updatePage(
Math.min(p + 1, data ? data.data.data.total_pages : 1), Math.min(page + 1, 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" className="p-2 rounded-lg border border-slate-600 text-slate-300 hover:bg-slate-700/50 disabled:opacity-50"
> >
<ChevronRight className="w-5 h-5" /> <ChevronRight className="w-5 h-5" />
</button> </button>