From 4e9b2f3bd866486ea75b1bedd268bb10250934bd Mon Sep 17 00:00:00 2001 From: Samandar Turgunboyev Date: Sat, 1 Nov 2025 16:18:36 +0500 Subject: [PATCH] bug fix --- index.html | 2 +- public/Logo.svg | 4 + src/App.tsx | 90 +++-- src/pages/tours/lib/api.ts | 61 +++ src/pages/tours/lib/column.tsx | 59 +++ src/pages/tours/lib/form.ts | 10 +- src/pages/tours/lib/store.ts | 43 ++- src/pages/tours/lib/type.ts | 43 ++- src/pages/tours/ui/Amenities.tsx | 357 ++++++++++++++++++ src/pages/tours/ui/StepOne.tsx | 323 ++++++++-------- src/pages/tours/ui/StepTwo.tsx | 74 ++-- src/pages/tours/ui/TourDetail.tsx | 1 + src/pages/tours/ui/ToursSetting.tsx | 35 +- src/shared/config/api/URLs.ts | 2 + .../config/i18n/locales/ru/translation.json | 1 + .../config/i18n/locales/uz/translation.json | 1 + src/shared/lib/iconTranslations.ts | 70 ++++ src/shared/ui/iocnSelect.tsx | 205 +++------- src/widgets/sidebar/ui/Sidebar.tsx | 2 +- 19 files changed, 959 insertions(+), 424 deletions(-) create mode 100644 public/Logo.svg create mode 100644 src/pages/tours/ui/Amenities.tsx create mode 100644 src/shared/lib/iconTranslations.ts diff --git a/index.html b/index.html index b86a998..c30e5ae 100644 --- a/index.html +++ b/index.html @@ -2,7 +2,7 @@ - + Simple Travel diff --git a/public/Logo.svg b/public/Logo.svg new file mode 100644 index 0000000..5d149b0 --- /dev/null +++ b/public/Logo.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/App.tsx b/src/App.tsx index 246eafe..5e6d524 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -33,6 +33,7 @@ import UserDetail from "@/pages/users/ui/UserDetail"; import { getMe } from "@/shared/config/api/auth/api"; import "@/shared/config/i18n"; import { getAuthToken } from "@/shared/lib/authCookies"; +import { cn } from "@/shared/lib/utils"; import { Sidebar } from "@/widgets/sidebar/ui/Sidebar"; import { useQuery } from "@tanstack/react-query"; import { useEffect } from "react"; @@ -77,47 +78,54 @@ const App = () => {
{shouldShowSidebar && } - - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } - /> - } /> - } /> - } /> - } - /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - +
+ + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } + /> + } /> + } /> + } /> + } + /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + +
); diff --git a/src/pages/tours/lib/api.ts b/src/pages/tours/lib/api.ts index e581fac..1a973ba 100644 --- a/src/pages/tours/lib/api.ts +++ b/src/pages/tours/lib/api.ts @@ -1,5 +1,7 @@ import type { + AllAmenitiesData, CreateTourRes, + DetailAmenitiesData, GetAllTours, GetDetailTours, GetHotelRes, @@ -19,6 +21,7 @@ import type { } from "@/pages/tours/lib/type"; import httpClient from "@/shared/config/api/httpClient"; import { + AMENITIES, GET_TICKET, HOTEL, HOTEL_BADGE, @@ -444,13 +447,71 @@ const hotelFeatureTypeDelete = async ({ id }: { id: number }) => { return response; }; +//Amenities + +const getAllAmenities = async ({ + page, + page_size, +}: { + page_size: number; + page: number; +}): Promise> => { + const response = await httpClient.get(AMENITIES, { + params: { page, page_size }, + }); + return response; +}; + +const amenitiesCreate = async ({ + body, +}: { + body: { + name: string; + name_ru: string; + icon_name: string; + }; +}) => { + const response = await httpClient.post(AMENITIES, body); + return response; +}; + +const deleteAmenities = async ({ id }: { id: number }) => { + const response = await httpClient.delete(`${AMENITIES}${id}/`); + return response; +}; + +const getDetailAmenities = async ({ + id, +}: { + id: number; +}): Promise> => { + const response = await httpClient.get(`${AMENITIES}${id}/`); + return response; +}; + +const amenitiesUpdate = async ({ + id, + body, +}: { + id: number; + body: { name: string; name_ru: string; icon_name: string }; +}) => { + const response = await httpClient.patch(`${AMENITIES}${id}/`, body); + return response; +}; + export { addedPopularTours, + amenitiesCreate, + amenitiesUpdate, createHotel, createTours, + deleteAmenities, deleteTours, editHotel, + getAllAmenities, getAllTours, + getDetailAmenities, getDetailToursId, getHotel, getOneTours, diff --git a/src/pages/tours/lib/column.tsx b/src/pages/tours/lib/column.tsx index 4bce532..549c857 100644 --- a/src/pages/tours/lib/column.tsx +++ b/src/pages/tours/lib/column.tsx @@ -1,6 +1,7 @@ "use client"; import type { + AllAmenitiesDataRes, Badge, HotelFeatures, HotelFeaturesType, @@ -10,6 +11,8 @@ import type { } from "@/pages/tours/lib/type"; import { Button } from "@/shared/ui/button"; import { type ColumnDef } from "@tanstack/react-table"; +import * as LucideIcons from "lucide-react"; +import { XIcon } from "lucide-react"; import type { Dispatch, SetStateAction } from "react"; import { useTranslation } from "react-i18next"; @@ -305,3 +308,59 @@ export const FeatureTypeColumns = ( }, }, ]; + +export const AmenitiesColumns = ( + onEdit: (id: number) => void, + onDelete: (id: number) => void, + t: (key: string) => string, +): ColumnDef[] => [ + { + accessorKey: "id", + header: "ID", + cell: ({ row }) => { + return {row.original.id}; + }, + }, + { + accessorKey: "name", + header: t("Nomi"), + cell: ({ row }) => {row.original.name}, + }, + { + accessorKey: "icon_name", + header: t("Icon"), + cell: ({ row }) => { + const Icon = (LucideIcons as any)[row.original.icon_name] || XIcon; + return ( + + + + ); + }, + }, + { + id: "actions", + header: () =>
{t("Harakatlar")}
, + cell: ({ row }) => { + const { t } = useTranslation(); + return ( +
+ + +
+ ); + }, + }, +]; diff --git a/src/pages/tours/lib/form.ts b/src/pages/tours/lib/form.ts index f82c32d..3f565c6 100644 --- a/src/pages/tours/lib/form.ts +++ b/src/pages/tours/lib/form.ts @@ -127,15 +127,7 @@ export const TourformSchema = z.object({ images: z .array(z.union([z.instanceof(File), z.string()])) .min(1, { message: "Kamida bitta rasm yuklang." }), - amenities: z - .array( - z.object({ - name: z.string().min(1, { message: "Majburiy maydon" }), - name_ru: z.string().min(1, { message: "Majburiy maydon" }), - icon_name: z.string().min(1, { message: "Majburiy maydon" }), - }), - ) - .min(1, { message: "Kamida bitta qulaylik kiriting." }), + amenities: z.array(z.number()).optional(), // 🔹 Quyidagilar endi ixtiyoriy (required emas) hotel_services: z diff --git a/src/pages/tours/lib/store.ts b/src/pages/tours/lib/store.ts index 31ac617..ef9052d 100644 --- a/src/pages/tours/lib/store.ts +++ b/src/pages/tours/lib/store.ts @@ -1,36 +1,41 @@ import { create } from "zustand"; -interface Amenity { - name: string; - name_ru: string; - icon_name: string; -} - interface TicketStore { - amenities: Amenity[]; + amenities: number[]; id: number | null; setId: (id: number) => void; - setAmenities: (amenities: Amenity[]) => void; - addAmenity: (amenity: Amenity) => void; - removeAmenity: (index: number) => void; - updateAmenity: (index: number, updated: Partial) => void; + setAmenities: (amenities: number[]) => void; + addAmenity: (amenity: number) => void; + removeAmenity: (id: number) => void; + updateAmenity: (index: number, updated: number) => void; } export const useTicketStore = create((set) => ({ amenities: [], id: null, + setId: (id) => set({ id }), + setAmenities: (amenities) => set({ amenities }), + addAmenity: (amenity) => - set((state) => ({ amenities: [...state.amenities, amenity] })), - removeAmenity: (index) => + set((state) => { + // agar qulaylik allaqachon mavjud bo‘lsa, qo‘shmaydi + if (state.amenities.includes(amenity)) return state; + return { amenities: [...state.amenities, amenity] }; + }), + + removeAmenity: (id) => set((state) => ({ - amenities: state.amenities.filter((_, i) => i !== index), + amenities: state.amenities.filter((a) => a !== id), })), + updateAmenity: (index, updated) => - set((state) => ({ - amenities: state.amenities.map((a, i) => - i === index ? { ...a, ...updated } : a, - ), - })), + set((state) => { + const newAmenities = [...state.amenities]; + if (index >= 0 && index < newAmenities.length) { + newAmenities[index] = updated; + } + return { amenities: newAmenities }; + }), })); diff --git a/src/pages/tours/lib/type.ts b/src/pages/tours/lib/type.ts index 438cdb6..0ac80fb 100644 --- a/src/pages/tours/lib/type.ts +++ b/src/pages/tours/lib/type.ts @@ -75,14 +75,7 @@ export interface GetOneTours { image: string; }, ]; - ticket_amenities: [ - { - name: string; - name_ru: string; - name_uz: string; - icon_name: string; - }, - ]; + ticket_amenities: number[]; ticket_included_services: [ { image: string; @@ -530,3 +523,37 @@ export interface GetHotelRes { ]; }; } + +export interface AllAmenitiesData { + status: boolean; + data: { + links: { + previous: string; + next: string; + }; + total_items: number; + total_pages: number; + page_size: number; + current_page: number; + results: AllAmenitiesDataRes[]; + }; +} + +export interface AllAmenitiesDataRes { + id: number; + name: string; + name_ru: string; + name_uz: string; + icon_name: string; +} + +export interface DetailAmenitiesData { + status: boolean; + data: { + id: number; + name: string; + name_ru: string; + name_uz: string; + icon_name: string; + }; +} diff --git a/src/pages/tours/ui/Amenities.tsx b/src/pages/tours/ui/Amenities.tsx new file mode 100644 index 0000000..fec0d44 --- /dev/null +++ b/src/pages/tours/ui/Amenities.tsx @@ -0,0 +1,357 @@ +import { + amenitiesCreate, + amenitiesUpdate, + deleteAmenities, + getDetailAmenities, +} from "@/pages/tours/lib/api"; +import { AmenitiesColumns } from "@/pages/tours/lib/column"; +import type { AllAmenitiesDataRes } from "@/pages/tours/lib/type"; +import { Button } from "@/shared/ui/button"; +import { Dialog, DialogContent } from "@/shared/ui/dialog"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/shared/ui/form"; +import { Input } from "@/shared/ui/input"; +import IconSelect from "@/shared/ui/iocnSelect"; +import { Label } from "@/shared/ui/label"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/shared/ui/table"; +import RealPagination from "@/widgets/real-pagination/ui/RealPagination"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { + flexRender, + getCoreRowModel, + useReactTable, +} from "@tanstack/react-table"; +import { Loader, PlusIcon } from "lucide-react"; +import { useEffect, useState } from "react"; +import { useForm } from "react-hook-form"; +import { useTranslation } from "react-i18next"; +import { toast } from "sonner"; +import z from "zod"; + +const formSchema = z.object({ + name: z.string().min(1, { message: "Majburiy maydon" }), + name_ru: z.string().min(1, { message: "Majburiy maydon" }), + icon_name: z.string().min(1, { message: "Majburiy maydon" }), +}); + +const Amenities = ({ + data, + page, + pageSize, +}: { + page: number; + pageSize: number; + data: + | { + links: { + previous: string; + next: string; + }; + total_items: number; + total_pages: number; + page_size: number; + current_page: number; + results: AllAmenitiesDataRes[]; + } + | undefined; +}) => { + const { t } = useTranslation(); + const [open, setOpen] = useState(false); + const [editId, setEditId] = useState(null); + const queryClient = useQueryClient(); + const [types, setTypes] = useState<"edit" | "create">("create"); + + const handleEdit = (id: number) => { + setTypes("edit"); + setOpen(true); + setEditId(id); + }; + + const { data: badgeDetail } = useQuery({ + queryKey: ["detail_amenities", editId], + queryFn: () => getDetailAmenities({ id: editId! }), + enabled: !!editId, + }); + + const { mutate: deleteMutate } = useMutation({ + mutationFn: ({ id }: { id: number }) => deleteAmenities({ id }), + onSuccess: () => { + queryClient.refetchQueries({ queryKey: ["detail_amenities"] }); + queryClient.refetchQueries({ queryKey: ["all_amenities"] }); + setOpen(false); + form.reset(); + }, + onError: () => { + toast.error(t("Xatolik yuz berdi"), { + position: "top-center", + richColors: true, + }); + }, + }); + + const handleDelete = (id: number) => { + deleteMutate({ id }); + }; + + const columns = AmenitiesColumns(handleEdit, handleDelete, t); + + const { mutate: create, isPending } = useMutation({ + mutationFn: ({ + body, + }: { + body: { name: string; name_ru: string; icon_name: string }; + }) => amenitiesCreate({ body }), + onSuccess: () => { + queryClient.refetchQueries({ queryKey: ["detail_amenities"] }); + queryClient.refetchQueries({ queryKey: ["all_amenities"] }); + setOpen(false); + form.reset(); + }, + onError: () => { + toast.error(t("Xatolik yuz berdi"), { + position: "top-center", + richColors: true, + }); + }, + }); + + const { mutate: update, isPending: updatePending } = useMutation({ + mutationFn: ({ + body, + id, + }: { + id: number; + body: { name: string; name_ru: string; icon_name: string }; + }) => amenitiesUpdate({ body, id }), + onSuccess: () => { + queryClient.refetchQueries({ queryKey: ["detail_amenities"] }); + queryClient.refetchQueries({ queryKey: ["all_amenities"] }); + setOpen(false); + form.reset(); + }, + onError: () => { + toast.error(t("Xatolik yuz berdi"), { + position: "top-center", + richColors: true, + }); + }, + }); + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + name: "", + name_ru: "", + }, + }); + + useEffect(() => { + if (badgeDetail) { + form.setValue("icon_name", badgeDetail.data.data.icon_name); + form.setValue("name", badgeDetail.data.data.name_uz); + form.setValue("name_ru", badgeDetail.data.data.name_ru); + } + }, [editId, badgeDetail]); + + function onSubmit(values: z.infer) { + if (types === "create") { + create({ + body: { + icon_name: values.icon_name, + name: values.name, + name_ru: values.name_ru, + }, + }); + } else if (types === "edit" && editId) { + update({ + id: editId, + body: { + icon_name: values.icon_name, + name: values.name, + name_ru: values.name_ru, + }, + }); + } + } + + const table = useReactTable({ + data: data?.results ?? [], + columns, + getCoreRowModel: getCoreRowModel(), + manualPagination: true, + pageCount: data?.total_pages ?? 0, + state: { + pagination: { + pageIndex: page - 1, + pageSize: pageSize, + }, + }, + }); + + return ( + <> +
+ +
+ +
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext(), + )} + + ))} + + ))} + + + + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext(), + )} + + ))} + + )) + ) : ( + + + {t("Ma'lumot topilmadi")} + + + )} + +
+
+ + + + + +

