api ulandi
This commit is contained in:
122
src/pages/site-page/lib/api.ts
Normal file
122
src/pages/site-page/lib/api.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
import type {
|
||||
GetAllHelpPage,
|
||||
GetAllOfferta,
|
||||
GetDetailHelpPage,
|
||||
GetDetailOfferta,
|
||||
} from "@/pages/site-page/lib/types";
|
||||
import httpClient from "@/shared/config/api/httpClient";
|
||||
import { HELP_PAGE, OFFERTA } from "@/shared/config/api/URLs";
|
||||
import type { AxiosResponse } from "axios";
|
||||
|
||||
const createOfferta = async ({
|
||||
body,
|
||||
}: {
|
||||
body: {
|
||||
title: string;
|
||||
content: string;
|
||||
person_type: "individual" | "legal_entity";
|
||||
is_active: boolean;
|
||||
};
|
||||
}) => {
|
||||
const res = await httpClient.post(OFFERTA, body);
|
||||
return res;
|
||||
};
|
||||
|
||||
const updateOfferta = async ({
|
||||
body,
|
||||
id,
|
||||
}: {
|
||||
id: number;
|
||||
body: {
|
||||
title?: string;
|
||||
content?: string;
|
||||
person_type?: "individual" | "legal_entity";
|
||||
is_active?: boolean;
|
||||
};
|
||||
}) => {
|
||||
const res = await httpClient.patch(`${OFFERTA}${id}/`, body);
|
||||
return res;
|
||||
};
|
||||
|
||||
const getAllOfferta = async (params: {
|
||||
page: number;
|
||||
page_size: number;
|
||||
}): Promise<AxiosResponse<GetAllOfferta>> => {
|
||||
const res = await httpClient.get(OFFERTA, { params });
|
||||
return res;
|
||||
};
|
||||
|
||||
const getOneOfferta = async (
|
||||
id: number,
|
||||
): Promise<AxiosResponse<GetDetailOfferta>> => {
|
||||
const res = await httpClient.get(`${OFFERTA}${id}/`);
|
||||
return res;
|
||||
};
|
||||
|
||||
const deleteOfferta = async (id: number) => {
|
||||
const res = await httpClient.delete(`${OFFERTA}${id}/`);
|
||||
return res;
|
||||
};
|
||||
|
||||
const createHelpPage = async ({
|
||||
body,
|
||||
}: {
|
||||
body: {
|
||||
title: string;
|
||||
content: string;
|
||||
page_type: "privacy_policy" | "user_agreement";
|
||||
is_active: boolean;
|
||||
};
|
||||
}) => {
|
||||
const res = await httpClient.post(HELP_PAGE, body);
|
||||
return res;
|
||||
};
|
||||
|
||||
const getAllHelpPage = async (params: {
|
||||
page: number;
|
||||
page_size: number;
|
||||
}): Promise<AxiosResponse<GetAllHelpPage>> => {
|
||||
const res = await httpClient.get(HELP_PAGE, { params });
|
||||
return res;
|
||||
};
|
||||
|
||||
const getDetailHelpPage = async (
|
||||
id: number,
|
||||
): Promise<AxiosResponse<GetDetailHelpPage>> => {
|
||||
const res = await httpClient.get(`${HELP_PAGE}${id}/`);
|
||||
return res;
|
||||
};
|
||||
|
||||
const updateHelpPage = async ({
|
||||
body,
|
||||
id,
|
||||
}: {
|
||||
id: number;
|
||||
body: {
|
||||
title?: string;
|
||||
content?: string;
|
||||
page_type?: "privacy_policy" | "user_agreement";
|
||||
is_active?: boolean;
|
||||
};
|
||||
}) => {
|
||||
const res = await httpClient.patch(`${HELP_PAGE}${id}/`, body);
|
||||
return res;
|
||||
};
|
||||
|
||||
const deleteHelpPage = async (id: number) => {
|
||||
const res = await httpClient.delete(`${HELP_PAGE}${id}/`);
|
||||
return res;
|
||||
};
|
||||
|
||||
export {
|
||||
createHelpPage,
|
||||
createOfferta,
|
||||
deleteHelpPage,
|
||||
deleteOfferta,
|
||||
getAllHelpPage,
|
||||
getAllOfferta,
|
||||
getDetailHelpPage,
|
||||
getOneOfferta,
|
||||
updateHelpPage,
|
||||
updateOfferta,
|
||||
};
|
||||
63
src/pages/site-page/lib/types.ts
Normal file
63
src/pages/site-page/lib/types.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
export interface GetAllOfferta {
|
||||
status: boolean;
|
||||
data: {
|
||||
links: {
|
||||
previous: string;
|
||||
next: string;
|
||||
};
|
||||
total_items: number;
|
||||
total_pages: number;
|
||||
page_size: number;
|
||||
current_page: number;
|
||||
results: {
|
||||
id: number;
|
||||
title: string;
|
||||
content: string;
|
||||
person_type: "individual" | "legal_entity";
|
||||
is_active: boolean;
|
||||
}[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface GetDetailOfferta {
|
||||
status: boolean;
|
||||
data: {
|
||||
id: number;
|
||||
title: string;
|
||||
content: string;
|
||||
person_type: "individual" | "legal_entity";
|
||||
is_active: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export interface GetAllHelpPage {
|
||||
status: boolean;
|
||||
data: {
|
||||
links: {
|
||||
previous: string;
|
||||
next: string;
|
||||
};
|
||||
total_items: number;
|
||||
total_pages: number;
|
||||
page_size: number;
|
||||
current_page: number;
|
||||
results: {
|
||||
id: number;
|
||||
title: string;
|
||||
content: string;
|
||||
page_type: "privacy_policy" | "user_agreement";
|
||||
is_active: boolean;
|
||||
}[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface GetDetailHelpPage {
|
||||
status: true;
|
||||
data: {
|
||||
id: number;
|
||||
title: string;
|
||||
content: string;
|
||||
page_type: "privacy_policy" | "user_agreement";
|
||||
is_active: true;
|
||||
};
|
||||
}
|
||||
@@ -1,3 +1,10 @@
|
||||
import {
|
||||
createHelpPage,
|
||||
deleteHelpPage,
|
||||
getAllHelpPage,
|
||||
getDetailHelpPage,
|
||||
updateHelpPage,
|
||||
} from "@/pages/site-page/lib/api";
|
||||
import { Button } from "@/shared/ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/shared/ui/card";
|
||||
import { Checkbox } from "@/shared/ui/checkbox";
|
||||
@@ -16,84 +23,150 @@ import {
|
||||
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";
|
||||
|
||||
type Offer = {
|
||||
id: string;
|
||||
title: string;
|
||||
audience: "Foydalanuvchi qo‘llanmasi" | "Maxfiylik siyosati";
|
||||
content: string;
|
||||
active: boolean;
|
||||
createdAt: string;
|
||||
};
|
||||
|
||||
const FAKE_DATA: Offer[] = [
|
||||
{
|
||||
id: "of-1",
|
||||
title: "Ommaviy oferta - Standart shartlar",
|
||||
audience: "Foydalanuvchi qo‘llanmasi",
|
||||
content:
|
||||
"Bu hujjat kompaniya va xizmatlardan foydalanish bo'yicha umumiy shartlarni o'z ichiga oladi.",
|
||||
active: true,
|
||||
createdAt: new Date().toISOString(),
|
||||
},
|
||||
{
|
||||
id: "of-2",
|
||||
title: "Foydalanuvchi qo‘llanmasi uchun oferta",
|
||||
audience: "Foydalanuvchi qo‘llanmasi",
|
||||
content: "Foydalanuvchi qo‘llanmasi uchun maxsus shartlar va kafolatlar.",
|
||||
active: false,
|
||||
createdAt: new Date(Date.now() - 86400000).toISOString(),
|
||||
},
|
||||
];
|
||||
|
||||
const STORAGE_KEY = "ommaviy_oferta_v1";
|
||||
import { toast } from "sonner";
|
||||
|
||||
export default function PolicyCrud() {
|
||||
const [items, setItems] = useState<Offer[]>([]);
|
||||
const [query, setQuery] = useState("");
|
||||
const [editing, setEditing] = useState<Offer | null>(null);
|
||||
const [form, setForm] = useState<Partial<Offer>>({
|
||||
title: "",
|
||||
audience: "Foydalanuvchi qo‘llanmasi",
|
||||
content: "",
|
||||
active: true,
|
||||
const { t } = useTranslation();
|
||||
const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
|
||||
const queryClient = useQueryClient();
|
||||
const { data: items } = useQuery({
|
||||
queryKey: ["help_page"],
|
||||
queryFn: () => {
|
||||
return getAllHelpPage({ page: 1, page_size: 99 });
|
||||
},
|
||||
select(data) {
|
||||
return data.data.data.results;
|
||||
},
|
||||
});
|
||||
|
||||
const [editing, setEditing] = useState<number | null>(null);
|
||||
|
||||
const { data: detail } = useQuery({
|
||||
queryKey: ["help_page_detail", editing],
|
||||
queryFn: () => {
|
||||
return getDetailHelpPage(editing!);
|
||||
},
|
||||
select(data) {
|
||||
return data.data.data;
|
||||
},
|
||||
enabled: !!editing,
|
||||
});
|
||||
|
||||
const [form, setForm] = useState<{
|
||||
title: string;
|
||||
content: string;
|
||||
page_type: "privacy_policy" | "user_agreement";
|
||||
is_active: boolean;
|
||||
}>({
|
||||
title: "",
|
||||
content: "",
|
||||
page_type: "privacy_policy",
|
||||
is_active: true,
|
||||
});
|
||||
|
||||
const [errors, setErrors] = useState<Record<string, string>>({});
|
||||
|
||||
useEffect(() => {
|
||||
const raw = localStorage.getItem(STORAGE_KEY);
|
||||
if (raw) {
|
||||
try {
|
||||
const parsed = JSON.parse(raw) as Offer[];
|
||||
setItems(parsed);
|
||||
} catch {
|
||||
setItems(FAKE_DATA);
|
||||
}
|
||||
} else {
|
||||
setItems(FAKE_DATA);
|
||||
}
|
||||
}, []);
|
||||
const { mutate: create } = useMutation({
|
||||
mutationFn: ({
|
||||
body,
|
||||
}: {
|
||||
body: {
|
||||
title: string;
|
||||
content: string;
|
||||
page_type: "privacy_policy" | "user_agreement";
|
||||
is_active: boolean;
|
||||
};
|
||||
}) => createHelpPage({ body }),
|
||||
onSuccess: () => {
|
||||
resetForm();
|
||||
queryClient.refetchQueries({ queryKey: ["help_page"] });
|
||||
queryClient.refetchQueries({ queryKey: ["help_page_detail"] });
|
||||
},
|
||||
onError: () => {
|
||||
toast.error(t("Xatolik yuz berdi"), {
|
||||
position: "top-center",
|
||||
richColors: true,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(items));
|
||||
}, [items]);
|
||||
const { mutate: update } = useMutation({
|
||||
mutationFn: ({
|
||||
body,
|
||||
id,
|
||||
}: {
|
||||
id: number;
|
||||
body: {
|
||||
title?: string;
|
||||
content?: string;
|
||||
page_type?: "privacy_policy" | "user_agreement";
|
||||
is_active?: boolean;
|
||||
};
|
||||
}) => updateHelpPage({ body, id }),
|
||||
onSuccess: () => {
|
||||
resetForm();
|
||||
queryClient.refetchQueries({ queryKey: ["help_page"] });
|
||||
queryClient.refetchQueries({ queryKey: ["help_page_detail"] });
|
||||
},
|
||||
onError: () => {
|
||||
toast.error(t("Xatolik yuz berdi"), {
|
||||
position: "top-center",
|
||||
richColors: true,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const { mutate: deleteHelp } = useMutation({
|
||||
mutationFn: (id: number) => deleteHelpPage(id),
|
||||
onSuccess: () => {
|
||||
resetForm();
|
||||
queryClient.refetchQueries({ queryKey: ["help_page"] });
|
||||
queryClient.refetchQueries({ queryKey: ["help_page_detail"] });
|
||||
},
|
||||
onError: () => {
|
||||
toast.error(t("Xatolik yuz berdi"), {
|
||||
position: "top-center",
|
||||
richColors: true,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
function resetForm() {
|
||||
setForm({
|
||||
title: "",
|
||||
audience: "Foydalanuvchi qo‘llanmasi",
|
||||
content: "",
|
||||
active: true,
|
||||
is_active: true,
|
||||
page_type: "privacy_policy",
|
||||
});
|
||||
setErrors({});
|
||||
setEditing(null);
|
||||
}
|
||||
|
||||
function validate(f: Partial<Offer>) {
|
||||
useEffect(() => {
|
||||
if (detail && editing) {
|
||||
setForm({
|
||||
content: detail.content,
|
||||
is_active: detail.is_active,
|
||||
page_type: detail.page_type,
|
||||
title: detail.title,
|
||||
});
|
||||
}
|
||||
}, [detail, editing]);
|
||||
|
||||
function validate(
|
||||
f: Partial<{
|
||||
title: string;
|
||||
content: string;
|
||||
page_type: "privacy_policy" | "user_agreement";
|
||||
is_active: boolean;
|
||||
}>,
|
||||
) {
|
||||
const e: Record<string, string> = {};
|
||||
if (!f.title || f.title.trim().length < 3)
|
||||
e.title = "Sarlavha kamida 3 ta belgidan iborat bo'lishi kerak";
|
||||
@@ -110,83 +183,78 @@ export default function PolicyCrud() {
|
||||
}
|
||||
|
||||
if (editing) {
|
||||
setItems((prev) =>
|
||||
prev.map((it) =>
|
||||
it.id === editing.id ? { ...it, ...(form as Offer) } : it,
|
||||
),
|
||||
);
|
||||
resetForm();
|
||||
update({
|
||||
body: {
|
||||
content: form.content,
|
||||
is_active: form.is_active,
|
||||
page_type: form.page_type,
|
||||
title: form.title,
|
||||
},
|
||||
id: editing,
|
||||
});
|
||||
} else {
|
||||
const newItem: Offer = {
|
||||
id: `of-${Date.now()}`,
|
||||
title: (form.title || "Untitled").trim(),
|
||||
audience: (form.audience as Offer["audience"]) || "Barcha",
|
||||
content: (form.content || "").trim(),
|
||||
active: form.active ?? true,
|
||||
createdAt: new Date().toISOString(),
|
||||
};
|
||||
setItems((prev) => [newItem, ...prev]);
|
||||
resetForm();
|
||||
create({
|
||||
body: {
|
||||
content: form.content,
|
||||
is_active: form.is_active,
|
||||
page_type: form.page_type,
|
||||
title: form.title,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function startEdit(item: Offer) {
|
||||
function startEdit(item: number) {
|
||||
setEditing(item);
|
||||
setForm({ ...item });
|
||||
setErrors({});
|
||||
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||
}
|
||||
|
||||
function removeItem(id: string) {
|
||||
setItems((prev) => prev.filter((p) => p.id !== id));
|
||||
function removeItem(id: number) {
|
||||
deleteHelp(id);
|
||||
}
|
||||
|
||||
function toggleActive(id: string) {
|
||||
setItems((prev) =>
|
||||
prev.map((p) => (p.id === id ? { ...p, active: !p.active } : p)),
|
||||
);
|
||||
function toggleActive(id: number, currentStatus: boolean) {
|
||||
update({
|
||||
id: id,
|
||||
body: {
|
||||
is_active: !currentStatus,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const filtered = items.filter((it) => {
|
||||
const q = query.trim().toLowerCase();
|
||||
if (!q) return true;
|
||||
return (
|
||||
it.title.toLowerCase().includes(q) ||
|
||||
it.content.toLowerCase().includes(q) ||
|
||||
it.audience.toLowerCase().includes(q)
|
||||
);
|
||||
});
|
||||
|
||||
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">Ommaviy oferta</h1>
|
||||
<h1 className="text-3xl font-bold">
|
||||
{t("Yordam sahifalari boshqaruvi")}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<Card className="bg-gray-900">
|
||||
<CardHeader>
|
||||
<CardTitle>
|
||||
{editing ? "Tahrirish" : "Yangi oferta yaratish"}
|
||||
{editing ? t("Tahrirlash") : t("Yangi yordam sahifasi yaratish")}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div>
|
||||
<label className="text-sm font-medium">Sarlavha</label>
|
||||
<label className="text-sm font-medium">{t("Sarlavha")}</label>
|
||||
<Input
|
||||
value={form.title || ""}
|
||||
onChange={(e) =>
|
||||
setForm((s) => ({ ...s, title: e.target.value }))
|
||||
}
|
||||
placeholder="Ommaviy oferta sarlavhasi"
|
||||
placeholder={t("Yordam sahifasi sarlavhasi")}
|
||||
className="mt-1"
|
||||
/>
|
||||
{errors.title && (
|
||||
<p className="text-destructive text-sm mt-1">{errors.title}</p>
|
||||
<p className="text-destructive text-sm mt-1">
|
||||
{t(errors.title)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="h-full w-[100%]">
|
||||
<label className="text-sm font-medium">Kontent</label>
|
||||
<div className="h-[280px] w-[100%]">
|
||||
<label className="text-sm font-medium">{t("Kontent")}</label>
|
||||
<div className="mt-1">
|
||||
<ReactQuill
|
||||
value={form.content || ""}
|
||||
@@ -194,37 +262,39 @@ export default function PolicyCrud() {
|
||||
setForm((s) => ({ ...s, content: value }))
|
||||
}
|
||||
className="bg-gray-900 h-48"
|
||||
placeholder="Oferta matnini kiriting..."
|
||||
placeholder={t("Yordam matnini kiriting...")}
|
||||
/>
|
||||
</div>
|
||||
{errors.content && (
|
||||
<p className="text-destructive text-sm mt-1">
|
||||
{errors.content}
|
||||
<p className="text-destructive text-sm mt-12">
|
||||
{t(errors.content)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 mt-24">
|
||||
<div className="grid grid-cols-1 gap-4 mt-5">
|
||||
<div>
|
||||
<label className="text-sm font-medium">Kimlar uchun</label>
|
||||
<label className="text-sm font-medium">
|
||||
{t("Sahifa turi")}
|
||||
</label>
|
||||
<Select
|
||||
value={form.audience || "Barcha"}
|
||||
value={form.page_type}
|
||||
onValueChange={(value) =>
|
||||
setForm((s) => ({
|
||||
...s,
|
||||
audience: value as Offer["audience"],
|
||||
page_type: value as "privacy_policy" | "user_agreement",
|
||||
}))
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="mt-1 w-full !h-12">
|
||||
<SelectValue />
|
||||
<SelectValue placeholder={t("Sahifa turini tanlang")} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="Foydalanuvchi qo‘llanmasi">
|
||||
Foydalanuvchi qo‘llanmasi
|
||||
<SelectItem value="user_agreement">
|
||||
{t("Qo‘llanma")}
|
||||
</SelectItem>
|
||||
<SelectItem value="Maxfiylik siyosati">
|
||||
Maxfiylik siyosati
|
||||
<SelectItem value="privacy_policy">
|
||||
{t("Maxfiylik siyosati")}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
@@ -233,12 +303,12 @@ export default function PolicyCrud() {
|
||||
<div className="flex items-end">
|
||||
<label className="flex items-center gap-2 text-sm cursor-pointer">
|
||||
<Checkbox
|
||||
checked={!!form.active}
|
||||
checked={!!form.is_active}
|
||||
onCheckedChange={(checked) =>
|
||||
setForm((s) => ({ ...s, active: checked ? true : false }))
|
||||
}
|
||||
/>
|
||||
<span>Faol</span>
|
||||
<span>{t("Faol")}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -248,92 +318,87 @@ export default function PolicyCrud() {
|
||||
onClick={handleCreateOrUpdate}
|
||||
className="bg-blue-600 hover:bg-blue-700"
|
||||
>
|
||||
{editing ? "Saqlash" : "Yaratish"}
|
||||
{editing ? t("Saqlash") : t("Yaratish")}
|
||||
</Button>
|
||||
<Button variant="outline" onClick={resetForm}>
|
||||
Bekor qilish
|
||||
{t("Bekor qilish")}
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
|
||||
<Input
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
placeholder="Qidirish sarlavha, kontent yoki auditoriya bo'yicha..."
|
||||
className="max-w-sm"
|
||||
/>
|
||||
<div className="flex gap-4 text-sm text-muted-foreground">
|
||||
<span>Natija: {filtered.length}</span>
|
||||
<span>Barcha: {items.length}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
{filtered.length === 0 && (
|
||||
{items?.length === 0 && (
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<p className="text-muted-foreground text-center">
|
||||
Natija topilmadi.
|
||||
{t("Natija topilmadi.")}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{filtered.map((it) => (
|
||||
{items?.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.audience} • {new Date(it.createdAt).toLocaleString()}
|
||||
</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)}
|
||||
onClick={() => startEdit(it.id)}
|
||||
variant="outline"
|
||||
className="w-full"
|
||||
size="sm"
|
||||
>
|
||||
<Edit2 className="w-4 h-4 mr-1" />
|
||||
Tahrirlash
|
||||
{t("Tahrirlash")}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onClick={() => toggleActive(it.id)}
|
||||
variant={it.active ? "default" : "outline"}
|
||||
onClick={() => toggleActive(it.id, it.is_active)}
|
||||
variant={it.is_active ? "default" : "outline"}
|
||||
size="sm"
|
||||
className={
|
||||
it.active ? "bg-green-600 hover:bg-green-700" : ""
|
||||
it.is_active
|
||||
? "bg-green-600 hover:bg-green-700 w-full"
|
||||
: "w-full"
|
||||
}
|
||||
>
|
||||
{it.active ? "Faol" : "Faol emas"}
|
||||
{it.is_active ? t("Faol") : t("Faol emas")}
|
||||
</Button>
|
||||
|
||||
<Dialog>
|
||||
<Dialog open={deleteOpen} onOpenChange={setDeleteOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="destructive" size="sm">
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
className="w-full"
|
||||
onClick={() => setDeleteOpen(true)}
|
||||
>
|
||||
<Trash2 className="w-4 h-4 mr-1" />
|
||||
O'chirish
|
||||
{t("O'chirish")}
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogTitle>O'chirish tasdiqlash</DialogTitle>
|
||||
<DialogTitle>{t("O‘chirishni tasdiqlash")}</DialogTitle>
|
||||
<DialogDescription>
|
||||
Haqiqatan ham bu ofertani o'chirmoqchimisiz? Bu amalni
|
||||
bekor qilib bo'lmaydi.
|
||||
{t(
|
||||
"Haqiqatan ham bu yordam sahifasini o‘chirmoqchimisiz? Bu amalni bekor qilib bo‘lmaydi.",
|
||||
)}
|
||||
</DialogDescription>
|
||||
<div className="flex gap-3 justify-end pt-4">
|
||||
<Button>Bekor qilish</Button>
|
||||
<Button onClick={() => setDeleteOpen(false)}>
|
||||
{t("Bekor qilish")}
|
||||
</Button>
|
||||
<Button
|
||||
variant={"destructive"}
|
||||
onClick={() => removeItem(it.id)}
|
||||
>
|
||||
O'chirish
|
||||
{t("O'chirish")}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
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 { Checkbox } from "@/shared/ui/checkbox";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -16,47 +22,25 @@ import {
|
||||
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";
|
||||
|
||||
type Offer = {
|
||||
id: string;
|
||||
title: string;
|
||||
audience: "Jismoniy shaxslar" | "Yuridik shaxslar";
|
||||
content: string;
|
||||
active: boolean;
|
||||
createdAt: string;
|
||||
};
|
||||
|
||||
const FAKE_DATA: Offer[] = [
|
||||
{
|
||||
id: "of-1",
|
||||
title: "Ommaviy oferta - Standart shartlar",
|
||||
audience: "Jismoniy shaxslar",
|
||||
content:
|
||||
"Bu hujjat kompaniya va xizmatlardan foydalanish bo'yicha umumiy shartlarni o'z ichiga oladi.",
|
||||
active: true,
|
||||
createdAt: new Date().toISOString(),
|
||||
},
|
||||
{
|
||||
id: "of-2",
|
||||
title: "Yuridik shaxslar uchun oferta",
|
||||
audience: "Yuridik shaxslar",
|
||||
content: "Yuridik shaxslar uchun maxsus shartlar va kafolatlar.",
|
||||
active: false,
|
||||
createdAt: new Date(Date.now() - 86400000).toISOString(),
|
||||
},
|
||||
];
|
||||
|
||||
const STORAGE_KEY = "ommaviy_oferta_v1";
|
||||
import { toast } from "sonner";
|
||||
|
||||
export default function OmmaviyOfertaCRUD() {
|
||||
const [items, setItems] = useState<Offer[]>([]);
|
||||
const [query, setQuery] = useState("");
|
||||
const [editing, setEditing] = useState<Offer | null>(null);
|
||||
const [form, setForm] = useState<Partial<Offer>>({
|
||||
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: "",
|
||||
@@ -64,24 +48,6 @@ export default function OmmaviyOfertaCRUD() {
|
||||
});
|
||||
const [errors, setErrors] = useState<Record<string, string>>({});
|
||||
|
||||
useEffect(() => {
|
||||
const raw = localStorage.getItem(STORAGE_KEY);
|
||||
if (raw) {
|
||||
try {
|
||||
const parsed = JSON.parse(raw) as Offer[];
|
||||
setItems(parsed);
|
||||
} catch {
|
||||
setItems(FAKE_DATA);
|
||||
}
|
||||
} else {
|
||||
setItems(FAKE_DATA);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(items));
|
||||
}, [items]);
|
||||
|
||||
function resetForm() {
|
||||
setForm({
|
||||
title: "",
|
||||
@@ -93,100 +59,214 @@ export default function OmmaviyOfertaCRUD() {
|
||||
setEditing(null);
|
||||
}
|
||||
|
||||
function validate(f: Partial<Offer>) {
|
||||
const e: Record<string, string> = {};
|
||||
if (!f.title || f.title.trim().length < 3)
|
||||
e.title = "Sarlavha kamida 3 ta belgidan iborat bo'lishi kerak";
|
||||
if (!f.content || f.content.trim().length < 10)
|
||||
e.content = "Kontent kamida 10 ta belgidan iborat bo'lishi kerak";
|
||||
return e;
|
||||
}
|
||||
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 validation = validate(form);
|
||||
if (Object.keys(validation).length) {
|
||||
setErrors(validation);
|
||||
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) {
|
||||
setItems((prev) =>
|
||||
prev.map((it) =>
|
||||
it.id === editing.id ? { ...it, ...(form as Offer) } : it,
|
||||
),
|
||||
);
|
||||
resetForm();
|
||||
} else {
|
||||
const newItem: Offer = {
|
||||
id: `of-${Date.now()}`,
|
||||
title: (form.title || "Untitled").trim(),
|
||||
audience: (form.audience as Offer["audience"]) || "Barcha",
|
||||
content: (form.content || "").trim(),
|
||||
active: form.active ?? true,
|
||||
createdAt: new Date().toISOString(),
|
||||
};
|
||||
setItems((prev) => [newItem, ...prev]);
|
||||
resetForm();
|
||||
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: Offer) {
|
||||
function startEdit(item: number) {
|
||||
setEditing(item);
|
||||
setForm({ ...item });
|
||||
setErrors({});
|
||||
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||
}
|
||||
|
||||
function removeItem(id: string) {
|
||||
setItems((prev) => prev.filter((p) => p.id !== id));
|
||||
function removeItem(id: number) {
|
||||
removeOfferta({ id });
|
||||
}
|
||||
|
||||
function toggleActive(id: string) {
|
||||
setItems((prev) =>
|
||||
prev.map((p) => (p.id === id ? { ...p, active: !p.active } : p)),
|
||||
);
|
||||
function toggleActive(id: number, currentStatus: boolean) {
|
||||
update({
|
||||
id: id,
|
||||
body: {
|
||||
is_active: !currentStatus,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const filtered = items.filter((it) => {
|
||||
const q = query.trim().toLowerCase();
|
||||
if (!q) return true;
|
||||
return (
|
||||
it.title.toLowerCase().includes(q) ||
|
||||
it.content.toLowerCase().includes(q) ||
|
||||
it.audience.toLowerCase().includes(q)
|
||||
);
|
||||
});
|
||||
|
||||
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">Ommaviy oferta</h1>
|
||||
<h1 className="text-3xl font-bold">{t("Ommaviy oferta")}</h1>
|
||||
</div>
|
||||
|
||||
<Card className="bg-gray-900">
|
||||
<CardHeader>
|
||||
<CardTitle>
|
||||
{editing ? "Tahrirish" : "Yangi oferta yaratish"}
|
||||
{editing ? t("Tahrirlash") : t("Yangi oferta yaratish")}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div>
|
||||
<label className="text-sm font-medium">Sarlavha</label>
|
||||
<label className="text-sm font-medium">{t("Sarlavha")}</label>
|
||||
<Input
|
||||
value={form.title || ""}
|
||||
onChange={(e) =>
|
||||
setForm((s) => ({ ...s, title: e.target.value }))
|
||||
}
|
||||
placeholder="Ommaviy oferta sarlavhasi"
|
||||
placeholder={t("Ommaviy oferta sarlavhasi")}
|
||||
className="mt-1"
|
||||
/>
|
||||
{errors.title && (
|
||||
<p className="text-destructive text-sm mt-1">{errors.title}</p>
|
||||
<p className="text-destructive text-sm mt-1">
|
||||
{t(errors.title)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="h-full w-[100%]">
|
||||
<label className="text-sm font-medium">Kontent</label>
|
||||
<div className="h-[280px] w-[100%]">
|
||||
<label className="text-sm font-medium">{t("Kontent")}</label>
|
||||
<div className="mt-1">
|
||||
<ReactQuill
|
||||
value={form.content || ""}
|
||||
@@ -194,54 +274,41 @@ export default function OmmaviyOfertaCRUD() {
|
||||
setForm((s) => ({ ...s, content: value }))
|
||||
}
|
||||
className="bg-gray-900 h-48"
|
||||
placeholder="Oferta matnini kiriting..."
|
||||
placeholder={t("Oferta matnini kiriting...")}
|
||||
/>
|
||||
</div>
|
||||
{errors.content && (
|
||||
<p className="text-destructive text-sm mt-1">
|
||||
{errors.content}
|
||||
<p className="text-destructive text-sm mt-12">
|
||||
{t(errors.content)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 mt-24">
|
||||
<div>
|
||||
<label className="text-sm font-medium">Kimlar uchun</label>
|
||||
<Select
|
||||
value={form.audience || "Barcha"}
|
||||
onValueChange={(value) =>
|
||||
setForm((s) => ({
|
||||
...s,
|
||||
audience: value as Offer["audience"],
|
||||
}))
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="mt-1 w-full !h-12">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="Barcha">Barcha</SelectItem>
|
||||
<SelectItem value="Jismoniy shaxslar">
|
||||
Jismoniy shaxslar uchun
|
||||
</SelectItem>
|
||||
<SelectItem value="Yuridik shaxslar">
|
||||
Yuridik shaxslar uchun
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="flex items-end">
|
||||
<label className="flex items-center gap-2 text-sm cursor-pointer">
|
||||
<Checkbox
|
||||
checked={!!form.active}
|
||||
onCheckedChange={(checked) =>
|
||||
setForm((s) => ({ ...s, active: checked ? true : false }))
|
||||
}
|
||||
/>
|
||||
<span>Faol</span>
|
||||
</label>
|
||||
</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">
|
||||
@@ -249,92 +316,91 @@ export default function OmmaviyOfertaCRUD() {
|
||||
onClick={handleCreateOrUpdate}
|
||||
className="bg-blue-600 hover:bg-blue-700"
|
||||
>
|
||||
{editing ? "Saqlash" : "Yaratish"}
|
||||
{editing ? t("Saqlash") : t("Qo'shish")}
|
||||
</Button>
|
||||
<Button variant="outline" onClick={resetForm}>
|
||||
Bekor qilish
|
||||
{t("Bekor qilish")}
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
|
||||
<Input
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
placeholder="Qidirish sarlavha, kontent yoki auditoriya bo'yicha..."
|
||||
className="max-w-sm"
|
||||
/>
|
||||
<div className="flex gap-4 text-sm text-muted-foreground">
|
||||
<span>Natija: {filtered.length}</span>
|
||||
<span>Barcha: {items.length}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
{filtered.length === 0 && (
|
||||
{allOfferta && allOfferta?.results.length === 0 && (
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<p className="text-muted-foreground text-center">
|
||||
Natija topilmadi.
|
||||
{t("Natija topilmadi.")}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{filtered.map((it) => (
|
||||
{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.audience} • {new Date(it.createdAt).toLocaleString()}
|
||||
{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)}
|
||||
onClick={() => startEdit(it.id)}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
>
|
||||
<Edit2 className="w-4 h-4 mr-1" />
|
||||
Tahrirlash
|
||||
{t("Tahrirlash")}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onClick={() => toggleActive(it.id)}
|
||||
variant={it.active ? "default" : "outline"}
|
||||
onClick={() => toggleActive(it.id, it.is_active)}
|
||||
variant={it.is_active ? "default" : "outline"}
|
||||
size="sm"
|
||||
className={
|
||||
it.active ? "bg-green-600 hover:bg-green-700" : ""
|
||||
it.is_active
|
||||
? "w-full bg-green-600 hover:bg-green-700"
|
||||
: "w-full"
|
||||
}
|
||||
>
|
||||
{it.active ? "Faol" : "Faol emas"}
|
||||
{it.is_active ? t("Faol") : t("Faol emas")}
|
||||
</Button>
|
||||
|
||||
<Dialog>
|
||||
<Dialog open={deleteOpen} onOpenChange={setDeleteOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="destructive" size="sm">
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
className="w-full"
|
||||
onClick={() => setDeleteOpen(true)}
|
||||
>
|
||||
<Trash2 className="w-4 h-4 mr-1" />
|
||||
O'chirish
|
||||
{t("O'chirish")}
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogTitle>O'chirish tasdiqlash</DialogTitle>
|
||||
<DialogTitle>{t("O'chirish tasdiqlash")}</DialogTitle>
|
||||
<DialogDescription>
|
||||
Haqiqatan ham bu ofertani o'chirmoqchimisiz? Bu amalni
|
||||
bekor qilib bo'lmaydi.
|
||||
{t(
|
||||
"Haqiqatan ham bu ofertani o'chirmoqchimisiz? Bu amalni bekor qilib bo'lmaydi.",
|
||||
)}
|
||||
</DialogDescription>
|
||||
<div className="flex gap-3 justify-end pt-4">
|
||||
<Button>Bekor qilish</Button>
|
||||
<Button onClick={() => setDeleteOpen(false)}>
|
||||
{t("Bekor qilish")}
|
||||
</Button>
|
||||
<Button
|
||||
variant={"destructive"}
|
||||
onClick={() => removeItem(it.id)}
|
||||
>
|
||||
O'chirish
|
||||
{t("O'chirish")}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
|
||||
Reference in New Issue
Block a user