From c6c01a4607d97521eb530b825d36ce8f71a355c1 Mon Sep 17 00:00:00 2001 From: Samandar Turgunboyev Date: Mon, 3 Nov 2025 17:56:48 +0500 Subject: [PATCH] history order status --- src/App.tsx | 70 +-- src/pages/auth/ui/Login.tsx | 22 + src/pages/employees/ui/EditEmployees.tsx | 2 +- src/pages/faq/ui/Faq.tsx | 9 +- src/pages/faq/ui/FaqCategory.tsx | 2 +- src/pages/finance/lib/api.ts | 33 +- src/pages/finance/lib/type.ts | 51 +++ src/pages/finance/ui/Finance.tsx | 1 - src/pages/finance/ui/FinanceDetailUsers.tsx | 91 +++- src/pages/finance/ui/OrderHistoryTable.tsx | 174 ++++++++ src/pages/payout-request/lib/api.ts | 31 ++ src/pages/payout-request/lib/types.ts | 23 + src/pages/payout-request/ui/PayoutRequest.tsx | 400 ++++++++++++++++++ .../payout-request/ui/WithdrawRequestForm.tsx | 73 ++++ src/shared/config/api/URLs.ts | 7 +- .../config/i18n/locales/ru/translation.json | 22 +- .../config/i18n/locales/uz/translation.json | 22 +- src/shared/ui/pagination.tsx | 127 ++++++ src/widgets/sidebar/ui/Sidebar.tsx | 8 +- 19 files changed, 1122 insertions(+), 46 deletions(-) create mode 100644 src/pages/finance/ui/OrderHistoryTable.tsx create mode 100644 src/pages/payout-request/lib/api.ts create mode 100644 src/pages/payout-request/lib/types.ts create mode 100644 src/pages/payout-request/ui/PayoutRequest.tsx create mode 100644 src/pages/payout-request/ui/WithdrawRequestForm.tsx create mode 100644 src/shared/ui/pagination.tsx 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")}

+ )} +
+
+ +
+

+ {item.user.role} +

+
+
+ +
+ + {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" && ( + + + + + + + {t("Pul olish so‘rovlari")} + +
+
+ + { + const onlyNumbers = e.target.value.replace(/\D/g, ""); + setAmount(onlyNumbers); + }} + placeholder={t("Miqdorni kiriting")} + className="bg-gray-800 border-gray-700 text-white" + /> +
+ +
+
+
+ )} +
+ + {/* 🔹 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 + + + +
+
+ + setAmount(e.target.value)} + placeholder="Masalan: 100000" + className="bg-gray-800 border-gray-700 text-white" + /> +
+ + +
+
+
+ ); +} 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 ( +