diff --git a/src/App.tsx b/src/App.tsx
index 5e6d524..505de9a 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -15,6 +15,7 @@ import {
import AddNews from "@/pages/news/ui/AddNews";
import News from "@/pages/news/ui/News";
import NewsCategory from "@/pages/news/ui/NewsCategory";
+import Page from "@/pages/profile/ui/Profile";
import Seo from "@/pages/seo/ui/Seo";
import SiteBannerAdmin from "@/pages/site-banner/ui/Banner";
import PolicyCrud from "@/pages/site-page/ui/PolicyCrud";
@@ -35,6 +36,8 @@ import "@/shared/config/i18n";
import { getAuthToken } from "@/shared/lib/authCookies";
import { cn } from "@/shared/lib/utils";
import { Sidebar } from "@/widgets/sidebar/ui/Sidebar";
+import { useWelcomeStore } from "@/widgets/welcome/lib/hook";
+import Welcome from "@/widgets/welcome/ui/welcome";
import { useQuery } from "@tanstack/react-query";
import { useEffect } from "react";
import {
@@ -49,85 +52,104 @@ const App = () => {
const token = getAuthToken();
const navigate = useNavigate();
const location = useLocation();
+ const { setOpenModal } = useWelcomeStore();
+
const { data: user } = useQuery({
queryKey: ["get_me"],
queryFn: () => getMe(),
- select(data) {
- return data.data.data;
- },
+ select: (data) => data.data.data,
enabled: !!token,
});
const hideSidebarPaths = ["/login"];
const shouldShowSidebar = !hideSidebarPaths.includes(location.pathname);
+ // 🔹 Avtorizatsiya yo‘nalishlari
useEffect(() => {
- if (token && user && user.role === "moderator") {
- navigate("/user");
- } else if (token && user && user.role === "tour_admin") {
- navigate("/finance ");
- } else if (token && user && user.role === "buxgalter") {
- navigate("/finance");
- } else if (!token && !user) {
+ if (token && user) {
+ if (user.role === "moderator") {
+ navigate("/user");
+ } else if (user.role === "tour_admin") {
+ navigate("/profile");
+ } else if (user.role === "buxgalter") {
+ navigate("/finance");
+ }
+ } else if (!token) {
navigate("/login");
}
}, [token, user]);
- return (
- <>
-
- {shouldShowSidebar && }
+ // 🔹 Faqat userda ism yoki familiya yo‘q bo‘lsa Welcome modalni ochamiz
+ useEffect(() => {
+ if (
+ user &&
+ (!user.first_name || !user.last_name) &&
+ location.pathname !== "/login" &&
+ user.role !== "moderator"
+ ) {
+ setOpenModal(true);
+ } else {
+ setOpenModal(false);
+ }
+ }, [user, location.pathname]);
-
-
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- }
- />
- } />
- } />
- } />
- }
- />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
-
-
-
- >
+ return (
+
+ {shouldShowSidebar && }
+
+
+ {/* ✅ Welcome faqat login sahifasida ko‘rinmaydi */}
+ {location.pathname !== "/login" && }
+
+
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ }
+ />
+ } />
+ } />
+ } />
+ }
+ />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+
+
);
};
diff --git a/src/pages/agencies/ui/AgencyDetail.tsx b/src/pages/agencies/ui/AgencyDetail.tsx
index 9f0843c..75605f4 100644
--- a/src/pages/agencies/ui/AgencyDetail.tsx
+++ b/src/pages/agencies/ui/AgencyDetail.tsx
@@ -49,6 +49,7 @@ export default function AgencyDetailPage() {
const params = useParams();
const router = useNavigate();
const { t } = useTranslation();
+ const [edit, setEdit] = useState(false);
const queryClient = useQueryClient();
const [openUser, setOpenUser] = useState(false);
const [user, setUser] = useState<{
@@ -97,6 +98,7 @@ export default function AgencyDetailPage() {
toast.success(t("Foydalanuvchi muvaffaqiyatli yangilandi"));
setOpenUser(true);
setUser(res.data);
+ setEdit(false);
},
onError: () => {
toast.error(t("Foydalanuvchini yangilashda xatolik"));
@@ -407,6 +409,8 @@ export default function AgencyDetailPage() {
{agency?.data.data && (
void;
onDelete: (userId: number) => void;
isLoading?: boolean;
+ edit: boolean;
+ setEdit: Dispatch>;
}
export function AgencyUsersSection({
users,
onEdit,
+ edit,
+ setEdit,
onDelete,
isLoading = false,
}: AgencyUsersProps) {
const { t } = useTranslation();
-
const getRoleBadge = (role: string) => {
const roleColors: Record = {
admin: "bg-purple-500/20 text-purple-300 border-purple-500/40",
@@ -45,7 +50,6 @@ export function AgencyUsersSection({
user: "bg-gray-500/20 text-gray-300 border-gray-500/40",
agent: "bg-green-500/20 text-green-300 border-green-500/40",
};
-
return (
{role.toUpperCase()}
@@ -134,18 +138,54 @@ export function AgencyUsersSection({
+ {/* Actions */}
- {/* Edit Button */}
-
+ {/* Edit Confirm Dialog */}
+
+
+ {/* Delete Confirm Dialog */}
+
+
+
+ {data && (
+ setOpenPayDialog(false)}
+ agencyId={data?.id}
+ />
+ )}
{/* Tour Summary Cards */}
@@ -156,8 +176,18 @@ export default function FinanceDetailTour() {
{t("Reviews")}
+
-
{activeTab === "overview" && (
@@ -355,6 +385,8 @@ export default function FinanceDetailTour() {
))}
)}
+
+ {activeTab === "payment" &&
}
diff --git a/src/pages/finance/ui/PayDialog.tsx b/src/pages/finance/ui/PayDialog.tsx
new file mode 100644
index 0000000..631420d
--- /dev/null
+++ b/src/pages/finance/ui/PayDialog.tsx
@@ -0,0 +1,140 @@
+"use client";
+
+import { payAgency } from "@/pages/finance/lib/api";
+import { Button } from "@/shared/ui/button";
+import {
+ Dialog,
+ DialogContent,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from "@/shared/ui/dialog";
+import { Input } from "@/shared/ui/input";
+import { Label } from "@/shared/ui/label";
+import { useMutation } from "@tanstack/react-query";
+import { Loader2 } from "lucide-react";
+import { useForm } from "react-hook-form";
+import { useTranslation } from "react-i18next";
+import { toast } from "sonner";
+
+interface PayDialogProps {
+ open: boolean;
+ onClose: () => void;
+ agencyId: number;
+}
+
+interface PayFormValues {
+ amount: string; // formatted value (e.g. "1 200 000")
+ note: string;
+}
+
+// Narxni formatlovchi funksiya
+function formatPrice(value: number | string): string {
+ const num = Number(value.toString().replace(/\D/g, ""));
+ if (isNaN(num)) return "";
+ return num.toLocaleString("ru-RU");
+}
+
+export function PayDialog({ open, onClose, agencyId }: PayDialogProps) {
+ const { t } = useTranslation();
+
+ const form = useForm({
+ defaultValues: {
+ amount: "",
+ note: "",
+ },
+ });
+
+ const { mutate, isPending } = useMutation({
+ mutationFn: ({
+ body,
+ }: {
+ body: {
+ travel_agency: number;
+ amount: number;
+ note: string;
+ };
+ }) => payAgency({ body }),
+ onSuccess: () => {
+ onClose();
+ },
+ onError: () => {
+ toast.error(t("Xatolik yuz berdi"), {
+ richColors: true,
+ position: "top-center",
+ });
+ },
+ });
+
+ const handleAmountChange = (e: React.ChangeEvent) => {
+ const raw = e.target.value.replace(/\D/g, "");
+ const formatted = formatPrice(raw);
+ form.setValue("amount", formatted);
+ };
+
+ const handleSubmit = async (values: PayFormValues) => {
+ const cleanAmount = Number(
+ values.amount.replace(/\s/g, "").replace(/,/g, ""),
+ );
+ mutate({
+ body: {
+ amount: cleanAmount,
+ note: values.note,
+ travel_agency: agencyId,
+ },
+ });
+ };
+
+ return (
+
+ );
+}
diff --git a/src/pages/finance/ui/PaymentHistory.tsx b/src/pages/finance/ui/PaymentHistory.tsx
new file mode 100644
index 0000000..c19bf5b
--- /dev/null
+++ b/src/pages/finance/ui/PaymentHistory.tsx
@@ -0,0 +1,110 @@
+"use client";
+
+import { getPaymentHistory } from "@/pages/finance/lib/api";
+import formatPrice from "@/shared/lib/formatPrice";
+import { Button } from "@/shared/ui/button";
+import { Card, CardContent, CardHeader, CardTitle } from "@/shared/ui/card";
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@/shared/ui/table";
+import { useQuery } from "@tanstack/react-query";
+import { ChevronLeft, ChevronRightIcon, Loader2 } from "lucide-react";
+import { useState } from "react";
+import { useTranslation } from "react-i18next";
+
+export function PaymentHistory() {
+ const { t } = useTranslation();
+ const [page, setPage] = useState(1);
+
+ const { data, isLoading, isError } = useQuery({
+ queryKey: ["payment-history", page],
+ queryFn: () => getPaymentHistory({ page, page_size: 10 }),
+ });
+
+ if (isLoading)
+ return (
+
+
+
+ );
+
+ if (isError || !data)
+ return (
+
+ Xatolik yuz berdi. Qayta urinib ko‘ring.
+
+ );
+
+ const history = data.data;
+
+ return (
+
+
+ {t("To'lovlar tarixi")}
+
+
+
+
+
+ ID
+ {t("Agentlik")}
+ {t("Telefon")}
+ {t("Summasi")}
+ {t("Izoh")}
+ {t("Buxgalter")}
+
+
+
+ {history.data.results.map((item) => (
+
+ {item.travel_agency.custom_id}
+ {item.travel_agency.name}
+ {item.travel_agency.phone}
+ {formatPrice(item.amount, true)}
+ {item.note || "-"}
+
+ {item.accountant.first_name} {item.accountant.last_name}
+
+
+ ))}
+
+
+
+ {/* Pagination */}
+
+
+ {t("Sahifa")} {history.data.current_page} /{" "}
+ {history.data.total_pages}
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/pages/profile/lib/api.ts b/src/pages/profile/lib/api.ts
new file mode 100644
index 0000000..65ac03d
--- /dev/null
+++ b/src/pages/profile/lib/api.ts
@@ -0,0 +1,10 @@
+import httpClient from "@/shared/config/api/httpClient";
+import { GET_ME } from "@/shared/config/api/URLs";
+import type { AxiosResponse } from "axios";
+
+const getMeAgency = async (): Promise> => {
+ const res = await httpClient.get(GET_ME);
+ return res;
+};
+
+export { getMeAgency };
diff --git a/src/pages/profile/ui/AgencyInfo.tsx b/src/pages/profile/ui/AgencyInfo.tsx
new file mode 100644
index 0000000..82f00f1
--- /dev/null
+++ b/src/pages/profile/ui/AgencyInfo.tsx
@@ -0,0 +1,145 @@
+"use client";
+
+import formatPhone from "@/shared/lib/formatPhone";
+import formatPrice from "@/shared/lib/formatPrice";
+import { Card, CardContent, CardHeader, CardTitle } from "@/shared/ui/card";
+import { DollarSign, Mail, Phone, Star, Ticket } from "lucide-react";
+import { useTranslation } from "react-i18next";
+
+export interface UserAgencyDetailData {
+ id: number;
+ name: string;
+ custom_id: string;
+ share_percentage: number;
+ addres: string;
+ email: string;
+ phone: string;
+ web_site: string;
+ paid: number;
+ pending: number;
+ ticket_sold_count: number;
+ ticket_count: string;
+ total_income: number;
+ platform_income: number;
+ total_booking_count: string;
+ rating: string;
+ orders: {
+ id: number;
+ user: {
+ id: number;
+ first_name: string;
+ last_name: string;
+ contact: string;
+ };
+ total_price: number;
+ departure_date: string;
+ arrival_time: string;
+ created_at: string;
+ }[];
+ comments: {
+ user: {
+ id: number;
+ first_name: string;
+ last_name: string;
+ contact: string;
+ };
+ text: string;
+ rating: number;
+ ticket: number;
+ created: string;
+ }[];
+}
+
+export function AgencyCard({ agency }: { agency: UserAgencyDetailData }) {
+ const { t } = useTranslation();
+ return (
+
+ {/* HEADER */}
+
+ {agency.name}
+
+ {t("Agentlik ID")}: {agency.custom_id}
+
+
+
+ {/* CONTENT */}
+
+ {/* Contact info */}
+
+
+
Email
+
+
+
{agency.email || "—"}
+
+
+
+
+
{t("Telefon")}
+
+
+
{formatPhone(agency.phone) || "—"}
+
+
+
+
+
{t("Veb-sayt")}
+
{agency.web_site || "—"}
+
+
+
+
{t("Manzil")}
+
{agency.addres || "—"}
+
+
+
+ {/* Financial data */}
+
+
+
+
+ {t("Jami daromad")}:{" "}
+ {formatPrice(agency.total_income, true)}
+
+
+
+
+ {t("To'langan")}: {formatPrice(agency.paid, true)}
+
+
+
+ {t("Kutilayotgan to‘lovlar")}:{" "}
+ {formatPrice(agency.pending)}
+
+
+
+ {t("Platformaga tegishli")}:{" "}
+ {formatPrice(agency.platform_income, true)}
+
+
+
+ {/* Stats */}
+
+
+
+
+ {t("Sotilgan turlar soni")}:{" "}
+ {agency.ticket_sold_count}
+
+
+
+
+
+
+ {t("Reyting")}: {agency.rating || "—"}
+
+
+
+
+ {t("Bookings")}: {agency.total_booking_count}
+
+
+
+
+ );
+}
diff --git a/src/pages/profile/ui/EditDialog.tsx b/src/pages/profile/ui/EditDialog.tsx
new file mode 100644
index 0000000..29abb4c
--- /dev/null
+++ b/src/pages/profile/ui/EditDialog.tsx
@@ -0,0 +1,119 @@
+"use client";
+
+import { Button } from "@/shared/ui/button";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from "@/shared/ui/dialog";
+import { Input } from "@/shared/ui/input";
+import { Auth_Api } from "@/widgets/welcome/lib/data";
+import { useMutation, useQueryClient } from "@tanstack/react-query";
+import { AxiosError } from "axios";
+import { Loader2 } from "lucide-react";
+import { useState } from "react";
+import { useTranslation } from "react-i18next";
+import { toast } from "sonner";
+
+interface EditDialogProps {
+ open: boolean;
+ onClose: () => void;
+ user: {
+ id: number;
+ first_name: string;
+ last_name: string;
+ };
+}
+
+export function EditDialog({ open, onClose, user }: EditDialogProps) {
+ const [firstName, setFirstName] = useState(user.first_name);
+ const { t } = useTranslation();
+ const queryClient = useQueryClient();
+ const [lastName, setLastName] = useState(user.last_name);
+
+ const { mutate, isPending } = useMutation({
+ mutationFn: ({
+ first_name,
+ last_name,
+ }: {
+ first_name: string;
+ last_name: string;
+ }) => {
+ return Auth_Api.updateUser({ first_name, last_name });
+ },
+ onSuccess() {
+ queryClient.resetQueries({ queryKey: ["get_me"] });
+ onClose();
+ },
+ onError(error: AxiosError<{ non_field_errors: [string] }>) {
+ toast.error(t("Xatolik yuz berdi"), {
+ icon: null,
+ description: error.name,
+ position: "bottom-right",
+ });
+ },
+ });
+
+ const handleSave = async () => {
+ mutate({
+ first_name: firstName,
+ last_name: lastName,
+ });
+ };
+
+ return (
+
+ );
+}
diff --git a/src/pages/profile/ui/Profile.tsx b/src/pages/profile/ui/Profile.tsx
new file mode 100644
index 0000000..652fbef
--- /dev/null
+++ b/src/pages/profile/ui/Profile.tsx
@@ -0,0 +1,64 @@
+"use client";
+
+import { getDetailAgencyOrder } from "@/pages/finance/lib/api";
+import { AgencyCard } from "@/pages/profile/ui/AgencyInfo";
+import { EditDialog } from "@/pages/profile/ui/EditDialog";
+import { ProfileCard, type ProfileData } from "@/pages/profile/ui/ProfileCard";
+import { getMe } from "@/shared/config/api/auth/api";
+import { useQuery } from "@tanstack/react-query";
+import { useState } from "react";
+
+export default function Page() {
+ const [editDialogOpen, setEditDialogOpen] = useState(false);
+ const [editingUser, setEditingUser] = useState(null);
+
+ const { data: get_me, isLoading: userLoading } = useQuery({
+ queryKey: ["get_me"],
+ queryFn: () => getMe(),
+ select(data) {
+ return data.data.data;
+ },
+ });
+
+ const { data: agencyData, isLoading: agencyLoading } = useQuery({
+ queryKey: ["agency", get_me?.travel_agency],
+ queryFn: () => getDetailAgencyOrder(Number(get_me?.travel_agency)),
+ enabled: !!get_me?.travel_agency,
+ select: (data) => data.data?.data,
+ });
+
+ const handleOpenEdit = (user: ProfileData) => {
+ setEditingUser(user);
+ setEditDialogOpen(true);
+ };
+
+ return (
+
+
+
Profile
+
+ {get_me && (
+
handleOpenEdit(get_me)}
+ isLoading={userLoading}
+ />
+ )}
+
+ {agencyData && }
+
+ {(userLoading || agencyLoading) && (
+ Loading...
+ )}
+
+ {editDialogOpen && editingUser && (
+ setEditDialogOpen(false)}
+ user={editingUser}
+ />
+ )}
+
+
+ );
+}
diff --git a/src/pages/profile/ui/ProfileCard.tsx b/src/pages/profile/ui/ProfileCard.tsx
new file mode 100644
index 0000000..e2cfdd9
--- /dev/null
+++ b/src/pages/profile/ui/ProfileCard.tsx
@@ -0,0 +1,67 @@
+"use client";
+
+import formatPhone from "@/shared/lib/formatPhone";
+import { Button } from "@/shared/ui/button";
+import { Card, CardContent, CardHeader, CardTitle } from "@/shared/ui/card";
+import { BadgeCheck, Phone } from "lucide-react";
+import { useTranslation } from "react-i18next";
+
+export interface ProfileData {
+ id: number;
+ first_name: string;
+ last_name: string;
+ email: string;
+ phone: string;
+ avatar?: string;
+ username: string;
+ is_active: boolean;
+}
+
+interface ProfileCardProps {
+ user: ProfileData;
+ onEditClick: () => void;
+ isLoading?: boolean;
+}
+
+export function ProfileCard({
+ user,
+ onEditClick,
+ isLoading,
+}: ProfileCardProps) {
+ const { t } = useTranslation();
+ return (
+
+
+
+
+
+ {user.first_name} {user.last_name}
+
+
@{user.username}
+
+
+
+
+
+
+
+
+
{formatPhone(user.phone)}
+
+
+
+ {user.is_active ? "Active" : "Inactive"}
+
+
+
+ );
+}
diff --git a/src/pages/support/lib/api.ts b/src/pages/support/lib/api.ts
index 541c61e..ce2db0c 100644
--- a/src/pages/support/lib/api.ts
+++ b/src/pages/support/lib/api.ts
@@ -4,7 +4,7 @@ import type {
} from "@/pages/support/lib/types";
import httpClient from "@/shared/config/api/httpClient";
import {
- GET_ALL_AGENCY,
+ APPROVED_AGENCY,
SUPPORT_AGENCY,
SUPPORT_USER,
TOUR_ADMIN,
@@ -87,7 +87,7 @@ const updateTour = async ({
status: "pending" | "approved" | "cancelled";
};
}) => {
- const res = await httpClient.patch(`${GET_ALL_AGENCY}${id}/`, body);
+ const res = await httpClient.patch(`${APPROVED_AGENCY}${id}/`, body);
return res;
};
diff --git a/src/pages/support/ui/SupportAgency.tsx b/src/pages/support/ui/SupportAgency.tsx
index 49b996f..136dae0 100644
--- a/src/pages/support/ui/SupportAgency.tsx
+++ b/src/pages/support/ui/SupportAgency.tsx
@@ -220,7 +220,7 @@ const SupportAgency = () => {
{t("Telefon raqam")}
-
{formatPhone(selected.phone)}
+
{formatPhone(selected.phone ?? "")}
@@ -321,7 +321,7 @@ const SupportAgency = () => {
{t("Telefon raqam (login)")}
- {formatPhone(user.data.phone)}
+ {formatPhone(user.data.phone ?? "")}
diff --git a/src/pages/tours/ui/CreateEditTour.tsx b/src/pages/tours/ui/CreateEditTour.tsx
index 91bbebc..14057d0 100644
--- a/src/pages/tours/ui/CreateEditTour.tsx
+++ b/src/pages/tours/ui/CreateEditTour.tsx
@@ -50,7 +50,14 @@ const CreateEditTour = () => {
id={id}
/>
)}
- {step === 2 && }
+ {step === 2 && (
+
+ )}
);
};
diff --git a/src/pages/tours/ui/StepOne.tsx b/src/pages/tours/ui/StepOne.tsx
index 2a72e74..184270a 100644
--- a/src/pages/tours/ui/StepOne.tsx
+++ b/src/pages/tours/ui/StepOne.tsx
@@ -354,6 +354,7 @@ const StepOne = ({
}
});
value.ticket_itinerary?.forEach((itinerary, i) => {
+ // Har bir itinerary uchun asosiy maydonlar
formData.append(`ticket_itinerary[${i}]title`, itinerary.title);
formData.append(`ticket_itinerary[${i}]title_ru`, itinerary.title_ru);
formData.append(
@@ -361,10 +362,17 @@ const StepOne = ({
String(itinerary.duration),
);
- // Rasmlar
+ // 🖼 Rasmlar (faqat yangi yuklangan File-larni yuborish)
if (Array.isArray(itinerary.ticket_itinerary_image)) {
itinerary.ticket_itinerary_image.forEach((img, j) => {
- const file = img instanceof File ? img : img.image;
+ // img -> File yoki { image: File | string } shaklida bo‘lishi mumkin
+ const file =
+ img instanceof File
+ ? img
+ : img?.image instanceof File
+ ? img.image
+ : null;
+
if (file) {
formData.append(
`ticket_itinerary[${i}]ticket_itinerary_image[${j}]image`,
@@ -374,7 +382,7 @@ const StepOne = ({
});
}
- // Destinations
+ // 📍 Destinations (yo‘nalishlar)
if (Array.isArray(itinerary.ticket_itinerary_destinations)) {
itinerary.ticket_itinerary_destinations.forEach((dest, k) => {
formData.append(
@@ -388,6 +396,7 @@ const StepOne = ({
});
}
});
+
value.hotel_meals.forEach((e, i) => {
if (e.image instanceof File) {
formData.append(`ticket_hotel_meals[${i}]image`, e.image);
diff --git a/src/pages/tours/ui/StepTwo.tsx b/src/pages/tours/ui/StepTwo.tsx
index 0678fc9..6713af5 100644
--- a/src/pages/tours/ui/StepTwo.tsx
+++ b/src/pages/tours/ui/StepTwo.tsx
@@ -32,9 +32,9 @@ import {
SelectValue,
} from "@/shared/ui/select";
import { zodResolver } from "@hookform/resolvers/zod";
-import { useMutation, useQuery } from "@tanstack/react-query";
+import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { X } from "lucide-react";
-import { useEffect, useState } from "react";
+import { useEffect, useState, type Dispatch, type SetStateAction } from "react";
import { useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
@@ -55,13 +55,17 @@ const formSchema = z.object({
const StepTwo = ({
data,
isEditMode,
+ setStep,
}: {
data: GetOneTours | undefined;
isEditMode: boolean;
+ step: number;
+ setStep: Dispatch>;
}) => {
const { amenities, id: ticketId } = useTicketStore();
const navigate = useNavigate();
const { t } = useTranslation();
+ const queryClient = useQueryClient();
// 🧩 Query - Hotel detail
const { data: hotelDetail } = useQuery({
@@ -77,16 +81,20 @@ const StepTwo = ({
defaultValues: {
title: "",
rating: "3.0",
- mealPlan: "",
+ mealPlan: "all_inclusive",
hotelType: [],
hotelFeatures: [],
hotelFeaturesType: [],
},
});
- // 🧩 Edit holati uchun formni to‘ldirish
useEffect(() => {
- if (isEditMode && hotelDetail?.[0]) {
+ if (
+ isEditMode &&
+ hotelDetail &&
+ hotelDetail.length > 0 &&
+ hotelDetail[0].meal_plan
+ ) {
const hotel = hotelDetail[0];
form.setValue("title", hotel.name);
@@ -101,8 +109,9 @@ const StepTwo = ({
? "Half Board"
: hotel.meal_plan === "full_board"
? "Full Board"
- : "All Inclusive";
+ : "all_inclusive";
+ // ✅ SetValue faqat backenddan qiymat kelganda chaqiriladi
form.setValue("mealPlan", mealPlan);
form.setValue(
@@ -117,7 +126,7 @@ const StepTwo = ({
...new Set(hotel.hotel_features?.map((f) => String(f.id)) ?? []),
]);
}
- }, [isEditMode, hotelDetail, form, data]);
+ }, [isEditMode, hotelDetail, form]);
// 🧩 Select ma'lumotlari
const [allHotelTypes, setAllHotelTypes] = useState([]);
@@ -222,8 +231,10 @@ const StepTwo = ({
const { mutate, isPending } = useMutation({
mutationFn: (body: FormData) => createHotel({ body }),
onSuccess: () => {
+ queryClient.refetchQueries({ queryKey: ["hotel_detail"] });
toast.success(t("Muvaffaqiyatli saqlandi"));
navigate("/tours");
+ setStep(1);
},
onError: () =>
toast.error(t("Xatolik yuz berdi"), {
@@ -236,8 +247,10 @@ const StepTwo = ({
mutationFn: ({ body, id }: { id: number; body: FormData }) =>
editHotel({ body, id }),
onSuccess: () => {
+ queryClient.refetchQueries({ queryKey: ["hotel_detail"] });
toast.success(t("Muvaffaqiyatli saqlandi"));
navigate("/tours");
+ setStep(1);
},
onError: () =>
toast.error(t("Xatolik yuz berdi"), {
diff --git a/src/shared/config/api/URLs.ts b/src/shared/config/api/URLs.ts
index 580a51d..1cc9d13 100644
--- a/src/shared/config/api/URLs.ts
+++ b/src/shared/config/api/URLs.ts
@@ -5,8 +5,10 @@ const AUTH_LOGIN = "auth/token/phone/";
const GET_ME = "auth/me/";
const GET_ALL_USERS = "dashboard/users/";
const DOWNLOAD_PDF = "get-order-pdf/";
-const UPDATE_USER = "/dashboard/users/";
+const UPDATE_USERS = "auth/user-update/";
+const UPDATE_USER = "dashboard/users/";
const GET_ALL_AGENCY = "dashboard/tour-agency/";
+const APPROVED_AGENCY = "dashboard/dashboard-travel-agency-request/";
const GET_ALL_EMPLOYEES = "dashboard/employees/";
const GET_TICKET = "dashboard/dashboard-tickets/";
const HOTEL_BADGE = "dashboard/dashboard-tickets-settings-badge/";
@@ -32,10 +34,12 @@ const AGENCY_ORDERS = "dashboard/dashboard-site-travel-agency-report/";
const POPULAR_TOURS = "dashboard/dashboard-ticket-featured/";
const BANNER = "dashboard/dashboard-site-banner/";
const TOUR_ADMIN = "dashboard/dashboard-tour-admin/";
+const PAYMENT_AGENCY = "dashboard/dashboard-site-agency-payments/";
export {
AGENCY_ORDERS,
AMENITIES,
+ APPROVED_AGENCY,
AUTH_LOGIN,
BANNER,
BASE_URL,
@@ -57,6 +61,7 @@ export {
NEWS,
NEWS_CATEGORY,
OFFERTA,
+ PAYMENT_AGENCY,
POPULAR_TOURS,
SITE_SEO,
SITE_SETTING,
@@ -65,5 +70,6 @@ export {
TOUR_ADMIN,
TOUR_TRANSPORT,
UPDATE_USER,
+ UPDATE_USERS,
USER_ORDERS,
};
diff --git a/src/shared/config/i18n/locales/ru/translation.json b/src/shared/config/i18n/locales/ru/translation.json
index dd425be..8e0c6e1 100644
--- a/src/shared/config/i18n/locales/ru/translation.json
+++ b/src/shared/config/i18n/locales/ru/translation.json
@@ -492,5 +492,23 @@
"Yangi foydalanuvchi ma'lumotlari": "Данные нового пользователя",
"Agentlik uchun tizimga kirish ma'lumotlari": "Входные данные для агентства",
"Ikona tanlang": "Выберите иконку0",
- "Haqiqatan ham bu foydalanuvchini o'chirmoqchimisiz? Bu amalni qaytarib bo'lmaydi.": "Вы уверены, что хотите удалить этого пользователя? Это действие необратимо."
+ "Last Name": "Фамилия",
+ "First Name": "Имя",
+ "Summani kiriting": "Введите сумму",
+ "Izoh": "Комментарий",
+ "To'lovlar tarixi": "История платежей",
+ "Qolgan summani to‘lash": "Оплатить оставшуюся сумму",
+ "Update your personal information below and click save when done.": "Обновите следующую личную информацию и нажмите Сохранить когда это будет сделано.",
+ "Haqiqatan ham bu foydalanuvchini o'chirmoqchimisiz? Bu amalni qaytarib bo'lmaydi.": "Вы уверены, что хотите удалить этого пользователя? Это действие необратимо.",
+ "Agentlik": "Агентство",
+ "Telefon": "Телефон",
+ "Summasi": "Сумма",
+ "Buxgalter": "Бухгалтер",
+ "Sahifa": "Страница",
+ "Keyingi": "Следующий",
+ "Oldingi": "Дальше",
+ "Foydalanuvchini tahrirlash": "Редактировать пользователя",
+ "Haqiqatan ham": "Вы действительно хотите",
+ "ni tahrirlamoqchimisiz?": "отредактировать?",
+ "Agentlik ID": "ID агентства"
}
diff --git a/src/shared/config/i18n/locales/uz/translation.json b/src/shared/config/i18n/locales/uz/translation.json
index 6d75f75..ad9302a 100644
--- a/src/shared/config/i18n/locales/uz/translation.json
+++ b/src/shared/config/i18n/locales/uz/translation.json
@@ -493,5 +493,23 @@
"Yangi foydalanuvchi ma'lumotlari": "Yangi foydalanuvchi ma'lumotlari",
"Ikona tanlang": "Ikona tanlang",
"Agentlik uchun tizimga kirish ma'lumotlari": "Agentlik uchun tizimga kirish ma'lumotlari",
- "Haqiqatan ham bu foydalanuvchini o'chirmoqchimisiz? Bu amalni qaytarib bo'lmaydi.": "Haqiqatan ham bu foydalanuvchini o'chirmoqchimisiz? Bu amalni qaytarib bo'lmaydi."
+ "Update your personal information below and click save when done.": "Quyidagi shaxsiy axborotlaringizni yangilang va bajarilganda saqlash tugmasini bosing.",
+ "Last Name": "Familiya",
+ "First Name": "Ismi",
+ "Qolgan summani to‘lash": "Qolgan summani to‘lash",
+ "Summani kiriting": "Summani kiriting",
+ "Izoh": "Izoh",
+ "To'lovlar tarixi": "To'lovlar tarixi",
+ "Haqiqatan ham bu foydalanuvchini o'chirmoqchimisiz? Bu amalni qaytarib bo'lmaydi.": "Haqiqatan ham bu foydalanuvchini o'chirmoqchimisiz? Bu amalni qaytarib bo'lmaydi.",
+ "Agentlik": "Agentlik",
+ "Telefon": "Telefon",
+ "Summasi": "Summasi",
+ "Buxgalter": "Buxgalter",
+ "Sahifa": "Sahifa",
+ "Keyingi": "Keyingi",
+ "Oldingi": "Oldinga",
+ "Foydalanuvchini tahrirlash": "Foydalanuvchini tahrirlash",
+ "Haqiqatan ham": "Haqiqatan ham",
+ "ni tahrirlamoqchimisiz?": "ni tahrirlamoqchimisiz?",
+ "Agentlik ID": "Agentlik ID"
}
diff --git a/src/widgets/sidebar/ui/Sidebar.tsx b/src/widgets/sidebar/ui/Sidebar.tsx
index 34c0a1d..17a4e50 100644
--- a/src/widgets/sidebar/ui/Sidebar.tsx
+++ b/src/widgets/sidebar/ui/Sidebar.tsx
@@ -52,6 +52,12 @@ const MENU_ITEMS = [
path: "/user",
roles: ["moderator", "admin", "superuser", "operator"],
},
+ {
+ label: "Profile",
+ icon: Users,
+ path: "/profile",
+ roles: ["tour_admin"],
+ },
{
label: "Tur firmalar",
icon: Building2,
@@ -251,9 +257,10 @@ export function Sidebar({ role }: SidebarProps) {
);
})}
-
-
+
+
+
+
+
+ );
+
+ return (
+ <>
+ {!isMobile ? (
+
+ ) : (
+
setOpen(false)}
+ PaperProps={{
+ sx: {
+ borderTopLeftRadius: 16,
+ borderTopRightRadius: 16,
+ width: "100vw",
+ height: "auto",
+ display: "flex",
+ flexDirection: "column",
+ p: 2,
+ },
+ }}
+ >
+
+
+
+ {t("Давайте познакомимся!")}
+
+
+ {t("Чтобы завершить регистрацию, пожалуйста, укажите ваше имя")}
+
+
+
+
+ {formContent}
+
+ )}
+ >
);
};