history order status

This commit is contained in:
Samandar Turgunboyev
2025-11-03 17:56:48 +05:00
parent 9803edc478
commit c6c01a4607
19 changed files with 1122 additions and 46 deletions

View File

@@ -94,6 +94,28 @@ const Login = () => {
});
}
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 (
<div className="flex min-h-screen items-center justify-center bg-gray-900 px-4 w-full">
<div className="absolute top-4 right-4 flex gap-2">

View File

@@ -110,7 +110,7 @@ const EditEmployees = ({
...(modalMode === "add" && { password: "" }),
});
}
}, [data, editId, showModal, modalMode]);
}, [data, editId, showModal, modalMode, form]);
const { mutate: create, isPending } = useMutation({
mutationFn: ({

View File

@@ -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<typeof faqForm>) {
if (editFaq === null) {

View File

@@ -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<typeof categoryFormSchema>) => {
if (categories !== null) {

View File

@@ -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<AxiosResponse<OrderHistoryData>> => {
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,
};

View File

@@ -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";
},
];
};
}

View File

@@ -63,7 +63,6 @@ export default function FinancePage({ user }: { user: Role }) {
});
}, [tab, currentPage, currentPageAgency, setSearchParams]);
// ✅ Param ozgarsa — holatni sinxronlashtirish
useEffect(() => {
if (tabParam && tabParam !== tab) {
setTab(tabParam);

View File

@@ -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() {
<User className="w-4 h-4" />
{t("User Details")}
</button>
<button
className={`px-6 py-4 font-medium flex items-center gap-2 transition-colors ${
activeTab === "history"
? "text-blue-400 border-b-2 border-blue-400"
: "text-gray-400 hover:text-gray-300"
}`}
onClick={() => setActiveTab("history")}
>
<Calendar className="w-4 h-4" />
{t("Order History")}
</button>
</div>
<div className="p-6">
@@ -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}
>
<SelectTrigger className="w-[180px] bg-gray-800 border-gray-700 text-gray-200">
@@ -423,6 +488,16 @@ export default function FinanceDetailUser() {
</div>
</div>
)}
{activeTab === "history" && history?.data && (
<div className="space-y-6">
<h2 className="text-xl font-bold mb-4">{t("Order History")}</h2>
<OrderHistoryTable
t={t}
data={history.data.data}
onPageChange={setHistoryPage}
/>
</div>
)}
</div>
</div>
</div>

View File

@@ -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 (
<div className="w-full space-y-4">
<div className="rounded-lg border">
<Table>
<TableHeader>
<TableRow>
<TableHead>{t("Kim tomonidan")}</TableHead>
<TableHead>{t("Lavozimi")}</TableHead>
<TableHead>{t("O'zgartirilgan status")}</TableHead>
<TableHead className="text-right">
{t("O'zgartirgan sanasi")}
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{data.results.length > 0 ? (
data.results.map((item) => (
<TableRow key={item.id}>
<TableCell className="font-medium">
<div>
{item.user.first_name.length > 0 ||
item.user.last_name.length > 0 ? (
<p className="font-semibold">
{item.user.first_name} {item.user.last_name}
</p>
) : (
<p>{t("No'malum")}</p>
)}
</div>
</TableCell>
<TableCell className="font-medium">
<div>
<p className="text-xs text-muted-foreground">
{item.user.role}
</p>
</div>
</TableCell>
<TableCell>
<div className="flex items-center gap-2">
<Badge variant={getStatusColor(item.old_status)}>
{formatStatus(t(item.old_status))}
</Badge>
<ArrowRight className="h-4 w-4 text-muted-foreground" />
<Badge variant={getStatusColor(item.new_status)}>
{formatStatus(t(item.new_status))}
</Badge>
</div>
</TableCell>
<TableCell className="text-right text-sm text-muted-foreground">
{item.created_at}
</TableCell>
</TableRow>
))
) : (
<TableRow>
<TableCell
colSpan={4}
className="text-center text-muted-foreground py-8"
>
{t("Hozircha tarix mavjud emas")}
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
{/* Pagination */}
{data.total_pages > 1 && (
<div className="flex justify-center">
<Pagination>
<PaginationContent>
{data.links.previous && (
<PaginationItem>
<PaginationPrevious
onClick={() => handlePageChange(currentPage - 1)}
/>
</PaginationItem>
)}
{Array.from({ length: data.total_pages }, (_, i) => i + 1).map(
(page) => (
<PaginationItem key={page}>
<PaginationLink
isActive={page === currentPage}
onClick={() => handlePageChange(page)}
>
{page}
</PaginationLink>
</PaginationItem>
),
)}
{data.links.next && (
<PaginationItem>
<PaginationNext
onClick={() => handlePageChange(currentPage + 1)}
/>
</PaginationItem>
)}
</PaginationContent>
</Pagination>
</div>
)}
</div>
);
}

View File

@@ -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<AxiosResponse<PayoutListData>> => {
const res = await httpClient.get(PAYOT_REQUEST, { params });
return res;
};
export { getPayoutList, sendpayout, upatePayout };

View File

@@ -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;
}[];
};
}

