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 */} + + + + + + + + {t("Foydalanuvchini tahrirlash")} + + + {t("Haqiqatan ham")}{" "} + + {user.first_name} {user.last_name} + {" "} + {t("ni tahrirlamoqchimisiz?")} + + - {/* Delete Alert 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 ( + + + + {t("Qolgan summani to‘lash")} + + +
+ {/* Summani kiritish */} +
+ + + {form.formState.errors.amount && ( +

+ {t("Summani to‘g‘ri kiriting")} +

+ )} +
+ + {/* Izoh */} +
+ + +
+ + {/* Tugmalar */} + + + + +
+
+
+ ); +} 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 ( + + + + {t("Tahrirlash")} + + {t( + "Update your personal information below and click save when done.", + )} + + + +
+
+ + setFirstName(e.target.value)} + placeholder={t("First Name")} + /> +
+
+ + setLastName(e.target.value)} + placeholder={t("Last Name")} + /> +
+
+ + + + + +
+
+ ); +} 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) { ); })} - - +
+ +
- + {isPending ? ( + + ) : ( + t("Сохранить") + )} + + + + ); + + return ( + <> + {!isMobile ? ( + + + + +
+

{t("Давайте познакомимся!")}

+

+ {t( + "Чтобы завершить регистрацию, пожалуйста, укажите ваше имя", + )} +

+
+ + + +
+ + {formContent} + +
+
+
+ ) : ( + setOpen(false)} + PaperProps={{ + sx: { + borderTopLeftRadius: 16, + borderTopRightRadius: 16, + width: "100vw", + height: "auto", + display: "flex", + flexDirection: "column", + p: 2, + }, + }} + > +
+
+

+ {t("Давайте познакомимся!")} +

+

+ {t("Чтобы завершить регистрацию, пожалуйста, укажите ваше имя")} +

+
+ +
+
{formContent}
+
+ )} + ); };