bug fix
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
import {
|
||||
createTours,
|
||||
getAllAmenities,
|
||||
hotelBadge,
|
||||
hotelTarif,
|
||||
hotelTransport,
|
||||
@@ -29,14 +30,12 @@ import {
|
||||
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";
|
||||
@@ -105,7 +104,7 @@ const StepOne = ({
|
||||
},
|
||||
});
|
||||
|
||||
const { addAmenity, setId } = useTicketStore();
|
||||
const { addAmenity, setId, removeAmenity } = useTicketStore();
|
||||
|
||||
useEffect(() => {
|
||||
if (!isEditMode || !data?.data) return;
|
||||
@@ -158,14 +157,7 @@ const StepOne = ({
|
||||
}
|
||||
|
||||
// 🔹 Qulayliklar (amenities)
|
||||
form.setValue(
|
||||
"amenities",
|
||||
tour.ticket_amenities?.map((a) => ({
|
||||
name: a.name ?? "",
|
||||
name_ru: a.name_ru ?? "",
|
||||
icon_name: a.icon_name ?? "",
|
||||
})) ?? [],
|
||||
);
|
||||
form.setValue("amenities", tour.ticket_amenities);
|
||||
|
||||
// 🔹 Xizmatlar (hotel_services)
|
||||
form.setValue(
|
||||
@@ -258,7 +250,6 @@ const StepOne = ({
|
||||
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) => {
|
||||
@@ -341,24 +332,20 @@ const StepOne = ({
|
||||
value.badges?.forEach((e, i) => {
|
||||
formData.append(`badge[${i}]`, String(e));
|
||||
});
|
||||
value.amenities?.forEach((e, i) => {
|
||||
formData.append(`ticket_amenities[${i}]`, String(e));
|
||||
});
|
||||
value.images.forEach((e) => {
|
||||
if (e instanceof File) {
|
||||
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.amenities?.forEach((e, i) => {
|
||||
formData.append(`badge[${i}]`, String(e));
|
||||
});
|
||||
value.hotel_services &&
|
||||
value.hotel_services.forEach((e, i) => {
|
||||
if (e instanceof File) {
|
||||
if (e.image instanceof File) {
|
||||
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);
|
||||
@@ -366,31 +353,43 @@ const StepOne = ({
|
||||
formData.append(`ticket_included_services[${i}]desc`, e.description);
|
||||
}
|
||||
});
|
||||
value.ticket_itinerary.forEach((e, i) => {
|
||||
e.ticket_itinerary_image.forEach((l, f) => {
|
||||
if (e instanceof File) {
|
||||
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));
|
||||
value.ticket_itinerary?.forEach((itinerary, i) => {
|
||||
formData.append(`ticket_itinerary[${i}]title`, itinerary.title);
|
||||
formData.append(`ticket_itinerary[${i}]title_ru`, itinerary.title_ru);
|
||||
formData.append(
|
||||
`ticket_itinerary[${i}]duration`,
|
||||
String(itinerary.duration),
|
||||
);
|
||||
|
||||
// Rasmlar
|
||||
if (Array.isArray(itinerary.ticket_itinerary_image)) {
|
||||
itinerary.ticket_itinerary_image.forEach((img, j) => {
|
||||
const file = img instanceof File ? img : img.image;
|
||||
if (file) {
|
||||
formData.append(
|
||||
`ticket_itinerary[${i}]ticket_itinerary_image[${j}]image`,
|
||||
file,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Destinations
|
||||
if (Array.isArray(itinerary.ticket_itinerary_destinations)) {
|
||||
itinerary.ticket_itinerary_destinations.forEach((dest, k) => {
|
||||
formData.append(
|
||||
`ticket_itinerary[${i}]ticket_itinerary_image[${f}]image`,
|
||||
l.image,
|
||||
`ticket_itinerary[${i}]ticket_itinerary_destinations[${k}]name`,
|
||||
dest.name,
|
||||
);
|
||||
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),
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
formData.append(
|
||||
`ticket_itinerary[${i}]ticket_itinerary_destinations[${k}]name_ru`,
|
||||
dest.name_ru,
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
value.hotel_meals.forEach((e, i) => {
|
||||
if (e instanceof File) {
|
||||
if (e.image instanceof File) {
|
||||
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);
|
||||
@@ -419,13 +418,16 @@ const StepOne = ({
|
||||
}
|
||||
}
|
||||
|
||||
console.log(form.formState.errors);
|
||||
|
||||
const { data: badge } = useQuery({
|
||||
queryKey: ["all_badge"],
|
||||
queryFn: () => hotelBadge({ page: 1, page_size: 10 }),
|
||||
});
|
||||
|
||||
const { data: amenitiesData } = useQuery({
|
||||
queryKey: ["all_amenities"],
|
||||
queryFn: () => getAllAmenities({ page: 1, page_size: 10 }),
|
||||
});
|
||||
|
||||
const { data: tariff } = useQuery({
|
||||
queryKey: ["all_tarif"],
|
||||
queryFn: () => hotelTarif({ page: 1, page_size: 10 }),
|
||||
@@ -973,6 +975,114 @@ const StepOne = ({
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="amenities"
|
||||
render={() => (
|
||||
<FormItem>
|
||||
<Label className="text-md">{t("Qulayliklar")}</Label>
|
||||
|
||||
<div className="flex flex-wrap gap-2 mb-3">
|
||||
{(form.watch("amenities") ?? []).length > 0 &&
|
||||
(form.watch("amenities") ?? []).map((badgeId: number) => {
|
||||
const badgeItem =
|
||||
amenitiesData?.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 || `Qulaylilar #${badgeId}`}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
const current = form.getValues("amenities") ?? [];
|
||||
form.setValue(
|
||||
"amenities",
|
||||
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("Qulaylik tanlang")}
|
||||
<ChevronDownIcon />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
|
||||
<PopoverContent className="p-0 w-[250px]" align="start">
|
||||
<Command>
|
||||
<CommandList>
|
||||
<CommandGroup heading={t("Mavjud qulayliklar")}>
|
||||
{badge?.data?.data?.results?.map((item: any) => {
|
||||
const currentBadges =
|
||||
form.getValues("amenities") ?? [];
|
||||
const selected = currentBadges.includes(item.id);
|
||||
|
||||
return (
|
||||
<CommandItem
|
||||
key={item.id}
|
||||
onSelect={() => {
|
||||
const selected = currentBadges.includes(
|
||||
item.id,
|
||||
);
|
||||
|
||||
if (selected) {
|
||||
removeAmenity(item.id);
|
||||
form.setValue(
|
||||
"amenities",
|
||||
currentBadges.filter(
|
||||
(b: number) => b !== item.id,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
addAmenity(item.id);
|
||||
form.setValue("amenities", [
|
||||
...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"
|
||||
@@ -1005,9 +1115,11 @@ const StepOne = ({
|
||||
onChange={(e) => {
|
||||
const raw = e.target.value.replace(/\D/g, "");
|
||||
const num = Number(raw);
|
||||
const currentTarifs = form.getValues("tarif") || [];
|
||||
const updatedTransport = currentTarifs.map((t, i) =>
|
||||
i === idx ? { ...t, price: num } : t,
|
||||
const currentTarifs =
|
||||
form.getValues("tarif") || [];
|
||||
const updatedTransport = currentTarifs.map(
|
||||
(t, i) =>
|
||||
i === idx ? { ...t, price: num } : t,
|
||||
);
|
||||
|
||||
form.setValue("tarif", updatedTransport);
|
||||
@@ -1057,7 +1169,9 @@ const StepOne = ({
|
||||
<CommandGroup heading={t("Mavjud tariflar")}>
|
||||
{tariff?.data?.data?.results?.map((item: any) => {
|
||||
const currentTarifs = form.getValues("tarif") || [];
|
||||
const selected = currentTarifs.some((t) => t.tariff === item.id);
|
||||
const selected = currentTarifs.some(
|
||||
(t) => t.tariff === item.id,
|
||||
);
|
||||
|
||||
return (
|
||||
<CommandItem
|
||||
@@ -1137,9 +1251,11 @@ const StepOne = ({
|
||||
onChange={(e) => {
|
||||
const raw = e.target.value.replace(/\D/g, "");
|
||||
const num = Number(raw);
|
||||
const currentTransports = form.getValues("transport") || [];
|
||||
const updatedTransport = currentTransports.map((t, i) =>
|
||||
i === idx ? { ...t, price: num } : t,
|
||||
const currentTransports =
|
||||
form.getValues("transport") || [];
|
||||
const updatedTransport = currentTransports.map(
|
||||
(t, i) =>
|
||||
i === idx ? { ...t, price: num } : t,
|
||||
);
|
||||
|
||||
form.setValue("transport", updatedTransport);
|
||||
@@ -1190,14 +1306,18 @@ const StepOne = ({
|
||||
<CommandList>
|
||||
<CommandGroup heading={t("Mavjud transportlar")}>
|
||||
{transport?.data?.data?.results?.map((item: any) => {
|
||||
const currentTransports = form.getValues("transport") || [];
|
||||
const selected = currentTransports.some((t) => t.transport === item.id);
|
||||
const currentTransports =
|
||||
form.getValues("transport") || [];
|
||||
const selected = currentTransports.some(
|
||||
(t) => t.transport === item.id,
|
||||
);
|
||||
|
||||
return (
|
||||
<CommandItem
|
||||
key={item.id}
|
||||
onSelect={() => {
|
||||
const current = form.getValues("transport") || [];
|
||||
const current =
|
||||
form.getValues("transport") || [];
|
||||
if (selected) {
|
||||
form.setValue(
|
||||
"transport",
|
||||
@@ -1279,99 +1399,6 @@ const StepOne = ({
|
||||
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="extra_service"
|
||||
|
||||
Reference in New Issue
Block a user