api ulandi

This commit is contained in:
Samandar Turgunboyev
2025-10-29 18:41:59 +05:00
parent a9e99f9755
commit 2d0285dafc
64 changed files with 6319 additions and 2352 deletions

View File

@@ -5,6 +5,7 @@ import {
hotelBadge,
hotelTarif,
hotelTransport,
updateTours,
} from "@/pages/tours/lib/api";
import { TourformSchema } from "@/pages/tours/lib/form";
import { useTicketStore } from "@/pages/tours/lib/store";
@@ -46,10 +47,12 @@ import z from "zod";
const StepOne = ({
setStep,
data,
id,
isEditMode,
}: {
setStep: Dispatch<SetStateAction<number>>;
data: GetOneTours | undefined;
id: string | undefined;
isEditMode: boolean;
}) => {
const [displayPrice, setDisplayPrice] = useState("");
@@ -88,6 +91,8 @@ const StepOne = ({
passenger_count: 1,
min_person: 1,
max_person: 1,
extra_service: [],
paid_extra_service: [],
languages: "",
duration: 1,
badges: [],
@@ -103,119 +108,152 @@ const StepOne = ({
const { addAmenity, setId } = useTicketStore();
useEffect(() => {
if (isEditMode && data?.data) {
const tourData = data.data;
if (!isEditMode || !data?.data) return;
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 || []);
const tour = data.data;
// 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
});
}
// 🔹 Oddiy text maydonlar
form.setValue("title", tour.title_uz ?? "");
form.setValue("title_ru", tour.title_ru ?? "");
form.setValue("price", tour.price ?? 0);
setDisplayPrice(formatPrice(tour.price ?? 0));
if (tourData.travel_time) {
const travelDate = new Date(tourData.travel_time);
form.setValue("travelDateTime", {
date: travelDate,
time: travelDate.toTimeString().slice(0, 8),
});
}
form.setValue("passenger_count", tour.passenger_count ?? 1);
form.setValue("min_person", tour.min_person ?? 1);
form.setValue("max_person", tour.max_person ?? 1);
// 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);
}
form.setValue("departure", tour.departure_uz ?? "");
form.setValue("departure_ru", tour.departure_ru ?? "");
form.setValue("destination", tour.destination_uz ?? "");
form.setValue("destination_ru", tour.destination_ru ?? "");
form.setValue("location_name", tour.location_name_uz ?? "");
form.setValue("location_name_ru", tour.location_name_ru ?? "");
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);
}
form.setValue("hotel_info", tour.hotel_info_uz ?? "");
form.setValue("hotel_info_ru", tour.hotel_info_ru ?? "");
form.setValue("hotel_meals_info", tour.hotel_meals_uz ?? "");
form.setValue("hotel_meals_info_ru", tour.hotel_meals_ru ?? "");
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);
}
form.setValue("languages", tour.languages ?? "");
form.setValue("duration", tour.duration_days ?? 1);
form.setValue("visa_required", tour.visa_required ? "yes" : "no");
form.setValue("badges", tour.badge ?? []);
// 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);
}
// 🔹 Jonash vaqti
if (tour.departure_time) {
const d = new Date(tour.departure_time);
form.setValue("departureDateTime", {
date: d,
time: d.toTimeString().slice(0, 8),
});
}
}, [isEditMode, data, form]);
// 🔹 Qaytish vaqti
if (tour.travel_time) {
const d = new Date(tour.travel_time);
form.setValue("travelDateTime", {
date: d,
time: d.toTimeString().slice(0, 8),
});
}
// 🔹 Qulayliklar (amenities)
form.setValue(
"amenities",
tour.ticket_amenities?.map((a) => ({
name: a.name ?? "",
name_ru: a.name_ru ?? "",
icon_name: a.icon_name ?? "",
})) ?? [],
);
// 🔹 Xizmatlar (hotel_services)
form.setValue(
"hotel_services",
tour.ticket_included_services?.map((s) => ({
image: s.image ?? null,
title: s.title_uz ?? "",
title_ru: s.title_ru ?? "",
description: s.desc_uz ?? "",
desc_ru: s.desc_ru ?? "",
})) ?? [],
);
// 🔹 Taomlar (hotel_meals)
form.setValue(
"hotel_meals",
tour.ticket_hotel_meals?.map((m) => ({
image: m.image ?? null,
title: m.name ?? "",
title_ru: m.name_ru ?? "",
description: m.desc ?? "",
desc_ru: m.desc_ru ?? "",
})) ?? [],
);
// 🔹 Transport
const transports =
tour.transports?.map((t, i) => ({
transport: i + 1,
price: t.price ?? 0,
})) ?? [];
form.setValue("transport", transports);
setTransportPrices(transports.map((t) => formatPrice(t.price ?? 0))); // 👈 YANGI QOSHILGAN
// 🔹 Tarif
const tariffs =
tour.tariff?.map((t) => ({
tariff: t.tariff ?? 0,
price: t.price ?? 0,
})) ?? [];
form.setValue("tarif", tariffs);
setTarifDisplayPrice(tariffs.map((t) => formatPrice(t.price ?? 0)));
// 🔹 Yonalishlar (ticket_itinerary)
form.setValue(
"ticket_itinerary",
tour.ticket_itinerary?.map((item) => ({
ticket_itinerary_image:
item.ticket_itinerary_image?.map((img) => ({
image: img.image,
})) ?? [],
title: item.title ?? "",
title_ru: item.title_ru ?? "",
duration: item.duration ?? 1,
ticket_itinerary_destinations:
item.ticket_itinerary_destinations?.map((d) => ({
name: d.name ?? "",
name_ru: d.name_ru ?? "",
})) ?? [],
})) ?? [],
);
// 🔹 Banner va rasmlar
form.setValue("banner", tour.image_banner ?? null);
form.setValue("images", tour.ticket_images?.map((img) => img.image) ?? []);
// 🔹 Bepul xizmatlar (extra_service)
form.setValue(
"extra_service",
tour.extra_service?.map((s) => ({
name: s.name_uz ?? s.name ?? "",
name_ru: s.name_ru ?? "",
})) ?? [],
);
// 🔹 Pullik xizmatlar (paid_extra_service)
form.setValue(
"paid_extra_service",
tour.paid_extra_service?.map((s) => ({
name: s.name_uz ?? s.name ?? "",
name_ru: s.name_ru ?? "",
price: s.price ?? 0,
})) ?? [],
);
// 🔹 TicketStore uchun id
setId(tour.id);
}, [isEditMode, data, form, setId]);
const { watch, setValue } = form;
const selectedDate = watch("departureDateTime.date");
@@ -238,6 +276,22 @@ const StepOne = ({
},
});
const { mutate: update } = useMutation({
mutationFn: ({ body, id }: { id: number; body: FormData }) => {
return updateTours({ body, id });
},
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();
@@ -273,7 +327,9 @@ const StepOne = ({
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);
if (value.banner instanceof File) {
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));
@@ -285,7 +341,11 @@ const StepOne = ({
value.badges?.forEach((e, i) => {
formData.append(`badge[${i}]`, String(e));
});
value.images.forEach((e) => formData.append("ticket_images", 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);
@@ -297,43 +357,67 @@ const StepOne = ({
});
});
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);
if (e 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);
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),
);
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));
formData.append(
`ticket_itinerary[${i}]ticket_itinerary_image[${f}]image`,
l.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);
if (e 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);
formData.append(`ticket_hotel_meals[${i}]desc`, e.description);
formData.append(`ticket_hotel_meals[${i}]desc_ru`, e.desc_ru);
}
});
create(formData);
value.extra_service.forEach((e, i) => {
formData.append(`extra_service[${i}]name`, e.name);
formData.append(`extra_service[${i}]name_ru`, e.name_ru);
});
value.paid_extra_service.forEach((e, i) => {
formData.append(`paid_extra_service[${i}]name`, e.name);
formData.append(`paid_extra_service[${i}]name_ru`, e.name_ru);
formData.append(`paid_extra_service[${i}]price`, String(e.price));
});
if (isEditMode && id) {
update({
body: formData,
id: Number(id),
});
} else {
create(formData);
}
}
console.log(form.formState.errors);
const { data: badge } = useQuery({
queryKey: ["all_badge"],
queryFn: () => hotelBadge({ page: 1, page_size: 10 }),
@@ -392,7 +476,9 @@ const StepOne = ({
name="price"
render={() => (
<FormItem>
<Label className="text-md">{t("Narx")} (1 kishi uchun)</Label>
<Label className="text-md">
{t("Narx")} {t("(1 kishi uchun)")}
</Label>
<FormControl>
<Input
type="text"
@@ -1157,11 +1243,11 @@ const StepOne = ({
name="visa_required"
render={({ field }) => (
<FormItem className="space-y-3">
<Label>{t("Visa talab qilinadimi?")}</Label>
<Label>{t("Visa talab qilinadimi")}?</Label>
<FormControl>
<RadioGroup
onValueChange={field.onChange}
defaultValue={field.value}
value={field.value}
className="flex gap-6"
>
<div className="flex items-center space-x-2">
@@ -1170,7 +1256,7 @@ const StepOne = ({
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="no" id="visa_no" />
<label htmlFor="visa_no">{t("Yoq")}</label>
<label htmlFor="visa_no">{t("Yo'q")}</label>
</div>
</RadioGroup>
</FormControl>
@@ -1287,6 +1373,206 @@ const StepOne = ({
)}
/>
<FormField
control={form.control}
name="extra_service"
render={() => (
<FormItem>
<Label className="text-md">{t("Bepul xizmatlar")}</Label>
<div className="flex flex-col gap-4">
{/* Ko'rsatilayotgan xizmatlar */}
<div className="flex flex-wrap gap-2">
{form.watch("extra_service").map((item, idx) => (
<Badge
key={idx}
variant="secondary"
className="px-3 py-1 text-sm flex items-center gap-2"
>
<span>{item.name}</span>
<button
type="button"
onClick={() => {
const current = form.getValues("extra_service");
form.setValue(
"extra_service",
current.filter((_, i) => i !== idx),
);
}}
className="ml-1 text-muted-foreground hover:text-destructive"
>
<XIcon className="size-4" />
</button>
</Badge>
))}
</div>
{/* Yangi xizmat qo'shish */}
<div className="flex gap-3 items-end flex-wrap">
<Input
id="extra_service_name"
placeholder={t("Xizmat nomi (UZ)")}
className="h-12 !text-md flex-1 min-w-[200px]"
/>
<Input
id="extra_service_name_ru"
placeholder={t("Xizmat nomi (RU)")}
className="h-12 !text-md flex-1 min-w-[200px]"
/>
<Button
type="button"
onClick={() => {
const nameInput = document.getElementById(
"extra_service_name",
) as HTMLInputElement;
const nameRuInput = document.getElementById(
"extra_service_name_ru",
) as HTMLInputElement;
if (nameInput.value && nameRuInput.value) {
const current = form.getValues("extra_service");
form.setValue("extra_service", [
...current,
{
name: nameInput.value,
name_ru: nameRuInput.value,
},
]);
nameInput.value = "";
nameRuInput.value = "";
}
}}
className="h-12"
>
{t("Qo'shish")}
</Button>
</div>
<FormMessage />
</div>
</FormItem>
)}
/>
<FormField
control={form.control}
name="paid_extra_service"
render={() => (
<FormItem>
<Label className="text-md">{t("Pullik xizmatlar")}</Label>
<div className="flex flex-col gap-4">
{/* Ro'yxat */}
<div className="flex flex-wrap gap-2">
{(form.watch("paid_extra_service") || []).map((item, idx) => (
<Badge
key={idx}
variant="secondary"
className="px-3 py-1 text-sm flex items-center gap-2"
>
<span>
{item.name} {" "}
<strong>{formatPrice(item.price)} som</strong>
</span>
<button
type="button"
onClick={() => {
const current = form.getValues("paid_extra_service");
form.setValue(
"paid_extra_service",
current.filter((_, i) => i !== idx),
);
}}
className="ml-1 text-muted-foreground hover:text-destructive"
>
<XIcon className="size-4" />
</button>
</Badge>
))}
</div>
{/* Qo'shish formasi */}
<div className="flex gap-3 items-end flex-wrap">
<Input
id="paid_service_name"
placeholder={t("Xizmat nomi (UZ)")}
className="h-12 !text-md flex-1 min-w-[200px]"
/>
<Input
id="paid_service_name_ru"
placeholder={t("Xizmat nomi (RU)")}
className="h-12 !text-md flex-1 min-w-[200px]"
/>
{/* Narx maydoni */}
<Input
id="paid_service_price"
type="text"
inputMode="numeric"
placeholder="1 500 000"
className="h-12 !text-md w-[150px]"
onInput={(e) => {
const input = e.target as HTMLInputElement;
const raw = input.value.replace(/\D/g, "");
input.value = raw
? Number(raw).toLocaleString("ru-RU")
: "";
}}
/>
{/* Qo'shish tugmasi */}
<Button
type="button"
onClick={() => {
const nameInput = document.getElementById(
"paid_service_name",
) as HTMLInputElement;
const nameRuInput = document.getElementById(
"paid_service_name_ru",
) as HTMLInputElement;
const priceInput = document.getElementById(
"paid_service_price",
) as HTMLInputElement;
const raw = priceInput.value.replace(/\D/g, "");
const num = Number(raw);
if (
nameInput.value.trim() &&
nameRuInput.value.trim() &&
!isNaN(num)
) {
const current = form.getValues("paid_extra_service");
form.setValue("paid_extra_service", [
...current,
{
name: nameInput.value.trim(),
name_ru: nameRuInput.value.trim(),
price: num, // 🟢 0 ham bolishi mumkin
},
]);
// inputlarni tozalaymiz
nameInput.value = "";
nameRuInput.value = "";
priceInput.value = "";
}
}}
className="h-12"
>
{t("Qo'shish")}
</Button>
</div>
<FormMessage />
</div>
</FormItem>
)}
/>
<FormField
control={form.control}
name="hotel_info"
@@ -1313,7 +1599,7 @@ const StepOne = ({
<Label className="text-md">{t("Mehmonxona haqida")} (ru)</Label>
<FormControl>
<Textarea
placeholder={t("Mehmonxona haqida (ru)")}
placeholder={t("Mehmonxona haqida") + " (ru)"}
{...field}
className="min-h-48 max-h-60 !text-md"
/>
@@ -1389,7 +1675,7 @@ const StepOne = ({
<Input
id="hotel_service_title_ru"
placeholder={t("Xizmat nomi (ru)")}
placeholder={t("Xizmat nomi") + " (ru)"}
className="h-12 !text-md"
/>
@@ -1401,7 +1687,7 @@ const StepOne = ({
<Textarea
id="hotel_service_desc_ru"
placeholder={t("Xizmat tavsifi (ru)")}
placeholder={t("Xizmat tavsifi") + " (ru)"}
className="min-h-24 !text-md"
/>
@@ -1487,7 +1773,7 @@ const StepOne = ({
</Label>
<FormControl>
<Textarea
placeholder={t("Mehmonxona taomlari haqida (ru)")}
placeholder={t("Mehmonxona taomlari haqida") + " (ru)"}
{...field}
className="min-h-48 max-h-60"
/>
@@ -1563,7 +1849,7 @@ const StepOne = ({
<Input
id="hotel_meals_title_ru"
placeholder={t("Taom nomi (ru)")}
placeholder={t("Taom nomi") + " (ru)"}
className="h-12 !text-md"
/>
@@ -1575,7 +1861,7 @@ const StepOne = ({
<Textarea
id="hotel_meals_desc_ru"
placeholder={t("Taom tavsifi (ru)")}
placeholder={t("Taom tavsifi") + " (ru)"}
className="min-h-24 !text-md"
/>
@@ -1662,7 +1948,7 @@ const StepOne = ({
{item.ticket_itinerary_destinations[0]?.name}
</p>
<p className="text-sm text-muted-foreground">
{item.duration} kun
{item.duration} {t("kun")}
</p>
</div>
<button
@@ -1704,7 +1990,7 @@ const StepOne = ({
<Input
id="ticket_itinerary_title_ru"
placeholder={t("Sarlavha (RU)")}
placeholder={t("Sarlavha") + " (ru)"}
className="h-12 !text-md"
/>
@@ -1724,7 +2010,7 @@ const StepOne = ({
<Input
id="ticket_itinerary_destination_ru"
placeholder={t("Manzil (RU)")}
placeholder={t("Manzil") + " (ru)"}
className="h-12 !text-md"
/>