booking
This commit is contained in:
@@ -44,6 +44,7 @@ import {
|
||||
useQueryClient,
|
||||
} from "@tanstack/react-query";
|
||||
import {
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
Loader2,
|
||||
Pencil,
|
||||
@@ -66,6 +67,7 @@ const faqForm = z.object({
|
||||
|
||||
const Faq = () => {
|
||||
const [activeTab, setActiveTab] = useState<string>("");
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const { t } = useTranslation();
|
||||
const [openModal, setOpenModal] = useState(false);
|
||||
const [editFaq, setEditFaq] = useState<number | null>(null);
|
||||
@@ -94,17 +96,24 @@ const Faq = () => {
|
||||
initialPageParam: 1,
|
||||
});
|
||||
|
||||
// Barcha kategoriyalarni birlashtirib olish
|
||||
useEffect(() => {
|
||||
setCurrentPage(1);
|
||||
}, [activeTab]);
|
||||
|
||||
const category =
|
||||
categoryData?.pages.flatMap((page) => page.data.data.results) ?? [];
|
||||
|
||||
const { data: faq } = useQuery({
|
||||
queryKey: ["all_faq", activeTab],
|
||||
queryKey: ["all_faq", activeTab, currentPage],
|
||||
queryFn: () => {
|
||||
return getAllFaq({ page: 1, page_size: 10, category: Number(activeTab) });
|
||||
return getAllFaq({
|
||||
page: currentPage,
|
||||
page_size: 10,
|
||||
category: Number(activeTab),
|
||||
});
|
||||
},
|
||||
select(data) {
|
||||
return data.data.data.results;
|
||||
return data.data.data;
|
||||
},
|
||||
enabled: !!activeTab,
|
||||
});
|
||||
@@ -323,7 +332,7 @@ const Faq = () => {
|
||||
{/* Tabs content */}
|
||||
{category.map((cat) => (
|
||||
<TabsContent key={cat.id} value={String(cat.id)}>
|
||||
{faq && faq?.length > 0 ? (
|
||||
{faq && faq?.results.length > 0 ? (
|
||||
<div className="border rounded-md overflow-hidden shadow-sm">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
@@ -337,7 +346,7 @@ const Faq = () => {
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{faq.map((faq, index) => (
|
||||
{faq.results.map((faq, index) => (
|
||||
<TableRow key={faq.id}>
|
||||
<TableCell className="text-center font-medium">
|
||||
{index + 1}
|
||||
@@ -380,6 +389,40 @@ const Faq = () => {
|
||||
))}
|
||||
</Tabs>
|
||||
|
||||
<div className="flex justify-end gap-2">
|
||||
<button
|
||||
disabled={currentPage === 1}
|
||||
onClick={() => setCurrentPage((p) => Math.max(p - 1, 1))}
|
||||
className="p-2 rounded-lg border border-slate-600 text-slate-300 hover:bg-slate-700/50 disabled:opacity-50 disabled:cursor-not-allowed transition-all hover:border-slate-500"
|
||||
>
|
||||
<ChevronLeft className="w-5 h-5" />
|
||||
</button>
|
||||
|
||||
{[...Array(faq?.total_pages)].map((_, i) => (
|
||||
<button
|
||||
key={i}
|
||||
onClick={() => setCurrentPage(i + 1)}
|
||||
className={`px-4 py-2 rounded-lg border transition-all font-medium ${
|
||||
currentPage === i + 1
|
||||
? "bg-gradient-to-r from-blue-600 to-cyan-600 border-blue-500 text-white shadow-lg shadow-cyan-500/50"
|
||||
: "border-slate-600 text-slate-300 hover:bg-slate-700/50 hover:border-slate-500"
|
||||
}`}
|
||||
>
|
||||
{i + 1}
|
||||
</button>
|
||||
))}
|
||||
|
||||
<button
|
||||
disabled={currentPage === faq?.total_pages}
|
||||
onClick={() =>
|
||||
setCurrentPage((p) => Math.min(p + 1, faq ? faq.total_pages : 0))
|
||||
}
|
||||
className="p-2 rounded-lg border border-slate-600 text-slate-300 hover:bg-slate-700/50 disabled:opacity-50 disabled:cursor-not-allowed transition-all hover:border-slate-500"
|
||||
>
|
||||
<ChevronRight className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<Dialog open={openModal} onOpenChange={setOpenModal}>
|
||||
<DialogContent className="sm:max-w-[500px] h-[80vh] overflow-y-scroll bg-gray-900">
|
||||
<DialogHeader>
|
||||
|
||||
@@ -9,6 +9,13 @@ import type { AxiosResponse } from "axios";
|
||||
const getAllOrder = async (params: {
|
||||
page: number;
|
||||
page_size: number;
|
||||
order_status:
|
||||
| "pending_payment"
|
||||
| "pending_confirmation"
|
||||
| "cancelled"
|
||||
| "confirmed"
|
||||
| "completed"
|
||||
| "";
|
||||
}): Promise<AxiosResponse<UserOrderData>> => {
|
||||
const res = await httpClient.get(USER_ORDERS, { params });
|
||||
return res;
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
Users,
|
||||
XCircle,
|
||||
} from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
@@ -119,12 +119,26 @@ export default function FinancePage() {
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [tab, setTab] = useState<"bookings" | "agencies">("bookings");
|
||||
const [filterStatus, setFilterStatus] = useState<
|
||||
"all" | "paid" | "pending" | "cancelled" | "refunded"
|
||||
>("all");
|
||||
| ""
|
||||
| "pending_payment"
|
||||
| "pending_confirmation"
|
||||
| "confirmed"
|
||||
| "completed"
|
||||
| "cancelled"
|
||||
>("");
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentPage(1);
|
||||
}, [filterStatus]);
|
||||
|
||||
const { data, isLoading, isError, refetch } = useQuery({
|
||||
queryKey: ["list_order_user", currentPage],
|
||||
queryFn: () => getAllOrder({ page: currentPage, page_size: 10 }),
|
||||
queryKey: ["list_order_user", currentPage, filterStatus],
|
||||
queryFn: () =>
|
||||
getAllOrder({
|
||||
page: currentPage,
|
||||
page_size: 10,
|
||||
order_status: filterStatus,
|
||||
}),
|
||||
});
|
||||
|
||||
const stats = [
|
||||
@@ -333,7 +347,14 @@ export default function FinancePage() {
|
||||
<>
|
||||
{/* Filter */}
|
||||
<div className="flex gap-2 mb-6 flex-wrap">
|
||||
{["all", "paid", "pending", "cancelled", "refunded"].map((s) => (
|
||||
{[
|
||||
"",
|
||||
"pending_payment",
|
||||
"pending_confirmation",
|
||||
"confirmed",
|
||||
"completed",
|
||||
"cancelled",
|
||||
].map((s) => (
|
||||
<button
|
||||
key={s}
|
||||
className={`px-4 py-2 rounded-lg transition-all ${
|
||||
@@ -344,23 +365,26 @@ export default function FinancePage() {
|
||||
onClick={() =>
|
||||
setFilterStatus(
|
||||
s as
|
||||
| "all"
|
||||
| "paid"
|
||||
| "pending"
|
||||
| "cancelled"
|
||||
| "refunded",
|
||||
| ""
|
||||
| "pending_payment"
|
||||
| "pending_confirmation"
|
||||
| "confirmed"
|
||||
| "completed"
|
||||
| "cancelled",
|
||||
)
|
||||
}
|
||||
>
|
||||
{s === "all"
|
||||
{s === ""
|
||||
? t("Barcha bandlovlar")
|
||||
: s === "paid"
|
||||
? t("To'langan")
|
||||
: s === "pending"
|
||||
? t("Kutilmoqda")
|
||||
: s === "cancelled"
|
||||
? t("Bekor qilindi")
|
||||
: t("Qaytarilgan")}
|
||||
: s === "pending_payment"
|
||||
? t("Pending Payment")
|
||||
: s === "pending_confirmation"
|
||||
? t("Pending Confirmation")
|
||||
: s === "confirmed"
|
||||
? t("Confirmed")
|
||||
: s === "completed"
|
||||
? t("Completed")
|
||||
: t("Cancelled")}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -9,8 +9,7 @@ export interface AllSeoData {
|
||||
total_pages: number;
|
||||
page_size: number;
|
||||
current_page: number;
|
||||
results: [
|
||||
{
|
||||
results: {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string;
|
||||
@@ -18,8 +17,7 @@ export interface AllSeoData {
|
||||
og_title: string;
|
||||
og_description: string;
|
||||
og_image: string;
|
||||
},
|
||||
];
|
||||
}[];
|
||||
};
|
||||
}
|
||||
|
||||
@@ -28,10 +26,20 @@ export interface DetailSeoData {
|
||||
data: {
|
||||
id: number;
|
||||
title: string;
|
||||
title_uz: string;
|
||||
title_ru: string;
|
||||
description: string;
|
||||
description_uz: string;
|
||||
description_ru: string;
|
||||
keywords: string;
|
||||
keywords_uz: string;
|
||||
keywords_ru: string;
|
||||
og_title: string;
|
||||
og_title_uz: string;
|
||||
og_title_ru: string;
|
||||
og_description: string;
|
||||
og_description_uz: string;
|
||||
og_description_ru: string;
|
||||
og_image: string;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -24,20 +24,30 @@ import { toast } from "sonner";
|
||||
|
||||
type SeoData = {
|
||||
title: string;
|
||||
title_ru: string;
|
||||
description: string;
|
||||
description_ru: string;
|
||||
keywords: string;
|
||||
keywords_ru: string;
|
||||
ogTitle: string;
|
||||
ogTitle_ru: string;
|
||||
ogDescription: string;
|
||||
ogDescription_ru: string;
|
||||
ogImage: File | null | string;
|
||||
};
|
||||
|
||||
export default function Seo() {
|
||||
const [formData, setFormData] = useState<SeoData>({
|
||||
title: "",
|
||||
title_ru: "",
|
||||
description: "",
|
||||
description_ru: "",
|
||||
keywords: "",
|
||||
keywords_ru: "",
|
||||
ogTitle: "",
|
||||
ogTitle_ru: "",
|
||||
ogDescription: "",
|
||||
ogDescription_ru: "",
|
||||
ogImage: null,
|
||||
});
|
||||
const { t } = useTranslation();
|
||||
@@ -52,6 +62,11 @@ export default function Seo() {
|
||||
});
|
||||
setFormData({
|
||||
description: "",
|
||||
description_ru: "",
|
||||
keywords_ru: "",
|
||||
ogDescription_ru: "",
|
||||
ogTitle_ru: "",
|
||||
title_ru: "",
|
||||
keywords: "",
|
||||
ogDescription: "",
|
||||
ogImage: null,
|
||||
@@ -80,6 +95,11 @@ export default function Seo() {
|
||||
setEdit(null);
|
||||
setFormData({
|
||||
description: "",
|
||||
description_ru: "",
|
||||
keywords_ru: "",
|
||||
ogDescription_ru: "",
|
||||
ogTitle_ru: "",
|
||||
title_ru: "",
|
||||
keywords: "",
|
||||
ogDescription: "",
|
||||
ogImage: null,
|
||||
@@ -104,6 +124,11 @@ export default function Seo() {
|
||||
setEdit(null);
|
||||
setFormData({
|
||||
description: "",
|
||||
description_ru: "",
|
||||
keywords_ru: "",
|
||||
ogDescription_ru: "",
|
||||
ogTitle_ru: "",
|
||||
title_ru: "",
|
||||
keywords: "",
|
||||
ogDescription: "",
|
||||
ogImage: null,
|
||||
@@ -170,12 +195,17 @@ export default function Seo() {
|
||||
useEffect(() => {
|
||||
if (detailSeo) {
|
||||
setFormData({
|
||||
description: detailSeo.description,
|
||||
keywords: detailSeo.keywords,
|
||||
ogDescription: detailSeo.og_description,
|
||||
description: detailSeo.description_uz,
|
||||
keywords: detailSeo.keywords_uz,
|
||||
ogDescription: detailSeo.og_description_uz,
|
||||
ogImage: detailSeo.og_image,
|
||||
ogTitle: detailSeo.og_title,
|
||||
title: detailSeo.title,
|
||||
ogTitle: detailSeo.og_title_uz,
|
||||
title: detailSeo.title_uz,
|
||||
description_ru: detailSeo.description_ru,
|
||||
keywords_ru: detailSeo.keywords_ru,
|
||||
ogDescription_ru: detailSeo.og_description_ru,
|
||||
ogTitle_ru: detailSeo.og_title_ru,
|
||||
title_ru: detailSeo.title_ru,
|
||||
});
|
||||
setImagePreview(detailSeo.og_image || null);
|
||||
}
|
||||
@@ -184,10 +214,15 @@ export default function Seo() {
|
||||
const handleSave = () => {
|
||||
const form = new FormData();
|
||||
form.append("title", formData.title);
|
||||
form.append("title_ru", formData.title_ru);
|
||||
form.append("description", formData.description);
|
||||
form.append("description_ru", formData.description_ru);
|
||||
form.append("keywords", formData.keywords);
|
||||
form.append("keywords_ru", formData.keywords_ru);
|
||||
form.append("og_title", formData.ogTitle);
|
||||
form.append("og_title_ru", formData.ogTitle_ru);
|
||||
form.append("og_description", formData.ogDescription);
|
||||
form.append("og_description_ru", formData.ogDescription_ru);
|
||||
|
||||
// faqat File bo‘lsa qo‘shamiz
|
||||
if (formData.ogImage instanceof File) {
|
||||
@@ -250,6 +285,29 @@ export default function Seo() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-white mb-2">
|
||||
<FileText className="inline w-4 h-4 mr-1" /> {t("Page Title")}{" "}
|
||||
(ru)
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="title_ru"
|
||||
value={formData.title_ru}
|
||||
onChange={handleChange}
|
||||
placeholder={t("Sahifa sarlavhasi (30–60 belgi)") + " ru"}
|
||||
className="w-full bg-slate-700 text-white px-4 py-2 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
<div className="flex items-center justify-between mt-2">
|
||||
{isValidTitle && (
|
||||
<CheckCircle className="w-5 h-5 text-green-400" />
|
||||
)}
|
||||
{getTitleLength() > 0 && !isValidTitle && (
|
||||
<AlertCircle className="w-5 h-5 text-yellow-400" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-white mb-2">
|
||||
@@ -275,6 +333,30 @@ export default function Seo() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-white mb-2">
|
||||
{t("Meta Description")} (ru)
|
||||
</label>
|
||||
<textarea
|
||||
name="description_ru"
|
||||
value={formData.description_ru}
|
||||
onChange={handleChange}
|
||||
placeholder={t("Sahifa tavsifi (120–160 belgi)") + " (ru)"}
|
||||
className="w-full bg-slate-700 text-white px-4 py-2 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 resize-none"
|
||||
/>
|
||||
<div className="flex items-center justify-between mt-2">
|
||||
<span className="text-sm text-slate-400">
|
||||
{getDescriptionLength()} / 160
|
||||
</span>
|
||||
{isValidDescription && (
|
||||
<CheckCircle className="w-5 h-5 text-green-400" />
|
||||
)}
|
||||
{getDescriptionLength() > 0 && !isValidDescription && (
|
||||
<AlertCircle className="w-5 h-5 text-yellow-400" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Keywords */}
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-white mb-2">
|
||||
@@ -293,6 +375,25 @@ export default function Seo() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-white mb-2">
|
||||
{t("Keywords")} (ru)
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="keywords_ru"
|
||||
value={formData.keywords_ru}
|
||||
onChange={handleChange}
|
||||
placeholder={
|
||||
t("Kalit so'zlar (vergul bilan ajratilgan)") + " (ru)"
|
||||
}
|
||||
className="w-full bg-slate-700 text-white px-4 py-2 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
<p className="text-xs text-slate-400 mt-1">
|
||||
{t("Masalan: Python, Web Development, Coding")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* OG Tags */}
|
||||
<div className="border-t border-slate-700 pt-6">
|
||||
<h3 className="text-sm font-semibold text-white mb-4">
|
||||
@@ -314,6 +415,20 @@ export default function Seo() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm text-slate-300 mb-2">
|
||||
{t("OG Title")} (ru)
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="ogTitle_ru"
|
||||
value={formData.ogTitle_ru}
|
||||
onChange={handleChange}
|
||||
placeholder={t("Ijtimoiy tarmoqdagi sarlavha") + " (ru)"}
|
||||
className="w-full bg-slate-700 text-white px-4 py-2 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm text-slate-300 mb-2">
|
||||
{t("OG Description")}
|
||||
@@ -327,6 +442,19 @@ export default function Seo() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm text-slate-300 mb-2">
|
||||
{t("OG Description")} (ru)
|
||||
</label>
|
||||
<textarea
|
||||
name="ogDescription_ru"
|
||||
value={formData.ogDescription_ru}
|
||||
onChange={handleChange}
|
||||
placeholder={t("Ijtimoiy tarmoqdagi tavsif") + " (ru)"}
|
||||
className="w-full bg-slate-700 text-white px-4 py-2 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 resize-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm text-slate-300 mb-2">
|
||||
<ImageIcon className="inline w-4 h-4 mr-1" />{" "}
|
||||
@@ -368,7 +496,7 @@ export default function Seo() {
|
||||
|
||||
<button
|
||||
onClick={handleSave}
|
||||
disabled={isPending}
|
||||
disabled={allSeo && allSeo.length !== 0}
|
||||
className="w-full bg-blue-600 hover:bg-blue-700 text-white font-semibold py-3 rounded-lg transition-colors disabled:opacity-50"
|
||||
>
|
||||
{isPending || editPending ? (
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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("Qo‘llanma")}
|
||||
</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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -19,13 +19,22 @@ import {
|
||||
DialogTitle,
|
||||
} from "@/shared/ui/dialog";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { AlertTriangle, Loader2, Phone, Trash2, User } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import {
|
||||
AlertTriangle,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
Loader2,
|
||||
Phone,
|
||||
Trash2,
|
||||
User,
|
||||
} from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { toast } from "sonner";
|
||||
|
||||
const SupportTours = () => {
|
||||
const { t } = useTranslation();
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [selected, setSelected] = useState<GetSupportUserRes | null>(null);
|
||||
const [selectedToDelete, setSelectedToDelete] =
|
||||
useState<GetSupportUserRes | null>(null);
|
||||
@@ -34,10 +43,18 @@ const SupportTours = () => {
|
||||
"" | "pending" | "done" | "failed"
|
||||
>("");
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentPage(1);
|
||||
}, [filterStatus]);
|
||||
|
||||
const { data, isLoading, isError, refetch } = useQuery({
|
||||
queryKey: ["support_user", filterStatus],
|
||||
queryKey: ["support_user", filterStatus, currentPage],
|
||||
queryFn: () =>
|
||||
getSupportUser({ page: 1, page_size: 99, status: filterStatus }),
|
||||
getSupportUser({
|
||||
page: currentPage,
|
||||
page_size: 10,
|
||||
status: filterStatus,
|
||||
}),
|
||||
});
|
||||
|
||||
const { data: agency } = useQuery({
|
||||
@@ -210,6 +227,45 @@ const SupportTours = () => {
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="flex justify-end gap-2">
|
||||
<button
|
||||
disabled={currentPage === 1}
|
||||
onClick={() => setCurrentPage((p) => Math.max(p - 1, 1))}
|
||||
className="p-2 rounded-lg border border-slate-600 text-slate-300 hover:bg-slate-700/50 disabled:opacity-50 disabled:cursor-not-allowed transition-all hover:border-slate-500"
|
||||
>
|
||||
<ChevronLeft className="w-5 h-5" />
|
||||
</button>
|
||||
|
||||
{[...Array(data?.data.data?.total_pages)].map((_, i) => (
|
||||
<button
|
||||
key={i}
|
||||
onClick={() => setCurrentPage(i + 1)}
|
||||
className={`px-4 py-2 rounded-lg border transition-all font-medium ${
|
||||
currentPage === i + 1
|
||||
? "bg-gradient-to-r from-blue-600 to-cyan-600 border-blue-500 text-white shadow-lg shadow-cyan-500/50"
|
||||
: "border-slate-600 text-slate-300 hover:bg-slate-700/50 hover:border-slate-500"
|
||||
}`}
|
||||
>
|
||||
{i + 1}
|
||||
</button>
|
||||
))}
|
||||
|
||||
<button
|
||||
disabled={currentPage === data?.data.data?.total_pages}
|
||||
onClick={() =>
|
||||
setCurrentPage((p) =>
|
||||
Math.min(
|
||||
p + 1,
|
||||
data?.data.data ? data?.data.data.total_pages : 0,
|
||||
),
|
||||
)
|
||||
}
|
||||
className="p-2 rounded-lg border border-slate-600 text-slate-300 hover:bg-slate-700/50 disabled:opacity-50 disabled:cursor-not-allowed transition-all hover:border-slate-500"
|
||||
>
|
||||
<ChevronRight className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Detail Modal */}
|
||||
<Dialog open={!!selected} onOpenChange={() => setSelected(null)}>
|
||||
<DialogContent className="bg-gray-800 border border-gray-700 text-gray-100 sm:max-w-lg">
|
||||
|
||||
@@ -12,6 +12,8 @@ const createSiteSetting = async (body: {
|
||||
longitude: string;
|
||||
telegram: string;
|
||||
instagram: string;
|
||||
telegram_chat: string;
|
||||
linkedin: string;
|
||||
facebook: string;
|
||||
twitter: string;
|
||||
email: string;
|
||||
@@ -32,6 +34,8 @@ const updateSiteSetting = async ({
|
||||
latitude: string;
|
||||
longitude: string;
|
||||
telegram: string;
|
||||
telegram_chat: string;
|
||||
linkedin: string;
|
||||
instagram: string;
|
||||
facebook: string;
|
||||
twitter: string;
|
||||
|
||||
@@ -15,6 +15,8 @@ export interface GetSiteSetting {
|
||||
latitude: string;
|
||||
longitude: string;
|
||||
telegram: string;
|
||||
telegram_chat: string;
|
||||
linkedin: string;
|
||||
instagram: string;
|
||||
facebook: string;
|
||||
twitter: string;
|
||||
@@ -33,6 +35,8 @@ export interface GetDetailSiteSetting {
|
||||
latitude: string;
|
||||
longitude: string;
|
||||
telegram: string;
|
||||
telegram_chat: string;
|
||||
linkedin: string;
|
||||
instagram: string;
|
||||
facebook: string;
|
||||
twitter: string;
|
||||
|
||||
@@ -33,6 +33,7 @@ type MapClickEvent = {
|
||||
type ContactInfo = {
|
||||
telegram: string;
|
||||
instagram: string;
|
||||
telegram_chat: string;
|
||||
facebook: string;
|
||||
twiter: string;
|
||||
linkedin: string;
|
||||
@@ -44,7 +45,7 @@ type ContactInfo = {
|
||||
|
||||
async function getAddressFromCoords(lat: number, lon: number) {
|
||||
const response = await fetch(
|
||||
`https://nominatim.openstreetmap.org/reverse?lat=${lat}&lon=${lon}&format=json`,
|
||||
`https://nominatim.openstreetmap.org/reverse?lat=${lat}&lon=${lon}&format=json&accept-language=uz`,
|
||||
);
|
||||
const data = await response.json();
|
||||
return data.display_name || "";
|
||||
@@ -57,6 +58,7 @@ export default function ContactSettings() {
|
||||
const [editing, setEditing] = useState<number | null>(null);
|
||||
const [form, setForm] = useState<ContactInfo>({
|
||||
telegram: "",
|
||||
telegram_chat: "",
|
||||
instagram: "",
|
||||
facebook: "",
|
||||
twiter: "",
|
||||
@@ -80,12 +82,15 @@ export default function ContactSettings() {
|
||||
facebook: string;
|
||||
twitter: string;
|
||||
email: string;
|
||||
telegram_chat: string;
|
||||
linkedin: string;
|
||||
main_phone: string;
|
||||
other_phone: string;
|
||||
}) => createSiteSetting(body),
|
||||
onSuccess: () => {
|
||||
setForm({
|
||||
telegram: "",
|
||||
telegram_chat: "",
|
||||
instagram: "",
|
||||
facebook: "",
|
||||
twiter: "",
|
||||
@@ -121,6 +126,8 @@ export default function ContactSettings() {
|
||||
telegram: string;
|
||||
instagram: string;
|
||||
facebook: string;
|
||||
telegram_chat: string;
|
||||
linkedin: string;
|
||||
twitter: string;
|
||||
email: string;
|
||||
main_phone: string;
|
||||
@@ -130,6 +137,7 @@ export default function ContactSettings() {
|
||||
onSuccess: () => {
|
||||
setForm({
|
||||
telegram: "",
|
||||
telegram_chat: "",
|
||||
instagram: "",
|
||||
facebook: "",
|
||||
twiter: "",
|
||||
@@ -157,6 +165,7 @@ export default function ContactSettings() {
|
||||
onSuccess: () => {
|
||||
setForm({
|
||||
telegram: "",
|
||||
telegram_chat: "",
|
||||
instagram: "",
|
||||
facebook: "",
|
||||
twiter: "",
|
||||
@@ -207,10 +216,11 @@ export default function ContactSettings() {
|
||||
email: detail.email,
|
||||
facebook: detail.facebook,
|
||||
instagram: detail.instagram,
|
||||
linkedin: detail.twitter,
|
||||
linkedin: detail.linkedin,
|
||||
phonePrimary: detail.main_phone,
|
||||
phoneSecondary: detail.other_phone,
|
||||
telegram: detail.telegram,
|
||||
telegram_chat: detail.telegram_chat,
|
||||
twiter: detail.twitter,
|
||||
});
|
||||
setCoords({
|
||||
@@ -237,14 +247,41 @@ export default function ContactSettings() {
|
||||
};
|
||||
|
||||
const saveContact = () => {
|
||||
const shortLat = Number(coords.latitude.toFixed(6)); // 6ta raqamgacha
|
||||
const shortLat = Number(coords.latitude.toFixed(6));
|
||||
const shortLon = Number(coords.longitude.toFixed(6));
|
||||
|
||||
// Majburiy maydonlar tekshiruvi
|
||||
if (!form.address.trim()) {
|
||||
toast.error(t("Manzilni kiriting!"), {
|
||||
position: "top-center",
|
||||
richColors: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!form.phonePrimary.trim() || form.phonePrimary.length < 9) {
|
||||
toast.error(t("Asosiy telefon raqamini to‘g‘ri kiriting!"), {
|
||||
position: "top-center",
|
||||
richColors: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!coords.latitude || !coords.longitude) {
|
||||
toast.error(t("Xaritadan joy tanlang! (latitude/longitude majburiy)"), {
|
||||
position: "top-center",
|
||||
richColors: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (editing) {
|
||||
update({
|
||||
body: {
|
||||
address: form.address,
|
||||
email: form.email,
|
||||
facebook: form.facebook,
|
||||
linkedin: form.linkedin,
|
||||
telegram_chat: form.telegram_chat,
|
||||
instagram: form.instagram,
|
||||
latitude: String(shortLat),
|
||||
longitude: String(shortLon),
|
||||
@@ -259,6 +296,8 @@ export default function ContactSettings() {
|
||||
create({
|
||||
address: form.address,
|
||||
email: form.email,
|
||||
linkedin: form.linkedin,
|
||||
telegram_chat: form.telegram_chat,
|
||||
facebook: form.facebook,
|
||||
instagram: form.instagram,
|
||||
latitude: String(shortLat),
|
||||
@@ -277,6 +316,7 @@ export default function ContactSettings() {
|
||||
instagram: "",
|
||||
facebook: "",
|
||||
twiter: "",
|
||||
telegram_chat: "",
|
||||
linkedin: "",
|
||||
address: "",
|
||||
email: "",
|
||||
@@ -370,12 +410,26 @@ export default function ContactSettings() {
|
||||
</div>
|
||||
<div className="text-sm">{e.telegram || "—"}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
Telegram Chat
|
||||
</div>
|
||||
<div className="text-sm">
|
||||
{e.telegram_chat || "—"}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
Instagram
|
||||
</div>
|
||||
<div className="text-sm">{e.instagram || "—"}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
Instagram
|
||||
</div>
|
||||
<div className="text-sm">{e.linkedin || "—"}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
Facebook
|
||||
@@ -438,6 +492,7 @@ export default function ContactSettings() {
|
||||
zoom: 13,
|
||||
}}
|
||||
width="100%"
|
||||
la
|
||||
height="400px"
|
||||
onClick={handleMapClick}
|
||||
>
|
||||
@@ -460,6 +515,13 @@ export default function ContactSettings() {
|
||||
onChange={(e) => handleChange("telegram", e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label>Telegram Chat</Label>
|
||||
<Input
|
||||
value={form.telegram_chat || ""}
|
||||
onChange={(e) => handleChange("telegram_chat", e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label>Instagram</Label>
|
||||
<Input
|
||||
|
||||
Reference in New Issue
Block a user