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")}
+
+
+ )}
+
+
+
+
+
+
+
+ >
+ );
+};
+
+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")}
+
+
+ )}
+
+
+
+
+
+
+
+ >
+ );
+};
+
+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 = ({
-
+
- {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 };