This commit is contained in:
Samandar Turgunboyev
2025-10-30 18:28:17 +05:00
parent 352efd6391
commit 39f5b8ca3c
13 changed files with 650 additions and 115 deletions

View File

@@ -13,7 +13,9 @@ const createOfferta = async ({
}: {
body: {
title: string;
title_ru: string;
content: string;
content_ru: string;
person_type: "individual" | "legal_entity";
is_active: boolean;
};
@@ -28,8 +30,10 @@ const updateOfferta = async ({
}: {
id: number;
body: {
title?: string;
content?: string;
title: string;
title_ru: string;
content: string;
content_ru: string;
person_type?: "individual" | "legal_entity";
is_active?: boolean;
};
@@ -41,6 +45,7 @@ const updateOfferta = async ({
const getAllOfferta = async (params: {
page: number;
page_size: number;
person_type?: "individual" | "legal_entity";
}): Promise<AxiosResponse<GetAllOfferta>> => {
const res = await httpClient.get(OFFERTA, { params });
return res;
@@ -63,6 +68,8 @@ const createHelpPage = async ({
}: {
body: {
title: string;
title_ru: string;
content_ru: string;
content: string;
page_type: "privacy_policy" | "user_agreement";
is_active: boolean;
@@ -75,6 +82,7 @@ const createHelpPage = async ({
const getAllHelpPage = async (params: {
page: number;
page_size: number;
page_type?: "privacy_policy" | "user_agreement";
}): Promise<AxiosResponse<GetAllHelpPage>> => {
const res = await httpClient.get(HELP_PAGE, { params });
return res;
@@ -93,8 +101,10 @@ const updateHelpPage = async ({
}: {
id: number;
body: {
title?: string;
content?: string;
title: string;
title_ru: string;
content_ru: string;
content: string;
page_type?: "privacy_policy" | "user_agreement";
is_active?: boolean;
};

View File

@@ -25,6 +25,8 @@ export interface GetDetailOfferta {
id: number;
title: string;
content: string;
title_ru: string;
content_ru: string;
person_type: "individual" | "legal_entity";
is_active: boolean;
};
@@ -56,7 +58,9 @@ export interface GetDetailHelpPage {
data: {
id: number;
title: string;
title_ru: string;
content: string;
content_ru: string;
page_type: "privacy_policy" | "user_agreement";
is_active: true;
};

View File

@@ -45,6 +45,34 @@ export default function PolicyCrud() {
},
});
const { data: PrivacyPolicy } = useQuery({
queryKey: ["privacy_policy"],
queryFn: () => {
return getAllHelpPage({
page: 1,
page_size: 99,
page_type: "privacy_policy",
});
},
select(data) {
return data.data.data;
},
});
const { data: UserAgreement } = useQuery({
queryKey: ["user_agreement"],
queryFn: () => {
return getAllHelpPage({
page: 1,
page_size: 99,
page_type: "user_agreement",
});
},
select(data) {
return data.data.data;
},
});
const [editing, setEditing] = useState<number | null>(null);
const { data: detail } = useQuery({
@@ -60,11 +88,15 @@ export default function PolicyCrud() {
const [form, setForm] = useState<{
title: string;
title_ru: string;
content_ru: string;
content: string;
page_type: "privacy_policy" | "user_agreement";
is_active: boolean;
}>({
title: "",
content_ru: "",
title_ru: "",
content: "",
page_type: "privacy_policy",
is_active: true,
@@ -78,6 +110,8 @@ export default function PolicyCrud() {
}: {
body: {
title: string;
title_ru: string;
content_ru: string;
content: string;
page_type: "privacy_policy" | "user_agreement";
is_active: boolean;
@@ -87,6 +121,8 @@ export default function PolicyCrud() {
resetForm();
queryClient.refetchQueries({ queryKey: ["help_page"] });
queryClient.refetchQueries({ queryKey: ["help_page_detail"] });
queryClient.refetchQueries({ queryKey: ["privacy_policy"] });
queryClient.refetchQueries({ queryKey: ["user_agreement"] });
},
onError: () => {
toast.error(t("Xatolik yuz berdi"), {
@@ -103,8 +139,10 @@ export default function PolicyCrud() {
}: {
id: number;
body: {
title?: string;
content?: string;
title: string;
title_ru: string;
content_ru: string;
content: string;
page_type?: "privacy_policy" | "user_agreement";
is_active?: boolean;
};
@@ -113,6 +151,8 @@ export default function PolicyCrud() {
resetForm();
queryClient.refetchQueries({ queryKey: ["help_page"] });
queryClient.refetchQueries({ queryKey: ["help_page_detail"] });
queryClient.refetchQueries({ queryKey: ["privacy_policy"] });
queryClient.refetchQueries({ queryKey: ["user_agreement"] });
},
onError: () => {
toast.error(t("Xatolik yuz berdi"), {
@@ -128,6 +168,8 @@ export default function PolicyCrud() {
resetForm();
queryClient.refetchQueries({ queryKey: ["help_page"] });
queryClient.refetchQueries({ queryKey: ["help_page_detail"] });
queryClient.refetchQueries({ queryKey: ["privacy_policy"] });
queryClient.refetchQueries({ queryKey: ["user_agreement"] });
},
onError: () => {
toast.error(t("Xatolik yuz berdi"), {
@@ -140,6 +182,8 @@ export default function PolicyCrud() {
function resetForm() {
setForm({
title: "",
title_ru: "",
content_ru: "",
content: "",
is_active: true,
page_type: "privacy_policy",
@@ -155,6 +199,8 @@ export default function PolicyCrud() {
is_active: detail.is_active,
page_type: detail.page_type,
title: detail.title,
content_ru: detail.content_ru,
title_ru: detail.title_ru,
});
}
}, [detail, editing]);
@@ -189,6 +235,8 @@ export default function PolicyCrud() {
is_active: form.is_active,
page_type: form.page_type,
title: form.title,
content_ru: form.content_ru,
title_ru: form.title_ru,
},
id: editing,
});
@@ -199,6 +247,8 @@ export default function PolicyCrud() {
is_active: form.is_active,
page_type: form.page_type,
title: form.title,
content_ru: form.content_ru,
title_ru: form.title_ru,
},
});
}
@@ -212,14 +262,13 @@ export default function PolicyCrud() {
deleteHelp(id);
}
function toggleActive(id: number, currentStatus: boolean) {
update({
id: id,
body: {
is_active: !currentStatus,
},
});
}
const individualExists = !!PrivacyPolicy?.results?.length;
const legalExists = !!UserAgreement?.results?.length;
const isAddDisabled =
!editing &&
((form.page_type === "privacy_policy" && individualExists) ||
(form.page_type === "user_agreement" && legalExists));
return (
<div className="min-h-screen w-full p-6 bg-gray-900">
@@ -253,6 +302,26 @@ export default function PolicyCrud() {
</p>
)}
</div>
<div>
<label className="text-sm font-medium">
{t("Sarlavha")} (ru)
</label>
<Input
value={form.title_ru || ""}
onChange={(e) =>
setForm((s) => ({ ...s, title_ru: e.target.value }))
}
placeholder={t("Yordam sahifasi sarlavhasi") + " (ru)"}
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">
@@ -272,6 +341,25 @@ export default function PolicyCrud() {
)}
</div>
<div className="h-[280px] w-[100%]">
<label className="text-sm font-medium">{t("Kontent")} (ru)</label>
<div className="mt-1">
<ReactQuill
value={form.content_ru || ""}
onChange={(value) =>
setForm((s) => ({ ...s, content_ru: value }))
}
className="bg-gray-900 h-48"
placeholder={t("Yordam matnini kiriting...") + " (ru)"}
/>
</div>
{errors.content && (
<p className="text-destructive text-sm mt-12">
{t(errors.content)}
</p>
)}
</div>
<div className="grid grid-cols-1 gap-4 mt-5">
<div>
<label className="text-sm font-medium">
@@ -285,15 +373,19 @@ export default function PolicyCrud() {
page_type: value as "privacy_policy" | "user_agreement",
}))
}
disabled={editing !== null}
>
<SelectTrigger className="mt-1 w-full !h-12">
<SelectValue placeholder={t("Sahifa turini tanlang")} />
</SelectTrigger>
<SelectContent>
<SelectItem value="user_agreement">
<SelectItem value="user_agreement" disabled={legalExists}>
{t("Qollanma")}
</SelectItem>
<SelectItem value="privacy_policy">
<SelectItem
value="privacy_policy"
disabled={individualExists}
>
{t("Maxfiylik siyosati")}
</SelectItem>
</SelectContent>
@@ -316,6 +408,7 @@ export default function PolicyCrud() {
<div className="flex gap-2 pt-4">
<Button
onClick={handleCreateOrUpdate}
disabled={isAddDisabled}
className="bg-blue-600 hover:bg-blue-700"
>
{editing ? t("Saqlash") : t("Yaratish")}
@@ -358,19 +451,6 @@ export default function PolicyCrud() {
{t("Tahrirlash")}
</Button>
<Button
onClick={() => toggleActive(it.id, it.is_active)}
variant={it.is_active ? "default" : "outline"}
size="sm"
className={
it.is_active
? "bg-green-600 hover:bg-green-700 w-full"
: "w-full"
}
>
{it.is_active ? t("Faol") : t("Faol emas")}
</Button>
<Dialog open={deleteOpen} onOpenChange={setDeleteOpen}>
<DialogTrigger asChild>
<Button

View File

@@ -33,15 +33,20 @@ import { toast } from "sonner";
export default function OmmaviyOfertaCRUD() {
const { t } = useTranslation();
const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
const [selectedId, setSelectedId] = useState<number | null>(null);
const queryClient = useQueryClient();
const [editing, setEditing] = useState<number | null>(null);
const [form, setForm] = useState<{
title: string;
title_ru: string;
content: string;
audience: string;
active: boolean;
content_ru: string;
}>({
title: "",
content_ru: "",
title_ru: "",
audience: "Jismoniy shaxslar",
content: "",
active: true,
@@ -51,6 +56,8 @@ export default function OmmaviyOfertaCRUD() {
function resetForm() {
setForm({
title: "",
content_ru: "",
title_ru: "",
audience: "Jismoniy shaxslar",
content: "",
active: true,
@@ -62,7 +69,9 @@ export default function OmmaviyOfertaCRUD() {
const { mutate: create } = useMutation({
mutationFn: (body: {
title: string;
title_ru: string;
content: string;
content_ru: string;
person_type: "individual" | "legal_entity";
is_active: boolean;
}) => createOfferta({ body }),
@@ -70,6 +79,8 @@ export default function OmmaviyOfertaCRUD() {
resetForm();
queryClient.refetchQueries({ queryKey: ["all_offerta"] });
queryClient.refetchQueries({ queryKey: ["detail_offerta"] });
queryClient.refetchQueries({ queryKey: ["individual_offerta"] });
queryClient.refetchQueries({ queryKey: ["legal_entity"] });
toast.success(t("Muvaffaqiyatli yaratildi"), {
position: "top-center",
richColors: true,
@@ -89,8 +100,10 @@ export default function OmmaviyOfertaCRUD() {
id,
}: {
body: {
title?: string;
content?: string;
title: string;
title_ru: string;
content: string;
content_ru: string;
person_type?: "individual" | "legal_entity";
is_active?: boolean;
};
@@ -101,6 +114,8 @@ export default function OmmaviyOfertaCRUD() {
setEditing(null);
queryClient.refetchQueries({ queryKey: ["all_offerta"] });
queryClient.refetchQueries({ queryKey: ["detail_offerta"] });
queryClient.refetchQueries({ queryKey: ["individual_offerta"] });
queryClient.refetchQueries({ queryKey: ["legal_entity"] });
toast.success(t("Muvaffaqiyatli yangilandi"), {
position: "top-center",
richColors: true,
@@ -118,8 +133,11 @@ export default function OmmaviyOfertaCRUD() {
mutationFn: ({ id }: { id: number }) => deleteOfferta(id),
onSuccess: () => {
resetForm();
setDeleteOpen(false);
queryClient.refetchQueries({ queryKey: ["all_offerta"] });
queryClient.refetchQueries({ queryKey: ["detail_offerta"] });
queryClient.refetchQueries({ queryKey: ["individual_offerta"] });
queryClient.refetchQueries({ queryKey: ["legal_entity"] });
toast.success(t("Muvaffaqiyatli o'chirildi"), {
position: "top-center",
richColors: true,
@@ -136,7 +154,38 @@ export default function OmmaviyOfertaCRUD() {
const { data: allOfferta } = useQuery({
queryKey: ["all_offerta"],
queryFn: () => {
return getAllOfferta({ page: 1, page_size: 99 });
return getAllOfferta({
page: 1,
page_size: 99,
});
},
select(data) {
return data.data.data;
},
});
const { data: individualOfferta } = useQuery({
queryKey: ["individual_offerta"],
queryFn: () => {
return getAllOfferta({
page: 1,
page_size: 99,
person_type: "individual",
});
},
select(data) {
return data.data.data;
},
});
const { data: legalOfferta } = useQuery({
queryKey: ["legal_entity"],
queryFn: () => {
return getAllOfferta({
page: 1,
page_size: 99,
person_type: "legal_entity",
});
},
select(data) {
return data.data.data;
@@ -158,6 +207,8 @@ export default function OmmaviyOfertaCRUD() {
if (editing && detailOfferta) {
setForm({
active: detailOfferta.is_active,
content_ru: detailOfferta.content_ru,
title_ru: detailOfferta.title_ru,
audience:
detailOfferta.person_type === "individual"
? "Jismoniy shaxslar"
@@ -197,6 +248,8 @@ export default function OmmaviyOfertaCRUD() {
if (editing === null) {
create({
content: form.content,
content_ru: form.content_ru,
title_ru: form.title_ru,
is_active: form.active,
person_type:
form.audience === "Jismoniy shaxslar" ? "individual" : "legal_entity",
@@ -207,6 +260,8 @@ export default function OmmaviyOfertaCRUD() {
body: {
content: form.content,
is_active: form.active,
content_ru: form.content_ru,
title_ru: form.title_ru,
person_type:
form.audience === "Jismoniy shaxslar"
? "individual"
@@ -222,18 +277,13 @@ export default function OmmaviyOfertaCRUD() {
setEditing(item);
}
function removeItem(id: number) {
removeOfferta({ id });
}
const individualExists = !!individualOfferta?.results?.length;
const legalExists = !!legalOfferta?.results?.length;
function toggleActive(id: number, currentStatus: boolean) {
update({
id: id,
body: {
is_active: !currentStatus,
},
});
}
const isAddDisabled =
!editing &&
((form.audience === "Jismoniy shaxslar" && individualExists) ||
(form.audience === "Yuridik shaxslar" && legalExists));
return (
<div className="min-h-screen w-full p-6 bg-gray-900">
@@ -265,7 +315,27 @@ export default function OmmaviyOfertaCRUD() {
</p>
)}
</div>
<div className="h-[280px] w-[100%]">
<div>
<label className="text-sm font-medium">
{t("Sarlavha")} (ru)
</label>
<Input
value={form.title_ru || ""}
onChange={(e) =>
setForm((s) => ({ ...s, title_ru: e.target.value }))
}
placeholder={t("Ommaviy oferta sarlavhasi") + " (ru)"}
className="mt-1"
/>
{errors.title && (
<p className="text-destructive text-sm mt-1">
{t(errors.title_ru)}
</p>
)}
</div>
<div className="h-[280px] w-full">
<label className="text-sm font-medium">{t("Kontent")}</label>
<div className="mt-1">
<ReactQuill
@@ -284,22 +354,48 @@ export default function OmmaviyOfertaCRUD() {
)}
</div>
<div className="h-[280px] w-full">
<label className="text-sm font-medium">{t("Kontent")} (ru)</label>
<div className="mt-1">
<ReactQuill
value={form.content_ru || ""}
onChange={(value) =>
setForm((s) => ({ ...s, content_ru: value }))
}
className="bg-gray-900 h-48"
placeholder={t("Oferta matnini kiriting...") + " (ru)"}
/>
</div>
{errors.content && (
<p className="text-destructive text-sm mt-12">
{t(errors.content_ru)}
</p>
)}
</div>
{/* Kimlar uchun */}
<div>
<label className="text-sm font-medium">{t("Kimlar uchun")}</label>
<Select
value={form.audience || t("Barcha")}
value={form.audience || ""}
onValueChange={(value) =>
setForm((s) => ({ ...s, audience: value }))
}
disabled={editing !== null}
>
<SelectTrigger className="mt-1 w-full !h-12">
<SelectValue />
<SelectValue placeholder={t("Offerta turini tanlang")} />
</SelectTrigger>
<SelectContent>
<SelectItem value="Jismoniy shaxslar">
<SelectItem
value="Jismoniy shaxslar"
disabled={individualExists}
>
{t("Jismoniy shaxslar uchun")}
</SelectItem>
<SelectItem value="Yuridik shaxslar">
<SelectItem value="Yuridik shaxslar" disabled={legalExists}>
{t("Yuridik shaxslar uchun")}
</SelectItem>
</SelectContent>
@@ -311,9 +407,11 @@ export default function OmmaviyOfertaCRUD() {
)}
</div>
{/* Tugmalar */}
<div className="flex gap-2 pt-4">
<Button
onClick={handleCreateOrUpdate}
disabled={isAddDisabled}
className="bg-blue-600 hover:bg-blue-700"
>
{editing ? t("Saqlash") : t("Qo'shish")}
@@ -335,7 +433,6 @@ export default function OmmaviyOfertaCRUD() {
</CardContent>
</Card>
)}
{allOfferta?.results.map((it) => (
<Card key={it.id} className="overflow-hidden">
<CardContent className="pt-6">
@@ -360,31 +457,30 @@ export default function OmmaviyOfertaCRUD() {
{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"
}
<Dialog
open={deleteOpen && selectedId === it.id}
onOpenChange={(open) => {
if (!open) {
setDeleteOpen(false);
setSelectedId(null);
}
}}
>
{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)}
onClick={() => {
setSelectedId(it.id);
setDeleteOpen(true);
}}
>
<Trash2 className="w-4 h-4 mr-1" />
{t("O'chirish")}
</Button>
</DialogTrigger>
<DialogContent>
<DialogTitle>{t("O'chirish tasdiqlash")}</DialogTitle>
<DialogDescription>
@@ -393,12 +489,21 @@ export default function OmmaviyOfertaCRUD() {
)}
</DialogDescription>
<div className="flex gap-3 justify-end pt-4">
<Button onClick={() => setDeleteOpen(false)}>
<Button
onClick={() => {
setDeleteOpen(false);
setSelectedId(null);
}}
>
{t("Bekor qilish")}
</Button>
<Button
variant={"destructive"}
onClick={() => removeItem(it.id)}
variant="destructive"
onClick={() => {
removeOfferta({ id: selectedId! });
setDeleteOpen(false);
setSelectedId(null);
}}
>
{t("O'chirish")}
</Button>