Files
simple-admin/src/pages/faq/ui/FaqCategory.tsx
2025-11-17 13:09:24 +05:00

405 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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("Qoshish")
)}
</Button>
</DialogFooter>
</form>
</Form>
</DialogContent>
</Dialog>
{/* Ochirish tasdigi */}
<Dialog open={!!deleteId} onOpenChange={() => setDeleteId(null)}>
<DialogContent className="sm:max-w-[400px]">
<DialogHeader>
<DialogTitle>{t("Haqiqatan ham ochirmoqchimisiz?")}</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;