405 lines
12 KiB
TypeScript
405 lines
12 KiB
TypeScript
"use client";
|
||
|
||
import {
|
||
createFaqCategory,
|
||
deleteFaqCategory,
|
||
getAllFaqCategory,
|
||
getDetailFaqCategory,
|
||
updateFaqCategory,
|
||
} from "@/pages/faq/lib/api";
|
||
import { Button } from "@/shared/ui/button";
|
||
import {
|
||
Dialog,
|
||
DialogContent,
|
||
DialogFooter,
|
||
DialogHeader,
|
||
DialogTitle,
|
||
} from "@/shared/ui/dialog";
|
||
import {
|
||
Form,
|
||
FormControl,
|
||
FormField,
|
||
FormItem,
|
||
FormMessage,
|
||
} from "@/shared/ui/form";
|
||
import { Input } from "@/shared/ui/input";
|
||
import { Label } from "@/shared/ui/label";
|
||
import {
|
||
Table,
|
||
TableBody,
|
||
TableCell,
|
||
TableHead,
|
||
TableHeader,
|
||
TableRow,
|
||
} from "@/shared/ui/table";
|
||
import { zodResolver } from "@hookform/resolvers/zod";
|
||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||
import {
|
||
ChevronLeft,
|
||
ChevronRight,
|
||
Loader,
|
||
Pencil,
|
||
PlusCircle,
|
||
Trash2,
|
||
} from "lucide-react";
|
||
import { useEffect, useState } from "react";
|
||
import { useForm } from "react-hook-form";
|
||
import { useTranslation } from "react-i18next";
|
||
import { useSearchParams } from "react-router-dom";
|
||
import { toast } from "sonner";
|
||
import z from "zod";
|
||
|
||
const categoryFormSchema = z.object({
|
||
name: z.string().min(1, { message: "Majburiy maydon" }),
|
||
name_ru: z.string().min(1, { message: "Majburiy maydon" }),
|
||
});
|
||
|
||
const FaqCategory = () => {
|
||
const [deleteId, setDeleteId] = useState<number | null>(null);
|
||
const [searchParams, setSearchParams] = useSearchParams();
|
||
|
||
const initialPage = Number(searchParams.get("page")) || 1;
|
||
const [page, setPage] = useState(initialPage);
|
||
const [categories, setCategories] = useState<number | null>(null);
|
||
const queryClient = useQueryClient();
|
||
|
||
const updatePage = (newPage: number) => {
|
||
setPage(newPage);
|
||
setSearchParams({ page: newPage.toString() });
|
||
};
|
||
|
||
const { data: category } = useQuery({
|
||
queryKey: ["all_faqcategory", page],
|
||
queryFn: () => {
|
||
return getAllFaqCategory({ page: page, page_size: 20 });
|
||
},
|
||
select(data) {
|
||
return data.data.data;
|
||
},
|
||
});
|
||
const { data: oneCategory } = useQuery({
|
||
queryKey: ["detail_faqcategory", categories],
|
||
queryFn: () => {
|
||
return getDetailFaqCategory(categories!);
|
||
},
|
||
select(data) {
|
||
return data.data.data;
|
||
},
|
||
enabled: !!categories,
|
||
});
|
||
const { mutate: create, isPending: createPending } = useMutation({
|
||
mutationFn: (body: { name: string; name_ru: string }) => {
|
||
return createFaqCategory(body);
|
||
},
|
||
onSuccess: () => {
|
||
queryClient.refetchQueries({ queryKey: ["all_faqcategory"] });
|
||
queryClient.refetchQueries({ queryKey: ["detail_faqcategory"] });
|
||
setOpenModal(false);
|
||
},
|
||
onError: () => {
|
||
toast.error(t("Xatolik yuz berdi"), {
|
||
position: "top-center",
|
||
richColors: true,
|
||
});
|
||
},
|
||
});
|
||
|
||
const { mutate: update, isPending: updatePending } = useMutation({
|
||
mutationFn: ({
|
||
body,
|
||
id,
|
||
}: {
|
||
id: number;
|
||
body: { name: string; name_ru: string };
|
||
}) => {
|
||
return updateFaqCategory({ body, id });
|
||
},
|
||
onSuccess: () => {
|
||
queryClient.refetchQueries({ queryKey: ["all_faqcategory"] });
|
||
queryClient.refetchQueries({ queryKey: ["detail_faqcategory"] });
|
||
setOpenModal(false);
|
||
setCategories(null);
|
||
},
|
||
onError: () => {
|
||
toast.error(t("Xatolik yuz berdi"), {
|
||
position: "top-center",
|
||
richColors: true,
|
||
});
|
||
},
|
||
});
|
||
const { mutate: deleteCategory, isPending: deletePending } = useMutation({
|
||
mutationFn: (id: number) => {
|
||
return deleteFaqCategory(id);
|
||
},
|
||
onSuccess: () => {
|
||
queryClient.refetchQueries({ queryKey: ["all_faqcategory"] });
|
||
queryClient.refetchQueries({ queryKey: ["detail_faqcategory"] });
|
||
setDeleteId(null);
|
||
},
|
||
onError: () => {
|
||
toast.error(t("Xatolik yuz berdi"), {
|
||
position: "top-center",
|
||
richColors: true,
|
||
});
|
||
},
|
||
});
|
||
const { t } = useTranslation();
|
||
const [openModal, setOpenModal] = useState(false);
|
||
|
||
const form = useForm<z.infer<typeof categoryFormSchema>>({
|
||
resolver: zodResolver(categoryFormSchema),
|
||
defaultValues: { name: "", name_ru: "" },
|
||
});
|
||
|
||
useEffect(() => {
|
||
if (oneCategory && categories) {
|
||
form.setValue("name", oneCategory.name_uz);
|
||
form.setValue("name_ru", oneCategory.name_ru);
|
||
}
|
||
}, [oneCategory, categories, form]);
|
||
|
||
const onSubmit = (values: z.infer<typeof categoryFormSchema>) => {
|
||
if (categories !== null) {
|
||
update({
|
||
id: categories,
|
||
body: {
|
||
name: values.name,
|
||
name_ru: values.name_ru,
|
||
},
|
||
});
|
||
} else {
|
||
create({
|
||
name: values.name,
|
||
name_ru: values.name_ru,
|
||
});
|
||
}
|
||
};
|
||
|
||
const handleEdit = (cat: number) => {
|
||
setCategories(cat);
|
||
setOpenModal(true);
|
||
};
|
||
|
||
const handleDelete = () => {
|
||
if (deleteId) {
|
||
deleteCategory(deleteId);
|
||
}
|
||
};
|
||
|
||
useEffect(() => {
|
||
if (!openModal) {
|
||
form.reset();
|
||
setCategories(null);
|
||
}
|
||
}, [openModal, form]);
|
||
|
||
return (
|
||
<div className="p-6 space-y-6 w-full">
|
||
<div className="flex items-center justify-between">
|
||
<h1 className="text-2xl font-semibold">{t("FAQ Kategoriyalar")}</h1>
|
||
<Button
|
||
className="gap-2"
|
||
onClick={() => {
|
||
setCategories(null);
|
||
setOpenModal(true);
|
||
}}
|
||
>
|
||
<PlusCircle className="w-4 h-4" /> {t("Qo'shish")}
|
||
</Button>
|
||
</div>
|
||
|
||
{/* Jadval */}
|
||
<div className="border rounded-md overflow-hidden shadow-sm">
|
||
<Table>
|
||
<TableHeader>
|
||
<TableRow>
|
||
<TableHead className="w-[50px] text-center">#</TableHead>
|
||
<TableHead>{t("Kategoriya nomi")}</TableHead>
|
||
{/* <TableHead className="text-center">Savollar soni</TableHead> */}
|
||
<TableHead className="w-[120px] text-center">
|
||
{t("Amallar")}
|
||
</TableHead>
|
||
</TableRow>
|
||
</TableHeader>
|
||
|
||
<TableBody>
|
||
{category && category.results.length > 0 ? (
|
||
category.results.map((cat, index) => (
|
||
<TableRow key={cat.id}>
|
||
<TableCell className="text-center font-medium">
|
||
{index + 1}
|
||
</TableCell>
|
||
<TableCell className="capitalize font-medium">
|
||
{cat.name}
|
||
</TableCell>
|
||
{/* <TableCell className="text-center">{cat.questionCount}</TableCell> */}
|
||
<TableCell className="flex justify-center gap-2">
|
||
<Button
|
||
variant="outline"
|
||
size="icon"
|
||
onClick={() => handleEdit(cat.id)}
|
||
>
|
||
<Pencil className="w-4 h-4" />
|
||
</Button>
|
||
<Button
|
||
variant="destructive"
|
||
size="icon"
|
||
onClick={() => setDeleteId(cat.id)}
|
||
>
|
||
<Trash2 className="w-4 h-4" />
|
||
</Button>
|
||
</TableCell>
|
||
</TableRow>
|
||
))
|
||
) : (
|
||
<TableRow>
|
||
<TableCell
|
||
colSpan={3}
|
||
className="text-center h-32 text-muted-foreground"
|
||
>
|
||
{t("Hech qanday kategoriya topilmadi")}
|
||
</TableCell>
|
||
</TableRow>
|
||
)}
|
||
</TableBody>
|
||
</Table>
|
||
</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(category?.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 === category?.total_pages}
|
||
onClick={() =>
|
||
updatePage(Math.min(page + 1, category?.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>
|
||
|
||
{/* Modal */}
|
||
<Dialog open={openModal} onOpenChange={setOpenModal}>
|
||
<DialogContent className="sm:max-w-[400px] bg-gray-900">
|
||
<DialogHeader>
|
||
<DialogTitle>
|
||
{categories ? t("Tahrirlash") : t("Qo'shish")}
|
||
</DialogTitle>
|
||
</DialogHeader>
|
||
|
||
<Form {...form}>
|
||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
|
||
<FormField
|
||
control={form.control}
|
||
name="name"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<Label className="text-md">{t("Kategoriya nomi")}</Label>
|
||
<FormControl>
|
||
<Input
|
||
placeholder={t("Kategoriya nomi")}
|
||
{...field}
|
||
className="h-12 bg-gray-800 border-gray-700 text-white"
|
||
/>
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
|
||
<FormField
|
||
control={form.control}
|
||
name="name_ru"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<Label className="text-md">
|
||
{t("Kategoriya nomi")} (ru)
|
||
</Label>
|
||
<FormControl>
|
||
<Input
|
||
placeholder={t("Kategoriya nomi") + " (ru)"}
|
||
{...field}
|
||
className="h-12 bg-gray-800 border-gray-700 text-white"
|
||
/>
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
|
||
<DialogFooter className="flex justify-end gap-3">
|
||
<Button
|
||
type="button"
|
||
onClick={() => setOpenModal(false)}
|
||
className="bg-gray-600 hover:bg-gray-700 text-white"
|
||
>
|
||
{t("Bekor qilish")}
|
||
</Button>
|
||
<Button
|
||
type="submit"
|
||
className="bg-blue-600 hover:bg-blue-700 text-white"
|
||
>
|
||
{createPending || updatePending ? (
|
||
<Loader className="animate-spin" />
|
||
) : categories ? (
|
||
t("Saqlash")
|
||
) : (
|
||
t("Qo‘shish")
|
||
)}
|
||
</Button>
|
||
</DialogFooter>
|
||
</form>
|
||
</Form>
|
||
</DialogContent>
|
||
</Dialog>
|
||
|
||
{/* O‘chirish tasdig‘i */}
|
||
<Dialog open={!!deleteId} onOpenChange={() => setDeleteId(null)}>
|
||
<DialogContent className="sm:max-w-[400px]">
|
||
<DialogHeader>
|
||
<DialogTitle>{t("Haqiqatan ham o‘chirmoqchimisiz?")}</DialogTitle>
|
||
</DialogHeader>
|
||
<DialogFooter>
|
||
<Button variant="outline" onClick={() => setDeleteId(null)}>
|
||
{t("Bekor qilish")}
|
||
</Button>
|
||
<Button variant="destructive" onClick={handleDelete}>
|
||
{deletePending ? (
|
||
<Loader className="animate-spin" />
|
||
) : (
|
||
t("O'chirish")
|
||
)}
|
||
</Button>
|
||
</DialogFooter>
|
||
</DialogContent>
|
||
</Dialog>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default FaqCategory;
|