From da46b6cac1c086558e7a1eae9ef6f58201b17ec8 Mon Sep 17 00:00:00 2001 From: Samandar Turgunboyev Date: Thu, 4 Dec 2025 12:41:01 +0500 Subject: [PATCH] coutry crud added --- package-lock.json | 8 +- package.json | 2 +- src/pages/tours/lib/api.ts | 130 ++++ src/pages/tours/lib/column.tsx | 110 ++++ src/pages/tours/lib/form.ts | 20 +- src/pages/tours/lib/type.ts | 134 +++- src/pages/tours/ui/CountryTable.tsx | 340 +++++++++++ src/pages/tours/ui/HotelType.tsx | 324 ++++++++++ src/pages/tours/ui/MealTable.tsx | 77 ++- src/pages/tours/ui/RegionTable.tsx | 335 ++++++++++ src/pages/tours/ui/StepOne.tsx | 572 ++++++++++++++++-- src/pages/tours/ui/StepTwo.tsx | 156 +++-- src/pages/tours/ui/TourDetail.tsx | 4 +- src/pages/tours/ui/Tours.tsx | 8 +- src/pages/tours/ui/ToursSetting.tsx | 122 +++- src/shared/config/api/URLs.ts | 6 + .../config/i18n/locales/ru/translation.json | 2 + .../config/i18n/locales/uz/translation.json | 2 + src/shared/ui/popover.tsx | 4 +- 19 files changed, 2193 insertions(+), 163 deletions(-) create mode 100644 src/pages/tours/ui/CountryTable.tsx create mode 100644 src/pages/tours/ui/HotelType.tsx create mode 100644 src/pages/tours/ui/RegionTable.tsx diff --git a/package-lock.json b/package-lock.json index 80dce6d..9778a69 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,7 +36,7 @@ "date-fns": "^4.1.0", "dayjs": "^1.11.18", "embla-carousel-react": "^8.6.0", - "framer-motion": "^12.23.24", + "framer-motion": "^12.23.25", "i18next": "^25.5.2", "i18next-browser-languagedetector": "^8.2.0", "js-cookie": "^3.0.5", @@ -4401,9 +4401,9 @@ } }, "node_modules/framer-motion": { - "version": "12.23.24", - "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.24.tgz", - "integrity": "sha512-HMi5HRoRCTou+3fb3h9oTLyJGBxHfW+HnNE25tAXOvVx/IvwMHK0cx7IR4a2ZU6sh3IX1Z+4ts32PcYBOqka8w==", + "version": "12.23.25", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.25.tgz", + "integrity": "sha512-gUHGl2e4VG66jOcH0JHhuJQr6ZNwrET9g31ZG0xdXzT0CznP7fHX4P8Bcvuc4MiUB90ysNnWX2ukHRIggkl6hQ==", "license": "MIT", "dependencies": { "motion-dom": "^12.23.23", diff --git a/package.json b/package.json index 02fcfcf..482fc37 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "date-fns": "^4.1.0", "dayjs": "^1.11.18", "embla-carousel-react": "^8.6.0", - "framer-motion": "^12.23.24", + "framer-motion": "^12.23.25", "i18next": "^25.5.2", "i18next-browser-languagedetector": "^8.2.0", "js-cookie": "^3.0.5", diff --git a/src/pages/tours/lib/api.ts b/src/pages/tours/lib/api.ts index 97e04c1..4642109 100644 --- a/src/pages/tours/lib/api.ts +++ b/src/pages/tours/lib/api.ts @@ -1,5 +1,7 @@ import type { AllAmenitiesData, + CountryDeatil, + CountryList, CreateTourRes, DetailAmenitiesData, GetAllTours, @@ -18,10 +20,13 @@ import type { HotelAllFeaturesType, HotelFeaturesDetail, HotelFeaturesTypeDetail, + HotelMealdetail, + HotelMealList, } from "@/pages/tours/lib/type"; import httpClient from "@/shared/config/api/httpClient"; import { AMENITIES, + COUNTRY, GET_TICKET, HOTEL, HOTEL_BADGE, @@ -29,7 +34,9 @@ import { HOTEL_FEATURES_TYPE, HOTEL_TARIF, HPTEL_TYPES, + MEAL_PLAN, POPULAR_TOURS, + REGION, TOUR_TRANSPORT, } from "@/shared/config/api/URLs"; import type { AxiosResponse } from "axios"; @@ -500,10 +507,124 @@ const amenitiesUpdate = async ({ return response; }; +const countryList = async (params: { + page: number; + page_size: number; + name?: string; +}): Promise> => { + const res = await httpClient.get(COUNTRY, { params }); + return res; +}; + +const countryDeatil = async ( + id: number, +): Promise> => { + const res = await httpClient.get(`${COUNTRY}${id}/`); + return res; +}; + +const countryCreate = async (body: { name: string; name_ru: string }) => { + const res = await httpClient.post(`${COUNTRY}`, body); + return res; +}; +const countryUpdate = async ({ + body, + id, +}: { + id: number; + body: { name: string; name_ru: string }; +}) => { + const res = await httpClient.patch(`${COUNTRY}${id}/`, body); + return res; +}; + +const countryDelete = async (id: number) => { + const res = await httpClient.delete(`${COUNTRY}${id}/`); + return res; +}; + +const regionList = async (params: { + page: number; + name?: string; + country: number; + page_size: number; +}): Promise> => { + const res = await httpClient.get(REGION, { params }); + return res; +}; + +const regionDeatil = async ( + id: number, +): Promise> => { + const res = await httpClient.get(`${REGION}${id}/`); + return res; +}; + +const regionCreate = async (body: { + name: string; + name_ru: string; + country: number; +}) => { + const res = await httpClient.post(`${REGION}`, body); + return res; +}; +const regionUpdate = async ({ + body, + id, +}: { + id: number; + body: { name: string; name_ru: string; country: number }; +}) => { + const res = await httpClient.patch(`${REGION}${id}/`, body); + return res; +}; + +const hotelMealList = async (params: { + page: number; + page_size: number; + name?: string; +}): Promise> => { + const res = await httpClient.get(`${MEAL_PLAN}`, { params }); + return res; +}; + +const hotelMealDetail = async ( + id: number, +): Promise> => { + const res = await httpClient.get(`${MEAL_PLAN}${id}/`); + return res; +}; + +const hotelMealDelete = async (id: number) => { + const res = await httpClient.delete(`${MEAL_PLAN}${id}/`); + return res; +}; + +const hotelMealCreate = async (body: { name: string; name_ru: string }) => { + const res = await httpClient.post(`${MEAL_PLAN}`, body); + return res; +}; + +const hotelMealUpdate = async ({ + body, + id, +}: { + id: number; + body: { name: string; name_ru: string }; +}) => { + const res = await httpClient.patch(`${MEAL_PLAN}${id}/`, body); + return res; +}; + export { addedPopularTours, amenitiesCreate, amenitiesUpdate, + countryCreate, + countryDeatil, + countryDelete, + countryList, + countryUpdate, createHotel, createTours, deleteAmenities, @@ -530,6 +651,11 @@ export { hotelFeatureTypeDetail, hotelFeatureTypeUpdate, hotelFeatureUpdate, + hotelMealCreate, + hotelMealDelete, + hotelMealDetail, + hotelMealList, + hotelMealUpdate, hotelTarfiDetail, hotelTarif, hotelTarifCreate, @@ -545,5 +671,9 @@ export { hotelTypeDelete, hotelTypeDetail, hotelTypeUpdate, + regionCreate, + regionDeatil, + regionList, + regionUpdate, updateTours, }; diff --git a/src/pages/tours/lib/column.tsx b/src/pages/tours/lib/column.tsx index 549c857..0526cb2 100644 --- a/src/pages/tours/lib/column.tsx +++ b/src/pages/tours/lib/column.tsx @@ -3,8 +3,10 @@ import type { AllAmenitiesDataRes, Badge, + CountryLisResult, HotelFeatures, HotelFeaturesType, + HotelMealListData, Tarif, Transport, Type, @@ -364,3 +366,111 @@ export const AmenitiesColumns = ( }, }, ]; + +export const CountryColumns = ( + onEdit: (id: number) => void, + onDelete: (id: number) => void, + t: (key: string) => string, + setActiveTab?: Dispatch>, + setFeatureId?: Dispatch>, +): ColumnDef[] => [ + { + accessorKey: "id", + header: "ID", + cell: ({ row }) => {row.original.id}, + }, + { + accessorKey: "name", + header: t("Nomi"), + cell: ({ row }) => {row.original.name_uz}, + }, + { + accessorKey: "name", + header: () =>
{t("Nomi")} (ru)
, + cell: ({ row }) => {row.original.name_ru}, + }, + { + id: "actions", + header: () =>
{t("Harakatlar")}
, + cell: ({ row }) => { + const { t } = useTranslation(); + return ( +
+ {setActiveTab && setFeatureId && ( + + )} + + +
+ ); + }, + }, +]; + +export const MealColumns = ( + onEdit: (id: number) => void, + onDelete: (id: number) => void, + t: (key: string) => string, +): ColumnDef[] => [ + { + accessorKey: "id", + header: "ID", + cell: ({ row }) => {row.original.id}, + }, + { + accessorKey: "name", + header: t("Nomi"), + cell: ({ row }) => {row.original.name_uz}, + }, + { + accessorKey: "name", + header: () =>
{t("Nomi")} (ru)
, + cell: ({ row }) => {row.original.name_ru}, + }, + { + 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 0dab75f..d3bdb4b 100644 --- a/src/pages/tours/lib/form.ts +++ b/src/pages/tours/lib/form.ts @@ -23,18 +23,18 @@ export const TourformSchema = z.object({ max_person: z.string().min(1, { message: "Kamida 1 yo'lovchi bo'lishi kerak.", }), - departure: z.string().min(2, { - message: "Ketish joyi eng kamida 2 ta belgidan iborat bo'lishi kerak.", + departure: z.string().min(1, { + message: "Ketish joyi eng kamida 1 ta belgidan iborat bo'lishi kerak.", }), - departure_ru: z.string().min(2, { - message: "Ketish joyi eng kamida 2 ta belgidan iborat bo'lishi kerak.", - }), - destination: z.string().min(2, { - message: "Borish joyi eng kamida 2 ta belgidan iborat bo'lishi kerak.", - }), - destination_ru: z.string().min(2, { - message: "Borish joyi eng kamida 2 ta belgidan iborat bo'lishi kerak.", + // departure_ru: z.string().min(2, { + // message: "Ketish joyi eng kamida 2 ta belgidan iborat bo'lishi kerak.", + // }), + destination: z.string().min(1, { + message: "Borish joyi eng kamida 1 ta belgidan iborat bo'lishi kerak.", }), + // destination_ru: z.string().min(2, { + // message: "Borish joyi eng kamida 2 ta belgidan iborat bo'lishi kerak.", + // }), location_name: z.string().min(2, { message: "Eng kamida 2 ta belgidan iborat bo'lishi kerak.", }), diff --git a/src/pages/tours/lib/type.ts b/src/pages/tours/lib/type.ts index fda6fe6..3cee5b6 100644 --- a/src/pages/tours/lib/type.ts +++ b/src/pages/tours/lib/type.ts @@ -11,7 +11,18 @@ export interface GetAllTours { current_page: number; results: { id: number; - destination: string; + destination: + | { + id: number; + name: string; + name_ru: string; + country: { + id: number; + name: string; + name_ru: string; + }; + } + | string; featured_tickets: boolean; duration_days: number; hotel_name: string; @@ -35,12 +46,26 @@ export interface GetOneTours { price: number; min_person: number; max_person: number; - departure: string; - departure_ru: string; - departure_uz: string; - destination: string; - destination_ru: string; - destination_uz: string; + departure: { + id: number; + name: string; + name_ru: string; + country: { + id: number; + name: string; + name_ru: string; + }; + }; + destination: { + id: number; + name: string; + name_ru: string; + country: { + id: number; + name: string; + name_ru: string; + }; + }; departure_time: string; travel_time: string; location_name: string; @@ -151,10 +176,10 @@ export interface CreateTourRes { price: number; min_person: number; max_person: number; - departure: string; - departure_ru: string; - destination: string; - destination_ru: string; + departure: number; + // departure_ru: string; + destination: number; + // destination_ru: string; departure_time: string; travel_time: string; location_name: string; @@ -349,6 +374,33 @@ export interface HotelFeaturesTypeDetail { }; } +export interface HotelMealList { + status: boolean; + data: { + links: { + previous: null | string; + next: null | string; + }; + total_items: number; + total_pages: number; + page_size: number; + current_page: number; + results: HotelMealListData[]; + }; +} + +export interface HotelMealdetail { + status: boolean; + data: HotelMealListData; +} + +export interface HotelMealListData { + id: number; + name: string; + name_ru: string; + name_uz: string; +} + export interface GetDetailTours { status: boolean; data: { @@ -363,12 +415,26 @@ export interface GetDetailTours { price: number; min_person: number; max_person: number; - departure: string; - departure_ru: string; - departure_uz: string; - destination: string; - destination_ru: string; - destination_uz: string; + departure: { + id: number; + name: string; + name_ru: string; + country: { + id: number; + name: string; + name_ru: string; + }; + }; + destination: { + id: number; + name: string; + name_ru: string; + country: { + id: number; + name: string; + name_ru: string; + }; + }; departure_time: string; travel_time: string; location_name: string; @@ -492,7 +558,7 @@ export interface GetHotelRes { id: number; name: string; rating: number; - meal_plan: string; + meal_plan: number; ticket: number; hotel_type: [ { @@ -560,3 +626,35 @@ export interface DetailAmenitiesData { icon_name: string; }; } + +export interface CountryList { + status: boolean; + data: { + links: { + previous: null | string; + next: null | string; + }; + total_items: number; + total_pages: number; + page_size: number; + current_page: number; + results: CountryLisResult[]; + }; +} + +export interface CountryLisResult { + id: number; + name: string; + name_ru: string; + name_uz: string; +} + +export interface CountryDeatil { + status: boolean; + data: { + id: number; + name: string; + name_ru: string; + name_uz: string; + }; +} diff --git a/src/pages/tours/ui/CountryTable.tsx b/src/pages/tours/ui/CountryTable.tsx new file mode 100644 index 0000000..e7201fa --- /dev/null +++ b/src/pages/tours/ui/CountryTable.tsx @@ -0,0 +1,340 @@ +import { + countryCreate, + countryDeatil, + countryDelete, + countryUpdate, +} from "@/pages/tours/lib/api"; +import { CountryColumns } from "@/pages/tours/lib/column"; +import type { CountryLisResult } 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 { + 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, type Dispatch, type SetStateAction } 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" }), +}); + +const CountryTable = ({ + data, + page, + pageSize, + setActiveTab, + setFeatureId, +}: { + page: number; + pageSize: number; + setActiveTab: Dispatch>; + setFeatureId: Dispatch>; + data: + | { + links: { + previous: string | null; + next: string | null; + }; + total_items: number; + total_pages: number; + page_size: number; + current_page: number; + results: CountryLisResult[]; + } + | 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_country", editId], + queryFn: () => countryDeatil(editId!), + enabled: !!editId, + }); + + const { mutate: deleteMutate } = useMutation({ + mutationFn: (id: number) => countryDelete(id), + onSuccess: () => { + queryClient.refetchQueries({ queryKey: ["all_country"] }); + queryClient.refetchQueries({ queryKey: ["detail_country"] }); + setOpen(false); + form.reset(); + }, + onError: () => { + toast.error(t("Xatolik yuz berdi"), { + position: "top-center", + richColors: true, + }); + }, + }); + + const handleDelete = (id: number) => { + deleteMutate(id); + }; + + const columns = CountryColumns( + handleEdit, + handleDelete, + t, + setActiveTab, + setFeatureId, + ); + + const { mutate: create, isPending } = useMutation({ + mutationFn: (body: { name: string; name_ru: string }) => + countryCreate(body), + onSuccess: () => { + queryClient.refetchQueries({ queryKey: ["all_country"] }); + queryClient.refetchQueries({ queryKey: ["detail_country"] }); + 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; + }; + }) => countryUpdate({ body, id }), + onSuccess: () => { + queryClient.refetchQueries({ queryKey: ["all_country"] }); + queryClient.refetchQueries({ queryKey: ["detail_country"] }); + 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("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({ + name: values.name, + name_ru: values.name_ru, + }); + } else if (types === "edit" && editId) { + update({ + id: editId, + body: { + 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")} +

+
+ + ( + + {t("Nomi")} + + + + + + )} + /> + ( + + {t("Nomi (ru)")} + + + + + + )} + /> + +
+ + +
+ + +
+
+ + ); +}; + +export default CountryTable; diff --git a/src/pages/tours/ui/HotelType.tsx b/src/pages/tours/ui/HotelType.tsx new file mode 100644 index 0000000..03ab384 --- /dev/null +++ b/src/pages/tours/ui/HotelType.tsx @@ -0,0 +1,324 @@ +import { + hotelTypeCreate, + hotelTypeDelete, + hotelTypeDetail, + hotelTypeUpdate, +} from "@/pages/tours/lib/api"; +import { TypeColumns } from "@/pages/tours/lib/column"; +import type { Type } 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 { + 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" }), +}); + +const HotelType = ({ + 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: Type[]; + } + | 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: typeDetail } = useQuery({ + queryKey: ["detail_type", editId], + queryFn: () => hotelTypeDetail({ id: editId! }), + enabled: !!editId, + }); + + const { mutate: deleteMutate } = useMutation({ + mutationFn: ({ id }: { id: number }) => hotelTypeDelete({ id }), + onSuccess: () => { + queryClient.refetchQueries({ queryKey: ["detail_type"] }); + queryClient.refetchQueries({ queryKey: ["all_type"] }); + setOpen(false); + form.reset(); + }, + onError: () => { + toast.error(t("Xatolik yuz berdi"), { + position: "top-center", + richColors: true, + }); + }, + }); + + const handleDelete = (id: number) => { + deleteMutate({ id }); + }; + + const columns = TypeColumns(handleEdit, handleDelete, t); + + const { mutate: create, isPending } = useMutation({ + mutationFn: ({ body }: { body: { name: string; name_ru: string } }) => + hotelTypeCreate({ body }), + onSuccess: () => { + queryClient.refetchQueries({ queryKey: ["detail_type"] }); + queryClient.refetchQueries({ queryKey: ["all_type"] }); + 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 }; + }) => hotelTypeUpdate({ body, id }), + onSuccess: () => { + queryClient.refetchQueries({ queryKey: ["detail_type"] }); + queryClient.refetchQueries({ queryKey: ["all_type"] }); + 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 (typeDetail) { + form.setValue("name", typeDetail.data.data.name_uz); + form.setValue("name_ru", typeDetail.data.data.name_ru); + } + }, [editId, typeDetail]); + + function onSubmit(values: z.infer) { + if (types === "create") { + create({ + body: { + name: values.name, + name_ru: values.name_ru, + }, + }); + } else if (types === "edit" && editId) { + update({ + id: editId, + body: { + 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")} +

+
+ + ( + + {t("Nomi")} + + + + + + )} + /> + ( + + {t("Nomi (ru)")} + + + + + + )} + /> + +
+ + +
+ + +
+
+ + ); +}; + +export default HotelType; diff --git a/src/pages/tours/ui/MealTable.tsx b/src/pages/tours/ui/MealTable.tsx index f434702..103c556 100644 --- a/src/pages/tours/ui/MealTable.tsx +++ b/src/pages/tours/ui/MealTable.tsx @@ -1,11 +1,11 @@ import { - hotelTypeCreate, - hotelTypeDelete, - hotelTypeDetail, - hotelTypeUpdate, + hotelMealCreate, + hotelMealDelete, + hotelMealDetail, + hotelMealUpdate, } from "@/pages/tours/lib/api"; -import { TypeColumns } from "@/pages/tours/lib/column"; -import type { Type } from "@/pages/tours/lib/type"; +import { MealColumns } from "@/pages/tours/lib/column"; +import type { HotelMealListData } from "@/pages/tours/lib/type"; import { Button } from "@/shared/ui/button"; import { Dialog, DialogContent } from "@/shared/ui/dialog"; import { @@ -45,7 +45,7 @@ const formSchema = z.object({ name_ru: z.string().min(1, { message: "Majburiy maydon" }), }); -const MealTable = ({ +const MealPlan = ({ data, page, pageSize, @@ -55,14 +55,14 @@ const MealTable = ({ data: | { links: { - previous: string; - next: string; + previous: string | null; + next: string | null; }; total_items: number; total_pages: number; page_size: number; current_page: number; - results: Type[]; + results: HotelMealListData[]; } | undefined; }) => { @@ -78,17 +78,17 @@ const MealTable = ({ setEditId(id); }; - const { data: typeDetail } = useQuery({ - queryKey: ["detail_type", editId], - queryFn: () => hotelTypeDetail({ id: editId! }), + const { data: badgeDetail } = useQuery({ + queryKey: ["detail_meal", editId], + queryFn: () => hotelMealDetail(editId!), enabled: !!editId, }); const { mutate: deleteMutate } = useMutation({ - mutationFn: ({ id }: { id: number }) => hotelTypeDelete({ id }), + mutationFn: ({ id }: { id: number }) => hotelMealDelete(id), onSuccess: () => { - queryClient.refetchQueries({ queryKey: ["detail_type"] }); - queryClient.refetchQueries({ queryKey: ["all_type"] }); + queryClient.refetchQueries({ queryKey: ["detail_meal"] }); + queryClient.refetchQueries({ queryKey: ["all_meal"] }); setOpen(false); form.reset(); }, @@ -104,14 +104,20 @@ const MealTable = ({ deleteMutate({ id }); }; - const columns = TypeColumns(handleEdit, handleDelete, t); + const columns = MealColumns(handleEdit, handleDelete, t); const { mutate: create, isPending } = useMutation({ - mutationFn: ({ body }: { body: { name: string; name_ru: string } }) => - hotelTypeCreate({ body }), + mutationFn: ({ + body, + }: { + body: { + name: string; + name_ru: string; + }; + }) => hotelMealCreate(body), onSuccess: () => { - queryClient.refetchQueries({ queryKey: ["detail_type"] }); - queryClient.refetchQueries({ queryKey: ["all_type"] }); + queryClient.refetchQueries({ queryKey: ["detail_meal"] }); + queryClient.refetchQueries({ queryKey: ["all_meal"] }); setOpen(false); form.reset(); }, @@ -129,11 +135,14 @@ const MealTable = ({ id, }: { id: number; - body: { name: string; name_ru: string }; - }) => hotelTypeUpdate({ body, id }), + body: { + name: string; + name_ru: string; + }; + }) => hotelMealUpdate({ body, id }), onSuccess: () => { - queryClient.refetchQueries({ queryKey: ["detail_type"] }); - queryClient.refetchQueries({ queryKey: ["all_type"] }); + queryClient.refetchQueries({ queryKey: ["detail_meal"] }); + queryClient.refetchQueries({ queryKey: ["all_meal"] }); setOpen(false); form.reset(); }, @@ -154,11 +163,11 @@ const MealTable = ({ }); useEffect(() => { - if (typeDetail) { - form.setValue("name", typeDetail.data.data.name_uz); - form.setValue("name_ru", typeDetail.data.data.name_ru); + if (badgeDetail) { + form.setValue("name", badgeDetail.data.data.name_uz); + form.setValue("name_ru", badgeDetail.data.data.name_ru); } - }, [editId, typeDetail]); + }, [editId, badgeDetail]); function onSubmit(values: z.infer) { if (types === "create") { @@ -192,7 +201,6 @@ const MealTable = ({ }, }, }); - return ( <>
@@ -256,7 +264,12 @@ const MealTable = ({
- + @@ -321,4 +334,4 @@ const MealTable = ({ ); }; -export default MealTable; +export default MealPlan; diff --git a/src/pages/tours/ui/RegionTable.tsx b/src/pages/tours/ui/RegionTable.tsx new file mode 100644 index 0000000..e66e74d --- /dev/null +++ b/src/pages/tours/ui/RegionTable.tsx @@ -0,0 +1,335 @@ +import { + hotelFeatureTypeDelete, + regionCreate, + regionDeatil, + regionUpdate, +} from "@/pages/tours/lib/api"; +import { CountryColumns } from "@/pages/tours/lib/column"; +import type { CountryLisResult } 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 { + 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" }), +}); + +const RegionTable = ({ + data, + page, + pageSize, + featureId, +}: { + page: number; + featureId: number | null; + pageSize: number; + data: + | { + links: { + previous: string | null; + next: string | null; + }; + total_items: number; + total_pages: number; + page_size: number; + current_page: number; + results: CountryLisResult[]; + } + | 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: ["region_detail", editId], + queryFn: () => regionDeatil(editId!), + enabled: !!editId, + }); + + const { mutate: deleteMutate } = useMutation({ + mutationFn: ({ id }: { id: number }) => hotelFeatureTypeDelete({ id }), + onSuccess: () => { + queryClient.refetchQueries({ queryKey: ["region_detail"] }); + queryClient.refetchQueries({ queryKey: ["all_region"] }); + setOpen(false); + form.reset(); + }, + onError: () => { + toast.error(t("Xatolik yuz berdi"), { + position: "top-center", + richColors: true, + }); + }, + }); + + const handleDelete = (id: number) => { + deleteMutate({ id }); + }; + + const columns = CountryColumns(handleEdit, handleDelete, t); + + const { mutate: create, isPending } = useMutation({ + mutationFn: (body: { name: string; name_ru: string; country: number }) => + regionCreate(body), + onSuccess: () => { + queryClient.refetchQueries({ queryKey: ["region_detail"] }); + queryClient.refetchQueries({ queryKey: ["all_region"] }); + 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; + country: number; + }; + }) => regionUpdate({ body, id }), + onSuccess: () => { + queryClient.refetchQueries({ queryKey: ["region_detail"] }); + queryClient.refetchQueries({ queryKey: ["all_region"] }); + 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("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({ + country: Number(featureId), + name: values.name, + name_ru: values.name_ru, + }); + } else if (types === "edit" && editId) { + update({ + id: editId, + body: { + country: Number(featureId), + 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")} +

+
+ + ( + + {t("Nomi")} + + + + + + )} + /> + ( + + {t("Nomi (ru)")} + + + + + + )} + /> + +
+ + +
+ + +
+
+ + ); +}; + +export default RegionTable; diff --git a/src/pages/tours/ui/StepOne.tsx b/src/pages/tours/ui/StepOne.tsx index 97971ed..1621ac0 100644 --- a/src/pages/tours/ui/StepOne.tsx +++ b/src/pages/tours/ui/StepOne.tsx @@ -1,10 +1,12 @@ "use client"; import { + countryList, createTours, getAllAmenities, hotelBadge, hotelTransport, + regionList, updateTours, } from "@/pages/tours/lib/api"; import { TourformSchema } from "@/pages/tours/lib/form"; @@ -12,12 +14,14 @@ import { useTicketStore } from "@/pages/tours/lib/store"; import type { GetOneTours } from "@/pages/tours/lib/type"; import TicketsImagesModel from "@/pages/tours/ui/TicketsImagesModel"; import formatPrice from "@/shared/lib/formatPrice"; +import { cn } from "@/shared/lib/utils"; import { Badge } from "@/shared/ui/badge"; import { Button } from "@/shared/ui/button"; import { Calendar } from "@/shared/ui/calendar"; import { Command, CommandGroup, + CommandInput, CommandItem, CommandList, } from "@/shared/ui/command"; @@ -35,7 +39,16 @@ import { RadioGroup, RadioGroupItem } from "@/shared/ui/radio-group"; import { Textarea } from "@/shared/ui/textarea"; import { zodResolver } from "@hookform/resolvers/zod"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; -import { ChevronDownIcon, Loader2, SquareCheckBig, XIcon } from "lucide-react"; +import { AnimatePresence, motion } from "framer-motion"; +import { + Check, + ChevronDownIcon, + ChevronsUpDown, + Loader2, + MoveLeft, + SquareCheckBig, + XIcon, +} from "lucide-react"; import { useEffect, useState, type Dispatch, type SetStateAction } from "react"; import { useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; @@ -72,9 +85,9 @@ const StepOne = ({ departure: "", // tarif: [], transport: [], - departure_ru: "", + // departure_ru: "", destination: "", - destination_ru: "", + // destination_ru: "", location_name: "", location_name_ru: "", departureDateTime: { @@ -151,10 +164,10 @@ const StepOne = ({ passenger_count: String(tour.passenger_count) ?? "1", min_person: String(tour.min_person) ?? "1", max_person: String(tour.max_person) ?? "1", - departure: tour.departure_uz ?? "", - departure_ru: tour.departure_ru ?? "", - destination: tour.destination_uz ?? "", - destination_ru: tour.destination_ru ?? "", + departure: tour.departure?.id.toString() ?? "", + // departure_ru: tour.departure_ru ?? "", + destination: tour.destination?.id.toString() ?? "", + // destination_ru: tour.destination_ru ?? "", location_name: tour.location_name_uz ?? "", location_name_ru: tour.location_name_ru ?? "", hotel_info: tour.hotel_info_uz ?? "", @@ -227,6 +240,10 @@ const StepOne = ({ // TicketStore uchun id setId(tour.id); + setSelectedCountry(data.data.departure?.country?.id); + setSearchCity(data.data.departure?.name); + setSelectedCountryDes(data.data.destination?.country.id); + setSearchCityDes(data.data.destination?.name); }, [isEditMode, data, form, setId]); const { watch, setValue } = form; @@ -289,9 +306,9 @@ const StepOne = ({ formData.append("min_person", String(value.min_person)); formData.append("max_person", String(value.max_person)); formData.append("departure", String(value.departure)); - formData.append("departure_ru", String(value.departure_ru)); + // formData.append("departure_ru", String(value.departure_ru)); formData.append("destination", String(value.destination)); - formData.append("destination_ru", String(value.destination_ru)); + // formData.append("destination_ru", String(value.destination_ru)); formData.append( "departure_time", value.departureDateTime.date?.toISOString(), @@ -488,11 +505,77 @@ const StepOne = ({ // queryFn: () => hotelTarif({ page: 1, page_size: 10 }), // }); + const [searchCountry, setSearchCountry] = useState(""); + const [searchCity, setSearchCity] = useState(""); + const [openCountry, setOpenCountry] = useState(false); + const [openCity, setOpenCity] = useState(false); + const [selectedCountry, setSelectedCountry] = useState(null); + + const { data: countryData, isLoading: countryLoad } = useQuery({ + queryKey: ["all_country", searchCountry], + queryFn: () => countryList({ page: 1, page_size: 10, name: searchCountry }), + select: (res) => res.data.data, + }); + + const { data: regionData, isLoading: regionLoad } = useQuery({ + queryKey: ["all_region", selectedCountry, searchCity], + queryFn: () => + regionList({ + page: 1, + page_size: 10, + country: selectedCountry!, + name: searchCity, + }), + select: (res) => res.data.data, + enabled: !!selectedCountry, + }); + //destions select country + const [searchCountryDes, setSearchCountryDes] = useState(""); + const [searchCityDes, setSearchCityDes] = useState(""); + const [openCountryDes, setOpenCountryDes] = useState(false); + const [openCityDes, setOpenCityDes] = useState(false); + const [selectedCountryDes, setSelectedCountryDes] = useState( + null, + ); + const { data: countryDataDes, isLoading: countryLoadDes } = useQuery({ + queryKey: ["all_country_des", searchCountryDes], + queryFn: () => + countryList({ page: 1, page_size: 10, name: searchCountryDes }), + select: (res) => res.data.data, + }); + + const { data: regionDataDes, isLoading: regionLoadDes } = useQuery({ + queryKey: ["all_region_des", selectedCountryDes, searchCityDes], + queryFn: () => + regionList({ + page: 1, + page_size: 10, + country: selectedCountryDes!, + name: searchCityDes, + }), + select: (res) => res.data.data, + enabled: !!selectedCountryDes, + }); + + const handleCountrySelect = (id: number | null) => { + setSelectedCountry(id); + form.setValue("departure", ""); + setOpenCountry(false); + }; + + const handleCountrySelectDes = (id: number | null) => { + setSelectedCountryDes(id); + form.setValue("destination", ""); + setOpenCountryDes(false); + }; + const { data: transport } = useQuery({ queryKey: ["all_transport"], queryFn: () => hotelTransport({ page: 1, page_size: 10 }), }); + console.log(form.formState.errors); + return (
@@ -620,24 +703,224 @@ const StepOne = ({
( - - - - - - - - )} + control={form.control} + render={({ field }) => { + const selectedCity = regionData?.results.find( + (u) => String(u.id) === field.value, + ); + + return ( + + + {!selectedCountry && ( + + + + + + + + + + + + + {countryLoad ? ( +
+ +
+ ) : ( + + {countryData?.results.map((c, index) => ( + + { + handleCountrySelect(c.id); + setOpenCountry(false); + setTimeout( + () => setOpenCity(true), + 200, + ); + }} + className="cursor-pointer transition-colors" + > + + {c.name} + + + ))} + + )} +
+
+
+
+
+
+ )} + + {/* CITY DROPDOWN */} + {selectedCountry && ( + + + + + + + + + + + + + + + + {regionLoad ? ( +
+ +
+ ) : ( + + {regionData?.results.map((r, index) => ( + + { + field.onChange(String(r.id)); + setOpenCity(false); + }} + className="cursor-pointer transition-colors" + > + + {r.name} + + + ))} + + )} +
+
+
+
+
+
+ )} + + +
+ ); + }} /> - ( @@ -653,27 +936,234 @@ const StepOne = ({ )} - /> + /> */} ( - - - - - - - - )} + control={form.control} + render={({ field }) => { + const selectedCity = regionDataDes?.results.find( + (u) => String(u.id) === field.value, + ); + + return ( + + + + {/* COUNTRY DROPDOWN */} + {!selectedCountryDes && ( + + + + + + + + + + + + + {countryLoadDes ? ( +
+ +
+ ) : ( + + {countryDataDes?.results.map((c, index) => ( + + { + handleCountrySelectDes(c.id); + setOpenCountryDes(false); + setTimeout( + () => setOpenCityDes(true), + 200, + ); + }} + className="cursor-pointer transition-colors" + > + + {c.name} + + + ))} + + )} +
+
+
+
+
+
+ )} + + {selectedCountryDes && ( + + + + + + + + + + + + + + + + {regionLoadDes ? ( +
+ +
+ ) : ( + + {regionDataDes?.results.map((r, index) => ( + + { + field.onChange(String(r.id)); + setOpenCityDes(false); + }} + className="cursor-pointer transition-colors" + > + + {r.name} + + + ))} + + )} +
+
+
+
+
+
+ )} + + +
+ ); + }} /> - ( @@ -689,7 +1179,7 @@ const StepOne = ({ )} - /> + /> */} (false); + const [searchMeal, setSearchMeal] = useState(""); + + const { data: mealData, isLoading: mealLoad } = useQuery({ + queryKey: ["all_meal", searchMeal], + queryFn: () => hotelMealList({ page: 1, page_size: 99, name: searchMeal }), + select: (res) => res.data.data, + }); + const removeFeatureType = (id: string) => form.setValue( "hotelFeaturesType", @@ -302,15 +312,7 @@ const StepTwo = ({ formData.append(`hotel_amenities[${i}]`, String(e)); }); - const mealPlan = - data.mealPlan === "Breakfast Only" - ? "breakfast" - : data.mealPlan === "Half Board" - ? "half_board" - : data.mealPlan === "Full Board" - ? "full_board" - : "all_inclusive"; - formData.append("meal_plan", mealPlan); + formData.append("meal_plan", data.mealPlan); data.hotelType && data.hotelType.forEach((id) => formData.append("hotel_type", id)); @@ -331,13 +333,6 @@ const StepTwo = ({ } }; - const mealPlans = [ - "Breakfast Only", - "Half Board", - "Full Board", - "All Inclusive", - ]; - return ( @@ -400,28 +395,89 @@ const StepTwo = ({ {/* Meal Plan */} ( - - - - - - - - )} + control={form.control} + render={({ field }) => { + const selectedUser = mealData?.results.find( + (u) => String(u.id) === field.value, + ); + return ( + + + + + + + + + + + + + + + + {mealLoad ? ( +
+ +
+ ) : mealData && mealData.results.length > 0 ? ( + + {mealData.results.map((u) => ( + { + field.onChange(String(u.id)); + setOpenMeal(false); + }} + > + + {u.name} + + ))} + + ) : ( + Taom rejasi topilmadi + )} +
+
+
+
+ + +
+ ); + }} /> {/* Hotel Type */} diff --git a/src/pages/tours/ui/TourDetail.tsx b/src/pages/tours/ui/TourDetail.tsx index d667c4c..ba07b00 100644 --- a/src/pages/tours/ui/TourDetail.tsx +++ b/src/pages/tours/ui/TourDetail.tsx @@ -221,7 +221,7 @@ export default function TourDetailPage() { {t("Jo'nash joyi")}

- {tour.departure} + {tour.departure?.name}

@@ -233,7 +233,7 @@ export default function TourDetailPage() { {t("Yo'nalish")}

- {tour.destination} + {tour.destination?.name}

diff --git a/src/pages/tours/ui/Tours.tsx b/src/pages/tours/ui/Tours.tsx index 0035b5d..f0458d6 100644 --- a/src/pages/tours/ui/Tours.tsx +++ b/src/pages/tours/ui/Tours.tsx @@ -188,7 +188,9 @@ const Tours = ({ user }: { user: Role }) => {
- {tour.destination} + {typeof tour.destination === "object" + ? tour.destination?.name + : tour.destination}
@@ -319,7 +321,9 @@ const Tours = ({ user }: { user: Role }) => {

- {tour.destination} + {typeof tour.destination === "object" + ? tour.destination?.name + : tour.destination}

{tour.duration_days} kun • {tour.hotel_name} diff --git a/src/pages/tours/ui/ToursSetting.tsx b/src/pages/tours/ui/ToursSetting.tsx index ae7076e..3a0e31f 100644 --- a/src/pages/tours/ui/ToursSetting.tsx +++ b/src/pages/tours/ui/ToursSetting.tsx @@ -1,19 +1,25 @@ "use client"; import { + countryList, getAllAmenities, hotelBadge, hotelFeature, hotelFeatureType, + hotelMealList, hotelTarif, hotelTransport, hotelType, + regionList, } from "@/pages/tours/lib/api"; import Amenities from "@/pages/tours/ui/Amenities"; import BadgeTable from "@/pages/tours/ui/BadgeTable"; +import CountryTable from "@/pages/tours/ui/CountryTable"; import FeaturesTable from "@/pages/tours/ui/FeaturesTable"; import FeaturesTableType from "@/pages/tours/ui/FeaturesTableType"; +import HotelType from "@/pages/tours/ui/HotelType"; import MealTable from "@/pages/tours/ui/MealTable"; +import RegionTable from "@/pages/tours/ui/RegionTable"; import TarifTable from "@/pages/tours/ui/TarifTable"; import TransportTable from "@/pages/tours/ui/TransportTable"; import { Button } from "@/shared/ui/button"; @@ -63,6 +69,7 @@ const ToursSetting: React.FC = () => { const [searchParams] = useSearchParams(); const [activeTab, setActiveTab] = useState("badge"); const [featureId, setFeatureId] = useState(null); + const [countryId, setCountryId] = useState(null); const navigate = useNavigate(); const page = parseInt(searchParams.get("page") || "1", 10); @@ -120,6 +127,47 @@ const ToursSetting: React.FC = () => { select: (res) => res.data.data, }); + const pageCountry = parseInt(searchParams.get("pageCountry") || "1", 10); + const pageSizeCountry = parseInt( + searchParams.get("pageSizeCountry") || "10", + 10, + ); + + const { + data: countryData, + isLoading: countryLoad, + isError: countryError, + refetch: countryRef, + } = useQuery({ + queryKey: ["all_country", pageCountry, pageSizeCountry], + queryFn: () => + countryList({ page: pageCountry, page_size: pageSizeCountry }), + select: (res) => res.data.data, + }); + + const pageRegion = parseInt(searchParams.get("pageRegion") || "1", 10); + const pageSizeRegion = parseInt( + searchParams.get("pageSizeRegion") || "10", + 10, + ); + + const { + data: regionData, + isLoading: regionLoad, + isError: regionError, + refetch: regionRef, + } = useQuery({ + queryKey: ["all_region", pageRegion, pageSizeRegion, countryId], + queryFn: () => + regionList({ + page: pageRegion, + page_size: pageSizeRegion, + country: countryId!, + }), + select: (res) => res.data.data, + enabled: !!countryId, + }); + const pageFeature = parseInt(searchParams.get("pageFeature") || "1", 10); const pageSizeFeature = parseInt( searchParams.get("pageSizeFeature") || "10", @@ -173,6 +221,20 @@ const ToursSetting: React.FC = () => { select: (res) => res.data.data, }); + const pageMeal = parseInt(searchParams.get("pageMeal") || "1", 10); + const pageSizeMeal = parseInt(searchParams.get("pageSizeMeal") || "10", 10); + + const { + data: mealData, + isLoading: mealLoad, + isError: mealError, + refetch: mealRef, + } = useQuery({ + queryKey: ["all_meal", pageMeal, pageSizeMeal], + queryFn: () => hotelMealList({ page: pageMeal, page_size: pageSizeMeal }), + select: (res) => res.data.data, + }); + const handleTabChange = (value: string) => { setActiveTab(value); navigate({ @@ -191,6 +253,9 @@ const ToursSetting: React.FC = () => { > {t("Belgilar (Badge)")} + + {t("Ovqatlanish turlari")} + {t("Tariflar")} {t("Transport")} {t("Qulayliklar")} @@ -198,6 +263,7 @@ const ToursSetting: React.FC = () => { {t("Otel sharoitlari")} + {t("Davlatlar")} @@ -273,7 +339,7 @@ const ToursSetting: React.FC = () => { onRetry={typeRef} /> ) : ( - { )} + + {mealLoad ? ( + + ) : mealError ? ( + + ) : ( + + )} + + {featureLoad ? ( @@ -317,6 +400,43 @@ const ToursSetting: React.FC = () => { /> )} + + + {countryLoad ? ( + + ) : countryError ? ( + + ) : ( + + )} + + + + {regionLoad ? ( + + ) : regionError ? ( + + ) : ( + + )} +

diff --git a/src/shared/config/api/URLs.ts b/src/shared/config/api/URLs.ts index 168fac1..3d3c347 100644 --- a/src/shared/config/api/URLs.ts +++ b/src/shared/config/api/URLs.ts @@ -36,6 +36,9 @@ const BANNER = "dashboard/dashboard-site-banner/"; const TOUR_ADMIN = "dashboard/dashboard-tour-admin/"; const PAYMENT_AGENCY = "dashboard/dashboard-site-agency-payments/"; const PAYOT_REQUEST = "dashboard/dashboard-agency-payout-request/"; +const COUNTRY = "dashboard/dashboard-ticket-settings-country/"; +const REGION = "dashboard/dashboard-ticket-settings-region/"; +const MEAL_PLAN = "dashboard/dashboard-hotel-meal-plan/"; export { AGENCY_ORDERS, @@ -45,6 +48,7 @@ export { AUTH_LOGIN, BANNER, BASE_URL, + COUNTRY, DOWNLOAD_PDF, FAQ, FAQ_CATEGORIES, @@ -60,12 +64,14 @@ export { HOTEL_FEATURES_TYPE, HOTEL_TARIF, HPTEL_TYPES, + MEAL_PLAN, NEWS, NEWS_CATEGORY, OFFERTA, PAYMENT_AGENCY, PAYOT_REQUEST, POPULAR_TOURS, + REGION, SITE_SEO, SITE_SETTING, SUPPORT_AGENCY, diff --git a/src/shared/config/i18n/locales/ru/translation.json b/src/shared/config/i18n/locales/ru/translation.json index 98c9119..56a1b72 100644 --- a/src/shared/config/i18n/locales/ru/translation.json +++ b/src/shared/config/i18n/locales/ru/translation.json @@ -10,6 +10,8 @@ "Xodimlar": "Сотрудники", "Siz agentlikga yangi foydalanuvchi qo'shmoqchimisiz": "Вы хотите добавить нового пользователя в агентство", "Foydalanuvchi qo'shish": "Добавить пользователя", + "Ovqatlanish turlari": "Виды питания", + "Davlatlar": "Государство", "Byudjet": "Бюджет", "Turlar": "Туры", "Tur sozlamalari": "Настройки туров", diff --git a/src/shared/config/i18n/locales/uz/translation.json b/src/shared/config/i18n/locales/uz/translation.json index 303186c..80db63a 100644 --- a/src/shared/config/i18n/locales/uz/translation.json +++ b/src/shared/config/i18n/locales/uz/translation.json @@ -12,6 +12,8 @@ "Turlar": "Turlar", "Siz agentlikga yangi foydalanuvchi qo'shmoqchimisiz": "Siz agentlikga yangi foydalanuvchi qo'shmoqchimisiz", "Foydalanuvchi qo'shish": "Foydalanuvchi qo'shish", + "Ovqalanish turlari": "Ovqalanish turlari", + "Davlatlar": "Davlatlar", "Tur sozlamalari": "Tur sozlamalari", "Bronlar": "Bronlar", "Yangiliklar": "Yangiliklar", diff --git a/src/shared/ui/popover.tsx b/src/shared/ui/popover.tsx index e0d317a..0d6f7c6 100644 --- a/src/shared/ui/popover.tsx +++ b/src/shared/ui/popover.tsx @@ -1,5 +1,5 @@ -import * as React from "react"; import * as PopoverPrimitive from "@radix-ui/react-popover"; +import * as React from "react"; import { cn } from "@/shared/lib/utils"; @@ -43,4 +43,4 @@ function PopoverAnchor({ return ; } -export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }; +export { Popover, PopoverAnchor, PopoverContent, PopoverTrigger };