coutry crud added
This commit is contained in:
8
package-lock.json
generated
8
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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<AxiosResponse<CountryList>> => {
|
||||
const res = await httpClient.get(COUNTRY, { params });
|
||||
return res;
|
||||
};
|
||||
|
||||
const countryDeatil = async (
|
||||
id: number,
|
||||
): Promise<AxiosResponse<CountryDeatil>> => {
|
||||
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<AxiosResponse<CountryList>> => {
|
||||
const res = await httpClient.get(REGION, { params });
|
||||
return res;
|
||||
};
|
||||
|
||||
const regionDeatil = async (
|
||||
id: number,
|
||||
): Promise<AxiosResponse<CountryDeatil>> => {
|
||||
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<AxiosResponse<HotelMealList>> => {
|
||||
const res = await httpClient.get(`${MEAL_PLAN}`, { params });
|
||||
return res;
|
||||
};
|
||||
|
||||
const hotelMealDetail = async (
|
||||
id: number,
|
||||
): Promise<AxiosResponse<HotelMealdetail>> => {
|
||||
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,
|
||||
};
|
||||
|
||||
@@ -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<SetStateAction<string>>,
|
||||
setFeatureId?: Dispatch<SetStateAction<number | null>>,
|
||||
): ColumnDef<CountryLisResult>[] => [
|
||||
{
|
||||
accessorKey: "id",
|
||||
header: "ID",
|
||||
cell: ({ row }) => <span>{row.original.id}</span>,
|
||||
},
|
||||
{
|
||||
accessorKey: "name",
|
||||
header: t("Nomi"),
|
||||
cell: ({ row }) => <span>{row.original.name_uz}</span>,
|
||||
},
|
||||
{
|
||||
accessorKey: "name",
|
||||
header: () => <div>{t("Nomi")} (ru)</div>,
|
||||
cell: ({ row }) => <span>{row.original.name_ru}</span>,
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
header: () => <div className="text-right">{t("Harakatlar")}</div>,
|
||||
cell: ({ row }) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className="flex justify-end gap-2">
|
||||
{setActiveTab && setFeatureId && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setActiveTab("region");
|
||||
setFeatureId(row.original.id);
|
||||
}}
|
||||
>
|
||||
{t("Ko'rish")}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => onEdit(row.original.id)}
|
||||
>
|
||||
{t("Tahrirlash")}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="destructive"
|
||||
onClick={() => onDelete(row.original.id)}
|
||||
>
|
||||
{t("O'chirish")}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const MealColumns = (
|
||||
onEdit: (id: number) => void,
|
||||
onDelete: (id: number) => void,
|
||||
t: (key: string) => string,
|
||||
): ColumnDef<HotelMealListData>[] => [
|
||||
{
|
||||
accessorKey: "id",
|
||||
header: "ID",
|
||||
cell: ({ row }) => <span>{row.original.id}</span>,
|
||||
},
|
||||
{
|
||||
accessorKey: "name",
|
||||
header: t("Nomi"),
|
||||
cell: ({ row }) => <span>{row.original.name_uz}</span>,
|
||||
},
|
||||
{
|
||||
accessorKey: "name",
|
||||
header: () => <div>{t("Nomi")} (ru)</div>,
|
||||
cell: ({ row }) => <span>{row.original.name_ru}</span>,
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
header: () => <div className="text-right">{t("Harakatlar")}</div>,
|
||||
cell: ({ row }) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => onEdit(row.original.id)}
|
||||
>
|
||||
{t("Tahrirlash")}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="destructive"
|
||||
onClick={() => onDelete(row.original.id)}
|
||||
>
|
||||
{t("O'chirish")}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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.",
|
||||
}),
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
340
src/pages/tours/ui/CountryTable.tsx
Normal file
340
src/pages/tours/ui/CountryTable.tsx
Normal file
@@ -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<SetStateAction<string>>;
|
||||
setFeatureId: Dispatch<SetStateAction<number | null>>;
|
||||
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<boolean>(false);
|
||||
const [editId, setEditId] = useState<number | null>(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<z.infer<typeof formSchema>>({
|
||||
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<typeof formSchema>) {
|
||||
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 (
|
||||
<>
|
||||
<div className="flex justify-end">
|
||||
<Button
|
||||
variant="default"
|
||||
onClick={() => {
|
||||
setOpen(true);
|
||||
setTypes("create");
|
||||
form.reset();
|
||||
}}
|
||||
>
|
||||
<PlusIcon className="mr-2" />
|
||||
{t("Qo‘shish")}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="border border-gray-700 rounded-md overflow-hidden mt-4">
|
||||
<Table key={data?.current_page}>
|
||||
<TableHeader>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<TableRow key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header) => (
|
||||
<TableHead key={header.id}>
|
||||
{header.isPlaceholder
|
||||
? null
|
||||
: flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext(),
|
||||
)}
|
||||
</TableHead>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableHeader>
|
||||
|
||||
<TableBody>
|
||||
{table.getRowModel().rows?.length ? (
|
||||
table.getRowModel().rows.map((row) => (
|
||||
<TableRow key={row.id}>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<TableCell key={cell.id}>
|
||||
{flexRender(
|
||||
cell.column.columnDef.cell,
|
||||
cell.getContext(),
|
||||
)}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={columns.length}
|
||||
className="h-24 text-center text-gray-400"
|
||||
>
|
||||
{t("Ma'lumot topilmadi")}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
<RealPagination
|
||||
table={table}
|
||||
totalPages={data?.total_pages}
|
||||
namePage="pageCountry"
|
||||
namePageSize="pageSizeCountry"
|
||||
/>
|
||||
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogContent>
|
||||
<p className="text-xl">
|
||||
{types === "create" ? t("Saqlash") : t("Tahrirlash")}
|
||||
</p>
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="space-y-6 p-2"
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("Nomi")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t("Nomi")} {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name_ru"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("Nomi (ru)")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t("Nomi (ru)")} {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="flex justify-end gap-4">
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => setOpen(false)}
|
||||
className="border-slate-600 text-white hover:bg-gray-600 bg-gray-600"
|
||||
>
|
||||
{t("Bekor qilish")}
|
||||
</Button>
|
||||
<Button type="submit">
|
||||
{isPending || updatePending ? (
|
||||
<Loader className="animate-spin" />
|
||||
) : types === "create" ? (
|
||||
t("Saqlash")
|
||||
) : (
|
||||
t("Tahrirlash")
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default CountryTable;
|
||||
324
src/pages/tours/ui/HotelType.tsx
Normal file
324
src/pages/tours/ui/HotelType.tsx
Normal file
@@ -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<boolean>(false);
|
||||
const [editId, setEditId] = useState<number | null>(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<z.infer<typeof formSchema>>({
|
||||
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<typeof formSchema>) {
|
||||
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 (
|
||||
<>
|
||||
<div className="flex justify-end">
|
||||
<Button
|
||||
variant="default"
|
||||
onClick={() => {
|
||||
setOpen(true);
|
||||
setTypes("create");
|
||||
form.reset();
|
||||
}}
|
||||
>
|
||||
<PlusIcon className="mr-2" />
|
||||
{t("Qo‘shish")}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="border border-gray-700 rounded-md overflow-hidden mt-4">
|
||||
<Table key={data?.current_page}>
|
||||
<TableHeader>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<TableRow key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header) => (
|
||||
<TableHead key={header.id}>
|
||||
{header.isPlaceholder
|
||||
? null
|
||||
: flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext(),
|
||||
)}
|
||||
</TableHead>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableHeader>
|
||||
|
||||
<TableBody>
|
||||
{table.getRowModel().rows?.length ? (
|
||||
table.getRowModel().rows.map((row) => (
|
||||
<TableRow key={row.id}>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<TableCell key={cell.id}>
|
||||
{flexRender(
|
||||
cell.column.columnDef.cell,
|
||||
cell.getContext(),
|
||||
)}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={columns.length}
|
||||
className="h-24 text-center text-gray-400"
|
||||
>
|
||||
{t("Ma'lumot topilmadi")}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
<RealPagination table={table} totalPages={data?.total_pages} />
|
||||
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogContent>
|
||||
<p className="text-xl">
|
||||
{types === "create" ? t("Saqlash") : t("Tahrirlash")}
|
||||
</p>
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="space-y-6 p-2"
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("Nomi")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t("Nomi")} {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name_ru"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("Nomi (ru)")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t("Nomi (ru)")} {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="flex justify-end gap-4">
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => setOpen(false)}
|
||||
className="border-slate-600 text-white hover:bg-gray-600 bg-gray-600"
|
||||
>
|
||||
{t("Bekor qilish")}
|
||||
</Button>
|
||||
<Button type="submit">
|
||||
{isPending || updatePending ? (
|
||||
<Loader className="animate-spin" />
|
||||
) : types === "create" ? (
|
||||
t("Saqlash")
|
||||
) : (
|
||||
t("Tahrirlash")
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default HotelType;
|
||||
@@ -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<typeof formSchema>) {
|
||||
if (types === "create") {
|
||||
@@ -192,7 +201,6 @@ const MealTable = ({
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex justify-end">
|
||||
@@ -256,7 +264,12 @@ const MealTable = ({
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
<RealPagination table={table} totalPages={data?.total_pages} />
|
||||
<RealPagination
|
||||
table={table}
|
||||
totalPages={data?.total_pages}
|
||||
namePage="pageMeal"
|
||||
namePageSize="pageSizeMeal"
|
||||
/>
|
||||
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogContent>
|
||||
@@ -321,4 +334,4 @@ const MealTable = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default MealTable;
|
||||
export default MealPlan;
|
||||
|
||||
335
src/pages/tours/ui/RegionTable.tsx
Normal file
335
src/pages/tours/ui/RegionTable.tsx
Normal file
@@ -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<boolean>(false);
|
||||
const [editId, setEditId] = useState<number | null>(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<z.infer<typeof formSchema>>({
|
||||
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<typeof formSchema>) {
|
||||
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 (
|
||||
<>
|
||||
<div className="flex justify-end">
|
||||
<Button
|
||||
variant="default"
|
||||
onClick={() => {
|
||||
setOpen(true);
|
||||
setTypes("create");
|
||||
form.reset();
|
||||
}}
|
||||
>
|
||||
<PlusIcon className="mr-2" />
|
||||
{t("Qo‘shish")}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="border border-gray-700 rounded-md overflow-hidden mt-4">
|
||||
<Table key={data?.current_page}>
|
||||
<TableHeader>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<TableRow key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header) => (
|
||||
<TableHead key={header.id}>
|
||||
{header.isPlaceholder
|
||||
? null
|
||||
: flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext(),
|
||||
)}
|
||||
</TableHead>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableHeader>
|
||||
|
||||
<TableBody>
|
||||
{table.getRowModel().rows?.length ? (
|
||||
table.getRowModel().rows.map((row) => (
|
||||
<TableRow key={row.id}>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<TableCell key={cell.id}>
|
||||
{flexRender(
|
||||
cell.column.columnDef.cell,
|
||||
cell.getContext(),
|
||||
)}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={columns.length}
|
||||
className="h-24 text-center text-gray-400"
|
||||
>
|
||||
{t("Ma'lumot topilmadi")}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
<RealPagination
|
||||
table={table}
|
||||
totalPages={data?.total_pages}
|
||||
namePage="pageRegion"
|
||||
namePageSize="pageSizeRegion"
|
||||
/>
|
||||
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogContent>
|
||||
<p className="text-xl">
|
||||
{types === "create" ? t("Saqlash") : t("Tahrirlash")}
|
||||
</p>
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="space-y-6 p-2"
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("Nomi")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t("Nomi")} {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name_ru"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("Nomi (ru)")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t("Nomi (ru)")} {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="flex justify-end gap-4">
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => setOpen(false)}
|
||||
className="border-slate-600 text-white hover:bg-gray-600 bg-gray-600"
|
||||
>
|
||||
{t("Bekor qilish")}
|
||||
</Button>
|
||||
<Button type="submit">
|
||||
{isPending || updatePending ? (
|
||||
<Loader className="animate-spin" />
|
||||
) : types === "create" ? (
|
||||
t("Saqlash")
|
||||
) : (
|
||||
t("Tahrirlash")
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default RegionTable;
|
||||
@@ -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<string>("");
|
||||
const [searchCity, setSearchCity] = useState<string>("");
|
||||
const [openCountry, setOpenCountry] = useState<boolean>(false);
|
||||
const [openCity, setOpenCity] = useState<boolean>(false);
|
||||
const [selectedCountry, setSelectedCountry] = useState<number | null>(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<string>("");
|
||||
const [searchCityDes, setSearchCityDes] = useState<string>("");
|
||||
const [openCountryDes, setOpenCountryDes] = useState<boolean>(false);
|
||||
const [openCityDes, setOpenCityDes] = useState<boolean>(false);
|
||||
const [selectedCountryDes, setSelectedCountryDes] = useState<number | null>(
|
||||
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 (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
|
||||
@@ -620,24 +703,224 @@ const StepOne = ({
|
||||
|
||||
<div className="grid grid-cols-2 gap-4 max-lg:grid-cols-1 items-start">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="departure"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<Label className="text-md">{t("Ketish joyi")}</Label>
|
||||
control={form.control}
|
||||
render={({ field }) => {
|
||||
const selectedCity = regionData?.results.find(
|
||||
(u) => String(u.id) === field.value,
|
||||
);
|
||||
|
||||
return (
|
||||
<FormItem className="flex flex-col gap-2">
|
||||
<Label className="text-md">Ketish joyi</Label>
|
||||
{!selectedCountry && (
|
||||
<Popover open={openCountry} onOpenChange={setOpenCountry}>
|
||||
<PopoverTrigger asChild>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="Toshkent"
|
||||
{...field}
|
||||
className="h-12 !text-md"
|
||||
/>
|
||||
<Button
|
||||
variant="outline"
|
||||
className={cn(
|
||||
"w-full h-12 justify-between transition-all duration-200",
|
||||
!selectedCountry && "text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{selectedCountry
|
||||
? countryData?.results.find(
|
||||
(c) => c.id === selectedCountry,
|
||||
)?.name
|
||||
: "Davlatni tanlang"}
|
||||
<ChevronsUpDown className="ml-2 h-4 opacity-50" />
|
||||
</Button>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</PopoverTrigger>
|
||||
<AnimatePresence mode="wait">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -10 }}
|
||||
transition={{ duration: 0.3, ease: "easeOut" }}
|
||||
>
|
||||
<PopoverContent className="!w-96 p-0">
|
||||
<Command shouldFilter={false}>
|
||||
<CommandInput
|
||||
placeholder="Qidirish..."
|
||||
value={searchCountry}
|
||||
onValueChange={setSearchCountry}
|
||||
/>
|
||||
<CommandList>
|
||||
{countryLoad ? (
|
||||
<div className="py-6 text-center">
|
||||
<Loader2 className="animate-spin mx-auto" />
|
||||
</div>
|
||||
) : (
|
||||
<CommandGroup>
|
||||
{countryData?.results.map((c, index) => (
|
||||
<motion.div
|
||||
key={c.id}
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{
|
||||
duration: 0.2,
|
||||
delay: index * 0.03,
|
||||
ease: "easeOut",
|
||||
}}
|
||||
>
|
||||
<CommandItem
|
||||
onSelect={() => {
|
||||
handleCountrySelect(c.id);
|
||||
setOpenCountry(false);
|
||||
setTimeout(
|
||||
() => setOpenCity(true),
|
||||
200,
|
||||
);
|
||||
}}
|
||||
className="cursor-pointer transition-colors"
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-4 transition-opacity duration-200",
|
||||
selectedCountry === c.id
|
||||
? "opacity-100"
|
||||
: "opacity-0",
|
||||
)}
|
||||
/>
|
||||
{c.name}
|
||||
</CommandItem>
|
||||
</motion.div>
|
||||
))}
|
||||
</CommandGroup>
|
||||
)}
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
</Popover>
|
||||
)}
|
||||
|
||||
<FormField
|
||||
{/* CITY DROPDOWN */}
|
||||
{selectedCountry && (
|
||||
<Popover open={openCity} onOpenChange={setOpenCity}>
|
||||
<PopoverTrigger asChild>
|
||||
<FormControl>
|
||||
<Button
|
||||
variant="outline"
|
||||
className={cn(
|
||||
"w-full h-12 justify-between transition-all duration-200",
|
||||
!field.value && "text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{selectedCity
|
||||
? selectedCity.name
|
||||
: "Shaharni tanlang"}
|
||||
<ChevronsUpDown className="ml-2 h-4 opacity-50" />
|
||||
</Button>
|
||||
</FormControl>
|
||||
</PopoverTrigger>
|
||||
<AnimatePresence>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, height: 0, y: -10 }}
|
||||
animate={{
|
||||
opacity: 1,
|
||||
height: "auto",
|
||||
y: 0,
|
||||
transition: {
|
||||
height: { duration: 0.3 },
|
||||
opacity: { duration: 0.3, delay: 0.1 },
|
||||
y: { duration: 0.3 },
|
||||
},
|
||||
}}
|
||||
exit={{
|
||||
opacity: 0,
|
||||
height: 0,
|
||||
y: -10,
|
||||
transition: {
|
||||
height: { duration: 0.25 },
|
||||
opacity: { duration: 0.15 },
|
||||
},
|
||||
}}
|
||||
style={{ overflow: "hidden" }}
|
||||
>
|
||||
<PopoverContent className="!w-96 p-0">
|
||||
<Command shouldFilter={false}>
|
||||
<CommandInput
|
||||
placeholder="Qidirish..."
|
||||
value={searchCity}
|
||||
onValueChange={setSearchCity}
|
||||
/>
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 0.1 }}
|
||||
>
|
||||
<Button
|
||||
className="w-fit ml-2 p-1 cursor-pointer hover:bg-accent transition-colors"
|
||||
size={"icon"}
|
||||
variant={"ghost"}
|
||||
onClick={() => {
|
||||
handleCountrySelect(null);
|
||||
setOpenCity(false);
|
||||
setTimeout(() => setOpenCountry(true), 200);
|
||||
}}
|
||||
>
|
||||
<MoveLeft className="mr-1" />
|
||||
<p>Orqaga</p>
|
||||
</Button>
|
||||
</motion.div>
|
||||
<CommandList>
|
||||
{regionLoad ? (
|
||||
<div className="py-6 text-center">
|
||||
<Loader2 className="animate-spin mx-auto" />
|
||||
</div>
|
||||
) : (
|
||||
<CommandGroup>
|
||||
{regionData?.results.map((r, index) => (
|
||||
<motion.div
|
||||
key={r.id}
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{
|
||||
duration: 0.2,
|
||||
delay: index * 0.03,
|
||||
ease: "easeOut",
|
||||
}}
|
||||
>
|
||||
<CommandItem
|
||||
onSelect={() => {
|
||||
field.onChange(String(r.id));
|
||||
setOpenCity(false);
|
||||
}}
|
||||
className="cursor-pointer transition-colors"
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-4 transition-opacity duration-200",
|
||||
field.value === String(r.id)
|
||||
? "opacity-100"
|
||||
: "opacity-0",
|
||||
)}
|
||||
/>
|
||||
{r.name}
|
||||
</CommandItem>
|
||||
</motion.div>
|
||||
))}
|
||||
</CommandGroup>
|
||||
)}
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
</Popover>
|
||||
)}
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* <FormField
|
||||
control={form.control}
|
||||
name="departure_ru"
|
||||
render={({ field }) => (
|
||||
@@ -653,27 +936,234 @@ const StepOne = ({
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
/> */}
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="destination"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<Label className="text-md">{t("Borish joyi")}</Label>
|
||||
control={form.control}
|
||||
render={({ field }) => {
|
||||
const selectedCity = regionDataDes?.results.find(
|
||||
(u) => String(u.id) === field.value,
|
||||
);
|
||||
|
||||
return (
|
||||
<FormItem className="flex flex-col gap-2">
|
||||
<Label className="text-md">Borish joyi</Label>
|
||||
|
||||
{/* COUNTRY DROPDOWN */}
|
||||
{!selectedCountryDes && (
|
||||
<Popover
|
||||
open={openCountryDes}
|
||||
onOpenChange={setOpenCountryDes}
|
||||
>
|
||||
<PopoverTrigger asChild>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="Dubai"
|
||||
{...field}
|
||||
className="h-12 !text-md"
|
||||
/>
|
||||
<Button
|
||||
variant="outline"
|
||||
className={cn(
|
||||
"w-full h-12 justify-between transition-all duration-200",
|
||||
!selectedCountryDes && "text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{selectedCountryDes
|
||||
? countryDataDes?.results.find(
|
||||
(c) => c.id === selectedCountryDes,
|
||||
)?.name
|
||||
: "Davlatni tanlang"}
|
||||
<ChevronsUpDown className="ml-2 h-4 opacity-50" />
|
||||
</Button>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</PopoverTrigger>
|
||||
<AnimatePresence mode="wait">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -10 }}
|
||||
transition={{ duration: 0.3, ease: "easeOut" }}
|
||||
>
|
||||
<PopoverContent className="!w-96 p-0">
|
||||
<Command shouldFilter={false}>
|
||||
<CommandInput
|
||||
placeholder="Qidirish..."
|
||||
value={searchCountryDes}
|
||||
onValueChange={setSearchCountryDes}
|
||||
/>
|
||||
<CommandList>
|
||||
{countryLoadDes ? (
|
||||
<div className="py-6 text-center">
|
||||
<Loader2 className="animate-spin mx-auto" />
|
||||
</div>
|
||||
) : (
|
||||
<CommandGroup>
|
||||
{countryDataDes?.results.map((c, index) => (
|
||||
<motion.div
|
||||
key={c.id}
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{
|
||||
duration: 0.2,
|
||||
delay: index * 0.03,
|
||||
ease: "easeOut",
|
||||
}}
|
||||
>
|
||||
<CommandItem
|
||||
onSelect={() => {
|
||||
handleCountrySelectDes(c.id);
|
||||
setOpenCountryDes(false);
|
||||
setTimeout(
|
||||
() => setOpenCityDes(true),
|
||||
200,
|
||||
);
|
||||
}}
|
||||
className="cursor-pointer transition-colors"
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-4 transition-opacity duration-200",
|
||||
selectedCountryDes === c.id
|
||||
? "opacity-100"
|
||||
: "opacity-0",
|
||||
)}
|
||||
/>
|
||||
{c.name}
|
||||
</CommandItem>
|
||||
</motion.div>
|
||||
))}
|
||||
</CommandGroup>
|
||||
)}
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
</Popover>
|
||||
)}
|
||||
|
||||
<FormField
|
||||
{selectedCountryDes && (
|
||||
<Popover open={openCityDes} onOpenChange={setOpenCityDes}>
|
||||
<PopoverTrigger asChild>
|
||||
<FormControl>
|
||||
<Button
|
||||
variant="outline"
|
||||
className={cn(
|
||||
"w-full h-12 justify-between transition-all duration-200",
|
||||
!field.value && "text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{selectedCity
|
||||
? selectedCity.name
|
||||
: "Shaharni tanlang"}
|
||||
<ChevronsUpDown className="ml-2 h-4 opacity-50" />
|
||||
</Button>
|
||||
</FormControl>
|
||||
</PopoverTrigger>
|
||||
<AnimatePresence>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, height: 0, y: -10 }}
|
||||
animate={{
|
||||
opacity: 1,
|
||||
height: "auto",
|
||||
y: 0,
|
||||
transition: {
|
||||
height: { duration: 0.3 },
|
||||
opacity: { duration: 0.3, delay: 0.1 },
|
||||
y: { duration: 0.3 },
|
||||
},
|
||||
}}
|
||||
exit={{
|
||||
opacity: 0,
|
||||
height: 0,
|
||||
y: -10,
|
||||
transition: {
|
||||
height: { duration: 0.25 },
|
||||
opacity: { duration: 0.15 },
|
||||
},
|
||||
}}
|
||||
style={{ overflow: "hidden" }}
|
||||
>
|
||||
<PopoverContent className="!w-96 p-0">
|
||||
<Command shouldFilter={false}>
|
||||
<CommandInput
|
||||
placeholder="Qidirish..."
|
||||
value={searchCityDes}
|
||||
onValueChange={setSearchCityDes}
|
||||
/>
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 0.1 }}
|
||||
>
|
||||
<Button
|
||||
className="w-fit ml-2 p-1 cursor-pointer hover:bg-accent transition-colors"
|
||||
size={"icon"}
|
||||
variant={"ghost"}
|
||||
onClick={() => {
|
||||
handleCountrySelectDes(null);
|
||||
setOpenCityDes(false);
|
||||
setTimeout(
|
||||
() => setOpenCountryDes(true),
|
||||
200,
|
||||
);
|
||||
}}
|
||||
>
|
||||
<MoveLeft className="mr-1" />
|
||||
<p>Orqaga</p>
|
||||
</Button>
|
||||
</motion.div>
|
||||
<CommandList>
|
||||
{regionLoadDes ? (
|
||||
<div className="py-6 text-center">
|
||||
<Loader2 className="animate-spin mx-auto" />
|
||||
</div>
|
||||
) : (
|
||||
<CommandGroup>
|
||||
{regionDataDes?.results.map((r, index) => (
|
||||
<motion.div
|
||||
key={r.id}
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{
|
||||
duration: 0.2,
|
||||
delay: index * 0.03,
|
||||
ease: "easeOut",
|
||||
}}
|
||||
>
|
||||
<CommandItem
|
||||
onSelect={() => {
|
||||
field.onChange(String(r.id));
|
||||
setOpenCityDes(false);
|
||||
}}
|
||||
className="cursor-pointer transition-colors"
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-4 transition-opacity duration-200",
|
||||
field.value === String(r.id)
|
||||
? "opacity-100"
|
||||
: "opacity-0",
|
||||
)}
|
||||
/>
|
||||
{r.name}
|
||||
</CommandItem>
|
||||
</motion.div>
|
||||
))}
|
||||
</CommandGroup>
|
||||
)}
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
</Popover>
|
||||
)}
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* <FormField
|
||||
control={form.control}
|
||||
name="destination_ru"
|
||||
render={({ field }) => (
|
||||
@@ -689,7 +1179,7 @@ const StepOne = ({
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
/> */}
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
getHotel,
|
||||
hotelFeature,
|
||||
hotelFeatureType,
|
||||
hotelMealList,
|
||||
hotelType,
|
||||
} from "@/pages/tours/lib/api";
|
||||
import { useTicketStore } from "@/pages/tours/lib/store";
|
||||
@@ -15,6 +16,16 @@ import type {
|
||||
HotelFeaturesType,
|
||||
Type,
|
||||
} from "@/pages/tours/lib/type";
|
||||
import { cn } from "@/shared/lib/utils";
|
||||
import { Button } from "@/shared/ui/button";
|
||||
import {
|
||||
Command,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
CommandList,
|
||||
} from "@/shared/ui/command";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@@ -24,6 +35,7 @@ import {
|
||||
} from "@/shared/ui/form";
|
||||
import { Input } from "@/shared/ui/input";
|
||||
import { Label } from "@/shared/ui/label";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/shared/ui/popover";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -33,7 +45,7 @@ import {
|
||||
} from "@/shared/ui/select";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { X } from "lucide-react";
|
||||
import { Check, ChevronsUpDown, Loader2, X } from "lucide-react";
|
||||
import { useEffect, useState, type Dispatch, type SetStateAction } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -100,18 +112,7 @@ const StepTwo = ({
|
||||
form.setValue("title", hotel.name);
|
||||
form.setValue("rating", String(hotel.rating));
|
||||
|
||||
const mealPlan =
|
||||
hotel.meal_plan === "breakfast"
|
||||
? "Breakfast Only"
|
||||
: hotel.meal_plan === "all_inclusive"
|
||||
? "All Inclusive"
|
||||
: hotel.meal_plan === "half_board"
|
||||
? "Half Board"
|
||||
: hotel.meal_plan === "full_board"
|
||||
? "Full Board"
|
||||
: "all_inclusive";
|
||||
|
||||
form.setValue("mealPlan", mealPlan);
|
||||
form.setValue("mealPlan", hotel.meal_plan.toString());
|
||||
|
||||
form.setValue(
|
||||
"hotelType",
|
||||
@@ -285,6 +286,15 @@ const StepTwo = ({
|
||||
);
|
||||
};
|
||||
|
||||
const [openMeal, setOpenMeal] = useState<boolean>(false);
|
||||
const [searchMeal, setSearchMeal] = useState<string>("");
|
||||
|
||||
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 (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
|
||||
@@ -400,28 +395,89 @@ const StepTwo = ({
|
||||
|
||||
{/* Meal Plan */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="mealPlan"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<Label>{t("Taom rejasi")}</Label>
|
||||
control={form.control}
|
||||
render={({ field }) => {
|
||||
const selectedUser = mealData?.results.find(
|
||||
(u) => String(u.id) === field.value,
|
||||
);
|
||||
return (
|
||||
<FormItem className="flex flex-col">
|
||||
<Label className="text-md">Taom rejasi</Label>
|
||||
|
||||
<Popover open={openMeal} onOpenChange={setOpenMeal}>
|
||||
<PopoverTrigger asChild>
|
||||
<FormControl>
|
||||
<Select value={field.value} onValueChange={field.onChange}>
|
||||
<SelectTrigger className="!h-12 w-full">
|
||||
<SelectValue placeholder={t("Tanlang")} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{mealPlans.map((plan) => (
|
||||
<SelectItem key={plan} value={plan}>
|
||||
{t(plan)}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={openMeal}
|
||||
className={cn(
|
||||
"w-full h-12 justify-between",
|
||||
!field.value && "text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{selectedUser
|
||||
? `${selectedUser.name}`
|
||||
: "Taom rejasi tanlang"}
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</FormControl>
|
||||
</PopoverTrigger>
|
||||
|
||||
<PopoverContent
|
||||
className="w-[--radix-popover-trigger-width] p-0"
|
||||
align="start"
|
||||
>
|
||||
<Command shouldFilter={false}>
|
||||
<CommandInput
|
||||
placeholder="Qidirish..."
|
||||
className="h-9"
|
||||
value={searchMeal}
|
||||
onValueChange={setSearchMeal}
|
||||
/>
|
||||
|
||||
<CommandList>
|
||||
{mealLoad ? (
|
||||
<div className="py-6 text-center text-sm">
|
||||
<Loader2 className="mx-auto h-4 w-4 animate-spin" />
|
||||
</div>
|
||||
) : mealData && mealData.results.length > 0 ? (
|
||||
<CommandGroup>
|
||||
{mealData.results.map((u) => (
|
||||
<CommandItem
|
||||
key={u.id}
|
||||
value={`${u.id}`}
|
||||
onSelect={() => {
|
||||
field.onChange(String(u.id));
|
||||
setOpenMeal(false);
|
||||
}}
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
field.value === String(u.id)
|
||||
? "opacity-100"
|
||||
: "opacity-0",
|
||||
)}
|
||||
/>
|
||||
{u.name}
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
) : (
|
||||
<CommandEmpty>Taom rejasi topilmadi</CommandEmpty>
|
||||
)}
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Hotel Type */}
|
||||
|
||||
@@ -221,7 +221,7 @@ export default function TourDetailPage() {
|
||||
{t("Jo'nash joyi")}
|
||||
</p>
|
||||
<p className="font-semibold text-white">
|
||||
{tour.departure}
|
||||
{tour.departure?.name}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -233,7 +233,7 @@ export default function TourDetailPage() {
|
||||
{t("Yo'nalish")}
|
||||
</p>
|
||||
<p className="font-semibold text-white">
|
||||
{tour.destination}
|
||||
{tour.destination?.name}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -188,7 +188,9 @@ const Tours = ({ user }: { user: Role }) => {
|
||||
<TableCell>
|
||||
<div className="flex items-center gap-2 font-semibold">
|
||||
<Plane className="w-4 h-4 text-primary" />
|
||||
{tour.destination}
|
||||
{typeof tour.destination === "object"
|
||||
? tour.destination?.name
|
||||
: tour.destination}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="text-sm text-primary font-medium">
|
||||
@@ -319,7 +321,9 @@ const Tours = ({ user }: { user: Role }) => {
|
||||
<Plane className="w-4 h-4 text-primary flex-shrink-0" />
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="font-semibold truncate">
|
||||
{tour.destination}
|
||||
{typeof tour.destination === "object"
|
||||
? tour.destination?.name
|
||||
: tour.destination}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{tour.duration_days} kun • {tour.hotel_name}
|
||||
|
||||
@@ -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<number | null>(null);
|
||||
const [countryId, setCountryId] = useState<number | null>(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 = () => {
|
||||
>
|
||||
<TabsList className="w-full">
|
||||
<TabsTrigger value="badge">{t("Belgilar (Badge)")}</TabsTrigger>
|
||||
<TabsTrigger value="meal_plan">
|
||||
{t("Ovqatlanish turlari")}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="tarif">{t("Tariflar")}</TabsTrigger>
|
||||
<TabsTrigger value="transport">{t("Transport")}</TabsTrigger>
|
||||
<TabsTrigger value="meal">{t("Qulayliklar")}</TabsTrigger>
|
||||
@@ -198,6 +263,7 @@ const ToursSetting: React.FC = () => {
|
||||
<TabsTrigger value="hotel_features">
|
||||
{t("Otel sharoitlari")}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="country">{t("Davlatlar")}</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="badge" className="space-y-4">
|
||||
@@ -273,7 +339,7 @@ const ToursSetting: React.FC = () => {
|
||||
onRetry={typeRef}
|
||||
/>
|
||||
) : (
|
||||
<MealTable
|
||||
<HotelType
|
||||
data={typeData}
|
||||
page={pageType}
|
||||
pageSize={pageSizeType}
|
||||
@@ -281,6 +347,23 @@ const ToursSetting: React.FC = () => {
|
||||
)}
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="meal_plan" className="space-y-4">
|
||||
{mealLoad ? (
|
||||
<LoadingDisplay />
|
||||
) : mealError ? (
|
||||
<ErrorDisplay
|
||||
message={t("Otel turlarini yuklashda xatolik yuz berdi.")}
|
||||
onRetry={mealRef}
|
||||
/>
|
||||
) : (
|
||||
<MealTable
|
||||
data={mealData}
|
||||
page={pageMeal}
|
||||
pageSize={pageSizeMeal}
|
||||
/>
|
||||
)}
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="hotel_features" className="space-y-4">
|
||||
{featureLoad ? (
|
||||
<LoadingDisplay />
|
||||
@@ -317,6 +400,43 @@ const ToursSetting: React.FC = () => {
|
||||
/>
|
||||
)}
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="country" className="space-y-4">
|
||||
{countryLoad ? (
|
||||
<LoadingDisplay />
|
||||
) : countryError ? (
|
||||
<ErrorDisplay
|
||||
message={t("Sharoit turlarini yuklashda xatolik yuz berdi.")}
|
||||
onRetry={countryRef}
|
||||
/>
|
||||
) : (
|
||||
<CountryTable
|
||||
data={countryData}
|
||||
page={pageCountry}
|
||||
setActiveTab={setActiveTab}
|
||||
setFeatureId={setCountryId}
|
||||
pageSize={pageSizeCountry}
|
||||
/>
|
||||
)}
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="region" className="space-y-4">
|
||||
{regionLoad ? (
|
||||
<LoadingDisplay />
|
||||
) : regionError ? (
|
||||
<ErrorDisplay
|
||||
message={t("Sharoit turlarini yuklashda xatolik yuz berdi.")}
|
||||
onRetry={regionRef}
|
||||
/>
|
||||
) : (
|
||||
<RegionTable
|
||||
data={regionData}
|
||||
page={pageRegion}
|
||||
featureId={countryId}
|
||||
pageSize={pageSizeRegion}
|
||||
/>
|
||||
)}
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
"Xodimlar": "Сотрудники",
|
||||
"Siz agentlikga yangi foydalanuvchi qo'shmoqchimisiz": "Вы хотите добавить нового пользователя в агентство",
|
||||
"Foydalanuvchi qo'shish": "Добавить пользователя",
|
||||
"Ovqatlanish turlari": "Виды питания",
|
||||
"Davlatlar": "Государство",
|
||||
"Byudjet": "Бюджет",
|
||||
"Turlar": "Туры",
|
||||
"Tur sozlamalari": "Настройки туров",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />;
|
||||
}
|
||||
|
||||
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor };
|
||||
export { Popover, PopoverAnchor, PopoverContent, PopoverTrigger };
|
||||
|
||||
Reference in New Issue
Block a user