1818 lines
67 KiB
TypeScript
1818 lines
67 KiB
TypeScript
"use client";
|
||
|
||
import {
|
||
createTours,
|
||
hotelBadge,
|
||
hotelTarif,
|
||
hotelTransport,
|
||
} from "@/pages/tours/lib/api";
|
||
import { TourformSchema } from "@/pages/tours/lib/form";
|
||
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 { Badge } from "@/shared/ui/badge";
|
||
import { Button } from "@/shared/ui/button";
|
||
import { Calendar } from "@/shared/ui/calendar";
|
||
import {
|
||
Command,
|
||
CommandGroup,
|
||
CommandItem,
|
||
CommandList,
|
||
} from "@/shared/ui/command";
|
||
import {
|
||
Form,
|
||
FormControl,
|
||
FormField,
|
||
FormItem,
|
||
FormMessage,
|
||
} from "@/shared/ui/form";
|
||
import { Input } from "@/shared/ui/input";
|
||
import IconSelect from "@/shared/ui/iocnSelect";
|
||
import { Label } from "@/shared/ui/label";
|
||
import { Popover, PopoverContent, PopoverTrigger } from "@/shared/ui/popover";
|
||
import { RadioGroup, RadioGroupItem } from "@/shared/ui/radio-group";
|
||
import { Textarea } from "@/shared/ui/textarea";
|
||
import { zodResolver } from "@hookform/resolvers/zod";
|
||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||
import * as LucideIcons from "lucide-react";
|
||
import { ChevronDownIcon, SquareCheckBig, XIcon } from "lucide-react";
|
||
import { useEffect, useState, type Dispatch, type SetStateAction } from "react";
|
||
import { useForm } from "react-hook-form";
|
||
import { useTranslation } from "react-i18next";
|
||
import { toast } from "sonner";
|
||
import z from "zod";
|
||
|
||
const StepOne = ({
|
||
setStep,
|
||
data,
|
||
isEditMode,
|
||
}: {
|
||
setStep: Dispatch<SetStateAction<number>>;
|
||
data: GetOneTours | undefined;
|
||
isEditMode: boolean;
|
||
}) => {
|
||
const [displayPrice, setDisplayPrice] = useState("");
|
||
const [tarifdisplayPrice, setTarifDisplayPrice] = useState<string[]>([]);
|
||
const [transportPrices, setTransportPrices] = useState<string[]>([]);
|
||
|
||
const { t } = useTranslation();
|
||
const [openDate, setOpenDate] = useState(false);
|
||
const [openDateTravel, setOpenDateTravel] = useState(false);
|
||
|
||
const form = useForm<z.infer<typeof TourformSchema>>({
|
||
resolver: zodResolver(TourformSchema),
|
||
defaultValues: {
|
||
title: "",
|
||
title_ru: "",
|
||
hotel_services: [],
|
||
price: 0,
|
||
departure: "",
|
||
tarif: [],
|
||
transport: [],
|
||
departure_ru: "",
|
||
destination: "",
|
||
destination_ru: "",
|
||
location_name: "",
|
||
location_name_ru: "",
|
||
departureDateTime: {
|
||
date: undefined,
|
||
time: "",
|
||
},
|
||
amenities: [],
|
||
hotel_meals: [],
|
||
travelDateTime: {
|
||
date: undefined,
|
||
time: "",
|
||
},
|
||
passenger_count: 1,
|
||
min_person: 1,
|
||
max_person: 1,
|
||
languages: "",
|
||
duration: 1,
|
||
badges: [],
|
||
images: [],
|
||
hotel_info: "",
|
||
hotel_info_ru: "",
|
||
hotel_meals_info: "",
|
||
hotel_meals_info_ru: "",
|
||
ticket_itinerary: [],
|
||
},
|
||
});
|
||
|
||
const { addAmenity, setId } = useTicketStore();
|
||
|
||
useEffect(() => {
|
||
if (isEditMode && data?.data) {
|
||
const tourData = data.data;
|
||
|
||
form.setValue("title", tourData.title);
|
||
form.setValue("title_ru", formatPrice(tourData.title));
|
||
form.setValue("price", tourData.price);
|
||
setDisplayPrice(formatPrice(tourData.price));
|
||
form.setValue("passenger_count", tourData.passenger_count || 1);
|
||
form.setValue("min_person", tourData.min_person || 1);
|
||
form.setValue("max_person", tourData.max_person || 1);
|
||
form.setValue("departure", tourData.departure || "");
|
||
form.setValue("departure_ru", tourData.departure || "");
|
||
form.setValue("destination", tourData.destination || "");
|
||
form.setValue("destination_ru", tourData.destination || "");
|
||
form.setValue("location_name", tourData.location_name || "");
|
||
form.setValue("location_name_ru", tourData.location_name || "");
|
||
form.setValue("hotel_info", tourData.hotel_info || "");
|
||
form.setValue("hotel_info_ru", tourData.hotel_info || "");
|
||
form.setValue("hotel_meals_info", tourData.hotel_meals || "");
|
||
form.setValue("hotel_meals_info_ru", tourData.hotel_meals || "");
|
||
form.setValue("languages", tourData.languages || "");
|
||
form.setValue("duration", tourData.duration_days || 1);
|
||
form.setValue("visa_required", tourData.visa_required ? "yes" : "no");
|
||
form.setValue("badges", tourData.badge || []);
|
||
|
||
// DateTime fields
|
||
if (tourData.departure_time) {
|
||
const departureDate = new Date(tourData.departure_time);
|
||
form.setValue("departureDateTime", {
|
||
date: departureDate,
|
||
time: departureDate.toTimeString().slice(0, 8), // HH:MM:SS
|
||
});
|
||
}
|
||
|
||
if (tourData.travel_time) {
|
||
const travelDate = new Date(tourData.travel_time);
|
||
form.setValue("travelDateTime", {
|
||
date: travelDate,
|
||
time: travelDate.toTimeString().slice(0, 8),
|
||
});
|
||
}
|
||
|
||
// Amenities
|
||
if (tourData.ticket_amenities && tourData.ticket_amenities.length > 0) {
|
||
const amenities = tourData.ticket_amenities.map((item) => ({
|
||
name: item.name,
|
||
name_ru: item.name_ru,
|
||
icon_name: item.icon_name,
|
||
}));
|
||
form.setValue("amenities", amenities);
|
||
}
|
||
|
||
if (
|
||
tourData.ticket_included_services &&
|
||
tourData.ticket_included_services.length > 0
|
||
) {
|
||
const services = tourData.ticket_included_services.map((item) => ({
|
||
image: item.image,
|
||
title: item.title,
|
||
title_ru: item.title_ru,
|
||
description: item.desc_uz || item.desc,
|
||
desc_ru: item.desc || item.desc,
|
||
}));
|
||
form.setValue("hotel_services", services);
|
||
}
|
||
|
||
if (
|
||
tourData.ticket_hotel_meals &&
|
||
tourData.ticket_hotel_meals.length > 0
|
||
) {
|
||
const meals = tourData.ticket_hotel_meals.map((item) => ({
|
||
image: item.image,
|
||
title: item.name,
|
||
title_ru: item.name_ru,
|
||
description: item.desc,
|
||
desc_ru: item.desc_ru,
|
||
}));
|
||
form.setValue("hotel_meals", meals);
|
||
}
|
||
|
||
// Transport
|
||
if (tourData.transports && tourData.transports.length > 0) {
|
||
const transports = tourData.transports.map((item, index) => ({
|
||
transport: index + 1, // Agar transport ID bo'lsa, uni ishlatish kerak
|
||
price: item.price,
|
||
}));
|
||
// const tariff = tourData.tar => ({
|
||
// transport: index + 1,
|
||
// price: item.price,
|
||
// }));
|
||
form.setValue("transport", transports);
|
||
// form.setValue("tarif", );
|
||
}
|
||
|
||
// Ticket itinerary
|
||
if (tourData.ticket_itinerary && tourData.ticket_itinerary.length > 0) {
|
||
const itinerary = tourData.ticket_itinerary.map((item) => ({
|
||
ticket_itinerary_image: [], // Image fayllarni alohida handle qilish kerak
|
||
title: item.title,
|
||
title_ru: item.title_ru,
|
||
duration: item.duration,
|
||
ticket_itinerary_destinations: [], // Agar destinations bo'lsa qo'shish kerak
|
||
}));
|
||
form.setValue("ticket_itinerary", itinerary);
|
||
}
|
||
|
||
form.setValue("banner", tourData.image_banner);
|
||
if (tourData.ticket_images && tourData.ticket_images.length > 0) {
|
||
const images = tourData.ticket_images.map((img) => img.image); // faqat linklarni olamiz
|
||
form.setValue("images", images);
|
||
}
|
||
}
|
||
}, [isEditMode, data, form]);
|
||
|
||
const { watch, setValue } = form;
|
||
const selectedDate = watch("departureDateTime.date");
|
||
const selectedDateTravel = watch("travelDateTime.date");
|
||
const [selectedIcon, setSelectedIcon] = useState("");
|
||
|
||
const { mutate: create } = useMutation({
|
||
mutationFn: (body: FormData) => {
|
||
return createTours({ body });
|
||
},
|
||
onSuccess: (res) => {
|
||
setId(res.data.data.id);
|
||
setStep(2);
|
||
},
|
||
onError: () => {
|
||
toast.error(t("Xatolik yuz berdi"), {
|
||
richColors: true,
|
||
position: "top-center",
|
||
});
|
||
},
|
||
});
|
||
|
||
function onSubmit(value: z.infer<typeof TourformSchema>) {
|
||
const formData = new FormData();
|
||
|
||
formData.append("title", value.title);
|
||
formData.append("location_name", value.location_name);
|
||
formData.append("location_name_ru", value.location_name_ru);
|
||
formData.append(
|
||
"visa_required",
|
||
value.visa_required === "yes" ? "true" : "false",
|
||
);
|
||
|
||
formData.append("title_ru", value.title_ru);
|
||
formData.append("price", String(value.price));
|
||
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("destination", String(value.destination));
|
||
formData.append("destination_ru", String(value.destination_ru));
|
||
formData.append(
|
||
"departure_time",
|
||
value.departureDateTime.date?.toISOString(),
|
||
);
|
||
formData.append(
|
||
"travel_time",
|
||
String(value.travelDateTime.date.toISOString()),
|
||
);
|
||
formData.append("passenger_count", String(value.passenger_count));
|
||
formData.append("languages", value.languages);
|
||
formData.append("hotel_info", value.hotel_info);
|
||
formData.append("hotel_info_ru", value.hotel_info_ru);
|
||
formData.append("hotel_meals", value.hotel_meals_info);
|
||
formData.append("hotel_meals_ru", value.hotel_meals_info_ru);
|
||
formData.append("duration_days", String(value.duration));
|
||
formData.append("rating", String("0.0"));
|
||
formData.append("image_banner", value.banner);
|
||
value.tarif.forEach((e, i) => {
|
||
formData.append(`tariff[${i}]tariff`, String(e.tariff));
|
||
formData.append(`tariff[${i}]price`, String(e.price));
|
||
});
|
||
value.transport.forEach((e, i) => {
|
||
formData.append(`transports[${i}]transport`, String(e.transport));
|
||
formData.append(`transports[${i}]price`, String(e.price));
|
||
});
|
||
value.badges?.forEach((e, i) => {
|
||
formData.append(`badge[${i}]`, String(e));
|
||
});
|
||
value.images.forEach((e) => formData.append("ticket_images", e));
|
||
value.amenities.forEach((e, i) => {
|
||
formData.append(`ticket_amenities[${i}]name`, e.name);
|
||
formData.append(`ticket_amenities[${i}]name_ru`, e.name_ru);
|
||
formData.append(`ticket_amenities[${i}]icon_name`, e.icon_name);
|
||
addAmenity({
|
||
icon_name: e.icon_name,
|
||
name: e.name,
|
||
name_ru: e.name_ru,
|
||
});
|
||
});
|
||
value.hotel_services.forEach((e, i) => {
|
||
formData.append(`ticket_included_services[${i}]image`, e.image);
|
||
formData.append(`ticket_included_services[${i}]title`, e.title);
|
||
formData.append(`ticket_included_services[${i}]title_ru`, e.title_ru);
|
||
formData.append(`ticket_included_services[${i}]desc_ru`, e.desc_ru);
|
||
formData.append(`ticket_included_services[${i}]desc`, e.description);
|
||
});
|
||
value.ticket_itinerary.forEach((e, i) => {
|
||
formData.append(`ticket_itinerary[${i}]title`, e.title);
|
||
formData.append(`ticket_itinerary[${i}]title_ru`, e.title_ru);
|
||
formData.append(`ticket_itinerary[${i}]duration`, String(e.duration));
|
||
e.ticket_itinerary_image.forEach((e, f) => {
|
||
formData.append(
|
||
`ticket_itinerary[${i}]ticket_itinerary_image[${f}]image`,
|
||
e.image,
|
||
);
|
||
});
|
||
e.ticket_itinerary_destinations.forEach((e, f) => {
|
||
formData.append(
|
||
`ticket_itinerary[${i}]ticket_itinerary_destinations[${f}]name`,
|
||
String(e.name),
|
||
);
|
||
formData.append(
|
||
`ticket_itinerary[${i}]ticket_itinerary_destinations[${f}]name_ru`,
|
||
String(e.name_ru),
|
||
);
|
||
});
|
||
});
|
||
value.hotel_meals.forEach((e, i) => {
|
||
formData.append(`ticket_hotel_meals[${i}]image`, e.image);
|
||
formData.append(`ticket_hotel_meals[${i}]name`, e.title);
|
||
formData.append(`ticket_hotel_meals[${i}]name_ru`, e.title_ru);
|
||
formData.append(`ticket_hotel_meals[${i}]desc`, e.description);
|
||
formData.append(`ticket_hotel_meals[${i}]desc_ru`, e.desc_ru);
|
||
});
|
||
create(formData);
|
||
}
|
||
|
||
const { data: badge } = useQuery({
|
||
queryKey: ["all_badge"],
|
||
queryFn: () => hotelBadge({ page: 1, page_size: 10 }),
|
||
});
|
||
|
||
const { data: tariff } = useQuery({
|
||
queryKey: ["all_tarif"],
|
||
queryFn: () => hotelTarif({ page: 1, page_size: 10 }),
|
||
});
|
||
|
||
const { data: transport } = useQuery({
|
||
queryKey: ["all_transport"],
|
||
queryFn: () => hotelTransport({ page: 1, page_size: 10 }),
|
||
});
|
||
|
||
return (
|
||
<Form {...form}>
|
||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
|
||
<div className="grid grid-cols-2 gap-4 max-lg:grid-cols-1 items-start">
|
||
<FormField
|
||
control={form.control}
|
||
name="title"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<Label className="text-md">{t("Sarlavha")}</Label>
|
||
<FormControl>
|
||
<Input
|
||
placeholder="Toshkent - Dubay"
|
||
{...field}
|
||
className="h-12 !text-md"
|
||
/>
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
<FormField
|
||
control={form.control}
|
||
name="title_ru"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<Label className="text-md">{t("Sarlavha")} (ru)</Label>
|
||
<FormControl>
|
||
<Input
|
||
placeholder="Ташкент - Дубай"
|
||
{...field}
|
||
className="h-12 !text-md"
|
||
/>
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
<FormField
|
||
control={form.control}
|
||
name="price"
|
||
render={() => (
|
||
<FormItem>
|
||
<Label className="text-md">{t("Narx")} (1 kishi uchun)</Label>
|
||
<FormControl>
|
||
<Input
|
||
type="text"
|
||
inputMode="numeric"
|
||
placeholder="1 500 000"
|
||
value={displayPrice}
|
||
onChange={(e) => {
|
||
const raw = e.target.value.replace(/\D/g, "");
|
||
const num = Number(raw);
|
||
if (!isNaN(num)) {
|
||
form.setValue("price", num);
|
||
setDisplayPrice(raw ? formatPrice(num) : "");
|
||
}
|
||
}}
|
||
className="h-12 !text-md"
|
||
/>
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
<div className="grid grid-cols-2 gap-4">
|
||
<FormField
|
||
control={form.control}
|
||
name="min_person"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<Label className="text-md">
|
||
{t("hamrohlar soni (eng kamida)")}
|
||
</Label>
|
||
<FormControl>
|
||
<Input
|
||
type="number"
|
||
inputMode="numeric"
|
||
placeholder="1"
|
||
{...field}
|
||
onChange={(e) =>
|
||
form.setValue("min_person", Number(e.target.value))
|
||
}
|
||
className="h-12 !text-md
|
||
[&::-webkit-inner-spin-button]:appearance-none
|
||
[&::-webkit-outer-spin-button]:appearance-none
|
||
[appearance:textfield]"
|
||
/>
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
<FormField
|
||
control={form.control}
|
||
name="max_person"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<Label className="text-md">
|
||
{t("hamrohlar soni (eng ko'pida)")}
|
||
</Label>
|
||
<FormControl>
|
||
<Input
|
||
type="number"
|
||
inputMode="numeric"
|
||
placeholder="1"
|
||
{...field}
|
||
onChange={(e) =>
|
||
form.setValue("max_person", Number(e.target.value))
|
||
}
|
||
className="h-12 !text-md
|
||
[&::-webkit-inner-spin-button]:appearance-none
|
||
[&::-webkit-outer-spin-button]:appearance-none
|
||
[appearance:textfield]"
|
||
/>
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<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>
|
||
<FormControl>
|
||
<Input
|
||
placeholder="Toshkent"
|
||
{...field}
|
||
className="h-12 !text-md"
|
||
/>
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
|
||
<FormField
|
||
control={form.control}
|
||
name="departure_ru"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<Label className="text-md">{t("Ketish joyi")} (ru)</Label>
|
||
<FormControl>
|
||
<Input
|
||
placeholder="Ташкент"
|
||
{...field}
|
||
className="h-12 !text-md"
|
||
/>
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
|
||
<FormField
|
||
control={form.control}
|
||
name="destination"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<Label className="text-md">{t("Borish joyi")}</Label>
|
||
<FormControl>
|
||
<Input
|
||
placeholder="Dubai"
|
||
{...field}
|
||
className="h-12 !text-md"
|
||
/>
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
|
||
<FormField
|
||
control={form.control}
|
||
name="destination_ru"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<Label className="text-md">{t("Borish joyi")} (ru)</Label>
|
||
<FormControl>
|
||
<Input
|
||
placeholder="Дубай"
|
||
{...field}
|
||
className="h-12 !text-md"
|
||
/>
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
|
||
<FormField
|
||
control={form.control}
|
||
name="location_name"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<Label className="text-md">{t("Manzil nomi")}</Label>
|
||
<FormControl>
|
||
<Input
|
||
placeholder="Dubai"
|
||
{...field}
|
||
className="h-12 !text-md"
|
||
/>
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
<FormField
|
||
control={form.control}
|
||
name="location_name_ru"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<Label className="text-md">{t("Manzil nomi")} (ru)</Label>
|
||
<FormControl>
|
||
<Input
|
||
placeholder="Дубай"
|
||
{...field}
|
||
className="h-12 !text-md"
|
||
/>
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
|
||
<FormField
|
||
control={form.control}
|
||
name="passenger_count"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<Label className="text-md">{t("Yo'lovchilar soni")}</Label>
|
||
<FormControl>
|
||
<Input
|
||
type="number"
|
||
inputMode="numeric"
|
||
placeholder="1"
|
||
{...field}
|
||
onChange={(e) =>
|
||
form.setValue("passenger_count", Number(e.target.value))
|
||
}
|
||
className="h-12 !text-md
|
||
[&::-webkit-inner-spin-button]:appearance-none
|
||
[&::-webkit-outer-spin-button]:appearance-none
|
||
[appearance:textfield]"
|
||
/>
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
</div>
|
||
<div className="grid grid-cols-2 max-lg:grid-cols-1 gap-4">
|
||
<FormField
|
||
control={form.control}
|
||
name="departureDateTime.date"
|
||
render={() => (
|
||
<FormItem>
|
||
<div>
|
||
<div className="flex flex-col gap-3">
|
||
<Label htmlFor="date-picker" className="px-1 text-md">
|
||
{t("Ketish sanasi")}
|
||
</Label>
|
||
<Popover open={openDate} onOpenChange={setOpenDate}>
|
||
<PopoverTrigger asChild>
|
||
<Button
|
||
variant="outline"
|
||
id="date-picker"
|
||
className="w-full justify-between font-normal h-12"
|
||
>
|
||
{selectedDate
|
||
? selectedDate.toLocaleDateString()
|
||
: t("Sana tanlang")}
|
||
<ChevronDownIcon />
|
||
</Button>
|
||
</PopoverTrigger>
|
||
<PopoverContent
|
||
className="w-auto overflow-hidden p-0"
|
||
align="start"
|
||
>
|
||
<Calendar
|
||
mode="single"
|
||
selected={selectedDate}
|
||
captionLayout="dropdown"
|
||
onSelect={(date) => {
|
||
setValue("departureDateTime.date", date!);
|
||
setOpenDate(false);
|
||
}}
|
||
/>
|
||
</PopoverContent>
|
||
</Popover>
|
||
</div>
|
||
</div>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
<FormField
|
||
control={form.control}
|
||
name="departureDateTime.time"
|
||
render={() => (
|
||
<FormItem>
|
||
<div className="flex flex-col gap-3">
|
||
<Label htmlFor="time-picker" className="px-1 text-md">
|
||
{t("Ketish vaqti")}
|
||
</Label>
|
||
<Input
|
||
type="time"
|
||
id="time-picker"
|
||
step="1"
|
||
value={watch("departureDateTime.time")}
|
||
onChange={(e) =>
|
||
setValue("departureDateTime.time", e.target.value)
|
||
}
|
||
className="bg-background !h-12 appearance-none [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none"
|
||
/>
|
||
</div>
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
</div>
|
||
|
||
<FormField
|
||
control={form.control}
|
||
name="travelDateTime.date"
|
||
render={() => (
|
||
<FormItem>
|
||
<div className="grid grid-cols-2 gap-4 max-lg:grid-cols-1">
|
||
<div className="flex flex-col gap-3">
|
||
<Label htmlFor="date-picker" className="px-1 text-md">
|
||
{t("Qaytish sanasi")}
|
||
</Label>
|
||
<Popover
|
||
open={openDateTravel}
|
||
onOpenChange={setOpenDateTravel}
|
||
>
|
||
<PopoverTrigger asChild>
|
||
<Button
|
||
variant="outline"
|
||
id="date-picker"
|
||
className="w-full justify-between font-normal h-12"
|
||
>
|
||
{selectedDateTravel
|
||
? selectedDateTravel.toLocaleDateString()
|
||
: t("Sana tanlang")}
|
||
<ChevronDownIcon />
|
||
</Button>
|
||
</PopoverTrigger>
|
||
<PopoverContent
|
||
className="w-auto overflow-hidden p-0"
|
||
align="start"
|
||
>
|
||
<Calendar
|
||
mode="single"
|
||
selected={selectedDateTravel}
|
||
captionLayout="dropdown"
|
||
onSelect={(date) => {
|
||
setValue("travelDateTime.date", date!);
|
||
setOpenDateTravel(false);
|
||
}}
|
||
/>
|
||
</PopoverContent>
|
||
</Popover>
|
||
</div>
|
||
<div className="flex flex-col gap-3">
|
||
<Label htmlFor="time-picker" className="px-1 text-md">
|
||
{t("Qaytish vaqti")}
|
||
</Label>
|
||
<Input
|
||
type="time"
|
||
id="time-picker"
|
||
step="1"
|
||
value={watch("travelDateTime.time")}
|
||
onChange={(e) =>
|
||
setValue("travelDateTime.time", e.target.value)
|
||
}
|
||
className="bg-background !h-12 appearance-none [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none"
|
||
/>
|
||
</div>
|
||
</div>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
<div className="grid grid-cols-2 max-lg:grid-cols-1 gap-6">
|
||
<FormField
|
||
control={form.control}
|
||
name="languages"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<Label className="text-md">{t("Tillar")}</Label>
|
||
<FormControl>
|
||
<Input
|
||
placeholder={t("Har bir tilni vergul (,) bilan ajrating")}
|
||
{...field}
|
||
className="h-12 !text-md"
|
||
/>
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
|
||
<FormField
|
||
control={form.control}
|
||
name="duration"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<Label className="text-md">{t("Tur davomiyligi")}</Label>
|
||
<FormControl>
|
||
<Input
|
||
type="number"
|
||
inputMode="numeric"
|
||
placeholder="1"
|
||
onChange={(e) => field.onChange(Number(e.target.value))}
|
||
value={field.value}
|
||
onBlur={field.onBlur}
|
||
className="h-12 !text-md
|
||
[&::-webkit-inner-spin-button]:appearance-none
|
||
[&::-webkit-outer-spin-button]:appearance-none
|
||
[appearance:textfield]"
|
||
/>
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
</div>
|
||
<div className="grid grid-cols-2 max-lg:grid-cols-1 gap-6 items-end">
|
||
<FormField
|
||
control={form.control}
|
||
name="badges"
|
||
render={() => (
|
||
<FormItem>
|
||
<Label className="text-md">{t("Belgilar (Badge)")}</Label>
|
||
|
||
<div className="flex flex-wrap gap-2 mb-3">
|
||
{(form.watch("badges") ?? []).length > 0 &&
|
||
(form.watch("badges") ?? []).map((badgeId: number) => {
|
||
const badgeItem = badge?.data?.data?.results?.find(
|
||
(b: any) => b.id === badgeId,
|
||
);
|
||
|
||
return (
|
||
<Badge
|
||
key={badgeId}
|
||
variant="secondary"
|
||
className="px-3 py-1 text-sm flex items-center gap-2"
|
||
>
|
||
{badgeItem?.name || `Badge #${badgeId}`}
|
||
<button
|
||
type="button"
|
||
onClick={() => {
|
||
const current = form.getValues("badges") ?? [];
|
||
form.setValue(
|
||
"badges",
|
||
current.filter((b: number) => b !== badgeId),
|
||
);
|
||
}}
|
||
className="ml-1 text-muted-foreground hover:text-destructive"
|
||
>
|
||
<XIcon className="size-4" />
|
||
</button>
|
||
</Badge>
|
||
);
|
||
})}
|
||
</div>
|
||
|
||
<Popover>
|
||
<PopoverTrigger asChild>
|
||
<Button
|
||
variant="outline"
|
||
className="w-full h-12 justify-between font-normal"
|
||
>
|
||
{t("Belgilarni tanlang")}
|
||
<ChevronDownIcon />
|
||
</Button>
|
||
</PopoverTrigger>
|
||
|
||
<PopoverContent className="p-0 w-[250px]" align="start">
|
||
<Command>
|
||
<CommandList>
|
||
<CommandGroup heading={t("Mavjud belgilar")}>
|
||
{badge?.data?.data?.results?.map((item: any) => {
|
||
const currentBadges =
|
||
form.getValues("badges") ?? [];
|
||
const selected = currentBadges.includes(item.id);
|
||
|
||
return (
|
||
<CommandItem
|
||
key={item.id}
|
||
onSelect={() => {
|
||
if (selected) {
|
||
form.setValue(
|
||
"badges",
|
||
currentBadges.filter(
|
||
(b: number) => b !== item.id,
|
||
),
|
||
);
|
||
} else {
|
||
form.setValue("badges", [
|
||
...currentBadges,
|
||
item.id,
|
||
]);
|
||
}
|
||
}}
|
||
className="flex items-center gap-2"
|
||
>
|
||
<SquareCheckBig
|
||
className={`size-4 ${
|
||
selected
|
||
? "text-green-500"
|
||
: "text-muted-foreground"
|
||
}`}
|
||
/>
|
||
{item.name}
|
||
</CommandItem>
|
||
);
|
||
})}
|
||
</CommandGroup>
|
||
</CommandList>
|
||
</Command>
|
||
</PopoverContent>
|
||
</Popover>
|
||
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
|
||
<FormField
|
||
control={form.control}
|
||
name="tarif"
|
||
render={() => (
|
||
<FormItem>
|
||
<Label className="text-md">{t("Tariflar")}</Label>
|
||
<div className="flex flex-col gap-3 mb-3">
|
||
{form.watch("tarif")?.map((tr, idx) => {
|
||
const transportItem = tariff?.data?.data?.results?.find(
|
||
(item: any) => item.id === tr.tariff,
|
||
);
|
||
|
||
return (
|
||
<div
|
||
key={idx}
|
||
className="flex items-center justify-between border rounded-lg p-2"
|
||
>
|
||
<div className="flex items-center gap-2">
|
||
<Badge
|
||
variant="secondary"
|
||
className="px-3 py-1 text-sm"
|
||
>
|
||
{transportItem?.name || `Tarif #${tr.tariff}`}
|
||
</Badge>
|
||
<Input
|
||
type="text"
|
||
inputMode="numeric"
|
||
placeholder="1 500 000"
|
||
value={tarifdisplayPrice[idx] || ""}
|
||
onChange={(e) => {
|
||
const raw = e.target.value.replace(/\D/g, "");
|
||
const num = Number(raw);
|
||
const updatedTransport = form
|
||
.getValues("tarif")
|
||
.map((t, i) =>
|
||
i === idx ? { ...t, price: num } : t,
|
||
);
|
||
|
||
form.setValue("tarif", updatedTransport);
|
||
|
||
// Local holatda ham yangilaymiz:
|
||
const updatedPrices = [...tarifdisplayPrice];
|
||
updatedPrices[idx] = raw ? formatPrice(num) : "";
|
||
setTarifDisplayPrice(updatedPrices);
|
||
}}
|
||
className="h-12 !text-md"
|
||
/>
|
||
</div>
|
||
<button
|
||
type="button"
|
||
onClick={() => {
|
||
const current = form.getValues("tarif");
|
||
form.setValue(
|
||
"tarif",
|
||
current.filter((_, i) => i !== idx),
|
||
);
|
||
const updatedPrices = tarifdisplayPrice.filter(
|
||
(_, i) => i !== idx,
|
||
);
|
||
setTarifDisplayPrice(updatedPrices);
|
||
}}
|
||
className="ml-2 text-muted-foreground hover:text-destructive"
|
||
>
|
||
<XIcon className="size-4" />
|
||
</button>
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
<Popover>
|
||
<PopoverTrigger asChild>
|
||
<Button
|
||
variant="outline"
|
||
className="w-full h-12 justify-between font-normal"
|
||
>
|
||
{t("Tarifni tanlang")}
|
||
<ChevronDownIcon />
|
||
</Button>
|
||
</PopoverTrigger>
|
||
<PopoverContent className="p-0 w-[250px]" align="start">
|
||
<Command>
|
||
<CommandList>
|
||
<CommandGroup heading={t("Mavjud tariflar")}>
|
||
{tariff?.data?.data?.results?.map((item: any) => {
|
||
const selected = form
|
||
.getValues("tarif")
|
||
.some((t) => t.tariff === item.id);
|
||
|
||
return (
|
||
<CommandItem
|
||
key={item.id}
|
||
onSelect={() => {
|
||
const current = form.getValues("tarif");
|
||
if (selected) {
|
||
form.setValue(
|
||
"tarif",
|
||
current.filter(
|
||
(t) => t.tariff !== item.id,
|
||
),
|
||
);
|
||
} else {
|
||
form.setValue("tarif", [
|
||
...current,
|
||
{ tariff: item.id, price: 0 },
|
||
]);
|
||
}
|
||
}}
|
||
className="flex items-center gap-2"
|
||
>
|
||
<SquareCheckBig
|
||
className={`size-4 ${
|
||
selected
|
||
? "text-green-500"
|
||
: "text-muted-foreground"
|
||
}`}
|
||
/>
|
||
{item.name}
|
||
</CommandItem>
|
||
);
|
||
})}
|
||
</CommandGroup>
|
||
</CommandList>
|
||
</Command>
|
||
</PopoverContent>
|
||
</Popover>
|
||
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-2 max-lg:grid-cols-1 gap-6 items-end">
|
||
<FormField
|
||
control={form.control}
|
||
name="transport"
|
||
render={() => (
|
||
<FormItem>
|
||
<Label className="text-md">{t("Transportlar")}</Label>
|
||
|
||
<div className="flex flex-col gap-3 mb-3">
|
||
{form.watch("transport")?.map((tr, idx) => {
|
||
const transportItem = transport?.data?.data?.results?.find(
|
||
(item: any) => item.id === tr.transport,
|
||
);
|
||
return (
|
||
<div
|
||
key={idx}
|
||
className="flex items-center justify-between border rounded-lg p-2"
|
||
>
|
||
<div className="flex items-center gap-2">
|
||
<Badge
|
||
variant="secondary"
|
||
className="px-3 py-1 text-sm"
|
||
>
|
||
{transportItem?.name ||
|
||
`Transport #${tr.transport}`}
|
||
</Badge>
|
||
<Input
|
||
type="text"
|
||
inputMode="numeric"
|
||
placeholder="1 500 000"
|
||
value={transportPrices[idx] || ""}
|
||
onChange={(e) => {
|
||
const raw = e.target.value.replace(/\D/g, "");
|
||
const num = Number(raw);
|
||
const updatedTransport = form
|
||
.getValues("transport")
|
||
.map((t, i) =>
|
||
i === idx ? { ...t, price: num } : t,
|
||
);
|
||
|
||
form.setValue("transport", updatedTransport);
|
||
|
||
// Local holatda ham yangilaymiz:
|
||
const updatedPrices = [...transportPrices];
|
||
updatedPrices[idx] = raw ? formatPrice(num) : "";
|
||
setTransportPrices(updatedPrices);
|
||
}}
|
||
className="h-12 !text-md"
|
||
/>
|
||
</div>
|
||
<button
|
||
type="button"
|
||
onClick={() => {
|
||
const current = form.getValues("transport");
|
||
form.setValue(
|
||
"transport",
|
||
current.filter((_, i) => i !== idx),
|
||
);
|
||
const updatedPrices = transportPrices.filter(
|
||
(_, i) => i !== idx,
|
||
);
|
||
setTransportPrices(updatedPrices);
|
||
}}
|
||
className="ml-2 text-muted-foreground hover:text-destructive"
|
||
>
|
||
<XIcon className="size-4" />
|
||
</button>
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
|
||
<Popover>
|
||
<PopoverTrigger asChild>
|
||
<Button
|
||
variant="outline"
|
||
className="w-full h-12 justify-between font-normal"
|
||
>
|
||
{t("Transport tanlang")}
|
||
<ChevronDownIcon />
|
||
</Button>
|
||
</PopoverTrigger>
|
||
|
||
<PopoverContent className="p-0 w-[250px]" align="start">
|
||
<Command>
|
||
<CommandList>
|
||
<CommandGroup heading={t("Mavjud transportlar")}>
|
||
{transport?.data?.data?.results?.map((item: any) => {
|
||
const selected = form
|
||
.getValues("transport")
|
||
.some((t) => t.transport === item.id);
|
||
|
||
return (
|
||
<CommandItem
|
||
key={item.id}
|
||
onSelect={() => {
|
||
const current = form.getValues("transport");
|
||
if (selected) {
|
||
form.setValue(
|
||
"transport",
|
||
current.filter(
|
||
(t) => t.transport !== item.id,
|
||
),
|
||
);
|
||
} else {
|
||
form.setValue("transport", [
|
||
...current,
|
||
{ transport: item.id, price: 0 },
|
||
]);
|
||
}
|
||
}}
|
||
className="flex items-center gap-2"
|
||
>
|
||
<SquareCheckBig
|
||
className={`size-4 ${
|
||
selected
|
||
? "text-green-500"
|
||
: "text-muted-foreground"
|
||
}`}
|
||
/>
|
||
{item.name}
|
||
</CommandItem>
|
||
);
|
||
})}
|
||
</CommandGroup>
|
||
</CommandList>
|
||
</Command>
|
||
</PopoverContent>
|
||
</Popover>
|
||
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
</div>
|
||
|
||
<FormField
|
||
control={form.control}
|
||
name="visa_required"
|
||
render={({ field }) => (
|
||
<FormItem className="space-y-3">
|
||
<Label>{t("Visa talab qilinadimi?")}</Label>
|
||
<FormControl>
|
||
<RadioGroup
|
||
onValueChange={field.onChange}
|
||
defaultValue={field.value}
|
||
className="flex gap-6"
|
||
>
|
||
<div className="flex items-center space-x-2">
|
||
<RadioGroupItem value="yes" id="visa_yes" />
|
||
<label htmlFor="visa_yes">{t("Ha")}</label>
|
||
</div>
|
||
<div className="flex items-center space-x-2">
|
||
<RadioGroupItem value="no" id="visa_no" />
|
||
<label htmlFor="visa_no">{t("Yo‘q")}</label>
|
||
</div>
|
||
</RadioGroup>
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
|
||
<TicketsImagesModel
|
||
form={form}
|
||
name="banner"
|
||
multiple={false}
|
||
label={t("Banner")}
|
||
imageUrl={data?.data?.image_banner}
|
||
/>
|
||
|
||
<TicketsImagesModel
|
||
form={form}
|
||
name="images"
|
||
label={t("Qo'shimcha rasmlar")}
|
||
imageUrl={data?.data.ticket_images?.map((img) => img.image)}
|
||
/>
|
||
|
||
<FormField
|
||
control={form.control}
|
||
name="amenities"
|
||
render={() => (
|
||
<FormItem>
|
||
<Label className="text-md">{t("Qulayliklar")}</Label>
|
||
|
||
<div className="flex flex-col gap-4">
|
||
<div className="flex flex-wrap gap-2">
|
||
{form.watch("amenities").map((item, idx) => {
|
||
const Icon = (LucideIcons as any)[item.icon_name] || XIcon;
|
||
return (
|
||
<Badge
|
||
key={idx}
|
||
variant="secondary"
|
||
className="px-3 py-1 text-sm flex items-center gap-2"
|
||
>
|
||
<Icon className="size-4" />
|
||
<span>{item.name}</span>
|
||
<button
|
||
type="button"
|
||
onClick={() => {
|
||
const current = form.getValues("amenities");
|
||
form.setValue(
|
||
"amenities",
|
||
current.filter((_, i: number) => i !== idx),
|
||
);
|
||
}}
|
||
className="ml-1 text-muted-foreground hover:text-destructive"
|
||
>
|
||
<XIcon className="size-4" />
|
||
</button>
|
||
</Badge>
|
||
);
|
||
})}
|
||
</div>
|
||
<div className="flex gap-3 items-end flex-wrap">
|
||
<div className="flex-1 min-w-[200px]">
|
||
<IconSelect
|
||
setSelectedIcon={setSelectedIcon}
|
||
selectedIcon={selectedIcon}
|
||
/>
|
||
</div>
|
||
|
||
<Input
|
||
id="amenity_name"
|
||
placeholder={t("Qulaylik nomi (masalan: Wi-Fi)")}
|
||
className="h-12 !text-md flex-1 min-w-[200px]"
|
||
/>
|
||
|
||
<Input
|
||
id="amenity_name_ru"
|
||
placeholder={t("Qulaylik nomi (ru)")}
|
||
className="h-12 !text-md flex-1 min-w-[200px]"
|
||
/>
|
||
|
||
<Button
|
||
type="button"
|
||
onClick={() => {
|
||
const nameInput = document.getElementById(
|
||
"amenity_name",
|
||
) as HTMLInputElement;
|
||
const nameRuInput = document.getElementById(
|
||
"amenity_name_ru",
|
||
) as HTMLInputElement;
|
||
|
||
if (selectedIcon && nameInput.value) {
|
||
const current = form.getValues("amenities");
|
||
form.setValue("amenities", [
|
||
...current,
|
||
{
|
||
icon_name: selectedIcon,
|
||
name: nameInput.value,
|
||
name_ru: nameRuInput.value,
|
||
},
|
||
]);
|
||
nameInput.value = "";
|
||
nameRuInput.value = "";
|
||
setSelectedIcon("");
|
||
}
|
||
}}
|
||
className="h-12"
|
||
>
|
||
{t("Qo'shish")}
|
||
</Button>
|
||
</div>
|
||
|
||
<FormMessage />
|
||
</div>
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
|
||
<FormField
|
||
control={form.control}
|
||
name="hotel_info"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<Label className="text-md">{t("Mehmonxona haqida")}</Label>
|
||
<FormControl>
|
||
<Textarea
|
||
placeholder={t("Mehmonxona haqida")}
|
||
{...field}
|
||
className="min-h-48 max-h-60 !text-md"
|
||
/>
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
|
||
<FormField
|
||
control={form.control}
|
||
name="hotel_info_ru"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<Label className="text-md">{t("Mehmonxona haqida")} (ru)</Label>
|
||
<FormControl>
|
||
<Textarea
|
||
placeholder={t("Mehmonxona haqida (ru)")}
|
||
{...field}
|
||
className="min-h-48 max-h-60 !text-md"
|
||
/>
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
|
||
<FormField
|
||
control={form.control}
|
||
name="hotel_services"
|
||
render={() => (
|
||
<FormItem>
|
||
<Label className="text-md">{t("Mehmonxona xizmatlari")}</Label>
|
||
|
||
<div className="flex flex-col gap-4">
|
||
<div className="flex flex-wrap gap-4">
|
||
{form.watch("hotel_services")?.map((service, idx) => (
|
||
<div
|
||
key={idx}
|
||
className="relative w-48 border rounded-xl overflow-hidden shadow-sm"
|
||
>
|
||
<img
|
||
src={
|
||
service.image instanceof File
|
||
? URL.createObjectURL(service.image)
|
||
: service.image
|
||
}
|
||
alt={service.title}
|
||
className="object-cover w-full h-32"
|
||
/>
|
||
<div className="p-2 text-center">
|
||
<p className="font-semibold">{service.title}</p>
|
||
<p className="text-sm text-muted-foreground line-clamp-2">
|
||
{service.description}
|
||
</p>
|
||
</div>
|
||
<button
|
||
type="button"
|
||
onClick={() => {
|
||
const current = form.getValues("hotel_services");
|
||
form.setValue(
|
||
"hotel_services",
|
||
current.filter((_, i) => i !== idx),
|
||
);
|
||
}}
|
||
className="absolute top-1 right-1 bg-white/80 rounded-full p-1 hover:bg-white shadow"
|
||
>
|
||
<XIcon className="size-4 text-destructive" />
|
||
</button>
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
<div className="flex flex-col gap-3 border rounded-xl p-4 bg-muted/10">
|
||
<Label className="text-md font-semibold">
|
||
{t("Yangi xizmat qo'shish")}
|
||
</Label>
|
||
|
||
<Input
|
||
type="file"
|
||
id="hotel_service_image"
|
||
accept="image/*"
|
||
className="h-12"
|
||
/>
|
||
|
||
<Input
|
||
id="hotel_service_title"
|
||
placeholder={t("Xizmat nomi")}
|
||
className="h-12 !text-md"
|
||
/>
|
||
|
||
<Input
|
||
id="hotel_service_title_ru"
|
||
placeholder={t("Xizmat nomi (ru)")}
|
||
className="h-12 !text-md"
|
||
/>
|
||
|
||
<Textarea
|
||
id="hotel_service_desc"
|
||
placeholder={t("Xizmat tavsifi")}
|
||
className="min-h-24 !text-md"
|
||
/>
|
||
|
||
<Textarea
|
||
id="hotel_service_desc_ru"
|
||
placeholder={t("Xizmat tavsifi (ru)")}
|
||
className="min-h-24 !text-md"
|
||
/>
|
||
|
||
<Button
|
||
type="button"
|
||
onClick={() => {
|
||
const imgInput = document.getElementById(
|
||
"hotel_service_image",
|
||
) as HTMLInputElement;
|
||
const titleInput = document.getElementById(
|
||
"hotel_service_title",
|
||
) as HTMLInputElement;
|
||
const titleRuInput = document.getElementById(
|
||
"hotel_service_title_ru",
|
||
) as HTMLInputElement;
|
||
const descInput = document.getElementById(
|
||
"hotel_service_desc",
|
||
) as HTMLTextAreaElement;
|
||
const descRuInput = document.getElementById(
|
||
"hotel_service_desc_ru",
|
||
) as HTMLTextAreaElement;
|
||
|
||
const file = imgInput.files?.[0];
|
||
if (file && titleInput.value && descInput.value) {
|
||
const current = form.getValues("hotel_services") || [];
|
||
form.setValue("hotel_services", [
|
||
...current,
|
||
{
|
||
image: file,
|
||
title: titleInput.value,
|
||
title_ru: titleRuInput.value,
|
||
description: descInput.value,
|
||
desc_ru: descRuInput.value,
|
||
},
|
||
]);
|
||
|
||
imgInput.value = "";
|
||
titleInput.value = "";
|
||
titleRuInput.value = "";
|
||
descInput.value = "";
|
||
descRuInput.value = "";
|
||
}
|
||
}}
|
||
className="h-12"
|
||
>
|
||
{t("Qo'shish")}
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
|
||
<FormField
|
||
control={form.control}
|
||
name="hotel_meals_info"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<Label className="text-md">
|
||
{t("Mehmonxona taomlari haqida")}
|
||
</Label>
|
||
<FormControl>
|
||
<Textarea
|
||
placeholder={t("Mehmonxona taomlari haqida")}
|
||
{...field}
|
||
className="min-h-48 max-h-60"
|
||
/>
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
|
||
<FormField
|
||
control={form.control}
|
||
name="hotel_meals_info_ru"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<Label className="text-md">
|
||
{t("Mehmonxona taomlari haqida")} (ru)
|
||
</Label>
|
||
<FormControl>
|
||
<Textarea
|
||
placeholder={t("Mehmonxona taomlari haqida (ru)")}
|
||
{...field}
|
||
className="min-h-48 max-h-60"
|
||
/>
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
|
||
<FormField
|
||
control={form.control}
|
||
name="hotel_meals"
|
||
render={() => (
|
||
<FormItem>
|
||
<Label className="text-md">{t("Mehmonxona taomlari")}</Label>
|
||
|
||
<div className="flex flex-col gap-4">
|
||
<div className="flex flex-wrap gap-4">
|
||
{form.watch("hotel_meals")?.map((service, idx) => (
|
||
<div
|
||
key={idx}
|
||
className="relative w-48 border rounded-xl overflow-hidden shadow-sm"
|
||
>
|
||
<img
|
||
src={
|
||
service.image instanceof File
|
||
? URL.createObjectURL(service.image)
|
||
: service.image
|
||
}
|
||
alt={service.title}
|
||
className="object-cover w-full h-32"
|
||
/>
|
||
<div className="p-2 text-center">
|
||
<p className="font-semibold">{service.title}</p>
|
||
<p className="text-sm text-muted-foreground line-clamp-2">
|
||
{service.description}
|
||
</p>
|
||
</div>
|
||
<button
|
||
type="button"
|
||
onClick={() => {
|
||
const current = form.getValues("hotel_meals") || [];
|
||
form.setValue(
|
||
"hotel_meals",
|
||
current.filter((_, i) => i !== idx),
|
||
);
|
||
}}
|
||
className="absolute top-1 right-1 bg-white/80 rounded-full p-1 hover:bg-white shadow"
|
||
>
|
||
<XIcon className="size-4 text-destructive" />
|
||
</button>
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
<div className="flex flex-col gap-3 border rounded-xl p-4 bg-muted/10">
|
||
<Label className="text-md font-semibold">
|
||
{t("Mehmonxona taomlari ro'yxati")}
|
||
</Label>
|
||
|
||
<Input
|
||
type="file"
|
||
id="hotel_meals_image"
|
||
accept="image/*"
|
||
className="h-12"
|
||
/>
|
||
|
||
<Input
|
||
id="hotel_meals_title"
|
||
placeholder={t("Taom nomi")}
|
||
className="h-12 !text-md"
|
||
/>
|
||
|
||
<Input
|
||
id="hotel_meals_title_ru"
|
||
placeholder={t("Taom nomi (ru)")}
|
||
className="h-12 !text-md"
|
||
/>
|
||
|
||
<Textarea
|
||
id="hotel_meals_desc"
|
||
placeholder={t("Taom tavsifi")}
|
||
className="min-h-24 !text-md"
|
||
/>
|
||
|
||
<Textarea
|
||
id="hotel_meals_desc_ru"
|
||
placeholder={t("Taom tavsifi (ru)")}
|
||
className="min-h-24 !text-md"
|
||
/>
|
||
|
||
<Button
|
||
type="button"
|
||
onClick={() => {
|
||
const imgInput = document.getElementById(
|
||
"hotel_meals_image",
|
||
) as HTMLInputElement;
|
||
const titleInput = document.getElementById(
|
||
"hotel_meals_title",
|
||
) as HTMLInputElement;
|
||
const titleRuInput = document.getElementById(
|
||
"hotel_meals_title_ru",
|
||
) as HTMLInputElement;
|
||
const descInput = document.getElementById(
|
||
"hotel_meals_desc",
|
||
) as HTMLTextAreaElement;
|
||
const descRuInput = document.getElementById(
|
||
"hotel_meals_desc_ru",
|
||
) as HTMLTextAreaElement;
|
||
|
||
const file = imgInput.files?.[0];
|
||
if (file && titleInput.value && descInput.value) {
|
||
const current = form.getValues("hotel_meals");
|
||
form.setValue("hotel_meals", [
|
||
...current,
|
||
{
|
||
image: file,
|
||
title: titleInput.value,
|
||
title_ru: titleRuInput.value,
|
||
description: descInput.value,
|
||
desc_ru: descRuInput.value,
|
||
},
|
||
]);
|
||
imgInput.value = "";
|
||
titleInput.value = "";
|
||
titleRuInput.value = "";
|
||
descInput.value = "";
|
||
descRuInput.value = "";
|
||
}
|
||
}}
|
||
className="h-12"
|
||
>
|
||
{t("Qo'shish")}
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
|
||
<FormField
|
||
control={form.control}
|
||
name="ticket_itinerary"
|
||
render={() => (
|
||
<FormItem>
|
||
<Label className="text-md">{t("Yo'nalishlar")}</Label>
|
||
|
||
<div className="flex flex-col gap-4">
|
||
{/* mavjud yo‘nalishlar */}
|
||
<div className="flex flex-wrap gap-4">
|
||
{form.watch("ticket_itinerary")?.map((item, idx) => (
|
||
<div
|
||
key={idx}
|
||
className="relative w-48 border rounded-xl overflow-hidden shadow-sm"
|
||
>
|
||
<img
|
||
src={
|
||
item.ticket_itinerary_image[0]?.image instanceof File
|
||
? URL.createObjectURL(
|
||
item.ticket_itinerary_image[0]?.image,
|
||
)
|
||
: item.ticket_itinerary_image[0]?.image // agar serverdan kelsa
|
||
}
|
||
alt={item.title}
|
||
className="object-cover w-full h-32"
|
||
/>
|
||
<div className="p-2 text-center">
|
||
<p className="font-semibold">{item.title}</p>
|
||
<p className="text-sm text-muted-foreground">
|
||
{item.ticket_itinerary_destinations[0]?.name}
|
||
</p>
|
||
<p className="text-sm text-muted-foreground">
|
||
{item.duration} kun
|
||
</p>
|
||
</div>
|
||
<button
|
||
type="button"
|
||
onClick={() => {
|
||
const current =
|
||
form.getValues("ticket_itinerary") || [];
|
||
form.setValue(
|
||
"ticket_itinerary",
|
||
current.filter((_, i) => i !== idx),
|
||
);
|
||
}}
|
||
className="absolute top-1 right-1 bg-white/80 rounded-full p-1 hover:bg-white shadow"
|
||
>
|
||
<XIcon className="size-4 text-destructive" />
|
||
</button>
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
{/* yangi yo‘nalish qo‘shish formasi */}
|
||
<div className="flex flex-col gap-3 border rounded-xl p-4 bg-muted/10">
|
||
<Label className="text-md font-semibold">
|
||
{t("Yo'nalish qo'shish")}
|
||
</Label>
|
||
|
||
<Input
|
||
type="file"
|
||
id="ticket_itinerary_image"
|
||
accept="image/*"
|
||
className="h-12"
|
||
/>
|
||
|
||
<Input
|
||
id="ticket_itinerary_title"
|
||
placeholder={t("Sarlavha")}
|
||
className="h-12 !text-md"
|
||
/>
|
||
|
||
<Input
|
||
id="ticket_itinerary_title_ru"
|
||
placeholder={t("Sarlavha (RU)")}
|
||
className="h-12 !text-md"
|
||
/>
|
||
|
||
<Input
|
||
id="ticket_itinerary_duration"
|
||
type="number"
|
||
min={1}
|
||
placeholder={t("Davomiylik (kun)")}
|
||
className="h-12 !text-md"
|
||
/>
|
||
|
||
<Input
|
||
id="ticket_itinerary_destination"
|
||
placeholder={t("Manzil")}
|
||
className="h-12 !text-md"
|
||
/>
|
||
|
||
<Input
|
||
id="ticket_itinerary_destination_ru"
|
||
placeholder={t("Manzil (RU)")}
|
||
className="h-12 !text-md"
|
||
/>
|
||
|
||
<Button
|
||
type="button"
|
||
onClick={() => {
|
||
const imgInput = document.getElementById(
|
||
"ticket_itinerary_image",
|
||
) as HTMLInputElement;
|
||
const titleInput = document.getElementById(
|
||
"ticket_itinerary_title",
|
||
) as HTMLInputElement;
|
||
const titleRuInput = document.getElementById(
|
||
"ticket_itinerary_title_ru",
|
||
) as HTMLInputElement;
|
||
const durationInput = document.getElementById(
|
||
"ticket_itinerary_duration",
|
||
) as HTMLInputElement;
|
||
const destInput = document.getElementById(
|
||
"ticket_itinerary_destination",
|
||
) as HTMLInputElement;
|
||
const destRuInput = document.getElementById(
|
||
"ticket_itinerary_destination_ru",
|
||
) as HTMLInputElement;
|
||
|
||
const file = imgInput.files?.[0];
|
||
const title = titleInput.value.trim();
|
||
const titleRu = titleRuInput.value.trim();
|
||
const duration = Number(durationInput.value);
|
||
const dest = destInput.value.trim();
|
||
const destRu = destRuInput.value.trim();
|
||
|
||
if (
|
||
file &&
|
||
title &&
|
||
titleRu &&
|
||
duration &&
|
||
dest &&
|
||
destRu
|
||
) {
|
||
const current =
|
||
form.getValues("ticket_itinerary") || [];
|
||
|
||
form.setValue("ticket_itinerary", [
|
||
...current,
|
||
{
|
||
ticket_itinerary_image: [{ image: file }],
|
||
title,
|
||
title_ru: titleRu,
|
||
duration,
|
||
ticket_itinerary_destinations: [
|
||
{ name: dest, name_ru: destRu },
|
||
],
|
||
},
|
||
]);
|
||
|
||
imgInput.value = "";
|
||
titleInput.value = "";
|
||
titleRuInput.value = "";
|
||
durationInput.value = "";
|
||
destInput.value = "";
|
||
destRuInput.value = "";
|
||
}
|
||
}}
|
||
className="h-12"
|
||
>
|
||
{t("Qo'shish")}
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
|
||
<div className="w-full flex justify-end">
|
||
<Button
|
||
type="submit"
|
||
className="mt-6 px-8 py-3 bg-blue-600 text-white rounded-md hover:bg-blue-600 cursor-pointer"
|
||
>
|
||
{t("Saqlash")}
|
||
</Button>
|
||
</div>
|
||
</form>
|
||
</Form>
|
||
);
|
||
};
|
||
|
||
export default StepOne;
|