View File

@@ -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<number | null>(null);
const [closeId, setCloseId] = useState<number | null>(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("Tolov 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("Sorov muvaffaqiyatli yuborildi!");
setAmount("");
setDialogOpen(false);
queryClient.invalidateQueries({ queryKey: ["withdraw-requests"] });
},
onError: () => {
toast.error("Sorov yuborishda xatolik yuz berdi!");
},
});
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
const numericAmount = Number(amount);
if (!numericAmount || numericAmount <= 0) {
toast.error("Iltimos, miqdorni togri kiriting!");
return;
}
mutation.mutate({ body: { amount: numericAmount } });
};
if (isLoading) {
return (
<div className="flex flex-col items-center justify-center min-h-screen bg-gray-900 text-white gap-4">
<Loader2 className="animate-spin w-10 h-10 text-cyan-400" />
</div>
);
}
if (isError) {
return (
<div className="flex flex-col items-center justify-center min-h-screen bg-slate-900 w-full text-center text-white gap-4">
<AlertTriangle className="w-10 h-10 text-red-500" />
<p className="text-lg">
{t("Ma'lumotlarni yuklashda xatolik yuz berdi.")}
</p>
<Button
onClick={() => refetch()}
className="bg-gradient-to-r from-blue-600 to-cyan-600 text-white rounded-lg px-5 py-2 hover:opacity-90"
>
{t("Qayta urinish")}
</Button>
</div>
);
}
const results = data?.data?.data.results ?? [];
return (
<div className="p-4">
{/* 🔹 FILTER TAB */}
<div className="flex flex-wrap items-center justify-between gap-3 mb-6">
<div className="flex gap-2 flex-wrap">
{tabs.map((tab) => (
<Button
key={tab.value}
onClick={() => {
setStatusFilter(tab.value);
setCurrentPage(1);
}}
className={`rounded-xl px-4 py-2 font-medium transition-all ${
statusFilter === tab.value
? "bg-gradient-to-r from-blue-600 to-cyan-600 text-white"
: "bg-gray-800 text-gray-300 hover:bg-gray-700"
}`}
>
{tab.label}
</Button>
))}
</div>
{user === "tour_admin" && (
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
<DialogTrigger asChild>
<Button className="bg-blue-600 hover:bg-blue-700">
{t("Pul olish so'rovi")}
</Button>
</DialogTrigger>
<DialogContent className="max-w-md">
<DialogHeader>
<DialogTitle>{t("Pul olish sorovlari")}</DialogTitle>
</DialogHeader>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block text-sm text-gray-400 mb-2">
{t("Miqdorni kiriting")}
</label>
<Input
type="text"
value={formatPrice(amount)}
onChange={(e) => {
const onlyNumbers = e.target.value.replace(/\D/g, "");
setAmount(onlyNumbers);
}}
placeholder={t("Miqdorni kiriting")}
className="bg-gray-800 border-gray-700 text-white"
/>
</div>
<Button
type="submit"
className="w-full bg-blue-600 hover:bg-blue-700"
disabled={!amount || mutation.isPending}
>
{mutation.isPending ? (
<Loader2 className="animate-spin" />
) : (
t("Yuborish")
)}
</Button>
</form>
</DialogContent>
</Dialog>
)}
</div>
{/* 🔹 TABLE */}
<Card className="bg-gray-900 border-gray-800 text-white">
<CardHeader>
<CardTitle>{t("Pul olish sorovlari")}</CardTitle>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead>ID</TableHead>
<TableHead>{t("Agentlik nomi")}</TableHead>
<TableHead>{t("Miqdor")}</TableHead>
<TableHead>{t("Status")}</TableHead>
<TableHead>{t("Yaratilgan sana")}</TableHead>
{(user === "admin" ||
user === "buxgalter" ||
user === "moderator" ||
user === "superuser") && (
<TableHead>{t("Harakatlar")}</TableHead>
)}
</TableRow>
</TableHeader>
<TableBody>
{results.length > 0 ? (
results.map((item) => (
<TableRow key={item.id}>
<TableCell>{item.id}</TableCell>
<TableCell>{item.travel_agency.name}</TableCell>
<TableCell>{formatPrice(item.amount, true)}</TableCell>
<TableCell>
<Badge
className={
item.status === "pending"
? "bg-yellow-600"
: item.status === "approved"
? "bg-green-600"
: "bg-red-600"
}
>
{t(item.status)}
</Badge>
</TableCell>
<TableCell>
{format(new Date(item.created_at), "yyyy-MM-dd HH:mm")}
</TableCell>
{(user === "admin" ||
user === "buxgalter" ||
user === "moderator" ||
user === "superuser") && (
<TableCell className="flex gap-4">
<Button
disabled={item.status !== "pending"}
onClick={() => {
setLoadingId(item.id);
mutate(
{
body: {
amount: item.amount,
travel_agency: item.travel_agency.id,
},
orderId: item.id,
},
{
onSettled: () => setLoadingId(null),
},
);
}}
className="bg-green-600 hover:bg-green-700 text-white flex items-center gap-2 cursor-pointer"
>
{loadingId === item.id ? (
<Loader2 className="animate-spin" />
) : (
<>
<Banknote />
{t("Pulni o'tkazish")}
</>
)}
</Button>
<Button
onClick={() => {
setCloseId(item.id);
updatePayed(
{
body: {
status: "cancelled",
},
id: item.id,
},
{
onSettled: () => setCloseId(null),
},
);
}}
disabled={item.status !== "pending"}
className="bg-red-600 hover:bg-red-700 text-white flex items-center gap-2 cursor-pointer"
>
{closeId === item.id ? (
<Loader2 className="animate-spin" />
) : (
<>
<XIcon />
{t("Bekor qilish")}
</>
)}
</Button>
</TableCell>
)}
</TableRow>
))
) : (
<TableRow>
<TableCell
colSpan={5}
className="text-center text-gray-400 py-4"
>
{t("Hozircha hech qanday pul olish sorovi mavjud emas.")}
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</CardContent>
</Card>
{/* 🔹 PAGINATION */}
<div className="flex justify-end gap-2 mt-5">
<button
disabled={currentPage === 1}
onClick={() => setCurrentPage((p) => Math.max(p - 1, 1))}
className="p-2 rounded-lg border border-slate-600 text-slate-300 hover:bg-slate-700/50 disabled:opacity-50 disabled:cursor-not-allowed transition-all hover:border-slate-500"
>
<ChevronLeft className="w-5 h-5" />
</button>
{[...Array(data?.data.data.total_pages)].map((_, i) => (
<button
key={i}
onClick={() => setCurrentPage(i + 1)}
className={`px-4 py-2 rounded-lg border transition-all font-medium ${
currentPage === i + 1
? "bg-gradient-to-r from-blue-600 to-cyan-600 border-blue-500 text-white shadow-lg shadow-cyan-500/50"
: "border-slate-600 text-slate-300 hover:bg-slate-700/50 hover:border-slate-500"
}`}
>
{i + 1}
</button>
))}
<button
disabled={currentPage === data?.data.data.total_pages}
onClick={() =>
setCurrentPage((p) =>
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"
>
<ChevronRight className="w-5 h-5" />
</button>
</div>
</div>
);
}

View File

@@ -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("Sorov muvaffaqiyatli yuborildi!");
setAmount("");
},
onError: () => {
toast.error("Sorov 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 togri kiriting!");
return;
}
mutation.mutate({ body: { amount: numericAmount } });
};
return (
<Card className="max-w-md mx-auto mt-10 bg-gray-900 border-gray-800 text-white">
<CardHeader>
<CardTitle className="text-xl font-semibold">
Pul olish sorovi
</CardTitle>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block text-sm text-gray-400 mb-2">
Miqdorni kiriting (som)
</label>
<Input
type="number"
value={amount}
onChange={(e) => setAmount(e.target.value)}
placeholder="Masalan: 100000"
className="bg-gray-800 border-gray-700 text-white"
/>
</div>
<Button
type="submit"
className="w-full bg-blue-600 hover:bg-blue-700"
disabled={!amount || mutation.isPending}
>
{mutation.isPending ? "Yuborilmoqda..." : "Sorov yuborish"}
</Button>
</form>
</CardContent>
</Card>
);
}