+ {types === "create" ? t("Saqlash") : t("Tahrirlash")} +

+
+ + ( + + + +
+ field.onChange(val)} + /> +
+ + +
+ )} + /> + + ( + + {t("Nomi")} + + + + + + )} + /> + ( + + {t("Nomi (ru)")} + + + + + + )} + /> + +
+ + +
+ + +
+
+ + ); +}; + +export default Amenities; diff --git a/src/pages/tours/ui/StepOne.tsx b/src/pages/tours/ui/StepOne.tsx index ec104b5..2a72e74 100644 --- a/src/pages/tours/ui/StepOne.tsx +++ b/src/pages/tours/ui/StepOne.tsx @@ -2,6 +2,7 @@ import { createTours, + getAllAmenities, hotelBadge, hotelTarif, hotelTransport, @@ -29,14 +30,12 @@ import { FormMessage, } from "@/shared/ui/form"; import { Input } from "@/shared/ui/input"; -import IconSelect from "@/shared/ui/iocnSelect"; import { Label } from "@/shared/ui/label"; import { Popover, PopoverContent, PopoverTrigger } from "@/shared/ui/popover"; 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 * as LucideIcons from "lucide-react"; import { ChevronDownIcon, SquareCheckBig, XIcon } from "lucide-react"; import { useEffect, useState, type Dispatch, type SetStateAction } from "react"; import { useForm } from "react-hook-form"; @@ -105,7 +104,7 @@ const StepOne = ({ }, }); - const { addAmenity, setId } = useTicketStore(); + const { addAmenity, setId, removeAmenity } = useTicketStore(); useEffect(() => { if (!isEditMode || !data?.data) return; @@ -158,14 +157,7 @@ const StepOne = ({ } // 🔹 Qulayliklar (amenities) - form.setValue( - "amenities", - tour.ticket_amenities?.map((a) => ({ - name: a.name ?? "", - name_ru: a.name_ru ?? "", - icon_name: a.icon_name ?? "", - })) ?? [], - ); + form.setValue("amenities", tour.ticket_amenities); // 🔹 Xizmatlar (hotel_services) form.setValue( @@ -258,7 +250,6 @@ const StepOne = ({ const { watch, setValue } = form; const selectedDate = watch("departureDateTime.date"); const selectedDateTravel = watch("travelDateTime.date"); - const [selectedIcon, setSelectedIcon] = useState(""); const { mutate: create } = useMutation({ mutationFn: (body: FormData) => { @@ -341,24 +332,20 @@ const StepOne = ({ value.badges?.forEach((e, i) => { formData.append(`badge[${i}]`, String(e)); }); + value.amenities?.forEach((e, i) => { + formData.append(`ticket_amenities[${i}]`, String(e)); + }); value.images.forEach((e) => { if (e instanceof File) { formData.append("ticket_images", e); } }); - value.amenities.forEach((e, i) => { - formData.append(`ticket_amenities[${i}]name`, e.name); - formData.append(`ticket_amenities[${i}]name_ru`, e.name_ru); - formData.append(`ticket_amenities[${i}]icon_name`, e.icon_name); - addAmenity({ - icon_name: e.icon_name, - name: e.name, - name_ru: e.name_ru, - }); + value.amenities?.forEach((e, i) => { + formData.append(`badge[${i}]`, String(e)); }); value.hotel_services && value.hotel_services.forEach((e, i) => { - if (e instanceof File) { + if (e.image instanceof File) { formData.append(`ticket_included_services[${i}]image`, e.image); formData.append(`ticket_included_services[${i}]title`, e.title); formData.append(`ticket_included_services[${i}]title_ru`, e.title_ru); @@ -366,31 +353,43 @@ const StepOne = ({ formData.append(`ticket_included_services[${i}]desc`, e.description); } }); - value.ticket_itinerary.forEach((e, i) => { - e.ticket_itinerary_image.forEach((l, f) => { - if (e instanceof File) { - formData.append(`ticket_itinerary[${i}]title`, e.title); - formData.append(`ticket_itinerary[${i}]title_ru`, e.title_ru); - formData.append(`ticket_itinerary[${i}]duration`, String(e.duration)); + value.ticket_itinerary?.forEach((itinerary, i) => { + formData.append(`ticket_itinerary[${i}]title`, itinerary.title); + formData.append(`ticket_itinerary[${i}]title_ru`, itinerary.title_ru); + formData.append( + `ticket_itinerary[${i}]duration`, + String(itinerary.duration), + ); + + // Rasmlar + if (Array.isArray(itinerary.ticket_itinerary_image)) { + itinerary.ticket_itinerary_image.forEach((img, j) => { + const file = img instanceof File ? img : img.image; + if (file) { + formData.append( + `ticket_itinerary[${i}]ticket_itinerary_image[${j}]image`, + file, + ); + } + }); + } + + // Destinations + if (Array.isArray(itinerary.ticket_itinerary_destinations)) { + itinerary.ticket_itinerary_destinations.forEach((dest, k) => { formData.append( - `ticket_itinerary[${i}]ticket_itinerary_image[${f}]image`, - l.image, + `ticket_itinerary[${i}]ticket_itinerary_destinations[${k}]name`, + dest.name, ); - e.ticket_itinerary_destinations.forEach((e, f) => { - formData.append( - `ticket_itinerary[${i}]ticket_itinerary_destinations[${f}]name`, - String(e.name), - ); - formData.append( - `ticket_itinerary[${i}]ticket_itinerary_destinations[${f}]name_ru`, - String(e.name_ru), - ); - }); - } - }); + formData.append( + `ticket_itinerary[${i}]ticket_itinerary_destinations[${k}]name_ru`, + dest.name_ru, + ); + }); + } }); value.hotel_meals.forEach((e, i) => { - if (e instanceof File) { + if (e.image instanceof File) { formData.append(`ticket_hotel_meals[${i}]image`, e.image); formData.append(`ticket_hotel_meals[${i}]name`, e.title); formData.append(`ticket_hotel_meals[${i}]name_ru`, e.title_ru); @@ -419,13 +418,16 @@ const StepOne = ({ } } - console.log(form.formState.errors); - const { data: badge } = useQuery({ queryKey: ["all_badge"], queryFn: () => hotelBadge({ page: 1, page_size: 10 }), }); + const { data: amenitiesData } = useQuery({ + queryKey: ["all_amenities"], + queryFn: () => getAllAmenities({ page: 1, page_size: 10 }), + }); + const { data: tariff } = useQuery({ queryKey: ["all_tarif"], queryFn: () => hotelTarif({ page: 1, page_size: 10 }), @@ -973,6 +975,114 @@ const StepOne = ({ )} /> + ( + + + +
+ {(form.watch("amenities") ?? []).length > 0 && + (form.watch("amenities") ?? []).map((badgeId: number) => { + const badgeItem = + amenitiesData?.data?.data?.results?.find( + (b: any) => b.id === badgeId, + ); + + return ( + + {badgeItem?.name || `Qulaylilar #${badgeId}`} + + + ); + })} +
+ + + + + + + + + + + {badge?.data?.data?.results?.map((item: any) => { + const currentBadges = + form.getValues("amenities") ?? []; + const selected = currentBadges.includes(item.id); + + return ( + { + const selected = currentBadges.includes( + item.id, + ); + + if (selected) { + removeAmenity(item.id); + form.setValue( + "amenities", + currentBadges.filter( + (b: number) => b !== item.id, + ), + ); + } else { + addAmenity(item.id); + form.setValue("amenities", [ + ...currentBadges, + item.id, + ]); + } + }} + className="flex items-center gap-2" + > + + {item.name} + + ); + })} + + + + + + + +
+ )} + /> + { const raw = e.target.value.replace(/\D/g, ""); const num = Number(raw); - const currentTarifs = form.getValues("tarif") || []; - const updatedTransport = currentTarifs.map((t, i) => - i === idx ? { ...t, price: num } : t, + const currentTarifs = + form.getValues("tarif") || []; + const updatedTransport = currentTarifs.map( + (t, i) => + i === idx ? { ...t, price: num } : t, ); form.setValue("tarif", updatedTransport); @@ -1057,7 +1169,9 @@ const StepOne = ({ {tariff?.data?.data?.results?.map((item: any) => { const currentTarifs = form.getValues("tarif") || []; - const selected = currentTarifs.some((t) => t.tariff === item.id); + const selected = currentTarifs.some( + (t) => t.tariff === item.id, + ); return ( { const raw = e.target.value.replace(/\D/g, ""); const num = Number(raw); - const currentTransports = form.getValues("transport") || []; - const updatedTransport = currentTransports.map((t, i) => - i === idx ? { ...t, price: num } : t, + const currentTransports = + form.getValues("transport") || []; + const updatedTransport = currentTransports.map( + (t, i) => + i === idx ? { ...t, price: num } : t, ); form.setValue("transport", updatedTransport); @@ -1190,14 +1306,18 @@ const StepOne = ({ {transport?.data?.data?.results?.map((item: any) => { - const currentTransports = form.getValues("transport") || []; - const selected = currentTransports.some((t) => t.transport === item.id); + const currentTransports = + form.getValues("transport") || []; + const selected = currentTransports.some( + (t) => t.transport === item.id, + ); return ( { - const current = form.getValues("transport") || []; + const current = + form.getValues("transport") || []; if (selected) { form.setValue( "transport", @@ -1279,99 +1399,6 @@ const StepOne = ({ imageUrl={data?.data.ticket_images?.map((img) => img.image)} /> - ( - - - -
-
- {form.watch("amenities").map((item, idx) => { - const Icon = (LucideIcons as any)[item.icon_name] || XIcon; - return ( - - - {item.name} - - - ); - })} -
-
-
- -
- - - - - - -
- - -
-
- )} - /> - { - if (selectedHotelFeatures.length === 0) { + if (selectedHotelFeatures && selectedHotelFeatures.length === 0) { setAllHotelFeatureType([]); setFeatureTypeMapping({}); return; } const loadFeatureTypes = async () => { - const selectedIds = selectedHotelFeatures.map(Number).filter(Boolean); + const selectedIds = + selectedHotelFeatures && + selectedHotelFeatures.map(Number).filter(Boolean); let allResults: HotelFeaturesType[] = []; const mapping: Record = {}; - for (const id of selectedIds) { + for (const id of selectedIds!) { let page = 1; let hasNext = true; const featureTypes: string[] = []; @@ -253,12 +249,12 @@ const StepTwo = ({ const removeHotelType = (id: string) => form.setValue( "hotelType", - form.getValues("hotelType").filter((v) => v !== id), + (form.getValues("hotelType") ?? []).filter((v) => v !== id), ); const removeHotelFeature = (id: string) => { - const current = form.getValues("hotelFeatures"); - const types = form.getValues("hotelFeaturesType"); + const current = form.getValues("hotelFeatures") ?? []; + const types = form.getValues("hotelFeaturesType") ?? []; const toRemove = featureTypeMapping[id] || []; form.setValue( @@ -274,7 +270,7 @@ const StepTwo = ({ const removeFeatureType = (id: string) => form.setValue( "hotelFeaturesType", - form.getValues("hotelFeaturesType").filter((v) => v !== id), + (form.getValues("hotelFeaturesType") ?? []).filter((v) => v !== id), ); // 🧩 Submit @@ -284,6 +280,9 @@ const StepTwo = ({ formData.append("ticket", ticketId ? String(ticketId) : ""); formData.append("name", data.title); formData.append("rating", data.rating); + amenities.forEach((e, i) => { + formData.append(`hotel_amenities[${i}]`, String(e)); + }); const mealPlan = data.mealPlan === "Breakfast Only" @@ -295,14 +294,10 @@ const StepTwo = ({ : "all_inclusive"; formData.append("meal_plan", mealPlan); - data.hotelType.forEach((id) => formData.append("hotel_type", id)); - data.hotelFeatures.forEach((id) => formData.append("hotel_features", id)); - - amenities.forEach((e, i) => { - formData.append(`hotel_amenities[${i}]name`, e.name); - formData.append(`hotel_amenities[${i}]name_ru`, e.name_ru); - formData.append(`hotel_amenities[${i}]icon_name`, e.icon_name); - }); + data.hotelType && + data.hotelType.forEach((id) => formData.append("hotel_type", id)); + data.hotelFeatures && + data.hotelFeatures.forEach((id) => formData.append("hotel_features", id)); if (isEditMode && hotelDetail) { edit({ @@ -416,7 +411,7 @@ const StepTwo = ({
- {field.value.length > 0 && ( + {field.value && field.value.length > 0 && (
{field.value.map((selectedValue) => { const selectedItem = allHotelTypes.find( @@ -444,7 +439,7 @@ const StepTwo = ({ { - if (!field.value.includes(value)) { + if (field.value && !field.value.includes(value)) { field.onChange([...field.value, value]); } }} @@ -528,7 +525,7 @@ const StepTwo = ({ 0 + field.value && field.value.length > 0 ? t("Yana tanlang...") : t("Tanlang") } @@ -537,7 +534,9 @@ const StepTwo = ({ {allHotelFeature .filter( - (type) => !field.value.includes(String(type.id)), + (type) => + field.value && + !field.value.includes(String(type.id)), ) .map((type) => ( @@ -562,7 +561,7 @@ const StepTwo = ({
- {field.value.length > 0 && ( + {field.value && field.value.length > 0 && (
{field.value.map((selectedValue) => { const selectedItem = allHotelFeatureType.find( @@ -590,7 +589,7 @@ const StepTwo = ({ - - - {selectedIcon ? ( -
- - {selectedIcon} -
- ) : ( -
- - {defaultIcon} -
- )} -
-
- - -
- - setSearchTerm(e.target.value)} - className="h-8 text-sm" - /> -
- - {filteredIcons.map((iconName) => ( - -
- - {iconName} -
-
- ))} - - {!searchTerm && isOpen && ( -
- {visibleIcons.length < icons.length && ( - - {t("Yuklanmoqda...")} - - )} -
- )} -
- - ); }; -export default IconSelect; +export default function IconSelect({ + selectedIcon, + setSelectedIcon, +}: IconSelectProps) { + const [search, setSearch] = useState(""); + + const filteredIcons = hotelIcons.filter( + (icon) => + icon.uz.toLowerCase().includes(search.toLowerCase()) || + icon.ru.toLowerCase().includes(search.toLowerCase()) || + icon.name.toLowerCase().includes(search.toLowerCase()), + ); + + return ( +
+ setSearch(e.target.value)} + /> + {filteredIcons.map(({ name, uz, ru }) => { + const LucideIcon = (Icons as any)[name]; + if (!LucideIcon) return null; + return ( + +
+ + {uz} ({ru}) +
+
+ ); + })} + + +
+ ); +} diff --git a/src/widgets/sidebar/ui/Sidebar.tsx b/src/widgets/sidebar/ui/Sidebar.tsx index 541856e..34c0a1d 100644 --- a/src/widgets/sidebar/ui/Sidebar.tsx +++ b/src/widgets/sidebar/ui/Sidebar.tsx @@ -268,7 +268,7 @@ export function Sidebar({ role }: SidebarProps) { ); return ( -
+
{/* Mobil versiya */}