Files
simple-admin/src/pages/tours/ui/StepOne.tsx
Samandar Turgunboyev 05b752daf2 api ulandi
2025-10-25 18:42:01 +05:00

1818 lines
67 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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("Yoq")}</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 yonalishlar */}
<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 yonalish qoshish 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;