Files
simple-admin/src/pages/site-page/ui/SitePage.tsx
Samandar Turgunboyev 2d0285dafc api ulandi
2025-10-29 18:41:59 +05:00

418 lines
13 KiB
TypeScript

import {
createOfferta,
deleteOfferta,
getAllOfferta,
getOneOfferta,
updateOfferta,
} from "@/pages/site-page/lib/api";
import { Button } from "@/shared/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/shared/ui/card";
import {
Dialog,
DialogContent,
DialogDescription,
DialogTitle,
DialogTrigger,
} from "@/shared/ui/dialog";
import { Input } from "@/shared/ui/input";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/shared/ui/select";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { Edit2, Trash2 } from "lucide-react";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import ReactQuill from "react-quill-new";
import "react-quill-new/dist/quill.snow.css";
import { toast } from "sonner";
export default function OmmaviyOfertaCRUD() {
const { t } = useTranslation();
const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
const queryClient = useQueryClient();
const [editing, setEditing] = useState<number | null>(null);
const [form, setForm] = useState<{
title: string;
content: string;
audience: string;
active: boolean;
}>({
title: "",
audience: "Jismoniy shaxslar",
content: "",
active: true,
});
const [errors, setErrors] = useState<Record<string, string>>({});
function resetForm() {
setForm({
title: "",
audience: "Jismoniy shaxslar",
content: "",
active: true,
});
setErrors({});
setEditing(null);
}
const { mutate: create } = useMutation({
mutationFn: (body: {
title: string;
content: string;
person_type: "individual" | "legal_entity";
is_active: boolean;
}) => createOfferta({ body }),
onSuccess: () => {
resetForm();
queryClient.refetchQueries({ queryKey: ["all_offerta"] });
queryClient.refetchQueries({ queryKey: ["detail_offerta"] });
toast.success(t("Muvaffaqiyatli yaratildi"), {
position: "top-center",
richColors: true,
});
},
onError: () => {
toast.error(t("Xatolik yuz berdi"), {
position: "top-center",
richColors: true,
});
},
});
const { mutate: update } = useMutation({
mutationFn: ({
body,
id,
}: {
body: {
title?: string;
content?: string;
person_type?: "individual" | "legal_entity";
is_active?: boolean;
};
id: number;
}) => updateOfferta({ body, id }),
onSuccess: () => {
resetForm();
setEditing(null);
queryClient.refetchQueries({ queryKey: ["all_offerta"] });
queryClient.refetchQueries({ queryKey: ["detail_offerta"] });
toast.success(t("Muvaffaqiyatli yangilandi"), {
position: "top-center",
richColors: true,
});
},
onError: () => {
toast.error(t("Xatolik yuz berdi"), {
position: "top-center",
richColors: true,
});
},
});
const { mutate: removeOfferta } = useMutation({
mutationFn: ({ id }: { id: number }) => deleteOfferta(id),
onSuccess: () => {
resetForm();
queryClient.refetchQueries({ queryKey: ["all_offerta"] });
queryClient.refetchQueries({ queryKey: ["detail_offerta"] });
toast.success(t("Muvaffaqiyatli o'chirildi"), {
position: "top-center",
richColors: true,
});
},
onError: () => {
toast.error(t("Xatolik yuz berdi"), {
position: "top-center",
richColors: true,
});
},
});
const { data: allOfferta } = useQuery({
queryKey: ["all_offerta"],
queryFn: () => {
return getAllOfferta({ page: 1, page_size: 99 });
},
select(data) {
return data.data.data;
},
});
const { data: detailOfferta } = useQuery({
queryKey: ["detail_offerta", editing],
queryFn: () => {
return getOneOfferta(editing!);
},
select(data) {
return data.data.data;
},
enabled: !!editing,
});
useEffect(() => {
if (editing && detailOfferta) {
setForm({
active: detailOfferta.is_active,
audience:
detailOfferta.person_type === "individual"
? "Jismoniy shaxslar"
: "Yuridik shaxslar",
content: detailOfferta.content,
title: detailOfferta.title,
});
}
}, [detailOfferta, editing]);
function handleCreateOrUpdate() {
const newErrors: Record<string, string> = {};
if (!form.title.trim()) {
newErrors.title = "Sarlavha kiritish majburiy";
} else if (form.title.trim().length < 3) {
newErrors.title = "Sarlavha kamida 3 ta belgidan iborat bo'lishi kerak";
}
const plainText = form.content.replace(/<[^>]+>/g, "").trim();
if (!plainText) {
newErrors.content = "Kontent kiritish majburiy";
} else if (plainText.length < 10) {
newErrors.content = "Kontent kamida 10 ta belgidan iborat bo'lishi kerak";
}
if (!form.audience) {
newErrors.audience = "Kimlar uchun degan maydonni tanlang";
}
setErrors(newErrors);
if (Object.keys(newErrors).length > 0) {
toast.error(t("Iltimos, barcha majburiy maydonlarni to'ldiring"), {
position: "top-center",
});
return;
}
if (editing === null) {
create({
content: form.content,
is_active: form.active,
person_type:
form.audience === "Jismoniy shaxslar" ? "individual" : "legal_entity",
title: form.title,
});
} else if (editing) {
update({
body: {
content: form.content,
is_active: form.active,
person_type:
form.audience === "Jismoniy shaxslar"
? "individual"
: "legal_entity",
title: form.title,
},
id: editing,
});
}
}
function startEdit(item: number) {
setEditing(item);
}
function removeItem(id: number) {
removeOfferta({ id });
}
function toggleActive(id: number, currentStatus: boolean) {
update({
id: id,
body: {
is_active: !currentStatus,
},
});
}
return (
<div className="min-h-screen w-full p-6 bg-gray-900">
<div className="max-w-[90%] mx-auto space-y-6">
<div className="flex items-center justify-between">
<h1 className="text-3xl font-bold">{t("Ommaviy oferta")}</h1>
</div>
<Card className="bg-gray-900">
<CardHeader>
<CardTitle>
{editing ? t("Tahrirlash") : t("Yangi oferta yaratish")}
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div>
<label className="text-sm font-medium">{t("Sarlavha")}</label>
<Input
value={form.title || ""}
onChange={(e) =>
setForm((s) => ({ ...s, title: e.target.value }))
}
placeholder={t("Ommaviy oferta sarlavhasi")}
className="mt-1"
/>
{errors.title && (
<p className="text-destructive text-sm mt-1">
{t(errors.title)}
</p>
)}
</div>
<div className="h-[280px] w-[100%]">
<label className="text-sm font-medium">{t("Kontent")}</label>
<div className="mt-1">
<ReactQuill
value={form.content || ""}
onChange={(value) =>
setForm((s) => ({ ...s, content: value }))
}
className="bg-gray-900 h-48"
placeholder={t("Oferta matnini kiriting...")}
/>
</div>
{errors.content && (
<p className="text-destructive text-sm mt-12">
{t(errors.content)}
</p>
)}
</div>
<div>
<label className="text-sm font-medium">{t("Kimlar uchun")}</label>
<Select
value={form.audience || t("Barcha")}
onValueChange={(value) =>
setForm((s) => ({ ...s, audience: value }))
}
>
<SelectTrigger className="mt-1 w-full !h-12">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="Jismoniy shaxslar">
{t("Jismoniy shaxslar uchun")}
</SelectItem>
<SelectItem value="Yuridik shaxslar">
{t("Yuridik shaxslar uchun")}
</SelectItem>
</SelectContent>
</Select>
{errors.audience && (
<p className="text-destructive text-sm mt-1">
{t(errors.audience)}
</p>
)}
</div>
<div className="flex gap-2 pt-4">
<Button
onClick={handleCreateOrUpdate}
className="bg-blue-600 hover:bg-blue-700"
>
{editing ? t("Saqlash") : t("Qo'shish")}
</Button>
<Button variant="outline" onClick={resetForm}>
{t("Bekor qilish")}
</Button>
</div>
</CardContent>
</Card>
<div className="space-y-3">
{allOfferta && allOfferta?.results.length === 0 && (
<Card>
<CardContent className="pt-6">
<p className="text-muted-foreground text-center">
{t("Natija topilmadi.")}
</p>
</CardContent>
</Card>
)}
{allOfferta?.results.map((it) => (
<Card key={it.id} className="overflow-hidden">
<CardContent className="pt-6">
<div className="flex flex-col md:flex-row md:items-start md:justify-between gap-4">
<div className="flex-1">
<h3 className="font-semibold text-lg">{it.title}</h3>
<p className="text-sm text-muted-foreground mt-1">
{it.person_type == "individual"
? t("Jismoniy shaxslar uchun")
: t("Yuridik shaxslar uchun")}
</p>
<p className="mt-3 text-sm line-clamp-3">{it.content}</p>
</div>
<div className="flex gap-2 items-center flex-wrap md:flex-col md:flex-nowrap">
<Button
onClick={() => startEdit(it.id)}
variant="outline"
size="sm"
>
<Edit2 className="w-4 h-4 mr-1" />
{t("Tahrirlash")}
</Button>
<Button
onClick={() => toggleActive(it.id, it.is_active)}
variant={it.is_active ? "default" : "outline"}
size="sm"
className={
it.is_active
? "w-full bg-green-600 hover:bg-green-700"
: "w-full"
}
>
{it.is_active ? t("Faol") : t("Faol emas")}
</Button>
<Dialog open={deleteOpen} onOpenChange={setDeleteOpen}>
<DialogTrigger asChild>
<Button
variant="destructive"
size="sm"
className="w-full"
onClick={() => setDeleteOpen(true)}
>
<Trash2 className="w-4 h-4 mr-1" />
{t("O'chirish")}
</Button>
</DialogTrigger>
<DialogContent>
<DialogTitle>{t("O'chirish tasdiqlash")}</DialogTitle>
<DialogDescription>
{t(
"Haqiqatan ham bu ofertani o'chirmoqchimisiz? Bu amalni bekor qilib bo'lmaydi.",
)}
</DialogDescription>
<div className="flex gap-3 justify-end pt-4">
<Button onClick={() => setDeleteOpen(false)}>
{t("Bekor qilish")}
</Button>
<Button
variant={"destructive"}
onClick={() => removeItem(it.id)}
>
{t("O'chirish")}
</Button>
</div>
</DialogContent>
</Dialog>
</div>
</div>
</CardContent>
</Card>
))}
</div>
</div>
</div>
);
}