bug fix loading
This commit is contained in:
@@ -41,7 +41,7 @@ export default function TourAgenciesPage() {
|
|||||||
queryFn: () => getAllAgency({ page: currentPage, page_size: itemsPerPage }),
|
queryFn: () => getAllAgency({ page: currentPage, page_size: itemsPerPage }),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { mutate } = useMutation({
|
const { mutate, isPending } = useMutation({
|
||||||
mutationFn: ({ id }: { id: number }) => {
|
mutationFn: ({ id }: { id: number }) => {
|
||||||
return deleteAgency({ id });
|
return deleteAgency({ id });
|
||||||
},
|
},
|
||||||
@@ -293,7 +293,11 @@ export default function TourAgenciesPage() {
|
|||||||
onClick={() => handleDelete(agency.id)}
|
onClick={() => handleDelete(agency.id)}
|
||||||
className="bg-red-600 hover:bg-red-700 text-white"
|
className="bg-red-600 hover:bg-red-700 text-white"
|
||||||
>
|
>
|
||||||
{t("O'chirish")}
|
{isPending ? (
|
||||||
|
<Loader2 className="animate-spin" />
|
||||||
|
) : (
|
||||||
|
t("O'chirish")
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
updateAgencyStatus,
|
updateAgencyStatus,
|
||||||
} from "@/pages/agencies/lib/api";
|
} from "@/pages/agencies/lib/api";
|
||||||
import { AgencyUsersSection } from "@/pages/agencies/ui/AgencyUsersSection";
|
import { AgencyUsersSection } from "@/pages/agencies/ui/AgencyUsersSection";
|
||||||
|
import { createTourAdmin } from "@/pages/support/lib/api";
|
||||||
import formatPhone from "@/shared/lib/formatPhone";
|
import formatPhone from "@/shared/lib/formatPhone";
|
||||||
import formatPrice from "@/shared/lib/formatPrice";
|
import formatPrice from "@/shared/lib/formatPrice";
|
||||||
import { Badge } from "@/shared/ui/badge";
|
import { Badge } from "@/shared/ui/badge";
|
||||||
@@ -50,6 +51,7 @@ export default function AgencyDetailPage() {
|
|||||||
const router = useNavigate();
|
const router = useNavigate();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [edit, setEdit] = useState<boolean>(false);
|
const [edit, setEdit] = useState<boolean>(false);
|
||||||
|
const [added, setAdded] = useState<boolean>(false);
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const [openUser, setOpenUser] = useState<boolean>(false);
|
const [openUser, setOpenUser] = useState<boolean>(false);
|
||||||
const [user, setUser] = useState<{
|
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({
|
const deleteUserMutation = useMutation({
|
||||||
mutationFn: (userId: number) => agencyUserDelete(userId),
|
mutationFn: (userId: number) => agencyUserDelete(userId),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
@@ -126,6 +144,10 @@ export default function AgencyDetailPage() {
|
|||||||
updateUserMutation.mutate(user);
|
updateUserMutation.mutate(user);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleAddeUser = () => {
|
||||||
|
createAdmin.mutate(Number(params.id!));
|
||||||
|
};
|
||||||
|
|
||||||
const handleDeleteUser = (userId: number) => {
|
const handleDeleteUser = (userId: number) => {
|
||||||
deleteUserMutation.mutate(userId);
|
deleteUserMutation.mutate(userId);
|
||||||
};
|
};
|
||||||
@@ -409,8 +431,14 @@ export default function AgencyDetailPage() {
|
|||||||
{agency?.data.data && (
|
{agency?.data.data && (
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<AgencyUsersSection
|
<AgencyUsersSection
|
||||||
|
agencyId={Number(params.id)}
|
||||||
|
added={added}
|
||||||
|
setAdded={setAdded}
|
||||||
edit={edit}
|
edit={edit}
|
||||||
|
handleAddeUser={handleAddeUser}
|
||||||
setEdit={setEdit}
|
setEdit={setEdit}
|
||||||
|
isPending={updateUserMutation.isPending}
|
||||||
|
createAdminPending={createAdmin.isPending}
|
||||||
users={
|
users={
|
||||||
Array.isArray(agency?.data.data)
|
Array.isArray(agency?.data.data)
|
||||||
? agency?.data.data
|
? agency?.data.data
|
||||||
@@ -419,6 +447,7 @@ export default function AgencyDetailPage() {
|
|||||||
onEdit={handleEditUser}
|
onEdit={handleEditUser}
|
||||||
onDelete={handleDeleteUser}
|
onDelete={handleDeleteUser}
|
||||||
isLoading={isLoadingUsers}
|
isLoading={isLoadingUsers}
|
||||||
|
deletePending={deleteUserMutation.isPending}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -13,7 +13,15 @@ import {
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@/shared/ui/dialog";
|
} 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 { type Dispatch, type SetStateAction } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
@@ -27,22 +35,36 @@ interface AgencyUser {
|
|||||||
|
|
||||||
interface AgencyUsersProps {
|
interface AgencyUsersProps {
|
||||||
users: AgencyUser[];
|
users: AgencyUser[];
|
||||||
|
agencyId: number;
|
||||||
onEdit: (userId: number) => void;
|
onEdit: (userId: number) => void;
|
||||||
|
handleAddeUser: () => void;
|
||||||
onDelete: (userId: number) => void;
|
onDelete: (userId: number) => void;
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
|
isPending: boolean;
|
||||||
|
createAdminPending: boolean;
|
||||||
edit: boolean;
|
edit: boolean;
|
||||||
|
added: boolean;
|
||||||
|
deletePending: boolean;
|
||||||
setEdit: Dispatch<SetStateAction<boolean>>;
|
setEdit: Dispatch<SetStateAction<boolean>>;
|
||||||
|
setAdded: Dispatch<SetStateAction<boolean>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AgencyUsersSection({
|
export function AgencyUsersSection({
|
||||||
users,
|
users,
|
||||||
onEdit,
|
onEdit,
|
||||||
|
setAdded,
|
||||||
|
added,
|
||||||
edit,
|
edit,
|
||||||
setEdit,
|
setEdit,
|
||||||
|
handleAddeUser,
|
||||||
|
createAdminPending,
|
||||||
onDelete,
|
onDelete,
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
|
deletePending,
|
||||||
|
isPending = false,
|
||||||
}: AgencyUsersProps) {
|
}: AgencyUsersProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const getRoleBadge = (role: string) => {
|
const getRoleBadge = (role: string) => {
|
||||||
const roleColors: Record<string, string> = {
|
const roleColors: Record<string, string> = {
|
||||||
admin: "bg-purple-500/20 text-purple-300 border-purple-500/40",
|
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 (
|
<Card className="border border-gray-700 shadow-lg bg-gray-800">
|
||||||
<Card className="border border-gray-700 shadow-lg bg-gray-800">
|
<CardHeader className="flex justify-between items-center">
|
||||||
<CardHeader>
|
<div>
|
||||||
<CardTitle className="text-2xl text-white flex items-center gap-2">
|
<CardTitle className="text-2xl text-white flex items-center gap-2">
|
||||||
<User className="w-6 h-6" />
|
<User className="w-6 h-6" />
|
||||||
{t("Agentlik foydalanuvchilari")}
|
{t("Agentlik foydalanuvchilari")}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
<p className="text-gray-400">
|
||||||
<CardContent>
|
{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">
|
<div className="text-center text-gray-400 py-8">
|
||||||
{t("Hozircha foydalanuvchilar yo'q")}
|
{t("Hozircha foydalanuvchilar yo'q")}
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
) : (
|
||||||
</Card>
|
<div className="space-y-3">
|
||||||
);
|
{users.map((user) => (
|
||||||
}
|
<div
|
||||||
|
key={user.id}
|
||||||
return (
|
className="p-5 border border-gray-700 rounded-xl bg-gray-800 hover:bg-gray-750 transition-all"
|
||||||
<Card className="border border-gray-700 shadow-lg bg-gray-800">
|
>
|
||||||
<CardHeader>
|
<div className="flex items-center justify-between">
|
||||||
<CardTitle className="text-2xl text-white flex items-center gap-2">
|
<div className="flex items-center gap-4 flex-1">
|
||||||
<User className="w-6 h-6" />
|
<div className="w-12 h-12 rounded-full bg-gradient-to-br from-blue-500 to-purple-600 flex items-center justify-center">
|
||||||
{t("Agentlik foydalanuvchilari")}
|
<User className="w-6 h-6 text-white" />
|
||||||
</CardTitle>
|
|
||||||
<p className="text-gray-400">
|
|
||||||
{t("Agentlik bilan bog'langan foydalanuvchilar ro'yxati")}
|
|
||||||
</p>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div className="space-y-3">
|
|
||||||
{users.map((user) => (
|
|
||||||
<div
|
|
||||||
key={user.id}
|
|
||||||
className="p-5 border border-gray-700 rounded-xl bg-gray-800 hover:bg-gray-750 transition-all"
|
|
||||||
>
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="flex items-center gap-4 flex-1">
|
|
||||||
<div className="w-12 h-12 rounded-full bg-gradient-to-br from-blue-500 to-purple-600 flex items-center justify-center">
|
|
||||||
<User className="w-6 h-6 text-white" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex-1">
|
|
||||||
<div className="flex items-center gap-3 mb-2">
|
|
||||||
<h3 className="text-lg font-semibold text-white">
|
|
||||||
{user.first_name} {user.last_name}
|
|
||||||
</h3>
|
|
||||||
{getRoleBadge(user.role)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-wrap items-center gap-4 text-sm">
|
<div className="flex-1">
|
||||||
<div className="flex items-center gap-2 text-gray-400">
|
<div className="flex items-center gap-3 mb-2">
|
||||||
<Phone className="w-4 h-4" />
|
<h3 className="text-lg font-semibold text-white">
|
||||||
<span>{formatPhone(user.phone)}</span>
|
{user.first_name} {user.last_name}
|
||||||
|
</h3>
|
||||||
|
{getRoleBadge(user.role)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 text-gray-400">
|
|
||||||
<Shield className="w-4 h-4" />
|
<div className="flex flex-wrap items-center gap-4 text-sm">
|
||||||
<span>ID: {user.id}</span>
|
<div className="flex items-center gap-2 text-gray-400">
|
||||||
|
<Phone className="w-4 h-4" />
|
||||||
|
<span>{formatPhone(user.phone)}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2 text-gray-400">
|
||||||
|
<Shield className="w-4 h-4" />
|
||||||
|
<span>ID: {user.id}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Actions */}
|
<div className="flex items-center gap-2">
|
||||||
<div className="flex items-center gap-2">
|
{/* ✏️ Edit */}
|
||||||
{/* Edit Confirm Dialog */}
|
<Dialog open={edit} onOpenChange={setEdit}>
|
||||||
<Dialog open={edit} onOpenChange={setEdit}>
|
<DialogTrigger asChild>
|
||||||
<DialogTrigger asChild>
|
<Button
|
||||||
<Button
|
variant="outline"
|
||||||
variant="outline"
|
size="icon"
|
||||||
size="icon"
|
className="border-gray-600 bg-gray-700 hover:bg-gray-600 text-blue-400 hover:text-blue-300"
|
||||||
className="border-gray-600 bg-gray-700 hover:bg-gray-600 text-blue-400 hover:text-blue-300"
|
>
|
||||||
>
|
<Pencil
|
||||||
<Pencil
|
className="w-4 h-4"
|
||||||
className="w-4 h-4"
|
onClick={() => setEdit(true)}
|
||||||
onClick={() => setEdit(true)}
|
/>
|
||||||
/>
|
</Button>
|
||||||
</Button>
|
</DialogTrigger>
|
||||||
</DialogTrigger>
|
<DialogContent className="bg-gray-800 border-gray-700 text-white">
|
||||||
<DialogContent className="bg-gray-800 border-gray-700 text-white">
|
<DialogHeader>
|
||||||
<DialogHeader>
|
<DialogTitle>
|
||||||
<DialogTitle>
|
{t("Foydalanuvchini tahrirlash")}
|
||||||
{t("Foydalanuvchini tahrirlash")}
|
</DialogTitle>
|
||||||
</DialogTitle>
|
<DialogDescription className="text-gray-400">
|
||||||
<DialogDescription className="text-gray-400">
|
{t("Haqiqatan ham")}{" "}
|
||||||
{t("Haqiqatan ham")}{" "}
|
<span className="font-semibold text-white">
|
||||||
<span className="font-semibold text-white">
|
{user.first_name} {user.last_name}
|
||||||
{user.first_name} {user.last_name}
|
</span>{" "}
|
||||||
</span>{" "}
|
{t("ni tahrirlamoqchimisiz?")}
|
||||||
{t("ni tahrirlamoqchimisiz?")}
|
</DialogDescription>
|
||||||
</DialogDescription>
|
</DialogHeader>
|
||||||
</DialogHeader>
|
|
||||||
|
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button
|
<Button
|
||||||
className="border-gray-600 bg-gray-700 hover:bg-gray-600 text-white"
|
className="border-gray-600 bg-gray-700 hover:bg-gray-600 text-white"
|
||||||
onClick={() => setEdit(true)}
|
onClick={() => setEdit(false)}
|
||||||
>
|
>
|
||||||
{t("Bekor qilish")}
|
{t("Bekor qilish")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => onEdit(user.id)}
|
onClick={() => onEdit(user.id)}
|
||||||
className="bg-blue-600 hover:bg-blue-700 text-white"
|
className="bg-blue-600 hover:bg-blue-700 text-white"
|
||||||
>
|
>
|
||||||
{t("Tahrirlash")}
|
{isPending ? (
|
||||||
</Button>
|
<Loader2 className="animate-spin" />
|
||||||
</DialogFooter>
|
) : (
|
||||||
</DialogContent>
|
t("Tahrirlash")
|
||||||
</Dialog>
|
)}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
{/* Delete Confirm Dialog */}
|
{/* 🗑 Delete */}
|
||||||
<Dialog>
|
<Dialog>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="icon"
|
|
||||||
className="border-gray-600 bg-gray-700 hover:bg-red-900/20 hover:border-red-500 text-red-400 hover:text-red-300"
|
|
||||||
>
|
|
||||||
<Trash2 className="w-4 h-4" />
|
|
||||||
</Button>
|
|
||||||
</DialogTrigger>
|
|
||||||
<DialogContent className="bg-gray-800 border-gray-700 text-white">
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>
|
|
||||||
{t("Foydalanuvchini o'chirish")}
|
|
||||||
</DialogTitle>
|
|
||||||
<DialogDescription className="text-gray-400">
|
|
||||||
{t("Haqiqatan ham")}{" "}
|
|
||||||
<span className="font-semibold text-white">
|
|
||||||
{user.first_name} {user.last_name}
|
|
||||||
</span>{" "}
|
|
||||||
{t(
|
|
||||||
"ni o'chirmoqchimisiz? Bu amalni qaytarib bo'lmaydi.",
|
|
||||||
)}
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<DialogFooter>
|
|
||||||
<Button className="border-gray-600 bg-gray-700 hover:bg-gray-600 text-white">
|
|
||||||
{t("Bekor qilish")}
|
|
||||||
</Button>
|
|
||||||
<Button
|
<Button
|
||||||
onClick={() => onDelete(user.id)}
|
variant="outline"
|
||||||
className="bg-red-600 hover:bg-red-700 text-white"
|
size="icon"
|
||||||
|
className="border-gray-600 bg-gray-700 hover:bg-red-900/20 hover:border-red-500 text-red-400 hover:text-red-300"
|
||||||
>
|
>
|
||||||
{t("O'chirish")}
|
<Trash2 className="w-4 h-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogTrigger>
|
||||||
</DialogContent>
|
<DialogContent className="bg-gray-800 border-gray-700 text-white">
|
||||||
</Dialog>
|
<DialogHeader>
|
||||||
|
<DialogTitle>
|
||||||
|
{t("Foydalanuvchini o'chirish")}
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogDescription className="text-gray-400">
|
||||||
|
{t("Haqiqatan ham")}{" "}
|
||||||
|
<span className="font-semibold text-white">
|
||||||
|
{user.first_name} {user.last_name}
|
||||||
|
</span>{" "}
|
||||||
|
{t(
|
||||||
|
"ni o'chirmoqchimisiz? Bu amalni qaytarib bo'lmaydi.",
|
||||||
|
)}
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button className="border-gray-600 bg-gray-700 hover:bg-gray-600 text-white">
|
||||||
|
{t("Bekor qilish")}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => onDelete(user.id)}
|
||||||
|
className="bg-red-600 hover:bg-red-700 text-white"
|
||||||
|
>
|
||||||
|
{deletePending ? (
|
||||||
|
<Loader2 className="animate-spin" />
|
||||||
|
) : (
|
||||||
|
t("O'chirish")
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
))}
|
||||||
))}
|
</div>
|
||||||
</div>
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ const formSchema = z.object({
|
|||||||
addres: z.string().min(1, "Manzil kiritish shart"),
|
addres: z.string().min(1, "Manzil kiritish shart"),
|
||||||
email: z.string().email("Email noto‘g‘ri"),
|
email: z.string().email("Email noto‘g‘ri"),
|
||||||
phone: z.string().min(3, "Telefon raqami 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>;
|
type FormData = z.infer<typeof formSchema>;
|
||||||
@@ -63,12 +63,12 @@ const EditAgency = () => {
|
|||||||
});
|
});
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const { data, isPending } = useQuery({
|
const { data } = useQuery({
|
||||||
queryKey: ["detail_agency", params.id],
|
queryKey: ["detail_agency", params.id],
|
||||||
queryFn: () => getDetailAgency({ id: Number(params.id) }),
|
queryFn: () => getDetailAgency({ id: Number(params.id) }),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { mutate } = useMutation({
|
const { mutate, isPending } = useMutation({
|
||||||
mutationFn: (body: {
|
mutationFn: (body: {
|
||||||
status: "pending" | "approved" | "cancelled";
|
status: "pending" | "approved" | "cancelled";
|
||||||
custom_id?: string;
|
custom_id?: string;
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ const NewsCategory = () => {
|
|||||||
setIsDialogOpen(true);
|
setIsDialogOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const { mutate: added } = useMutation({
|
const { mutate: added, isPending } = useMutation({
|
||||||
mutationFn: (body: { name: string; name_ru: string }) =>
|
mutationFn: (body: { name: string; name_ru: string }) =>
|
||||||
addNewsCategory(body),
|
addNewsCategory(body),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
@@ -111,7 +111,7 @@ const NewsCategory = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { mutate: edit } = useMutation({
|
const { mutate: edit, isPending: editPending } = useMutation({
|
||||||
mutationFn: ({
|
mutationFn: ({
|
||||||
body,
|
body,
|
||||||
id,
|
id,
|
||||||
@@ -349,7 +349,11 @@ const NewsCategory = () => {
|
|||||||
type="submit"
|
type="submit"
|
||||||
className="bg-blue-600 px-5 py-5 hover:bg-blue-700 text-white mt-4 cursor-pointer"
|
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>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { Label } from "@/shared/ui/label";
|
|||||||
import { Textarea } from "@/shared/ui/textarea";
|
import { Textarea } from "@/shared/ui/textarea";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
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 { useEffect, useRef } from "react";
|
||||||
import { useFieldArray, useForm } from "react-hook-form";
|
import { useFieldArray, useForm } from "react-hook-form";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -134,7 +134,7 @@ const StepTwo = ({
|
|||||||
if (file) form.setValue(`sections.${index}.image`, file);
|
if (file) form.setValue(`sections.${index}.image`, file);
|
||||||
};
|
};
|
||||||
|
|
||||||
const { mutate: added } = useMutation({
|
const { mutate: added, isPending } = useMutation({
|
||||||
mutationFn: (body: FormData) => addNews(body),
|
mutationFn: (body: FormData) => addNews(body),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.refetchQueries({ queryKey: ["all_news"] });
|
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 }) =>
|
mutationFn: ({ body, id }: { id: number; body: FormData }) =>
|
||||||
updateNews({ id, body }),
|
updateNews({ id, body }),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
@@ -404,7 +404,11 @@ const StepTwo = ({
|
|||||||
type="submit"
|
type="submit"
|
||||||
className="mt-6 px-8 py-3 bg-blue-600 text-white rounded-md hover:bg-blue-700 cursor-pointer"
|
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>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ import { RadioGroup, RadioGroupItem } from "@/shared/ui/radio-group";
|
|||||||
import { Textarea } from "@/shared/ui/textarea";
|
import { Textarea } from "@/shared/ui/textarea";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
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 { useEffect, useState, type Dispatch, type SetStateAction } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -231,7 +231,7 @@ const StepOne = ({
|
|||||||
const selectedDate = watch("departureDateTime.date");
|
const selectedDate = watch("departureDateTime.date");
|
||||||
const selectedDateTravel = watch("travelDateTime.date");
|
const selectedDateTravel = watch("travelDateTime.date");
|
||||||
|
|
||||||
const { mutate: create } = useMutation({
|
const { mutate: create, isPending } = useMutation({
|
||||||
mutationFn: (body: FormData) => {
|
mutationFn: (body: FormData) => {
|
||||||
return createTours({ body });
|
return createTours({ body });
|
||||||
},
|
},
|
||||||
@@ -2204,7 +2204,7 @@ const StepOne = ({
|
|||||||
type="submit"
|
type="submit"
|
||||||
className="mt-6 px-8 py-3 bg-blue-600 text-white rounded-md hover:bg-blue-600 cursor-pointer"
|
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>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ const Tours = ({ user }: { user: Role }) => {
|
|||||||
getAllTours({ page: 1, page_size: 10, featured_tickets: true }),
|
getAllTours({ page: 1, page_size: 10, featured_tickets: true }),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { mutate } = useMutation({
|
const { mutate, isPending } = useMutation({
|
||||||
mutationFn: (id: number) => deleteTours({ id }),
|
mutationFn: (id: number) => deleteTours({ id }),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.refetchQueries({ queryKey: ["all_tours"] });
|
queryClient.refetchQueries({ queryKey: ["all_tours"] });
|
||||||
@@ -270,8 +270,14 @@ const Tours = ({ user }: { user: Role }) => {
|
|||||||
variant="destructive"
|
variant="destructive"
|
||||||
onClick={() => confirmDelete(deleteId!)}
|
onClick={() => confirmDelete(deleteId!)}
|
||||||
>
|
>
|
||||||
<Trash2 className="w-4 h-4 mr-2" />
|
{isPending ? (
|
||||||
{t("O'chirish")}
|
<Loader2 className="animate-spin" />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Trash2 className="w-4 h-4 mr-2" />
|
||||||
|
{t("O'chirish")}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import {
|
|||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/shared/ui/form";
|
} from "@/shared/ui/form";
|
||||||
import { Input } from "@/shared/ui/input";
|
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 { useEffect } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
@@ -250,8 +250,14 @@ export default function EditUser() {
|
|||||||
disabled={isPending}
|
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"
|
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"
|
||||||
>
|
>
|
||||||
<Save className="w-5 h-5 mr-2" />
|
{isPending ? (
|
||||||
{t("Yangilash")}
|
<Loader2 className="animate-spin" />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Save className="w-5 h-5 mr-2" />
|
||||||
|
{t("Yangilash")}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
"Foydalanuvchilar": "Пользователи",
|
"Foydalanuvchilar": "Пользователи",
|
||||||
"Tur firmalar": "Турфирмы",
|
"Tur firmalar": "Турфирмы",
|
||||||
"Xodimlar": "Сотрудники",
|
"Xodimlar": "Сотрудники",
|
||||||
|
"Siz agentlikga yangi foydalanuvchi qo'shmoqchimisiz": "Вы хотите добавить нового пользователя в агентство",
|
||||||
|
"Foydalanuvchi qo'shish": "Добавить пользователя",
|
||||||
"Byudjet": "Бюджет",
|
"Byudjet": "Бюджет",
|
||||||
"Turlar": "Туры",
|
"Turlar": "Туры",
|
||||||
"Tur sozlamalari": "Настройки туров",
|
"Tur sozlamalari": "Настройки туров",
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
"Xodimlar": "Xodimlar",
|
"Xodimlar": "Xodimlar",
|
||||||
"Byudjet": "Byudjet",
|
"Byudjet": "Byudjet",
|
||||||
"Turlar": "Turlar",
|
"Turlar": "Turlar",
|
||||||
|
"Siz agentlikga yangi foydalanuvchi qo'shmoqchimisiz": "Siz agentlikga yangi foydalanuvchi qo'shmoqchimisiz",
|
||||||
|
"Foydalanuvchi qo'shish": "Foydalanuvchi qo'shish",
|
||||||
"Tur sozlamalari": "Tur sozlamalari",
|
"Tur sozlamalari": "Tur sozlamalari",
|
||||||
"Bronlar": "Bronlar",
|
"Bronlar": "Bronlar",
|
||||||
"Yangiliklar": "Yangiliklar",
|
"Yangiliklar": "Yangiliklar",
|
||||||
|
|||||||
Reference in New Issue
Block a user