bug fix loading
This commit is contained in:
@@ -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 ? (
|
||||
<Loader2 className="animate-spin" />
|
||||
) : (
|
||||
t("O'chirish")
|
||||
)}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
@@ -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<boolean>(false);
|
||||
const [added, setAdded] = useState<boolean>(false);
|
||||
const queryClient = useQueryClient();
|
||||
const [openUser, setOpenUser] = useState<boolean>(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 && (
|
||||
<div className="mb-8">
|
||||
<AgencyUsersSection
|
||||
agencyId={Number(params.id)}
|
||||
added={added}
|
||||
setAdded={setAdded}
|
||||
edit={edit}
|
||||
handleAddeUser={handleAddeUser}
|
||||
setEdit={setEdit}
|
||||
isPending={updateUserMutation.isPending}
|
||||
createAdminPending={createAdmin.isPending}
|
||||
users={
|
||||
Array.isArray(agency?.data.data)
|
||||
? agency?.data.data
|
||||
@@ -419,6 +447,7 @@ export default function AgencyDetailPage() {
|
||||
onEdit={handleEditUser}
|
||||
onDelete={handleDeleteUser}
|
||||
isLoading={isLoadingUsers}
|
||||
deletePending={deleteUserMutation.isPending}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -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<SetStateAction<boolean>>;
|
||||
setAdded: Dispatch<SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
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<string, string> = {
|
||||
admin: "bg-purple-500/20 text-purple-300 border-purple-500/40",
|
||||
@@ -75,27 +97,10 @@ export function AgencyUsersSection({
|
||||
);
|
||||
}
|
||||
|
||||
if (!users || users.length === 0) {
|
||||
return (
|
||||
<Card className="border border-gray-700 shadow-lg bg-gray-800">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-2xl text-white flex items-center gap-2">
|
||||
<User className="w-6 h-6" />
|
||||
{t("Agentlik foydalanuvchilari")}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-center text-gray-400 py-8">
|
||||
{t("Hozircha foydalanuvchilar yo'q")}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className="border border-gray-700 shadow-lg bg-gray-800">
|
||||
<CardHeader>
|
||||
<CardHeader className="flex justify-between items-center">
|
||||
<div>
|
||||
<CardTitle className="text-2xl text-white flex items-center gap-2">
|
||||
<User className="w-6 h-6" />
|
||||
{t("Agentlik foydalanuvchilari")}
|
||||
@@ -103,8 +108,54 @@ export function AgencyUsersSection({
|
||||
<p className="text-gray-400">
|
||||
{t("Agentlik bilan bog'langan foydalanuvchilar ro'yxati")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Dialog open={added} onOpenChange={setAdded}>
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="border-gray-600 bg-gray-700 hover:bg-gray-600 text-blue-400 hover:text-blue-300"
|
||||
>
|
||||
<UserPlus className="w-4 h-4" onClick={() => setAdded(true)} />
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="bg-gray-800 border-gray-700 text-white">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t("Foydalanuvchi qo'shish")}</DialogTitle>
|
||||
<DialogDescription className="text-gray-400">
|
||||
{t("Siz agentlikga yangi foydalanuvchi qo'shmoqchimisiz")}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogFooter>
|
||||
<Button
|
||||
className="border-gray-600 bg-gray-700 hover:bg-gray-600 text-white"
|
||||
onClick={() => setAdded(false)}
|
||||
>
|
||||
{t("Bekor qilish")}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => handleAddeUser()}
|
||||
className="bg-blue-600 hover:bg-blue-700 text-white"
|
||||
>
|
||||
{createAdminPending ? (
|
||||
<Loader2 className="animate-spin" />
|
||||
) : (
|
||||
t("Qo'shish")
|
||||
)}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent>
|
||||
{users.length === 0 ? (
|
||||
<div className="text-center text-gray-400 py-8">
|
||||
{t("Hozircha foydalanuvchilar yo'q")}
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{users.map((user) => (
|
||||
<div
|
||||
@@ -138,9 +189,8 @@ export function AgencyUsersSection({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex items-center gap-2">
|
||||
{/* Edit Confirm Dialog */}
|
||||
{/* ✏️ Edit */}
|
||||
<Dialog open={edit} onOpenChange={setEdit}>
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
@@ -171,7 +221,7 @@ export function AgencyUsersSection({
|
||||
<DialogFooter>
|
||||
<Button
|
||||
className="border-gray-600 bg-gray-700 hover:bg-gray-600 text-white"
|
||||
onClick={() => setEdit(true)}
|
||||
onClick={() => setEdit(false)}
|
||||
>
|
||||
{t("Bekor qilish")}
|
||||
</Button>
|
||||
@@ -179,13 +229,17 @@ export function AgencyUsersSection({
|
||||
onClick={() => onEdit(user.id)}
|
||||
className="bg-blue-600 hover:bg-blue-700 text-white"
|
||||
>
|
||||
{t("Tahrirlash")}
|
||||
{isPending ? (
|
||||
<Loader2 className="animate-spin" />
|
||||
) : (
|
||||
t("Tahrirlash")
|
||||
)}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* Delete Confirm Dialog */}
|
||||
{/* 🗑 Delete */}
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
@@ -219,7 +273,11 @@ export function AgencyUsersSection({
|
||||
onClick={() => onDelete(user.id)}
|
||||
className="bg-red-600 hover:bg-red-700 text-white"
|
||||
>
|
||||
{t("O'chirish")}
|
||||
{deletePending ? (
|
||||
<Loader2 className="animate-spin" />
|
||||
) : (
|
||||
t("O'chirish")
|
||||
)}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
@@ -229,6 +287,7 @@ export function AgencyUsersSection({
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
|
||||
@@ -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<typeof formSchema>;
|
||||
@@ -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;
|
||||
|
||||
@@ -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 ? (
|
||||
<Loader2 className="animate-spin" />
|
||||
) : (
|
||||
t("Saqlash")
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -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 ? (
|
||||
<Loader2 className="animate-spin" />
|
||||
) : (
|
||||
t("Saqlash")
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -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 ? <Loader2 className="animate-spin" /> : t("Saqlash")}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -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!)}
|
||||
>
|
||||
{isPending ? (
|
||||
<Loader2 className="animate-spin" />
|
||||
) : (
|
||||
<>
|
||||
<Trash2 className="w-4 h-4 mr-2" />
|
||||
{t("O'chirish")}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
@@ -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"
|
||||
>
|
||||
{isPending ? (
|
||||
<Loader2 className="animate-spin" />
|
||||
) : (
|
||||
<>
|
||||
<Save className="w-5 h-5 mr-2" />
|
||||
{t("Yangilash")}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
"Foydalanuvchilar": "Пользователи",
|
||||
"Tur firmalar": "Турфирмы",
|
||||
"Xodimlar": "Сотрудники",
|
||||
"Siz agentlikga yangi foydalanuvchi qo'shmoqchimisiz": "Вы хотите добавить нового пользователя в агентство",
|
||||
"Foydalanuvchi qo'shish": "Добавить пользователя",
|
||||
"Byudjet": "Бюджет",
|
||||
"Turlar": "Туры",
|
||||
"Tur sozlamalari": "Настройки туров",
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user