From 64f8467f41afad194c91404732fbb5889e1a2938 Mon Sep 17 00:00:00 2001 From: Samandar Turgunboyev Date: Tue, 11 Nov 2025 16:05:22 +0500 Subject: [PATCH] bug fix loading --- src/pages/agencies/ui/Agencies.tsx | 8 +- src/pages/agencies/ui/AgencyDetail.tsx | 29 ++ src/pages/agencies/ui/AgencyUsersSection.tsx | 327 +++++++++++------- src/pages/agencies/ui/EditAgecy.tsx | 6 +- src/pages/news/ui/NewsCategory.tsx | 10 +- src/pages/news/ui/StepTwo.tsx | 12 +- src/pages/tours/ui/StepOne.tsx | 6 +- src/pages/tours/ui/Tours.tsx | 12 +- src/pages/users/ui/Edit.tsx | 12 +- .../config/i18n/locales/ru/translation.json | 2 + .../config/i18n/locales/uz/translation.json | 2 + 11 files changed, 271 insertions(+), 155 deletions(-) diff --git a/src/pages/agencies/ui/Agencies.tsx b/src/pages/agencies/ui/Agencies.tsx index 97133fa..d4cae5e 100644 --- a/src/pages/agencies/ui/Agencies.tsx +++ b/src/pages/agencies/ui/Agencies.tsx @@ -41,7 +41,7 @@ export default function TourAgenciesPage() { queryFn: () => getAllAgency({ page: currentPage, page_size: itemsPerPage }), }); - const { mutate } = useMutation({ + const { mutate, isPending } = useMutation({ mutationFn: ({ id }: { id: number }) => { return deleteAgency({ id }); }, @@ -293,7 +293,11 @@ export default function TourAgenciesPage() { onClick={() => handleDelete(agency.id)} className="bg-red-600 hover:bg-red-700 text-white" > - {t("O'chirish")} + {isPending ? ( + + ) : ( + t("O'chirish") + )} diff --git a/src/pages/agencies/ui/AgencyDetail.tsx b/src/pages/agencies/ui/AgencyDetail.tsx index 75605f4..fc5393b 100644 --- a/src/pages/agencies/ui/AgencyDetail.tsx +++ b/src/pages/agencies/ui/AgencyDetail.tsx @@ -8,6 +8,7 @@ import { updateAgencyStatus, } from "@/pages/agencies/lib/api"; import { AgencyUsersSection } from "@/pages/agencies/ui/AgencyUsersSection"; +import { createTourAdmin } from "@/pages/support/lib/api"; import formatPhone from "@/shared/lib/formatPhone"; import formatPrice from "@/shared/lib/formatPrice"; import { Badge } from "@/shared/ui/badge"; @@ -50,6 +51,7 @@ export default function AgencyDetailPage() { const router = useNavigate(); const { t } = useTranslation(); const [edit, setEdit] = useState(false); + const [added, setAdded] = useState(false); const queryClient = useQueryClient(); const [openUser, setOpenUser] = useState(false); const [user, setUser] = useState<{ @@ -105,6 +107,22 @@ export default function AgencyDetailPage() { }, }); + const createAdmin = useMutation({ + mutationFn: (id: number) => createTourAdmin(id), + onSuccess: (res) => { + queryClient.refetchQueries({ queryKey: ["agency_user"] }); + setOpenUser(true); + setUser(res.data); + setAdded(false); + }, + onError: () => { + toast.error(t("Xatolik yuz berdi"), { + richColors: true, + position: "top-center", + }); + }, + }); + const deleteUserMutation = useMutation({ mutationFn: (userId: number) => agencyUserDelete(userId), onSuccess: () => { @@ -126,6 +144,10 @@ export default function AgencyDetailPage() { updateUserMutation.mutate(user); }; + const handleAddeUser = () => { + createAdmin.mutate(Number(params.id!)); + }; + const handleDeleteUser = (userId: number) => { deleteUserMutation.mutate(userId); }; @@ -409,8 +431,14 @@ export default function AgencyDetailPage() { {agency?.data.data && (
)} diff --git a/src/pages/agencies/ui/AgencyUsersSection.tsx b/src/pages/agencies/ui/AgencyUsersSection.tsx index 8bddbb8..4e0e237 100644 --- a/src/pages/agencies/ui/AgencyUsersSection.tsx +++ b/src/pages/agencies/ui/AgencyUsersSection.tsx @@ -13,7 +13,15 @@ import { DialogTitle, DialogTrigger, } from "@/shared/ui/dialog"; -import { Pencil, Phone, Shield, Trash2, User } from "lucide-react"; +import { + Loader2, + Pencil, + Phone, + Shield, + Trash2, + User, + UserPlus, +} from "lucide-react"; import { type Dispatch, type SetStateAction } from "react"; import { useTranslation } from "react-i18next"; @@ -27,22 +35,36 @@ interface AgencyUser { interface AgencyUsersProps { users: AgencyUser[]; + agencyId: number; onEdit: (userId: number) => void; + handleAddeUser: () => void; onDelete: (userId: number) => void; isLoading?: boolean; + isPending: boolean; + createAdminPending: boolean; edit: boolean; + added: boolean; + deletePending: boolean; setEdit: Dispatch>; + setAdded: Dispatch>; } export function AgencyUsersSection({ users, onEdit, + setAdded, + added, edit, setEdit, + handleAddeUser, + createAdminPending, onDelete, isLoading = false, + deletePending, + isPending = false, }: AgencyUsersProps) { const { t } = useTranslation(); + const getRoleBadge = (role: string) => { const roleColors: Record = { admin: "bg-purple-500/20 text-purple-300 border-purple-500/40", @@ -75,160 +97,197 @@ export function AgencyUsersSection({ ); } - if (!users || users.length === 0) { - return ( - - + return ( + + +
{t("Agentlik foydalanuvchilari")} - - +

+ {t("Agentlik bilan bog'langan foydalanuvchilar ro'yxati")} +

+
+ + + + + + + + {t("Foydalanuvchi qo'shish")} + + {t("Siz agentlikga yangi foydalanuvchi qo'shmoqchimisiz")} + + + + + + + + + +
+ + + {users.length === 0 ? (
{t("Hozircha foydalanuvchilar yo'q")}
-
-
- ); - } - - return ( - - - - - {t("Agentlik foydalanuvchilari")} - -

- {t("Agentlik bilan bog'langan foydalanuvchilar ro'yxati")} -

-
- -
- {users.map((user) => ( -
-
-
-
- -
- -
-
-

- {user.first_name} {user.last_name} -

- {getRoleBadge(user.role)} + ) : ( +
+ {users.map((user) => ( +
+
+
+
+
-
-
- - {formatPhone(user.phone)} +
+
+

+ {user.first_name} {user.last_name} +

+ {getRoleBadge(user.role)}
-
- - ID: {user.id} + +
+
+ + {formatPhone(user.phone)} +
+
+ + ID: {user.id} +
-
- {/* Actions */} -
- {/* Edit Confirm Dialog */} - - - - - - - - {t("Foydalanuvchini tahrirlash")} - - - {t("Haqiqatan ham")}{" "} - - {user.first_name} {user.last_name} - {" "} - {t("ni tahrirlamoqchimisiz?")} - - +
+ {/* ✏️ Edit */} + + + + + + + + {t("Foydalanuvchini tahrirlash")} + + + {t("Haqiqatan ham")}{" "} + + {user.first_name} {user.last_name} + {" "} + {t("ni tahrirlamoqchimisiz?")} + + - - - - - - + + + + + +
- {/* Delete Confirm Dialog */} - - - - - - - - {t("Foydalanuvchini o'chirish")} - - - {t("Haqiqatan ham")}{" "} - - {user.first_name} {user.last_name} - {" "} - {t( - "ni o'chirmoqchimisiz? Bu amalni qaytarib bo'lmaydi.", - )} - - - - + {/* 🗑 Delete */} + + - - - + + + + + {t("Foydalanuvchini o'chirish")} + + + {t("Haqiqatan ham")}{" "} + + {user.first_name} {user.last_name} + {" "} + {t( + "ni o'chirmoqchimisiz? Bu amalni qaytarib bo'lmaydi.", + )} + + + + + + + + +
-
- ))} -
+ ))} +
+ )} ); diff --git a/src/pages/agencies/ui/EditAgecy.tsx b/src/pages/agencies/ui/EditAgecy.tsx index 9dabf41..ac78fe4 100644 --- a/src/pages/agencies/ui/EditAgecy.tsx +++ b/src/pages/agencies/ui/EditAgecy.tsx @@ -40,7 +40,7 @@ const formSchema = z.object({ addres: z.string().min(1, "Manzil kiritish shart"), email: z.string().email("Email noto‘g‘ri"), phone: z.string().min(3, "Telefon raqami noto‘g‘ri"), - web_site: z.string().url("URL noto‘g‘ri"), + web_site: z.string().min(1, "URL noto‘g‘ri"), }); type FormData = z.infer; @@ -63,12 +63,12 @@ const EditAgency = () => { }); const queryClient = useQueryClient(); - const { data, isPending } = useQuery({ + const { data } = useQuery({ queryKey: ["detail_agency", params.id], queryFn: () => getDetailAgency({ id: Number(params.id) }), }); - const { mutate } = useMutation({ + const { mutate, isPending } = useMutation({ mutationFn: (body: { status: "pending" | "approved" | "cancelled"; custom_id?: string; diff --git a/src/pages/news/ui/NewsCategory.tsx b/src/pages/news/ui/NewsCategory.tsx index 44b6267..f42fbd6 100644 --- a/src/pages/news/ui/NewsCategory.tsx +++ b/src/pages/news/ui/NewsCategory.tsx @@ -95,7 +95,7 @@ const NewsCategory = () => { setIsDialogOpen(true); }; - const { mutate: added } = useMutation({ + const { mutate: added, isPending } = useMutation({ mutationFn: (body: { name: string; name_ru: string }) => addNewsCategory(body), onSuccess: () => { @@ -111,7 +111,7 @@ const NewsCategory = () => { }, }); - const { mutate: edit } = useMutation({ + const { mutate: edit, isPending: editPending } = useMutation({ mutationFn: ({ body, id, @@ -349,7 +349,11 @@ const NewsCategory = () => { type="submit" className="bg-blue-600 px-5 py-5 hover:bg-blue-700 text-white mt-4 cursor-pointer" > - {t("Saqlash")} + {isPending || editPending ? ( + + ) : ( + t("Saqlash") + )}
diff --git a/src/pages/news/ui/StepTwo.tsx b/src/pages/news/ui/StepTwo.tsx index 959a4b9..695714c 100644 --- a/src/pages/news/ui/StepTwo.tsx +++ b/src/pages/news/ui/StepTwo.tsx @@ -16,7 +16,7 @@ import { Label } from "@/shared/ui/label"; import { Textarea } from "@/shared/ui/textarea"; import { zodResolver } from "@hookform/resolvers/zod"; import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { ImagePlus, PlusCircle, Trash2 } from "lucide-react"; +import { ImagePlus, Loader2, PlusCircle, Trash2 } from "lucide-react"; import { useEffect, useRef } from "react"; import { useFieldArray, useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; @@ -134,7 +134,7 @@ const StepTwo = ({ if (file) form.setValue(`sections.${index}.image`, file); }; - const { mutate: added } = useMutation({ + const { mutate: added, isPending } = useMutation({ mutationFn: (body: FormData) => addNews(body), onSuccess: () => { queryClient.refetchQueries({ queryKey: ["all_news"] }); @@ -150,7 +150,7 @@ const StepTwo = ({ }, }); - const { mutate: update } = useMutation({ + const { mutate: update, isPending: updatePending } = useMutation({ mutationFn: ({ body, id }: { id: number; body: FormData }) => updateNews({ id, body }), onSuccess: () => { @@ -404,7 +404,11 @@ const StepTwo = ({ type="submit" className="mt-6 px-8 py-3 bg-blue-600 text-white rounded-md hover:bg-blue-700 cursor-pointer" > - {t("Saqlash")} + {isPending || updatePending ? ( + + ) : ( + t("Saqlash") + )}
diff --git a/src/pages/tours/ui/StepOne.tsx b/src/pages/tours/ui/StepOne.tsx index 68367a0..0c0bb68 100644 --- a/src/pages/tours/ui/StepOne.tsx +++ b/src/pages/tours/ui/StepOne.tsx @@ -35,7 +35,7 @@ import { RadioGroup, RadioGroupItem } from "@/shared/ui/radio-group"; import { Textarea } from "@/shared/ui/textarea"; import { zodResolver } from "@hookform/resolvers/zod"; import { useMutation, useQuery } from "@tanstack/react-query"; -import { ChevronDownIcon, SquareCheckBig, XIcon } from "lucide-react"; +import { ChevronDownIcon, Loader2, SquareCheckBig, XIcon } from "lucide-react"; import { useEffect, useState, type Dispatch, type SetStateAction } from "react"; import { useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; @@ -231,7 +231,7 @@ const StepOne = ({ const selectedDate = watch("departureDateTime.date"); const selectedDateTravel = watch("travelDateTime.date"); - const { mutate: create } = useMutation({ + const { mutate: create, isPending } = useMutation({ mutationFn: (body: FormData) => { return createTours({ body }); }, @@ -2204,7 +2204,7 @@ const StepOne = ({ type="submit" className="mt-6 px-8 py-3 bg-blue-600 text-white rounded-md hover:bg-blue-600 cursor-pointer" > - {t("Saqlash")} + {isPending ? : t("Saqlash")}
diff --git a/src/pages/tours/ui/Tours.tsx b/src/pages/tours/ui/Tours.tsx index 9110971..e565c50 100644 --- a/src/pages/tours/ui/Tours.tsx +++ b/src/pages/tours/ui/Tours.tsx @@ -68,7 +68,7 @@ const Tours = ({ user }: { user: Role }) => { getAllTours({ page: 1, page_size: 10, featured_tickets: true }), }); - const { mutate } = useMutation({ + const { mutate, isPending } = useMutation({ mutationFn: (id: number) => deleteTours({ id }), onSuccess: () => { queryClient.refetchQueries({ queryKey: ["all_tours"] }); @@ -270,8 +270,14 @@ const Tours = ({ user }: { user: Role }) => { variant="destructive" onClick={() => confirmDelete(deleteId!)} > - - {t("O'chirish")} + {isPending ? ( + + ) : ( + <> + + {t("O'chirish")} + + )} diff --git a/src/pages/users/ui/Edit.tsx b/src/pages/users/ui/Edit.tsx index 9a5e39c..6ff4884 100644 --- a/src/pages/users/ui/Edit.tsx +++ b/src/pages/users/ui/Edit.tsx @@ -19,7 +19,7 @@ import { FormMessage, } from "@/shared/ui/form"; import { Input } from "@/shared/ui/input"; -import { ArrowLeft, Mail, Phone, Save, User } from "lucide-react"; +import { ArrowLeft, Loader2, Mail, Phone, Save, User } from "lucide-react"; import { useEffect } from "react"; import { useTranslation } from "react-i18next"; import { useNavigate, useParams } from "react-router-dom"; @@ -250,8 +250,14 @@ export default function EditUser() { disabled={isPending} className="flex-1 h-11 rounded-lg bg-gradient-to-r from-emerald-600 to-teal-700 hover:from-emerald-700 hover:to-teal-800 text-white font-medium" > - - {t("Yangilash")} + {isPending ? ( + + ) : ( + <> + + {t("Yangilash")} + + )}
diff --git a/src/shared/config/i18n/locales/ru/translation.json b/src/shared/config/i18n/locales/ru/translation.json index 66a8390..98c9119 100644 --- a/src/shared/config/i18n/locales/ru/translation.json +++ b/src/shared/config/i18n/locales/ru/translation.json @@ -8,6 +8,8 @@ "Foydalanuvchilar": "Пользователи", "Tur firmalar": "Турфирмы", "Xodimlar": "Сотрудники", + "Siz agentlikga yangi foydalanuvchi qo'shmoqchimisiz": "Вы хотите добавить нового пользователя в агентство", + "Foydalanuvchi qo'shish": "Добавить пользователя", "Byudjet": "Бюджет", "Turlar": "Туры", "Tur sozlamalari": "Настройки туров", diff --git a/src/shared/config/i18n/locales/uz/translation.json b/src/shared/config/i18n/locales/uz/translation.json index 305c999..303186c 100644 --- a/src/shared/config/i18n/locales/uz/translation.json +++ b/src/shared/config/i18n/locales/uz/translation.json @@ -10,6 +10,8 @@ "Xodimlar": "Xodimlar", "Byudjet": "Byudjet", "Turlar": "Turlar", + "Siz agentlikga yangi foydalanuvchi qo'shmoqchimisiz": "Siz agentlikga yangi foydalanuvchi qo'shmoqchimisiz", + "Foydalanuvchi qo'shish": "Foydalanuvchi qo'shish", "Tur sozlamalari": "Tur sozlamalari", "Bronlar": "Bronlar", "Yangiliklar": "Yangiliklar",