diff --git a/src/App.tsx b/src/App.tsx
index 633a7d9..f7d64e2 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -15,6 +15,7 @@ import {
import AddNews from "@/pages/news/ui/AddNews";
import News from "@/pages/news/ui/News";
import NewsCategory from "@/pages/news/ui/NewsCategory";
+import WithdrawRequestsList from "@/pages/payout-request/ui/PayoutRequest";
import Page from "@/pages/profile/ui/Profile";
import Seo from "@/pages/seo/ui/Seo";
import SiteBannerAdmin from "@/pages/site-banner/ui/Banner";
@@ -39,14 +40,9 @@ import { Sidebar } from "@/widgets/sidebar/ui/Sidebar";
import { useWelcomeStore } from "@/widgets/welcome/lib/hook";
import Welcome from "@/widgets/welcome/ui/welcome";
import { useQuery } from "@tanstack/react-query";
+import { Loader2 } from "lucide-react";
import { useEffect } from "react";
-import {
- Navigate,
- Route,
- Routes,
- useLocation,
- useNavigate,
-} from "react-router-dom";
+import { Route, Routes, useLocation, useNavigate } from "react-router-dom";
const App = () => {
const token = getAuthToken();
@@ -54,9 +50,9 @@ const App = () => {
const location = useLocation();
const { setOpenModal } = useWelcomeStore();
- const { data: user } = useQuery({
+ const { data: user, isLoading } = useQuery({
queryKey: ["get_me"],
- queryFn: () => getMe(),
+ queryFn: getMe,
select: (data) => data.data.data,
enabled: !!token,
});
@@ -64,19 +60,7 @@ const App = () => {
const hideSidebarPaths = ["/login"];
const shouldShowSidebar = !hideSidebarPaths.includes(location.pathname);
- useEffect(() => {
- if (token && user) {
- if (location.pathname === "/") {
- if (user.role === "moderator") navigate("/user");
- else if (user.role === "tour_admin") navigate("/profile");
- else if (user.role === "buxgalter") navigate("/finance");
- }
- } else if (!token) {
- navigate("/login");
- }
- }, [token, user]);
-
- // 🔹 Faqat userda ism yoki familiya yo‘q bo‘lsa Welcome modalni ochamiz
+ // ✅ Modalni boshqarish
useEffect(() => {
if (
user &&
@@ -88,7 +72,39 @@ const App = () => {
} else {
setOpenModal(false);
}
- }, [user, location.pathname]);
+ }, [user, location.pathname, setOpenModal]);
+ useEffect(() => {
+ if (!token) {
+ navigate("/login", { replace: true });
+ return;
+ }
+
+ if (!user) return;
+
+ if (location.pathname === "/") {
+ if (user.role === "tour_admin") {
+ navigate("/profile", { replace: true });
+ } else if (user.role === "moderator") {
+ navigate("/user", { replace: true });
+ } else if (user.role === "buxgalter") {
+ navigate("/finance", { replace: true });
+ } else {
+ navigate("/login", { replace: true });
+ }
+ }
+ }, [token, user, location.pathname, navigate]);
+
+ if (!token) {
+ return ;
+ }
+
+ if (isLoading) {
+ return (
+
+
+
+ );
+ }
return (
@@ -100,11 +116,9 @@ const App = () => {
shouldShowSidebar ? "lg:ml-64" : "ml-0",
)}
>
- {/* ✅ Welcome faqat login sahifasida ko‘rinmaydi */}
{location.pathname !== "/login" &&
}
- } />
} />
} />
} />
@@ -115,6 +129,12 @@ const App = () => {
} />
} />
} />
+
+ }
+ />
} />
{
});
}
+ useEffect(() => {
+ if (data) {
+ const userData = data.data.data;
+ setUser(userData);
+
+ if (userData.role === "user") {
+ toast.error(t("Sizga tizimga kirishga ruxsat berilmagan"), {
+ richColors: true,
+ position: "top-center",
+ });
+
+ setAuthToken("");
+ setAuthRefToken("");
+ queryClient.clear();
+
+ navigate("/login", { replace: true });
+ } else {
+ navigate("/", { replace: true });
+ }
+ }
+ }, [data]);
+
return (
diff --git a/src/pages/employees/ui/EditEmployees.tsx b/src/pages/employees/ui/EditEmployees.tsx
index e947e26..e084ec7 100644
--- a/src/pages/employees/ui/EditEmployees.tsx
+++ b/src/pages/employees/ui/EditEmployees.tsx
@@ -110,7 +110,7 @@ const EditEmployees = ({
...(modalMode === "add" && { password: "" }),
});
}
- }, [data, editId, showModal, modalMode]);
+ }, [data, editId, showModal, modalMode, form]);
const { mutate: create, isPending } = useMutation({
mutationFn: ({
diff --git a/src/pages/faq/ui/Faq.tsx b/src/pages/faq/ui/Faq.tsx
index 72efba4..8f12897 100644
--- a/src/pages/faq/ui/Faq.tsx
+++ b/src/pages/faq/ui/Faq.tsx
@@ -51,7 +51,7 @@ import {
PlusCircle,
Trash2,
} from "lucide-react";
-import { useEffect, useRef, useState } from "react";
+import { useEffect, useMemo, useRef, useState } from "react";
import { useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
@@ -100,8 +100,9 @@ const Faq = () => {
setCurrentPage(1);
}, [activeTab]);
- const category =
- categoryData?.pages.flatMap((page) => page.data.data.results) ?? [];
+ const category = useMemo(() => {
+ return categoryData?.pages.flatMap((page) => page.data.data.results) ?? [];
+ }, [categoryData]);
const { data: faq } = useQuery({
queryKey: ["all_faq", activeTab, currentPage],
@@ -239,7 +240,7 @@ const Faq = () => {
form.setValue("answer_ru", detailFaq.text_ru);
form.setValue("categories", activeTab);
}
- }, [detailFaq, form]);
+ }, [detailFaq, form, activeTab]);
function onSubmit(value: z.infer
) {
if (editFaq === null) {
diff --git a/src/pages/faq/ui/FaqCategory.tsx b/src/pages/faq/ui/FaqCategory.tsx
index dabbd22..db6661e 100644
--- a/src/pages/faq/ui/FaqCategory.tsx
+++ b/src/pages/faq/ui/FaqCategory.tsx
@@ -146,7 +146,7 @@ const FaqCategory = () => {
form.setValue("name", oneCategory.name_uz);
form.setValue("name_ru", oneCategory.name_ru);
}
- }, [oneCategory, categories]);
+ }, [oneCategory, categories, form]);
const onSubmit = (values: z.infer) => {
if (categories !== null) {
diff --git a/src/pages/finance/lib/api.ts b/src/pages/finance/lib/api.ts
index 38f9141..d0fbafc 100644
--- a/src/pages/finance/lib/api.ts
+++ b/src/pages/finance/lib/api.ts
@@ -1,6 +1,7 @@
import type {
AgencyOrderData,
History,
+ OrderHistoryData,
UserAgencyDetailData,
UserOrderData,
UserOrderDetailData,
@@ -8,6 +9,7 @@ import type {
import httpClient from "@/shared/config/api/httpClient";
import {
AGENCY_ORDERS,
+ AGENCY_ORDERS_HISTORY,
PAYMENT_AGENCY,
USER_ORDERS,
} from "@/shared/config/api/URLs";
@@ -74,7 +76,7 @@ const payAgency = async ({
body: {
travel_agency: number;
amount: number;
- note: string;
+ note?: string;
};
}) => {
const res = await httpClient.post(PAYMENT_AGENCY, body);
@@ -89,12 +91,41 @@ const getPaymentHistory = async (params: {
return res;
};
+const getOrderHistory = async (params: {
+ page: number;
+ page_size: number;
+ order: number;
+}): Promise> => {
+ const res = await httpClient.get(AGENCY_ORDERS_HISTORY, { params });
+ return res;
+};
+
+const savedOrderHistory = async (body: {
+ order: number;
+ old_status:
+ | "pending_payment"
+ | "pending_confirmation"
+ | "cancelled"
+ | "confirmed"
+ | "completed";
+ new_status:
+ | "pending_payment"
+ | "pending_confirmation"
+ | "cancelled"
+ | "confirmed"
+ | "completed";
+}) => {
+ const res = await httpClient.post(AGENCY_ORDERS_HISTORY, body);
+ return res;
+};
export {
getAllOrder,
getAllOrderAgecy,
getDetailAgencyOrder,
getDetailOrder,
+ getOrderHistory,
getPaymentHistory,
payAgency,
+ savedOrderHistory,
updateDetailOrder,
};
diff --git a/src/pages/finance/lib/type.ts b/src/pages/finance/lib/type.ts
index 1586f5b..af5d328 100644
--- a/src/pages/finance/lib/type.ts
+++ b/src/pages/finance/lib/type.ts
@@ -211,3 +211,54 @@ export interface History {
}[];
};
}
+
+export interface OrderHistoryData {
+ status: boolean;
+ data: {
+ links: {
+ previous: string;
+ next: string;
+ };
+ total_items: number;
+ total_pages: number;
+ page_size: number;
+ current_page: number;
+ results: [
+ {
+ id: number;
+ user: {
+ id: number;
+ last_login: string;
+ is_superuser: boolean;
+ first_name: string;
+ last_name: string;
+ is_staff: boolean;
+ is_active: boolean;
+ date_joined: string;
+ phone: string;
+ email: string;
+ username: string;
+ avatar: string;
+ validated_at: string;
+ role: string;
+ total_spent: number;
+ travel_agency: number;
+ };
+ created_at: string;
+ order: number;
+ old_status:
+ | "pending_payment"
+ | "pending_confirmation"
+ | "cancelled"
+ | "confirmed"
+ | "completed";
+ new_status:
+ | "pending_payment"
+ | "pending_confirmation"
+ | "cancelled"
+ | "confirmed"
+ | "completed";
+ },
+ ];
+ };
+}
diff --git a/src/pages/finance/ui/Finance.tsx b/src/pages/finance/ui/Finance.tsx
index e10aacc..e4f6319 100644
--- a/src/pages/finance/ui/Finance.tsx
+++ b/src/pages/finance/ui/Finance.tsx
@@ -63,7 +63,6 @@ export default function FinancePage({ user }: { user: Role }) {
});
}, [tab, currentPage, currentPageAgency, setSearchParams]);
- // ✅ Param o‘zgarsa — holatni sinxronlashtirish
useEffect(() => {
if (tabParam && tabParam !== tab) {
setTab(tabParam);
diff --git a/src/pages/finance/ui/FinanceDetailUsers.tsx b/src/pages/finance/ui/FinanceDetailUsers.tsx
index bab4531..8f28ae7 100644
--- a/src/pages/finance/ui/FinanceDetailUsers.tsx
+++ b/src/pages/finance/ui/FinanceDetailUsers.tsx
@@ -1,7 +1,13 @@
"use client";
-import { getDetailOrder, updateDetailOrder } from "@/pages/finance/lib/api";
+import {
+ getDetailOrder,
+ getOrderHistory,
+ savedOrderHistory,
+ updateDetailOrder,
+} from "@/pages/finance/lib/api";
import type { OrderStatus } from "@/pages/finance/lib/type";
+import { OrderHistoryTable } from "@/pages/finance/ui/OrderHistoryTable";
import formatPhone from "@/shared/lib/formatPhone";
import formatPrice from "@/shared/lib/formatPrice";
import {
@@ -31,10 +37,10 @@ import { toast } from "sonner";
export default function FinanceDetailUser() {
const { t } = useTranslation();
const queryClient = useQueryClient();
- const [activeTab, setActiveTab] = useState<"bookings" | "details">(
- "bookings",
- );
-
+ const [activeTab, setActiveTab] = useState<
+ "bookings" | "details" | "history"
+ >("bookings");
+ const [historyPage, setHistoryPage] = useState(1);
const params = useParams();
const { data } = useQuery({
@@ -76,6 +82,49 @@ export default function FinanceDetailUser() {
},
});
+ const { mutate: savedHistory } = useMutation({
+ mutationFn: (body: {
+ order: number;
+ old_status:
+ | "pending_payment"
+ | "pending_confirmation"
+ | "cancelled"
+ | "confirmed"
+ | "completed";
+ new_status:
+ | "pending_payment"
+ | "pending_confirmation"
+ | "cancelled"
+ | "confirmed"
+ | "completed";
+ }) => savedOrderHistory(body),
+ onSuccess: () => {
+ queryClient.refetchQueries({ queryKey: ["detail_order"] });
+ queryClient.refetchQueries({ queryKey: ["list_order_user"] });
+ queryClient.refetchQueries({ queryKey: ["order_history"] });
+ toast.success(t("Status muvaffaqiyatli yangilandi"), {
+ richColors: true,
+ position: "top-center",
+ });
+ },
+ onError: () => {
+ toast.error(t("Statusni yangilashda xatolik yuz berdi"), {
+ richColors: true,
+ position: "top-center",
+ });
+ },
+ });
+
+ const { data: history } = useQuery({
+ queryKey: ["order_history", historyPage, params.id],
+ queryFn: () =>
+ getOrderHistory({
+ page: historyPage,
+ page_size: 10,
+ order: Number(params.id),
+ }),
+ });
+
const getStatusBadge = (status: OrderStatus["order_status"]) => {
const base =
"px-3 py-1 rounded-full text-sm font-medium inline-flex items-center gap-2";
@@ -168,6 +217,17 @@ export default function FinanceDetailUser() {
{t("User Details")}
+
@@ -194,12 +254,17 @@ export default function FinanceDetailUser() {
| "cancelled"
| "confirmed"
| "completed",
- ) =>
+ ) => {
mutate({
id: data.id,
body: { order_status: value },
- })
- }
+ });
+ savedHistory({
+ new_status: value,
+ old_status: data.order_status,
+ order: data.id,
+ });
+ }}
value={data.order_status}
>
@@ -423,6 +488,16 @@ export default function FinanceDetailUser() {
)}
+ {activeTab === "history" && history?.data && (
+
+
{t("Order History")}
+
+
+ )}
diff --git a/src/pages/finance/ui/OrderHistoryTable.tsx b/src/pages/finance/ui/OrderHistoryTable.tsx
new file mode 100644
index 0000000..4eb79a7
--- /dev/null
+++ b/src/pages/finance/ui/OrderHistoryTable.tsx
@@ -0,0 +1,174 @@
+"use client";
+
+import type { OrderHistoryData } from "@/pages/finance/lib/type";
+import { Badge } from "@/shared/ui/badge";
+import {
+ Pagination,
+ PaginationContent,
+ PaginationItem,
+ PaginationLink,
+ PaginationNext,
+ PaginationPrevious,
+} from "@/shared/ui/pagination";
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@/shared/ui/table";
+import { ArrowRight } from "lucide-react";
+import { useState } from "react";
+
+interface OrderHistoryTableProps {
+ data: OrderHistoryData["data"];
+ onPageChange: (page: number) => void;
+ t: (key: string) => string;
+}
+
+export function OrderHistoryTable({
+ data,
+ onPageChange,
+ t,
+}: OrderHistoryTableProps) {
+ const [currentPage, setCurrentPage] = useState(data.current_page);
+
+ const handlePageChange = (page: number) => {
+ setCurrentPage(page);
+ onPageChange(page);
+ };
+
+ const getStatusColor = (
+ status: string,
+ ): "default" | "secondary" | "destructive" | "outline" => {
+ switch (status.toLowerCase()) {
+ case "pending_payment":
+ return "secondary";
+ case "pending_confirmation":
+ return "default";
+ case "confirmed":
+ return "default";
+ case "completed":
+ return "default";
+ case "cancelled":
+ return "destructive";
+ default:
+ return "outline";
+ }
+ };
+
+ const formatStatus = (status: string) => {
+ return status
+ .split("_")
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
+ .join(" ");
+ };
+
+ return (
+
+
+
+
+
+ {t("Kim tomonidan")}
+ {t("Lavozimi")}
+ {t("O'zgartirilgan status")}
+
+ {t("O'zgartirgan sanasi")}
+
+
+
+
+ {data.results.length > 0 ? (
+ data.results.map((item) => (
+
+
+
+ {item.user.first_name.length > 0 ||
+ item.user.last_name.length > 0 ? (
+
+ {item.user.first_name} {item.user.last_name}
+
+ ) : (
+
{t("No'malum")}
+ )}
+
+
+
+
+
+
+
+
+ {formatStatus(t(item.old_status))}
+
+
+
+ {formatStatus(t(item.new_status))}
+
+
+
+
+ {item.created_at}
+
+
+ ))
+ ) : (
+
+
+ {t("Hozircha tarix mavjud emas")}
+
+
+ )}
+
+
+
+
+ {/* Pagination */}
+ {data.total_pages > 1 && (
+
+
+
+ {data.links.previous && (
+
+ handlePageChange(currentPage - 1)}
+ />
+
+ )}
+
+ {Array.from({ length: data.total_pages }, (_, i) => i + 1).map(
+ (page) => (
+
+ handlePageChange(page)}
+ >
+ {page}
+
+
+ ),
+ )}
+
+ {data.links.next && (
+
+ handlePageChange(currentPage + 1)}
+ />
+
+ )}
+
+
+
+ )}
+
+ );
+}
diff --git a/src/pages/payout-request/lib/api.ts b/src/pages/payout-request/lib/api.ts
new file mode 100644
index 0000000..42a2768
--- /dev/null
+++ b/src/pages/payout-request/lib/api.ts
@@ -0,0 +1,31 @@
+import type { PayoutListData } from "@/pages/payout-request/lib/types";
+import httpClient from "@/shared/config/api/httpClient";
+import { PAYOT_REQUEST } from "@/shared/config/api/URLs";
+import type { AxiosResponse } from "axios";
+
+const sendpayout = async ({ body }: { body: { amount: number } }) => {
+ const res = await httpClient.post(PAYOT_REQUEST, body);
+ return res;
+};
+
+const upatePayout = async ({
+ body,
+ id,
+}: {
+ id: number;
+ body: { status: string };
+}) => {
+ const res = await httpClient.patch(`${PAYOT_REQUEST}${id}/`, body);
+ return res;
+};
+
+const getPayoutList = async (params: {
+ page_size: number;
+ page: number;
+ status: "pending" | "approved" | "cancelled" | "";
+}): Promise> => {
+ const res = await httpClient.get(PAYOT_REQUEST, { params });
+ return res;
+};
+
+export { getPayoutList, sendpayout, upatePayout };
diff --git a/src/pages/payout-request/lib/types.ts b/src/pages/payout-request/lib/types.ts
new file mode 100644
index 0000000..0fe4d91
--- /dev/null
+++ b/src/pages/payout-request/lib/types.ts
@@ -0,0 +1,23 @@
+export interface PayoutListData {
+ status: boolean;
+ data: {
+ links: {
+ previous: string;
+ next: string;
+ };
+ total_items: number;
+ total_pages: number;
+ page_size: number;
+ current_page: number;
+ results: {
+ id: number;
+ travel_agency: {
+ id: number;
+ name: string;
+ };
+ amount: number;
+ status: "pending" | "approved" | "cancelled";
+ created_at: string;
+ }[];
+ };
+}
diff --git a/src/pages/payout-request/ui/PayoutRequest.tsx b/src/pages/payout-request/ui/PayoutRequest.tsx
new file mode 100644
index 0000000..0d1783d
--- /dev/null
+++ b/src/pages/payout-request/ui/PayoutRequest.tsx
@@ -0,0 +1,400 @@
+"use client";
+
+import { payAgency } from "@/pages/finance/lib/api";
+import {
+ getPayoutList,
+ sendpayout,
+ upatePayout,
+} from "@/pages/payout-request/lib/api";
+import formatPrice from "@/shared/lib/formatPrice";
+import { Badge } from "@/shared/ui/badge";
+import { Button } from "@/shared/ui/button";
+import { Card, CardContent, CardHeader, CardTitle } from "@/shared/ui/card";
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@/shared/ui/dialog";
+import { Input } from "@/shared/ui/input";
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@/shared/ui/table";
+import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
+import { format } from "date-fns";
+import {
+ AlertTriangle,
+ Banknote,
+ ChevronLeft,
+ ChevronRight,
+ Loader2,
+ XIcon,
+} from "lucide-react";
+import { useState } from "react";
+import { useTranslation } from "react-i18next";
+import { toast } from "sonner";
+
+export default function WithdrawRequests({
+ user,
+}: {
+ user:
+ | "superuser"
+ | "admin"
+ | "moderator"
+ | "tour_admin"
+ | "buxgalter"
+ | "operator"
+ | "user";
+}) {
+ const queryClient = useQueryClient();
+ const [currentPage, setCurrentPage] = useState(1);
+ const [statusFilter, setStatusFilter] = useState<
+ "pending" | "approved" | "cancelled" | ""
+ >("");
+ const { t } = useTranslation();
+ const tabs = [
+ { label: t("Barchasi"), value: "" as const },
+ { label: t("Kutilmoqda"), value: "pending" as const },
+ { label: t("Confirmed"), value: "approved" as const },
+ { label: t("cancelled"), value: "cancelled" as const },
+ ];
+ const [dialogOpen, setDialogOpen] = useState(false);
+ const [amount, setAmount] = useState("");
+ const [loadingId, setLoadingId] = useState(null);
+ const [closeId, setCloseId] = useState(null);
+
+ const { data, isLoading, isError, refetch } = useQuery({
+ queryKey: ["withdraw-requests", currentPage, statusFilter],
+ queryFn: () =>
+ getPayoutList({
+ page: currentPage,
+ page_size: 10,
+ status: statusFilter,
+ }),
+ });
+
+ const { mutate: updatePayed } = useMutation({
+ mutationFn: ({ body, id }: { id: number; body: { status: string } }) =>
+ upatePayout({ body, id }),
+ onSuccess: () => {
+ queryClient.refetchQueries({ queryKey: ["withdraw-requests"] });
+ },
+ });
+
+ const { mutate } = useMutation({
+ mutationFn: ({
+ body,
+ }: {
+ body: {
+ travel_agency: number;
+ amount: number;
+ note?: string;
+ };
+ orderId: number;
+ }) => payAgency({ body }),
+ onSuccess: (__, variables) => {
+ toast.success(t("To‘lov muvaffaqiyatli amalga oshirildi!"), {
+ position: "top-center",
+ });
+ updatePayed({
+ body: {
+ status: "approved",
+ },
+ id: variables.orderId,
+ });
+ },
+ onError: () => {
+ toast.error(t("Xatolik yuz berdi"), {
+ richColors: true,
+ position: "top-center",
+ });
+ },
+ });
+
+ const mutation = useMutation({
+ mutationFn: ({ body }: { body: { amount: number } }) =>
+ sendpayout({ body }),
+ onSuccess: () => {
+ toast.success("So‘rov muvaffaqiyatli yuborildi!");
+ setAmount("");
+ setDialogOpen(false);
+ queryClient.invalidateQueries({ queryKey: ["withdraw-requests"] });
+ },
+ onError: () => {
+ toast.error("So‘rov yuborishda xatolik yuz berdi!");
+ },
+ });
+
+ const handleSubmit = (e: React.FormEvent) => {
+ e.preventDefault();
+ const numericAmount = Number(amount);
+ if (!numericAmount || numericAmount <= 0) {
+ toast.error("Iltimos, miqdorni to‘g‘ri kiriting!");
+ return;
+ }
+ mutation.mutate({ body: { amount: numericAmount } });
+ };
+
+ if (isLoading) {
+ return (
+
+
+
+ );
+ }
+
+ if (isError) {
+ return (
+
+
+
+ {t("Ma'lumotlarni yuklashda xatolik yuz berdi.")}
+
+
+
+ );
+ }
+
+ const results = data?.data?.data.results ?? [];
+
+ return (
+
+ {/* 🔹 FILTER TAB */}
+
+
+ {tabs.map((tab) => (
+
+ ))}
+
+
+ {user === "tour_admin" && (
+
+ )}
+
+
+ {/* 🔹 TABLE */}
+
+
+ {t("Pul olish so‘rovlari")}
+
+
+
+
+
+ ID
+ {t("Agentlik nomi")}
+ {t("Miqdor")}
+ {t("Status")}
+ {t("Yaratilgan sana")}
+ {(user === "admin" ||
+ user === "buxgalter" ||
+ user === "moderator" ||
+ user === "superuser") && (
+ {t("Harakatlar")}
+ )}
+
+
+
+ {results.length > 0 ? (
+ results.map((item) => (
+
+ {item.id}
+ {item.travel_agency.name}
+ {formatPrice(item.amount, true)}
+
+
+ {t(item.status)}
+
+
+
+ {format(new Date(item.created_at), "yyyy-MM-dd HH:mm")}
+
+ {(user === "admin" ||
+ user === "buxgalter" ||
+ user === "moderator" ||
+ user === "superuser") && (
+
+
+
+
+ )}
+
+ ))
+ ) : (
+
+
+ {t("Hozircha hech qanday pul olish so‘rovi mavjud emas.")}
+
+
+ )}
+
+
+
+
+
+ {/* 🔹 PAGINATION */}
+
+
+
+ {[...Array(data?.data.data.total_pages)].map((_, i) => (
+
+ ))}
+
+
+
+
+ );
+}
diff --git a/src/pages/payout-request/ui/WithdrawRequestForm.tsx b/src/pages/payout-request/ui/WithdrawRequestForm.tsx
new file mode 100644
index 0000000..da3acb4
--- /dev/null
+++ b/src/pages/payout-request/ui/WithdrawRequestForm.tsx
@@ -0,0 +1,73 @@
+"use client";
+
+import { sendpayout } from "@/pages/payout-request/lib/api";
+import { Button } from "@/shared/ui/button";
+import { Card, CardContent, CardHeader, CardTitle } from "@/shared/ui/card";
+import { Input } from "@/shared/ui/input";
+import { useMutation } from "@tanstack/react-query";
+import { useState } from "react";
+import { toast } from "sonner";
+
+export default function WithdrawRequestForm() {
+ const [amount, setAmount] = useState("");
+
+ const mutation = useMutation({
+ mutationFn: async ({ body }: { body: { amount: number } }) => {
+ return sendpayout({ body });
+ },
+ onSuccess: () => {
+ toast.success("So‘rov muvaffaqiyatli yuborildi!");
+ setAmount("");
+ },
+ onError: () => {
+ toast.error("So‘rov yuborishda xatolik yuz berdi!");
+ },
+ });
+
+ const handleSubmit = (e: React.FormEvent) => {
+ e.preventDefault();
+
+ // Amount bo'sh bo'lmasligi va raqam ekanligini tekshirish
+ const numericAmount = Number(amount);
+ if (!numericAmount || numericAmount <= 0) {
+ toast.error("Iltimos, miqdorni to‘g‘ri kiriting!");
+ return;
+ }
+
+ mutation.mutate({ body: { amount: numericAmount } });
+ };
+
+ return (
+
+
+
+ Pul olish so‘rovi
+
+
+
+
+
+
+ );
+}
diff --git a/src/shared/config/api/URLs.ts b/src/shared/config/api/URLs.ts
index 1cc9d13..168fac1 100644
--- a/src/shared/config/api/URLs.ts
+++ b/src/shared/config/api/URLs.ts
@@ -1,5 +1,4 @@
-const BASE_URL =
- import.meta.env.VITE_API_URL || "https://simple-travel.felixits.uz/api/v1/";
+const BASE_URL = import.meta.env.VITE_API_URL;
const AUTH_LOGIN = "auth/token/phone/";
const GET_ME = "auth/me/";
@@ -31,13 +30,16 @@ const SUPPORT_USER = "dashboard/dashboard-support/";
const SUPPORT_AGENCY = "dashboard/dashboard-travel-agency-request/";
const USER_ORDERS = "dashboard/dashboard-ticket-order/";
const AGENCY_ORDERS = "dashboard/dashboard-site-travel-agency-report/";
+const AGENCY_ORDERS_HISTORY = "dashboard/dashboard-order-status-history/";
const POPULAR_TOURS = "dashboard/dashboard-ticket-featured/";
const BANNER = "dashboard/dashboard-site-banner/";
const TOUR_ADMIN = "dashboard/dashboard-tour-admin/";
const PAYMENT_AGENCY = "dashboard/dashboard-site-agency-payments/";
+const PAYOT_REQUEST = "dashboard/dashboard-agency-payout-request/";
export {
AGENCY_ORDERS,
+ AGENCY_ORDERS_HISTORY,
AMENITIES,
APPROVED_AGENCY,
AUTH_LOGIN,
@@ -62,6 +64,7 @@ export {
NEWS_CATEGORY,
OFFERTA,
PAYMENT_AGENCY,
+ PAYOT_REQUEST,
POPULAR_TOURS,
SITE_SEO,
SITE_SETTING,
diff --git a/src/shared/config/i18n/locales/ru/translation.json b/src/shared/config/i18n/locales/ru/translation.json
index 8e0c6e1..cb620e5 100644
--- a/src/shared/config/i18n/locales/ru/translation.json
+++ b/src/shared/config/i18n/locales/ru/translation.json
@@ -510,5 +510,25 @@
"Foydalanuvchini tahrirlash": "Редактировать пользователя",
"Haqiqatan ham": "Вы действительно хотите",
"ni tahrirlamoqchimisiz?": "отредактировать?",
- "Agentlik ID": "ID агентства"
+ "Agentlik ID": "ID агентства",
+ "Order History": "История статуса заказа",
+ "Kim tomonidan": "Кем изменено",
+ "Lavozimi": "Должность",
+ "No'malum": "Неизвестно",
+ "O'zgartirilgan status": "Изменённый статус",
+ "O'zgartirgan sanasi": "Дата изменения",
+ "Hozircha tarix mavjud emas": "История пока отсутствует",
+ "pending_payment": "Ожидается оплата",
+ "pending_confirmation": "Ожидается подтверждение",
+ "cancelled": "Отменён",
+ "confirmed": "Подтверждён",
+ "completed": "Завершён",
+ "Pul olish so‘rovlari": "Запросы на получение средств",
+ "Pul olish so'rovi": "Запрос на снятие средств",
+ "Miqdorni kiriting": "Введите сумму",
+ "Hozircha hech qanday pul olish so‘rovi mavjud emas.": "Пока нет запросов на получение денег.",
+ "Yuborish": "Отправить",
+ "Pulni o'tkazish": "Перевод средств",
+ "To‘lov muvaffaqiyatli amalga oshirildi!": "Оплата успешно выполнена!",
+ "Sizga tizimga kirishga ruxsat berilmagan": "Вам не разрешен доступ к системе."
}
diff --git a/src/shared/config/i18n/locales/uz/translation.json b/src/shared/config/i18n/locales/uz/translation.json
index ad9302a..211a81e 100644
--- a/src/shared/config/i18n/locales/uz/translation.json
+++ b/src/shared/config/i18n/locales/uz/translation.json
@@ -511,5 +511,25 @@
"Foydalanuvchini tahrirlash": "Foydalanuvchini tahrirlash",
"Haqiqatan ham": "Haqiqatan ham",
"ni tahrirlamoqchimisiz?": "ni tahrirlamoqchimisiz?",
- "Agentlik ID": "Agentlik ID"
+ "Agentlik ID": "Agentlik ID",
+ "Order History": "Buyurtma holati tarixi",
+ "Kim tomonidan": "Kim tomonidan",
+ "Lavozimi": "Lavozimi",
+ "No'malum": "Noma’lum",
+ "O'zgartirilgan status": "O‘zgartirilgan holat",
+ "O'zgartirgan sanasi": "O‘zgartirilgan sana",
+ "Hozircha tarix mavjud emas": "Hozircha tarix mavjud emas",
+ "pending_payment": "To‘lov kutilmoqda",
+ "pending_confirmation": "Tasdiqlanish kutilmoqda",
+ "cancelled": "Bekor qilingan",
+ "confirmed": "Tasdiqlangan",
+ "completed": "Yakunlangan",
+ "Pul olish so‘rovlari": "Pul olish so‘rovlari",
+ "Pul olish so'rovi": "Pul olish so'rovi",
+ "Miqdorni kiriting": "Miqdorni kiriting",
+ "Hozircha hech qanday pul olish so‘rovi mavjud emas.": "Hozircha hech qanday pul olish so‘rovi mavjud emas.",
+ "Yuborish": "Yuborish",
+ "Pulni o'tkazish": "Pulni o'tkazish",
+ "To‘lov muvaffaqiyatli amalga oshirildi!": "To‘lov muvaffaqiyatli amalga oshirildi!",
+ "Sizga tizimga kirishga ruxsat berilmagan": "Sizga tizimga kirishga ruxsat berilmagan"
}
diff --git a/src/shared/ui/pagination.tsx b/src/shared/ui/pagination.tsx
new file mode 100644
index 0000000..a70a46e
--- /dev/null
+++ b/src/shared/ui/pagination.tsx
@@ -0,0 +1,127 @@
+import * as React from "react"
+import {
+ ChevronLeftIcon,
+ ChevronRightIcon,
+ MoreHorizontalIcon,
+} from "lucide-react"
+
+import { cn } from "@/shared/lib/utils"
+import { Button, buttonVariants } from "@/shared/ui/button"
+
+function Pagination({ className, ...props }: React.ComponentProps<"nav">) {
+ return (
+
+ )
+}
+
+function PaginationContent({
+ className,
+ ...props
+}: React.ComponentProps<"ul">) {
+ return (
+
+ )
+}
+
+function PaginationItem({ ...props }: React.ComponentProps<"li">) {
+ return
+}
+
+type PaginationLinkProps = {
+ isActive?: boolean
+} & Pick, "size"> &
+ React.ComponentProps<"a">
+
+function PaginationLink({
+ className,
+ isActive,
+ size = "icon",
+ ...props
+}: PaginationLinkProps) {
+ return (
+
+ )
+}
+
+function PaginationPrevious({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+ Previous
+
+ )
+}
+
+function PaginationNext({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ Next
+
+
+ )
+}
+
+function PaginationEllipsis({
+ className,
+ ...props
+}: React.ComponentProps<"span">) {
+ return (
+
+
+ More pages
+
+ )
+}
+
+export {
+ Pagination,
+ PaginationContent,
+ PaginationLink,
+ PaginationItem,
+ PaginationPrevious,
+ PaginationNext,
+ PaginationEllipsis,
+}
diff --git a/src/widgets/sidebar/ui/Sidebar.tsx b/src/widgets/sidebar/ui/Sidebar.tsx
index 17a4e50..5ee53d8 100644
--- a/src/widgets/sidebar/ui/Sidebar.tsx
+++ b/src/widgets/sidebar/ui/Sidebar.tsx
@@ -148,6 +148,12 @@ const MENU_ITEMS = [
{ label: "Sayt uchun Banner", path: "/site-banner/" },
],
},
+ {
+ label: "Pul olish so‘rovlari",
+ icon: Wallet,
+ path: "/withdraw/list",
+ roles: ["buxgalter", "moderator", "tour_admin"],
+ },
];
export function Sidebar({ role }: SidebarProps) {
@@ -275,7 +281,7 @@ export function Sidebar({ role }: SidebarProps) {
);
return (
-