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

@@ -160,7 +160,7 @@ const BadgeTable = ({
useEffect(() => {
if (badgeDetail) {
form.setValue("color", badgeDetail.data.data.color);
form.setValue("name", badgeDetail.data.data.name);
form.setValue("name", badgeDetail.data.data.name_uz);
form.setValue("name_ru", badgeDetail.data.data.name_ru);
}
}, [editId, badgeDetail]);

View File

@@ -43,7 +43,12 @@ const CreateEditTour = () => {
</div>
</div>
{step === 1 && (
<StepOne setStep={setStep} data={data} isEditMode={isEditMode} />
<StepOne
setStep={setStep}
data={data}
isEditMode={isEditMode}
id={id}
/>
)}
{step === 2 && <StepTwo data={data} isEditMode={isEditMode} />}
</div>

View File

@@ -174,7 +174,7 @@ const FeaturesTable = ({
useEffect(() => {
if (badgeDetail) {
form.setValue("name", badgeDetail.data.data.hotel_feature_type_name);
form.setValue("name", badgeDetail.data.data.hotel_feature_type_name_uz);
form.setValue(
"name_ru",
badgeDetail.data.data.hotel_feature_type_name_ru,

View File

@@ -167,7 +167,7 @@ const FeaturesTableType = ({
useEffect(() => {
if (badgeDetail) {
form.setValue("name", badgeDetail.data.data.feature_name);
form.setValue("name", badgeDetail.data.data.feature_name_uz);
form.setValue("name_ru", badgeDetail.data.data.feature_name_ru);
}
}, [editId, badgeDetail]);

View File

@@ -155,7 +155,7 @@ const MealTable = ({
useEffect(() => {
if (typeDetail) {
form.setValue("name", typeDetail.data.data.name);
form.setValue("name", typeDetail.data.data.name_uz);
form.setValue("name_ru", typeDetail.data.data.name_ru);
}
}, [editId, typeDetail]);

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"
/>

View File

@@ -2,6 +2,8 @@
import {
createHotel,
editHotel,
getHotel,
hotelFeature,
hotelFeatureType,
hotelType,
@@ -30,7 +32,7 @@ import {
SelectValue,
} from "@/shared/ui/select";
import { zodResolver } from "@hookform/resolvers/zod";
import { useMutation } from "@tanstack/react-query";
import { useMutation, useQuery } from "@tanstack/react-query";
import { X } from "lucide-react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
@@ -64,9 +66,18 @@ const StepTwo = ({
isEditMode: boolean;
}) => {
const { amenities, id: ticketId } = useTicketStore();
const navigator = useNavigate();
const navigate = useNavigate();
const { t } = useTranslation();
// 🧩 Query - Hotel detail
const { data: hotelDetail } = useQuery({
queryKey: ["hotel_detail", data?.data.id],
queryFn: () => getHotel(data?.data.id!),
select: (res) => res.data.data.results,
enabled: !!data?.data.id,
});
// 🧩 React Hook Form
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
@@ -79,78 +90,94 @@ const StepTwo = ({
},
});
// 🧩 Edit holati uchun formni toldirish
useEffect(() => {
if (isEditMode && data?.data) {
const tourData = data.data;
if (isEditMode && hotelDetail?.[0]) {
const hotel = hotelDetail[0];
form.setValue("title", tourData.hotel_name);
form.setValue("rating", tourData.hotel_rating);
form.setValue("mealPlan", tourData.hotel_meals);
form.setValue("title", hotel.name);
form.setValue("rating", String(hotel.rating));
const mealPlan =
hotel.meal_plan === "breakfast"
? "Breakfast Only"
: hotel.meal_plan === "all_inclusive"
? "All Inclusive"
: hotel.meal_plan === "half_board"
? "Half Board"
: hotel.meal_plan === "full_board"
? "Full Board"
: "All Inclusive";
form.setValue("mealPlan", mealPlan);
form.setValue(
"hotelType",
hotel.hotel_type?.map((t) => String(t.id)) ?? [],
);
form.setValue(
"hotelFeatures",
hotel.hotel_features?.map((f) => String(f.feature_type.id)) ?? [],
);
form.setValue("hotelFeaturesType", [
...new Set(hotel.hotel_features?.map((f) => String(f.id)) ?? []),
]);
}
}, [isEditMode, data, form]);
}, [isEditMode, hotelDetail, form, data]);
const mealPlans = [
"Breakfast Only",
"Half Board",
"Full Board",
"All Inclusive",
];
// 🧩 Select ma'lumotlari
const [allHotelTypes, setAllHotelTypes] = useState<Type[]>([]);
const [allHotelFeature, setAllHotelFeature] = useState<HotelFeatures[]>([]);
const [allHotelFeatureType, setAllHotelFeatureType] = useState<
HotelFeaturesType[]
>([]);
const [featureTypeMapping, setFeatureTypeMapping] = useState<
Record<string, string[]>
>({});
const selectedHotelFeatures = form.watch("hotelFeatures");
// 🔹 Hotel Types yuklash
useEffect(() => {
const loadAll = async () => {
try {
let page = 1;
let results: Type[] = [];
let hasNext = true;
const loadHotelTypes = async () => {
let page = 1;
let results: Type[] = [];
let hasNext = true;
while (hasNext) {
const res = await hotelType({ page, page_size: 50 });
const data = res.data.data;
results = [...results, ...data.results];
hasNext = !!data.links.next;
page++;
}
setAllHotelTypes(results);
} catch (err) {
console.error(err);
while (hasNext) {
const res = await hotelType({ page, page_size: 50 });
const data = res.data.data;
results = [...results, ...data.results];
hasNext = !!data.links.next;
page++;
}
setAllHotelTypes(results);
};
loadAll();
loadHotelTypes();
}, []);
// 🔹 Hotel Features yuklash
useEffect(() => {
const loadAll = async () => {
try {
let page = 1;
let results: HotelFeatures[] = [];
let hasNext = true;
const loadHotelFeatures = async () => {
let page = 1;
let results: HotelFeatures[] = [];
let hasNext = true;
while (hasNext) {
const res = await hotelFeature({ page, page_size: 50 });
const data = res.data.data;
results = [...results, ...data.results];
hasNext = !!data.links.next;
page++;
}
setAllHotelFeature(results);
} catch (err) {
console.error(err);
while (hasNext) {
const res = await hotelFeature({ page, page_size: 50 });
const data = res.data.data;
results = [...results, ...data.results];
hasNext = !!data.links.next;
page++;
}
setAllHotelFeature(results);
};
loadAll();
loadHotelFeatures();
}, []);
// 🔹 Feature type'larni yuklash (tanlangan feature boyicha)
useEffect(() => {
if (selectedHotelFeatures.length === 0) {
setAllHotelFeatureType([]);
@@ -158,107 +185,118 @@ const StepTwo = ({
return;
}
const loadAll = async () => {
try {
const selectedFeatureIds = selectedHotelFeatures
.map((featureId) => Number(featureId))
.filter((id) => !isNaN(id));
const loadFeatureTypes = async () => {
const selectedIds = selectedHotelFeatures.map(Number).filter(Boolean);
let allResults: HotelFeaturesType[] = [];
const mapping: Record<string, string[]> = {};
if (selectedFeatureIds.length === 0) return;
for (const id of selectedIds) {
let page = 1;
let hasNext = true;
const featureTypes: string[] = [];
let allResults: HotelFeaturesType[] = [];
const newMapping: Record<string, string[]> = {};
for (const featureId of selectedFeatureIds) {
let page = 1;
let hasNext = true;
const featureTypes: string[] = [];
while (hasNext) {
const res = await hotelFeatureType({
page,
page_size: 50,
feature_type: featureId,
});
const data = res.data.data;
allResults = [...allResults, ...data.results];
data.results.forEach((item: HotelFeaturesType) => {
featureTypes.push(String(item.id));
});
hasNext = !!data.links.next;
page++;
}
newMapping[String(featureId)] = featureTypes;
while (hasNext) {
const res = await hotelFeatureType({
page,
page_size: 50,
feature_type: id,
});
const data = res.data.data;
allResults = [...allResults, ...data.results];
data.results.forEach((ft: HotelFeaturesType) =>
featureTypes.push(String(ft.id)),
);
hasNext = !!data.links.next;
page++;
}
const uniqueResults = allResults.filter(
(item, index, self) =>
index === self.findIndex((t) => t.id === item.id),
);
setAllHotelFeatureType(uniqueResults);
setFeatureTypeMapping(newMapping);
} catch (err) {
console.error(err);
mapping[String(id)] = featureTypes;
}
const uniqueResults = allResults.filter(
(v, i, a) => a.findIndex((t) => t.id === v.id) === i,
);
setAllHotelFeatureType(uniqueResults);
setFeatureTypeMapping(mapping);
};
loadAll();
loadFeatureTypes();
}, [selectedHotelFeatures]);
const { mutate, isPending } = useMutation({
mutationFn: (body: FormData) => createHotel({ body }),
onSuccess: () => {
navigator("/tours");
toast.success(t("Muvaffaqiyatli saqlandi"), {
richColors: true,
position: "top-center",
});
toast.success(t("Muvaffaqiyatli saqlandi"));
navigate("/tours");
},
onError: () => {
onError: () =>
toast.error(t("Xatolik yuz berdi"), {
richColors: true,
position: "top-center",
});
},
}),
});
const removeHotelType = (typeId: string) => {
const current = form.getValues("hotelType");
const { mutate: edit, isPending: editPending } = useMutation({
mutationFn: ({ body, id }: { id: number; body: FormData }) =>
editHotel({ body, id }),
onSuccess: () => {
toast.success(t("Muvaffaqiyatli saqlandi"));
navigate("/tours");
},
onError: () =>
toast.error(t("Xatolik yuz berdi"), {
richColors: true,
position: "top-center",
}),
});
const removeHotelType = (id: string) =>
form.setValue(
"hotelType",
current.filter((val) => val !== typeId),
form.getValues("hotelType").filter((v) => v !== id),
);
const removeHotelFeature = (id: string) => {
const current = form.getValues("hotelFeatures");
const types = form.getValues("hotelFeaturesType");
const toRemove = featureTypeMapping[id] || [];
form.setValue(
"hotelFeatures",
current.filter((v) => v !== id),
);
form.setValue(
"hotelFeaturesType",
types.filter((v) => !toRemove.includes(v)),
);
};
const onSubmit = (data: z.infer<typeof formSchema>) => {
const formData = new FormData();
formData.append("ticket", ticketId ? ticketId?.toString() : "");
formData.append("name", data.title);
formData.append("rating", String(data.rating));
formData.append(
"meal_plan",
data.mealPlan === "Breakfast Only"
? "breakfast"
: data.mealPlan === "All Inclusive"
? "all_inclusive"
: data.mealPlan === "Half Board"
? "half_board"
: data.mealPlan === "Full Board"
? "full_board"
: "all_inclusive",
const removeFeatureType = (id: string) =>
form.setValue(
"hotelFeaturesType",
form.getValues("hotelFeaturesType").filter((v) => v !== id),
);
data.hotelType.forEach((typeId) => {
formData.append("hotel_type", typeId);
});
// 🧩 Submit
const onSubmit = (data: z.infer<typeof formSchema>) => {
const formData = new FormData();
data.hotelFeaturesType.forEach((typeId) => {
formData.append("hotel_features", typeId);
});
formData.append("ticket", ticketId ? String(ticketId) : "");
formData.append("name", data.title);
formData.append("rating", data.rating);
const mealPlan =
data.mealPlan === "Breakfast Only"
? "breakfast"
: data.mealPlan === "Half Board"
? "half_board"
: data.mealPlan === "Full Board"
? "full_board"
: "all_inclusive";
formData.append("meal_plan", mealPlan);
data.hotelType.forEach((id) => formData.append("hotel_type", id));
data.hotelFeatures.forEach((id) => formData.append("hotel_features", id));
amenities.forEach((e, i) => {
formData.append(`hotel_amenities[${i}]name`, e.name);
@@ -266,33 +304,22 @@ const StepTwo = ({
formData.append(`hotel_amenities[${i}]icon_name`, e.icon_name);
});
mutate(formData);
if (isEditMode && hotelDetail) {
edit({
body: formData,
id: Number(hotelDetail[0].id),
});
} else {
mutate(formData);
}
};
const removeHotelFeature = (featureId: string) => {
const currentFeatures = form.getValues("hotelFeatures");
const currentFeatureTypes = form.getValues("hotelFeaturesType");
const typesToRemove = featureTypeMapping[featureId] || [];
form.setValue(
"hotelFeatures",
currentFeatures.filter((val) => val !== featureId),
);
form.setValue(
"hotelFeaturesType",
currentFeatureTypes.filter((val) => !typesToRemove.includes(val)),
);
};
const removeFeatureType = (typeId: string) => {
const currentValues = form.getValues("hotelFeaturesType");
form.setValue(
"hotelFeaturesType",
currentValues.filter((val) => val !== typeId),
);
};
const mealPlans = [
"Breakfast Only",
"Half Board",
"Full Board",
"All Inclusive",
];
return (
<Form {...form}>
@@ -465,6 +492,8 @@ const StepTwo = ({
const selectedItem = allHotelFeature.find(
(item) => String(item.id) === selectedValue,
);
console.log(allHotelFeature);
return (
<div
key={selectedValue}
@@ -608,7 +637,7 @@ const StepTwo = ({
disabled={isPending}
className="mt-6 px-6 py-3 bg-blue-600 text-white rounded-md disabled:opacity-50 disabled:cursor-not-allowed"
>
{isPending ? t("Yuklanmoqda...") : t("Saqlash")}
{isPending || editPending ? t("Yuklanmoqda...") : t("Saqlash")}
</button>
</form>
</Form>

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,10 @@
"use client";
import { deleteTours, getAllTours } from "@/pages/tours/lib/api";
import {
addedPopularTours,
deleteTours,
getAllTours,
} from "@/pages/tours/lib/api";
import formatPrice from "@/shared/lib/formatPrice";
import { Button } from "@/shared/ui/button";
import {
@@ -10,6 +14,7 @@ import {
DialogHeader,
DialogTitle,
} from "@/shared/ui/dialog";
import { Switch } from "@/shared/ui/switch";
import {
Table,
TableBody,
@@ -28,15 +33,18 @@ import {
Plane,
PlusCircle,
Trash2,
X,
} from "lucide-react";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
import { toast } from "sonner";
const Tours = () => {
const { t } = useTranslation();
const [page, setPage] = useState(1);
const [deleteId, setDeleteId] = useState<number | null>(null);
const [showPopularDialog, setShowPopularDialog] = useState(false);
const navigate = useNavigate();
const queryClient = useQueryClient();
@@ -45,18 +53,54 @@ const Tours = () => {
queryFn: () => getAllTours({ page: page, page_size: 10 }),
});
const { data: popularTour } = useQuery({
queryKey: ["popular_tours"],
queryFn: () =>
getAllTours({ page: 1, page_size: 10, featured_tickets: true }),
});
const { mutate } = useMutation({
mutationFn: (id: number) => deleteTours({ id }),
onSuccess: () => {
queryClient.refetchQueries({ queryKey: ["all_tours"] });
setDeleteId(null);
},
onError: () => {
toast.error(t("Xatolik yuz berdi"), {
richColors: true,
position: "top-center",
});
},
});
const { mutate: popular } = useMutation({
mutationFn: ({ id, value }: { id: number; value: number }) =>
addedPopularTours({ id, value }),
onSuccess: () => {
queryClient.refetchQueries({ queryKey: ["all_tours"] });
queryClient.refetchQueries({ queryKey: ["popular_tours"] });
},
onError: () => {
if (popularTour?.data.data.results.length === 5) {
setShowPopularDialog(true);
} else {
toast.error(t("Xatolik yuz berdi"), {
richColors: true,
position: "top-center",
});
}
},
});
const confirmDelete = (id: number) => {
mutate(id);
};
const removeFromPopular = (id: number) => {
popular({ id, value: 0 });
setShowPopularDialog(false);
};
if (isLoading) {
return (
<div className="flex flex-col items-center justify-center min-h-screen bg-slate-900 text-white gap-4 w-full">
@@ -105,6 +149,9 @@ const Tours = () => {
</TableHead>
<TableHead className="min-w-[180px]">{t("Mehmonxona")}</TableHead>
<TableHead className="min-w-[200px]">{t("Narxi")}</TableHead>
<TableHead className="min-w-[120px] text-center">
{t("Popular")}
</TableHead>
<TableHead className="min-w-[150px] text-center">
{t("Операции")}
</TableHead>
@@ -139,6 +186,18 @@ const Tours = () => {
</span>
</TableCell>
<TableCell className="text-center">
<Switch
checked={tour.featured_tickets}
onCheckedChange={() =>
popular({
id: tour.id,
value: tour.featured_tickets ? 0 : 1,
})
}
/>
</TableCell>
<TableCell className="text-center">
<div className="flex gap-2 justify-center">
<Button
@@ -172,6 +231,7 @@ const Tours = () => {
</Table>
</div>
{/* Delete Tour Dialog */}
<Dialog open={deleteId !== null} onOpenChange={() => setDeleteId(null)}>
<DialogContent className="sm:max-w-[425px] bg-gray-900">
<DialogHeader>
@@ -201,6 +261,63 @@ const Tours = () => {
</DialogContent>
</Dialog>
{/* Popular Tours Dialog */}
<Dialog open={showPopularDialog} onOpenChange={setShowPopularDialog}>
<DialogContent className="sm:max-w-[600px] bg-gray-900">
<DialogHeader>
<DialogTitle className="text-xl">
{t("Popular turlar (5/5)")}
</DialogTitle>
</DialogHeader>
<div className="py-4">
<p className="text-muted-foreground mb-4">
{t(
"Popular turlar ro'yxati to'lgan. Yangi tur qo'shish uchun biror turni o'chiring.",
)}
</p>
<div className="space-y-2 max-h-[400px] overflow-y-auto">
{popularTour?.data.data.results.map((tour) => (
<div
key={tour.id}
className="flex items-center justify-between p-3 border border-slate-700 rounded-lg hover:bg-slate-800/50 transition-colors"
>
<div className="flex items-center gap-3 flex-1">
<Plane className="w-4 h-4 text-primary flex-shrink-0" />
<div className="flex-1 min-w-0">
<p className="font-semibold truncate">
{tour.destination}
</p>
<p className="text-xs text-muted-foreground">
{tour.duration_days} kun {tour.hotel_name}
</p>
</div>
<span className="text-green-600 font-bold text-sm flex-shrink-0">
{formatPrice(tour.price, true)}
</span>
</div>
<Button
variant="ghost"
size="icon"
onClick={() => removeFromPopular(tour.id)}
className="ml-2 text-red-500 hover:text-red-600 hover:bg-red-500/10"
>
<X className="w-4 h-4" />
</Button>
</div>
))}
</div>
</div>
<DialogFooter>
<Button
variant="outline"
onClick={() => setShowPopularDialog(false)}
>
{t("Yopish")}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
<div className="flex justify-end mt-10 gap-3">
<button
disabled={page === 1}

View File

@@ -69,7 +69,9 @@ const TransportTable = ({
const [open, setOpen] = useState(false);
const [editId, setEditId] = useState<number | null>(null);
const [types, setTypes] = useState<"edit" | "create">("create");
const [selectedIcon, setSelectedIcon] = useState("Bus");
const [selectedIcon, setSelectedIcon] = useState("");
console.log(selectedIcon);
const queryClient = useQueryClient();
const form = useForm<z.infer<typeof formSchema>>({
@@ -83,34 +85,35 @@ const TransportTable = ({
useEffect(() => {
form.setValue("icon_name", selectedIcon);
}, [selectedIcon]);
}, [selectedIcon, form]);
const handleEdit = (id: number) => {
setTypes("edit");
setOpen(true);
setEditId(id);
setOpen(true);
};
const { data: transportDetail } = useQuery({
const { data: transportDetail, isLoading: isDetailLoading } = useQuery({
queryKey: ["detail_transport", editId],
queryFn: () => hotelTransportDetail({ id: editId! }),
enabled: !!editId,
});
useEffect(() => {
if (transportDetail) {
form.setValue("name", transportDetail.data.data.name);
if (transportDetail && editId) {
const iconName = transportDetail.data.data.icon_name || "HelpCircle";
form.setValue("name", transportDetail.data.data.name_uz);
form.setValue("name_ru", transportDetail.data.data.name_ru);
form.setValue("icon_name", transportDetail.data.data.icon_name);
setSelectedIcon(transportDetail.data.data.icon_name);
form.setValue("icon_name", iconName);
setSelectedIcon(iconName);
}
}, [transportDetail, editId, form]);
}, [transportDetail, editId, form, selectedIcon]);
const { mutate: deleteMutate } = useMutation({
mutationFn: ({ id }: { id: number }) => hotelTransportDelete({ id }),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["all_transport"] });
toast.success(t("Ochirildi"), { position: "top-center" });
toast.success(t("O'chirildi"), { position: "top-center" });
},
onError: () =>
toast.error(t("Xatolik yuz berdi"), { position: "top-center" }),
@@ -124,9 +127,8 @@ const TransportTable = ({
}) => hotelTranportCreate({ body }),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["all_transport"] });
setOpen(false);
form.reset();
toast.success(t("Muvaffaqiyatli qoshildi"), { position: "top-center" });
handleCloseDialog();
toast.success(t("Muvaffaqiyatli qo'shildi"), { position: "top-center" });
},
onError: () =>
toast.error(t("Xatolik yuz berdi"), { position: "top-center" }),
@@ -142,8 +144,7 @@ const TransportTable = ({
}) => hotelTransportUpdate({ body, id }),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["all_transport"] });
setOpen(false);
form.reset();
handleCloseDialog();
toast.success(t("Tahrirlandi"), { position: "top-center" });
},
onError: () =>
@@ -163,6 +164,12 @@ const TransportTable = ({
const handleDelete = (id: number) => deleteMutate({ id });
const handleCloseDialog = () => {
setOpen(false);
setEditId(null);
form.reset();
};
const columns = TranportColumns(handleEdit, handleDelete, t);
const table = useReactTable({
@@ -185,14 +192,15 @@ const TransportTable = ({
<Button
variant="default"
onClick={() => {
setOpen(true);
setTypes("create");
setEditId(null);
setSelectedIcon("HelpCircle");
form.reset();
setSelectedIcon("");
setOpen(true);
}}
>
<PlusIcon className="mr-2" />
{t("Qoshish")}
{t("Qo'shish")}
</Button>
</div>
@@ -250,81 +258,96 @@ const TransportTable = ({
namePageSize="pageTransportSize"
/>
<Dialog open={open} onOpenChange={setOpen}>
<Dialog
open={open}
onOpenChange={(isOpen) => {
if (!isOpen) {
handleCloseDialog();
}
}}
>
<DialogContent>
<p className="text-xl font-semibold mb-4">
{types === "create"
? t("Yangi transport qoshish")
? t("Yangi transport qo'shish")
: t("Tahrirlash")}
</p>
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-6 p-2"
>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>{t("Nomi (uz)")}</FormLabel>
<FormControl>
<Input placeholder={t("Nomi (uz)")} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="name_ru"
render={({ field }) => (
<FormItem>
<FormLabel>{t("Nomi (ru)")}</FormLabel>
<FormControl>
<Input placeholder={t("Nomi (ru)")} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="icon_name"
render={() => (
<FormItem>
<FormLabel>{t("Belgi (Icon)")}</FormLabel>
<FormControl className="w-full">
<IconSelect
setSelectedIcon={setSelectedIcon}
selectedIcon={selectedIcon}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="flex justify-end gap-4">
<Button
type="button"
onClick={() => setOpen(false)}
className="border-slate-600 text-white hover:bg-gray-600 bg-gray-600"
>
{t("Bekor qilish")}
</Button>
<Button type="submit" disabled={isPending || updatePending}>
{isPending || updatePending ? (
<Loader className="animate-spin" />
) : types === "create" ? (
t("Saqlash")
) : (
t("Tahrirlash")
{isDetailLoading && types === "edit" ? (
<div className="flex justify-center items-center h-64">
<Loader className="animate-spin w-8 h-8" />
</div>
) : (
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-6 p-2"
>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>{t("Nomi (uz)")}</FormLabel>
<FormControl>
<Input placeholder={t("Nomi (uz)")} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
</Button>
</div>
</form>
</Form>
/>
<FormField
control={form.control}
name="name_ru"
render={({ field }) => (
<FormItem>
<FormLabel>{t("Nomi (ru)")}</FormLabel>
<FormControl>
<Input placeholder={t("Nomi (ru)")} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="icon_name"
render={() => (
<FormItem>
<FormLabel>{t("Belgi (Icon)")}</FormLabel>
<FormControl className="w-full">
<IconSelect
setSelectedIcon={setSelectedIcon}
selectedIcon={selectedIcon}
defaultIcon="HelpCircle"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="flex justify-end gap-4">
<Button
type="button"
onClick={handleCloseDialog}
className="border-slate-600 text-white hover:bg-gray-600 bg-gray-600"
>
{t("Bekor qilish")}
</Button>
<Button type="submit" disabled={isPending || updatePending}>
{isPending || updatePending ? (
<Loader className="animate-spin" />
) : types === "create" ? (
t("Saqlash")
) : (
t("Tahrirlash")
)}
</Button>
</div>
</form>
</Form>
)}
</DialogContent>
</Dialog>
</>