update
This commit is contained in:
@@ -496,7 +496,7 @@ export default function Seo() {
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
disabled={allSeo && allSeo.length !== 0}
|
disabled={!edit && allSeo && allSeo.length !== 0}
|
||||||
className="w-full bg-blue-600 hover:bg-blue-700 text-white font-semibold py-3 rounded-lg transition-colors disabled:opacity-50"
|
className="w-full bg-blue-600 hover:bg-blue-700 text-white font-semibold py-3 rounded-lg transition-colors disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{isPending || editPending ? (
|
{isPending || editPending ? (
|
||||||
|
|||||||
@@ -51,10 +51,10 @@ export const TourformSchema = z.object({
|
|||||||
message: "Iltimos, visa talabliligini tanlang",
|
message: "Iltimos, visa talabliligini tanlang",
|
||||||
}),
|
}),
|
||||||
departureDateTime: z.object({
|
departureDateTime: z.object({
|
||||||
date: z.date({ message: "Jo‘nash vaqti majburiy" }),
|
date: z.date({ message: "Jo'nash vaqti majburiy" }),
|
||||||
time: z
|
time: z
|
||||||
.string()
|
.string()
|
||||||
.min(1, { message: "Jo‘nash vaqti majburiy" })
|
.min(1, { message: "Jo'nash vaqti majburiy" })
|
||||||
.refine(
|
.refine(
|
||||||
(val) => {
|
(val) => {
|
||||||
const parts = val.split(":");
|
const parts = val.split(":");
|
||||||
@@ -76,10 +76,10 @@ export const TourformSchema = z.object({
|
|||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
travelDateTime: z.object({
|
travelDateTime: z.object({
|
||||||
date: z.date({ message: "Jo‘nash vaqti majburiy" }),
|
date: z.date({ message: "Jo'nash vaqti majburiy" }),
|
||||||
time: z
|
time: z
|
||||||
.string()
|
.string()
|
||||||
.min(1, { message: "Jo‘nash vaqti majburiy" })
|
.min(1, { message: "Jo'nash vaqti majburiy" })
|
||||||
.refine(
|
.refine(
|
||||||
(val) => {
|
(val) => {
|
||||||
const parts = val.split(":");
|
const parts = val.split(":");
|
||||||
@@ -125,14 +125,19 @@ export const TourformSchema = z.object({
|
|||||||
.optional(),
|
.optional(),
|
||||||
banner: z.any().nullable(),
|
banner: z.any().nullable(),
|
||||||
images: z
|
images: z
|
||||||
.array(z.union([z.instanceof(File), z.string()]))
|
.array(
|
||||||
.min(1, { message: "Kamida bitta rasm yuklang." }),
|
z.object({
|
||||||
|
id: z.number().optional(),
|
||||||
|
image: z.union([z.instanceof(File), z.string()]).nullable(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.min(1, { message: "Kamida bitta rasm kiriting." }),
|
||||||
amenities: z.array(z.number()).optional(),
|
amenities: z.array(z.number()).optional(),
|
||||||
|
|
||||||
// 🔹 Quyidagilar endi ixtiyoriy (required emas)
|
|
||||||
hotel_services: z
|
hotel_services: z
|
||||||
.array(
|
.array(
|
||||||
z.object({
|
z.object({
|
||||||
|
id: z.number().optional(),
|
||||||
image: z.any().nullable(),
|
image: z.any().nullable(),
|
||||||
title: z.string().min(1, "Xizmat nomi majburiy"),
|
title: z.string().min(1, "Xizmat nomi majburiy"),
|
||||||
title_ru: z.string().min(1, { message: "Majburiy maydon" }),
|
title_ru: z.string().min(1, { message: "Majburiy maydon" }),
|
||||||
@@ -145,6 +150,7 @@ export const TourformSchema = z.object({
|
|||||||
hotel_meals: z
|
hotel_meals: z
|
||||||
.array(
|
.array(
|
||||||
z.object({
|
z.object({
|
||||||
|
id: z.number().optional(),
|
||||||
image: z.any().nullable(),
|
image: z.any().nullable(),
|
||||||
title: z.string().min(1, "Xizmat nomi majburiy"),
|
title: z.string().min(1, "Xizmat nomi majburiy"),
|
||||||
title_ru: z.string().min(1, "Majburiy maydon"),
|
title_ru: z.string().min(1, "Majburiy maydon"),
|
||||||
@@ -157,10 +163,15 @@ export const TourformSchema = z.object({
|
|||||||
ticket_itinerary: z
|
ticket_itinerary: z
|
||||||
.array(
|
.array(
|
||||||
z.object({
|
z.object({
|
||||||
|
id: z.number().optional(), // Edit uchun
|
||||||
ticket_itinerary_image: z.array(
|
ticket_itinerary_image: z.array(
|
||||||
z.object({
|
z.union([
|
||||||
image: z.union([z.instanceof(File), z.string()]),
|
z.object({
|
||||||
}),
|
image: z.union([z.instanceof(File), z.string()]),
|
||||||
|
}),
|
||||||
|
z.instanceof(File),
|
||||||
|
z.string(),
|
||||||
|
]),
|
||||||
),
|
),
|
||||||
title: z.string().min(1, "Sarlavha majburiy"),
|
title: z.string().min(1, "Sarlavha majburiy"),
|
||||||
title_ru: z.string().min(1, "Sarlavha (RU) majburiy"),
|
title_ru: z.string().min(1, "Sarlavha (RU) majburiy"),
|
||||||
|
|||||||
@@ -70,11 +70,7 @@ export interface GetOneTours {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
ticket_images: [
|
ticket_images: [{ id: number; image: string }];
|
||||||
{
|
|
||||||
image: string;
|
|
||||||
},
|
|
||||||
];
|
|
||||||
ticket_amenities: {
|
ticket_amenities: {
|
||||||
icon_name: string;
|
icon_name: string;
|
||||||
id: number;
|
id: number;
|
||||||
@@ -84,6 +80,7 @@ export interface GetOneTours {
|
|||||||
}[];
|
}[];
|
||||||
ticket_included_services: [
|
ticket_included_services: [
|
||||||
{
|
{
|
||||||
|
id: number;
|
||||||
image: string;
|
image: string;
|
||||||
title: string;
|
title: string;
|
||||||
title_ru: string;
|
title_ru: string;
|
||||||
@@ -94,6 +91,7 @@ export interface GetOneTours {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
ticket_itinerary: {
|
ticket_itinerary: {
|
||||||
|
id: number;
|
||||||
title: string;
|
title: string;
|
||||||
title_ru: string;
|
title_ru: string;
|
||||||
title_uz: string;
|
title_uz: string;
|
||||||
@@ -111,17 +109,16 @@ export interface GetOneTours {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
}[];
|
}[];
|
||||||
ticket_hotel_meals: [
|
ticket_hotel_meals: {
|
||||||
{
|
id: number;
|
||||||
image: string;
|
image: string;
|
||||||
name: string;
|
name: string;
|
||||||
name_ru: string;
|
name_ru: string;
|
||||||
name_uz: string;
|
name_uz: string;
|
||||||
desc: string;
|
desc: string;
|
||||||
desc_ru: string;
|
desc_ru: string;
|
||||||
desc_uz: string;
|
desc_uz: string;
|
||||||
},
|
}[];
|
||||||
];
|
|
||||||
tariff: [
|
tariff: [
|
||||||
{
|
{
|
||||||
tariff: number;
|
tariff: number;
|
||||||
|
|||||||
@@ -111,87 +111,33 @@ const StepOne = ({
|
|||||||
|
|
||||||
const tour = data.data;
|
const tour = data.data;
|
||||||
|
|
||||||
// 🔹 Oddiy text maydonlar
|
// 🔹 Jo'nash vaqti
|
||||||
form.setValue("title", tour.title_uz ?? "");
|
let departureDateTime = undefined;
|
||||||
form.setValue("title_ru", tour.title_ru ?? "");
|
|
||||||
form.setValue("price", tour.price ?? 0);
|
|
||||||
setDisplayPrice(formatPrice(tour.price ?? 0));
|
|
||||||
|
|
||||||
form.setValue("passenger_count", tour.passenger_count ?? 1);
|
|
||||||
form.setValue("min_person", tour.min_person ?? 1);
|
|
||||||
form.setValue("max_person", tour.max_person ?? 1);
|
|
||||||
|
|
||||||
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 ?? "");
|
|
||||||
|
|
||||||
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 ?? "");
|
|
||||||
|
|
||||||
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 ?? []);
|
|
||||||
|
|
||||||
// 🔹 Jo‘nash vaqti
|
|
||||||
if (tour.departure_time) {
|
if (tour.departure_time) {
|
||||||
const d = new Date(tour.departure_time);
|
const d = new Date(tour.departure_time);
|
||||||
form.setValue("departureDateTime", {
|
departureDateTime = {
|
||||||
date: d,
|
date: d,
|
||||||
time: d.toTimeString().slice(0, 8),
|
time: d.toTimeString().slice(0, 8),
|
||||||
});
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🔹 Qaytish vaqti
|
// 🔹 Qaytish vaqti
|
||||||
|
let travelDateTime = undefined;
|
||||||
if (tour.travel_time) {
|
if (tour.travel_time) {
|
||||||
const d = new Date(tour.travel_time);
|
const d = new Date(tour.travel_time);
|
||||||
form.setValue("travelDateTime", {
|
travelDateTime = {
|
||||||
date: d,
|
date: d,
|
||||||
time: d.toTimeString().slice(0, 8),
|
time: d.toTimeString().slice(0, 8),
|
||||||
});
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
form.setValue(
|
|
||||||
"amenities",
|
|
||||||
tour.ticket_amenities.map((e) => e.id),
|
|
||||||
);
|
|
||||||
|
|
||||||
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
|
// 🔹 Transport
|
||||||
const transports =
|
const transports =
|
||||||
tour.transports?.map((t, i) => ({
|
tour.transports?.map((t, i) => ({
|
||||||
transport: i + 1,
|
transport: i + 1,
|
||||||
price: t.price ?? 0,
|
price: t.price ?? 0,
|
||||||
})) ?? [];
|
})) ?? [];
|
||||||
form.setValue("transport", transports);
|
setTransportPrices(transports.map((t) => formatPrice(t.price ?? 0)));
|
||||||
setTransportPrices(transports.map((t) => formatPrice(t.price ?? 0))); // 👈 YANGI QO‘SHILGAN
|
|
||||||
|
|
||||||
// 🔹 Tarif
|
// 🔹 Tarif
|
||||||
const tariffs =
|
const tariffs =
|
||||||
@@ -199,52 +145,90 @@ const StepOne = ({
|
|||||||
tariff: t.tariff ?? 0,
|
tariff: t.tariff ?? 0,
|
||||||
price: t.price ?? 0,
|
price: t.price ?? 0,
|
||||||
})) ?? [];
|
})) ?? [];
|
||||||
form.setValue("tarif", tariffs);
|
|
||||||
setTarifDisplayPrice(tariffs.map((t) => formatPrice(t.price ?? 0)));
|
setTarifDisplayPrice(tariffs.map((t) => formatPrice(t.price ?? 0)));
|
||||||
|
|
||||||
// 🔹 Yo‘nalishlar (ticket_itinerary)
|
form.reset({
|
||||||
form.setValue(
|
title: tour.title_uz ?? "",
|
||||||
"ticket_itinerary",
|
title_ru: tour.title_ru ?? "",
|
||||||
tour.ticket_itinerary?.map((item) => ({
|
price: tour.price ?? 0,
|
||||||
ticket_itinerary_image:
|
passenger_count: tour.passenger_count ?? 1,
|
||||||
item.ticket_itinerary_image?.map((img) => ({
|
min_person: tour.min_person ?? 1,
|
||||||
image: img.image,
|
max_person: tour.max_person ?? 1,
|
||||||
})) ?? [],
|
departure: tour.departure_uz ?? "",
|
||||||
title: item.title ?? "",
|
departure_ru: tour.departure_ru ?? "",
|
||||||
title_ru: item.title_ru ?? "",
|
destination: tour.destination_uz ?? "",
|
||||||
duration: item.duration ?? 1,
|
destination_ru: tour.destination_ru ?? "",
|
||||||
ticket_itinerary_destinations:
|
location_name: tour.location_name_uz ?? "",
|
||||||
item.ticket_itinerary_destinations?.map((d) => ({
|
location_name_ru: tour.location_name_ru ?? "",
|
||||||
name: d.name ?? "",
|
hotel_info: tour.hotel_info_uz ?? "",
|
||||||
name_ru: d.name_ru ?? "",
|
hotel_info_ru: tour.hotel_info_ru ?? "",
|
||||||
})) ?? [],
|
hotel_meals_info: tour.hotel_meals_uz ?? "",
|
||||||
})) ?? [],
|
hotel_meals_info_ru: tour.hotel_meals_ru ?? "",
|
||||||
);
|
languages: tour.languages ?? "",
|
||||||
|
duration: tour.duration_days ?? 1,
|
||||||
|
visa_required: tour.visa_required ? "yes" : "no",
|
||||||
|
badges: tour.badge ?? [],
|
||||||
|
departureDateTime,
|
||||||
|
travelDateTime,
|
||||||
|
amenities: tour.ticket_amenities.map((e) => e.id),
|
||||||
|
hotel_services:
|
||||||
|
tour.ticket_included_services?.map((service) => ({
|
||||||
|
id: service.id,
|
||||||
|
image: service.image ?? null,
|
||||||
|
title: service.title_uz ?? service.title ?? "",
|
||||||
|
title_ru: service.title_ru ?? "",
|
||||||
|
description: service.desc_uz ?? service.desc ?? "",
|
||||||
|
desc_ru: service.desc_ru ?? "",
|
||||||
|
})) ?? [],
|
||||||
|
hotel_meals:
|
||||||
|
tour.ticket_hotel_meals?.map((meal) => ({
|
||||||
|
id: meal.id,
|
||||||
|
image: meal.image ?? null,
|
||||||
|
title: meal.name_uz ?? meal.name ?? "",
|
||||||
|
title_ru: meal.name_ru ?? "",
|
||||||
|
description: meal.desc_uz ?? meal.desc ?? "",
|
||||||
|
desc_ru: meal.desc_ru ?? "",
|
||||||
|
})) ?? [],
|
||||||
|
transport: transports,
|
||||||
|
tarif: tariffs,
|
||||||
|
ticket_itinerary:
|
||||||
|
tour.ticket_itinerary?.map((item) => ({
|
||||||
|
id: item.id,
|
||||||
|
title: item.title_uz ?? item.title ?? "",
|
||||||
|
title_ru: item.title_ru ?? "",
|
||||||
|
duration: item.duration ?? 1,
|
||||||
|
ticket_itinerary_image:
|
||||||
|
item.ticket_itinerary_image?.map((img) => ({
|
||||||
|
image: img.image,
|
||||||
|
})) ?? [],
|
||||||
|
ticket_itinerary_destinations:
|
||||||
|
item.ticket_itinerary_destinations?.map((dest) => ({
|
||||||
|
name: dest.name_uz ?? dest.name ?? "",
|
||||||
|
name_ru: dest.name_ru ?? "",
|
||||||
|
})) ?? [],
|
||||||
|
})) ?? [],
|
||||||
|
banner: tour.image_banner ?? null,
|
||||||
|
images: tour.ticket_images.map((img) => ({
|
||||||
|
id: img.id,
|
||||||
|
image: img.image,
|
||||||
|
})),
|
||||||
|
extra_service:
|
||||||
|
tour.extra_service?.map((s) => ({
|
||||||
|
name: s.name_uz ?? s.name ?? "",
|
||||||
|
name_ru: s.name_ru ?? "",
|
||||||
|
})) ?? [],
|
||||||
|
paid_extra_service:
|
||||||
|
tour.paid_extra_service?.map((s) => ({
|
||||||
|
name: s.name_uz ?? s.name ?? "",
|
||||||
|
name_ru: s.name_ru ?? "",
|
||||||
|
price: s.price ?? 0,
|
||||||
|
})) ?? [],
|
||||||
|
});
|
||||||
|
|
||||||
// 🔹 Banner va rasmlar
|
// Display price
|
||||||
form.setValue("banner", tour.image_banner ?? null);
|
setDisplayPrice(formatPrice(tour.price ?? 0));
|
||||||
form.setValue("images", tour.ticket_images?.map((img) => img.image) ?? []);
|
|
||||||
|
|
||||||
// 🔹 Bepul xizmatlar (extra_service)
|
// TicketStore uchun id
|
||||||
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);
|
setId(tour.id);
|
||||||
}, [isEditMode, data, form, setId]);
|
}, [isEditMode, data, form, setId]);
|
||||||
|
|
||||||
@@ -287,6 +271,7 @@ const StepOne = ({
|
|||||||
function onSubmit(value: z.infer<typeof TourformSchema>) {
|
function onSubmit(value: z.infer<typeof TourformSchema>) {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
|
|
||||||
|
// Asosiy ma'lumotlar
|
||||||
formData.append("title", value.title);
|
formData.append("title", value.title);
|
||||||
formData.append("location_name", value.location_name);
|
formData.append("location_name", value.location_name);
|
||||||
formData.append("location_name_ru", value.location_name_ru);
|
formData.append("location_name_ru", value.location_name_ru);
|
||||||
@@ -294,7 +279,6 @@ const StepOne = ({
|
|||||||
"visa_required",
|
"visa_required",
|
||||||
value.visa_required === "yes" ? "true" : "false",
|
value.visa_required === "yes" ? "true" : "false",
|
||||||
);
|
);
|
||||||
|
|
||||||
formData.append("title_ru", value.title_ru);
|
formData.append("title_ru", value.title_ru);
|
||||||
formData.append("price", String(value.price));
|
formData.append("price", String(value.price));
|
||||||
formData.append("min_person", String(value.min_person));
|
formData.append("min_person", String(value.min_person));
|
||||||
@@ -319,61 +303,96 @@ const StepOne = ({
|
|||||||
formData.append("hotel_meals_ru", value.hotel_meals_info_ru);
|
formData.append("hotel_meals_ru", value.hotel_meals_info_ru);
|
||||||
formData.append("duration_days", String(value.duration));
|
formData.append("duration_days", String(value.duration));
|
||||||
formData.append("rating", String("0.0"));
|
formData.append("rating", String("0.0"));
|
||||||
|
|
||||||
if (value.banner instanceof File) {
|
if (value.banner instanceof File) {
|
||||||
formData.append("image_banner", value.banner);
|
formData.append("image_banner", value.banner);
|
||||||
}
|
}
|
||||||
|
console.log(value.banner, "value.banner");
|
||||||
|
|
||||||
|
// Tarif va transport
|
||||||
value.tarif?.forEach((e, i) => {
|
value.tarif?.forEach((e, i) => {
|
||||||
formData.append(`tariff[${i}]tariff`, String(e.tariff));
|
formData.append(`tariff[${i}]tariff`, String(e.tariff));
|
||||||
formData.append(`tariff[${i}]price`, String(e.price));
|
formData.append(`tariff[${i}]price`, String(e.price));
|
||||||
});
|
});
|
||||||
|
|
||||||
value.transport?.forEach((e, i) => {
|
value.transport?.forEach((e, i) => {
|
||||||
formData.append(`transports[${i}]transport`, String(e.transport));
|
formData.append(`transports[${i}]transport`, String(e.transport));
|
||||||
formData.append(`transports[${i}]price`, String(e.price));
|
formData.append(`transports[${i}]price`, String(e.price));
|
||||||
});
|
});
|
||||||
|
|
||||||
value.badges?.forEach((e) => {
|
value.badges?.forEach((e) => {
|
||||||
formData.append(`badge`, String(e));
|
formData.append(`badge`, String(e));
|
||||||
});
|
});
|
||||||
|
|
||||||
value.amenities?.forEach((e) => {
|
value.amenities?.forEach((e) => {
|
||||||
formData.append(`ticket_amenities`, String(e));
|
formData.append(`ticket_amenities`, String(e));
|
||||||
});
|
});
|
||||||
|
|
||||||
value.images.forEach((e) => {
|
value.images.forEach((e) => {
|
||||||
if (e instanceof File) {
|
if (e.id) {
|
||||||
formData.append(`ticket_images`, e);
|
formData.append(`ticket_images_ids`, String(e.id));
|
||||||
|
} else if (e.id === undefined) {
|
||||||
|
formData.append(`ticket_images`, e.image!);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 🔹 Hotel Services - edit rejimida ID yuborish, yangi bo'lsa to'liq ma'lumot yuborish
|
||||||
value.hotel_services &&
|
value.hotel_services &&
|
||||||
value.hotel_services.forEach((e, i) => {
|
value.hotel_services.forEach((e, i) => {
|
||||||
if (e.image instanceof File) {
|
if (e.id && typeof e.image === "string") {
|
||||||
formData.append(`ticket_included_services[${i}]image`, e.image);
|
formData.append(`ticket_included_services_ids`, String(e.id));
|
||||||
|
} else {
|
||||||
|
// Yangi xizmat yoki o'zgartirilgan xizmat
|
||||||
|
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`, e.title);
|
||||||
formData.append(`ticket_included_services[${i}]title_ru`, e.title_ru);
|
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_ru`, e.desc_ru);
|
||||||
formData.append(`ticket_included_services[${i}]desc`, e.description);
|
formData.append(`ticket_included_services[${i}]desc`, e.description);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
value.ticket_itinerary?.forEach((itinerary, i) => {
|
|
||||||
itinerary.ticket_itinerary_image.forEach((img) => {
|
|
||||||
if (img.image instanceof File) {
|
|
||||||
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),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Har bir itinerary uchun asosiy maydonlar
|
|
||||||
|
|
||||||
// 🖼 Rasmlar (faqat yangi yuklangan File-larni yuborish)
|
// 🔹 Ticket Itinerary - edit rejimida ID yuborish
|
||||||
|
value.ticket_itinerary?.forEach((itinerary, i) => {
|
||||||
|
if (itinerary.id) {
|
||||||
|
// Mavjud itinerary (o'zgartirilmagan)
|
||||||
|
const hasNewImages = itinerary.ticket_itinerary_image?.some(
|
||||||
|
(img) =>
|
||||||
|
img instanceof File ||
|
||||||
|
(typeof img === "object" &&
|
||||||
|
"image" in img &&
|
||||||
|
img.image instanceof File),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!hasNewImages) {
|
||||||
|
formData.append(`ticket_itinerary_ids`, String(itinerary.id));
|
||||||
|
return; // Faqat ID yuborish, boshqa ma'lumot kerak emas
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Yangi itinerary yoki o'zgartirilgan itinerary
|
||||||
|
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)) {
|
if (Array.isArray(itinerary.ticket_itinerary_image)) {
|
||||||
itinerary.ticket_itinerary_image.forEach((img, j) => {
|
itinerary.ticket_itinerary_image.forEach((img, j) => {
|
||||||
// img -> File yoki { image: File | string } shaklida bo‘lishi mumkin
|
let file: File | null = null;
|
||||||
const file =
|
|
||||||
img instanceof File
|
if (img instanceof File) {
|
||||||
? img
|
file = img;
|
||||||
: img?.image instanceof File
|
} else if (
|
||||||
? img.image
|
typeof img === "object" &&
|
||||||
: null;
|
"image" in img &&
|
||||||
|
img.image instanceof File
|
||||||
|
) {
|
||||||
|
file = img.image;
|
||||||
|
}
|
||||||
|
|
||||||
if (file) {
|
if (file) {
|
||||||
formData.append(
|
formData.append(
|
||||||
@@ -383,45 +402,52 @@ const StepOne = ({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
itinerary.ticket_itinerary_image.forEach((img) => {
|
|
||||||
if (Array.isArray(itinerary.ticket_itinerary_destinations)) {
|
// Destinations
|
||||||
if (img.image instanceof File) {
|
if (Array.isArray(itinerary.ticket_itinerary_destinations)) {
|
||||||
// 📍 Destinations (yo‘nalishlar)
|
itinerary.ticket_itinerary_destinations.forEach((dest, k) => {
|
||||||
itinerary.ticket_itinerary_destinations.forEach((dest, k) => {
|
formData.append(
|
||||||
formData.append(
|
`ticket_itinerary[${i}]ticket_itinerary_destinations[${k}]name`,
|
||||||
`ticket_itinerary[${i}]ticket_itinerary_destinations[${k}]name`,
|
dest.name,
|
||||||
dest.name,
|
);
|
||||||
);
|
formData.append(
|
||||||
formData.append(
|
`ticket_itinerary[${i}]ticket_itinerary_destinations[${k}]name_ru`,
|
||||||
`ticket_itinerary[${i}]ticket_itinerary_destinations[${k}]name_ru`,
|
dest.name_ru,
|
||||||
dest.name_ru,
|
);
|
||||||
);
|
});
|
||||||
});
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
value.hotel_meals.forEach((e, i) => {
|
value.hotel_meals.forEach((e, i) => {
|
||||||
if (e.image instanceof File) {
|
if (e.id && typeof e.image === "string") {
|
||||||
formData.append(`ticket_hotel_meals[${i}]image`, e.image);
|
// Mavjud meal (o'zgartirilmagan)
|
||||||
|
formData.append(`ticket_hotel_meals_ids`, String(e.id));
|
||||||
|
} else {
|
||||||
|
// Yangi meal yoki o'zgartirilgan meal
|
||||||
|
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`, e.title);
|
||||||
formData.append(`ticket_hotel_meals[${i}]name_ru`, e.title_ru);
|
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`, e.description);
|
||||||
formData.append(`ticket_hotel_meals[${i}]desc_ru`, e.desc_ru);
|
formData.append(`ticket_hotel_meals[${i}]desc_ru`, e.desc_ru);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Extra services
|
||||||
value.extra_service &&
|
value.extra_service &&
|
||||||
value.extra_service.forEach((e, i) => {
|
value.extra_service.forEach((e, i) => {
|
||||||
formData.append(`extra_service[${i}]name`, e.name);
|
formData.append(`extra_service[${i}]name`, e.name);
|
||||||
formData.append(`extra_service[${i}]name_ru`, e.name_ru);
|
formData.append(`extra_service[${i}]name_ru`, e.name_ru);
|
||||||
});
|
});
|
||||||
|
|
||||||
value.paid_extra_service &&
|
value.paid_extra_service &&
|
||||||
value.paid_extra_service.forEach((e, i) => {
|
value.paid_extra_service.forEach((e, i) => {
|
||||||
formData.append(`paid_extra_service[${i}]name`, e.name);
|
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}]name_ru`, e.name_ru);
|
||||||
formData.append(`paid_extra_service[${i}]price`, String(e.price));
|
formData.append(`paid_extra_service[${i}]price`, String(e.price));
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isEditMode && id) {
|
if (isEditMode && id) {
|
||||||
update({
|
update({
|
||||||
body: formData,
|
body: formData,
|
||||||
@@ -1412,7 +1438,15 @@ const StepOne = ({
|
|||||||
form={form}
|
form={form}
|
||||||
name="images"
|
name="images"
|
||||||
label={t("Qo'shimcha rasmlar")}
|
label={t("Qo'shimcha rasmlar")}
|
||||||
imageUrl={data?.data.ticket_images?.map((img) => img.image)}
|
includeId={true}
|
||||||
|
imageUrl={
|
||||||
|
isEditMode && data?.data?.ticket_images
|
||||||
|
? data.data.ticket_images.map((img) => ({
|
||||||
|
id: img.id,
|
||||||
|
image: img.image,
|
||||||
|
}))
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
@@ -1963,172 +1997,198 @@ const StepOne = ({
|
|||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="ticket_itinerary"
|
name="ticket_itinerary"
|
||||||
render={() => (
|
render={() => {
|
||||||
<FormItem>
|
// Helper function to get image source
|
||||||
<Label className="text-md">{t("Yo'nalishlar")}</Label>
|
const getImageSrc = (imageItem: any): string => {
|
||||||
|
// Agar object bo'lsa va image property bo'lsa
|
||||||
|
if (
|
||||||
|
imageItem &&
|
||||||
|
typeof imageItem === "object" &&
|
||||||
|
"image" in imageItem
|
||||||
|
) {
|
||||||
|
const img = imageItem.image;
|
||||||
|
// File bo'lsa
|
||||||
|
if (img instanceof File) {
|
||||||
|
return URL.createObjectURL(img);
|
||||||
|
}
|
||||||
|
// String (URL) bo'lsa
|
||||||
|
if (typeof img === "string") {
|
||||||
|
return img;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Agar to'g'ridan-to'g'ri File bo'lsa
|
||||||
|
if (imageItem instanceof File) {
|
||||||
|
return URL.createObjectURL(imageItem);
|
||||||
|
}
|
||||||
|
// Agar string bo'lsa
|
||||||
|
if (typeof imageItem === "string") {
|
||||||
|
return imageItem;
|
||||||
|
}
|
||||||
|
// Default fallback
|
||||||
|
return "";
|
||||||
|
};
|
||||||
|
|
||||||
<div className="flex flex-col gap-4">
|
return (
|
||||||
{/* mavjud yo‘nalishlar */}
|
<FormItem>
|
||||||
<div className="flex flex-wrap gap-4">
|
<Label className="text-md">{t("Yo'nalishlar")}</Label>
|
||||||
{form.watch("ticket_itinerary")?.map((item, idx) => (
|
|
||||||
<div
|
<div className="flex flex-col gap-4">
|
||||||
key={idx}
|
{/* mavjud yo'nalishlar */}
|
||||||
className="relative w-48 border rounded-xl overflow-hidden shadow-sm"
|
<div className="flex flex-wrap gap-4">
|
||||||
>
|
{form.watch("ticket_itinerary")?.map((item, idx) => (
|
||||||
<img
|
<div
|
||||||
src={
|
key={idx}
|
||||||
item.ticket_itinerary_image[0]?.image instanceof File
|
className="relative w-48 border rounded-xl overflow-hidden shadow-sm"
|
||||||
? URL.createObjectURL(
|
>
|
||||||
item.ticket_itinerary_image[0]?.image,
|
<img
|
||||||
)
|
src={getImageSrc(item.ticket_itinerary_image[0])}
|
||||||
: item.ticket_itinerary_image[0]?.image // agar serverdan kelsa
|
alt={item.title}
|
||||||
}
|
className="object-cover w-full h-32"
|
||||||
alt={item.title}
|
/>
|
||||||
className="object-cover w-full h-32"
|
<div className="p-2 text-center">
|
||||||
/>
|
<p className="font-semibold">{item.title}</p>
|
||||||
<div className="p-2 text-center">
|
<p className="text-sm text-muted-foreground">
|
||||||
<p className="font-semibold">{item.title}</p>
|
{item.ticket_itinerary_destinations[0]?.name}
|
||||||
<p className="text-sm text-muted-foreground">
|
</p>
|
||||||
{item.ticket_itinerary_destinations[0]?.name}
|
<p className="text-sm text-muted-foreground">
|
||||||
</p>
|
{item.duration} {t("kun")}
|
||||||
<p className="text-sm text-muted-foreground">
|
</p>
|
||||||
{item.duration} {t("kun")}
|
</div>
|
||||||
</p>
|
<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>
|
||||||
<button
|
))}
|
||||||
type="button"
|
</div>
|
||||||
onClick={() => {
|
|
||||||
|
{/* yangi yo'nalish qo'shish formasi */}
|
||||||
|
<div className="flex flex-col gap-3 border rounded-xl p-4 bg-muted/10">
|
||||||
|
<Label className="text-md font-semibold">
|
||||||
|
{t("Yo'nalish qo'shish")}
|
||||||
|
</Label>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
type="file"
|
||||||
|
id="ticket_itinerary_image"
|
||||||
|
accept="image/*"
|
||||||
|
className="h-12"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
id="ticket_itinerary_title"
|
||||||
|
placeholder={t("Sarlavha")}
|
||||||
|
className="h-12 !text-md"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
id="ticket_itinerary_title_ru"
|
||||||
|
placeholder={t("Sarlavha") + " (ru)"}
|
||||||
|
className="h-12 !text-md"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
id="ticket_itinerary_duration"
|
||||||
|
type="number"
|
||||||
|
min={1}
|
||||||
|
placeholder={t("Davomiylik (kun)")}
|
||||||
|
className="h-12 !text-md"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
id="ticket_itinerary_destination"
|
||||||
|
placeholder={t("Manzil")}
|
||||||
|
className="h-12 !text-md"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
id="ticket_itinerary_destination_ru"
|
||||||
|
placeholder={t("Manzil") + " (ru)"}
|
||||||
|
className="h-12 !text-md"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
const imgInput = document.getElementById(
|
||||||
|
"ticket_itinerary_image",
|
||||||
|
) as HTMLInputElement;
|
||||||
|
const titleInput = document.getElementById(
|
||||||
|
"ticket_itinerary_title",
|
||||||
|
) as HTMLInputElement;
|
||||||
|
const titleRuInput = document.getElementById(
|
||||||
|
"ticket_itinerary_title_ru",
|
||||||
|
) as HTMLInputElement;
|
||||||
|
const durationInput = document.getElementById(
|
||||||
|
"ticket_itinerary_duration",
|
||||||
|
) as HTMLInputElement;
|
||||||
|
const destInput = document.getElementById(
|
||||||
|
"ticket_itinerary_destination",
|
||||||
|
) as HTMLInputElement;
|
||||||
|
const destRuInput = document.getElementById(
|
||||||
|
"ticket_itinerary_destination_ru",
|
||||||
|
) as HTMLInputElement;
|
||||||
|
|
||||||
|
const file = imgInput.files?.[0];
|
||||||
|
const title = titleInput.value.trim();
|
||||||
|
const titleRu = titleRuInput.value.trim();
|
||||||
|
const duration = Number(durationInput.value);
|
||||||
|
const dest = destInput.value.trim();
|
||||||
|
const destRu = destRuInput.value.trim();
|
||||||
|
|
||||||
|
if (
|
||||||
|
file &&
|
||||||
|
title &&
|
||||||
|
titleRu &&
|
||||||
|
duration &&
|
||||||
|
dest &&
|
||||||
|
destRu
|
||||||
|
) {
|
||||||
const current =
|
const current =
|
||||||
form.getValues("ticket_itinerary") || [];
|
form.getValues("ticket_itinerary") || [];
|
||||||
form.setValue(
|
|
||||||
"ticket_itinerary",
|
form.setValue("ticket_itinerary", [
|
||||||
current.filter((_, i) => i !== idx),
|
...current,
|
||||||
);
|
{
|
||||||
}}
|
ticket_itinerary_image: [{ image: file }],
|
||||||
className="absolute top-1 right-1 bg-white/80 rounded-full p-1 hover:bg-white shadow"
|
title,
|
||||||
>
|
title_ru: titleRu,
|
||||||
<XIcon className="size-4 text-destructive" />
|
duration,
|
||||||
</button>
|
ticket_itinerary_destinations: [
|
||||||
</div>
|
{ 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>
|
</div>
|
||||||
|
|
||||||
{/* yangi yo‘nalish qo‘shish formasi */}
|
<FormMessage />
|
||||||
<div className="flex flex-col gap-3 border rounded-xl p-4 bg-muted/10">
|
</FormItem>
|
||||||
<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">
|
<div className="w-full flex justify-end">
|
||||||
|
|||||||
@@ -12,12 +12,18 @@ import { ImagePlus, XIcon } from "lucide-react";
|
|||||||
import { useEffect, useId, useState } from "react";
|
import { useEffect, useId, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
interface ImageObject {
|
||||||
|
id?: number;
|
||||||
|
image: string | File;
|
||||||
|
}
|
||||||
|
|
||||||
interface TicketsImagesModelProps {
|
interface TicketsImagesModelProps {
|
||||||
form: any; // React Hook Form control
|
form: any;
|
||||||
name: string;
|
name: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
imageUrl?: string | string[] | undefined;
|
imageUrl?: string | File | ImageObject | (string | File | ImageObject)[];
|
||||||
multiple?: boolean;
|
multiple?: boolean;
|
||||||
|
includeId?: boolean; // agar true bo‘lsa — {id, image} jo‘natiladi
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function TicketsImagesModel({
|
export default function TicketsImagesModel({
|
||||||
@@ -26,20 +32,92 @@ export default function TicketsImagesModel({
|
|||||||
label = "Rasmlar",
|
label = "Rasmlar",
|
||||||
multiple = true,
|
multiple = true,
|
||||||
imageUrl,
|
imageUrl,
|
||||||
|
includeId = false,
|
||||||
}: TicketsImagesModelProps) {
|
}: TicketsImagesModelProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [previews, setPreviews] = useState<string[]>([]);
|
const [previews, setPreviews] = useState<string[]>([]);
|
||||||
const inputId = useId();
|
const inputId = useId();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (imageUrl) {
|
if (!imageUrl) return;
|
||||||
const urls = Array.isArray(imageUrl) ? imageUrl : [imageUrl];
|
const items = Array.isArray(imageUrl) ? imageUrl : [imageUrl];
|
||||||
setPreviews(urls);
|
|
||||||
|
|
||||||
// 🔥 form bilan sinxronlash
|
if (multiple) {
|
||||||
form.setValue(name, urls);
|
const values: any[] = [];
|
||||||
|
const urls: string[] = [];
|
||||||
|
|
||||||
|
items.forEach((item) => {
|
||||||
|
if (typeof item === "string") {
|
||||||
|
urls.push(item);
|
||||||
|
values.push(includeId ? { image: item } : item);
|
||||||
|
} else if (item instanceof File) {
|
||||||
|
urls.push(URL.createObjectURL(item));
|
||||||
|
values.push(includeId ? { image: item } : item);
|
||||||
|
} else if (typeof item === "object" && "image" in item) {
|
||||||
|
const img = item.image;
|
||||||
|
urls.push(typeof img === "string" ? img : URL.createObjectURL(img));
|
||||||
|
values.push(includeId ? { id: item.id, image: img } : img);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setPreviews(urls);
|
||||||
|
form.setValue(name, values);
|
||||||
|
} else {
|
||||||
|
const single = items[0];
|
||||||
|
let url = "";
|
||||||
|
let valueToSet: any = null;
|
||||||
|
|
||||||
|
if (typeof single === "string") {
|
||||||
|
url = single;
|
||||||
|
valueToSet = single;
|
||||||
|
} else if (single instanceof File) {
|
||||||
|
url = URL.createObjectURL(single);
|
||||||
|
valueToSet = single;
|
||||||
|
} else if (typeof single === "object" && "image" in single) {
|
||||||
|
const img = single.image;
|
||||||
|
url = typeof img === "string" ? img : URL.createObjectURL(img);
|
||||||
|
valueToSet =
|
||||||
|
includeId && single.id ? { id: single.id, image: img } : img;
|
||||||
|
}
|
||||||
|
|
||||||
|
setPreviews(url ? [url] : []);
|
||||||
|
form.setValue(name, valueToSet); // 🔥 array emas
|
||||||
}
|
}
|
||||||
}, [imageUrl, form, name]);
|
}, [imageUrl, form, name, multiple, includeId]);
|
||||||
|
|
||||||
|
const handleFileChange = (files: FileList | null) => {
|
||||||
|
if (!files) return;
|
||||||
|
const newFiles = Array.from(files);
|
||||||
|
|
||||||
|
if (multiple) {
|
||||||
|
const current = form.getValues(name) || [];
|
||||||
|
const newValue = includeId
|
||||||
|
? [...current, ...newFiles.map((f) => ({ image: f }))]
|
||||||
|
: [...current, ...newFiles];
|
||||||
|
form.setValue(name, newValue);
|
||||||
|
setPreviews([
|
||||||
|
...previews,
|
||||||
|
...newFiles.map((f) => URL.createObjectURL(f)),
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
const singleFile = newFiles[0];
|
||||||
|
const valueToSet = includeId ? { image: singleFile } : singleFile;
|
||||||
|
form.setValue(name, valueToSet); // 🔥 array emas
|
||||||
|
setPreviews(singleFile ? [URL.createObjectURL(singleFile)] : []);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = (index: number) => {
|
||||||
|
if (multiple) {
|
||||||
|
const current = form.getValues(name) || [];
|
||||||
|
const newValue = current.filter((_: any, i: number) => i !== index);
|
||||||
|
form.setValue(name, newValue);
|
||||||
|
setPreviews(previews.filter((_, i) => i !== index));
|
||||||
|
} else {
|
||||||
|
form.setValue(name, null);
|
||||||
|
setPreviews([]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormField
|
<FormField
|
||||||
@@ -48,47 +126,17 @@ export default function TicketsImagesModel({
|
|||||||
render={() => (
|
render={() => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<Label className="text-md">{label}</Label>
|
<Label className="text-md">{label}</Label>
|
||||||
|
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
{/* File Input */}
|
|
||||||
<Input
|
<Input
|
||||||
id={inputId}
|
id={inputId}
|
||||||
type="file"
|
type="file"
|
||||||
accept="image/*"
|
accept="image/*"
|
||||||
multiple={multiple}
|
multiple={multiple}
|
||||||
className="hidden"
|
className="hidden"
|
||||||
onChange={(e) => {
|
onChange={(e) => handleFileChange(e.target.files)}
|
||||||
const newFiles = e.target.files
|
|
||||||
? Array.from(e.target.files)
|
|
||||||
: [];
|
|
||||||
const currentValue = form.getValues(name) || [];
|
|
||||||
const currentUrls = previews || [];
|
|
||||||
|
|
||||||
if (multiple) {
|
|
||||||
// ✅ eski URL’larni va yangi fayllarni birlashtirish
|
|
||||||
const combined = [...currentValue, ...newFiles];
|
|
||||||
form.setValue(name, combined);
|
|
||||||
|
|
||||||
const newPreviews = [
|
|
||||||
...currentUrls,
|
|
||||||
...newFiles.map((file: File) =>
|
|
||||||
URL.createObjectURL(file),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
setPreviews(newPreviews);
|
|
||||||
} else {
|
|
||||||
// ✅ bitta rasm holati
|
|
||||||
const singleFile = newFiles[0] || null;
|
|
||||||
form.setValue(name, singleFile);
|
|
||||||
setPreviews(
|
|
||||||
singleFile ? [URL.createObjectURL(singleFile)] : [],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Upload Zone */}
|
|
||||||
<label
|
<label
|
||||||
htmlFor={inputId}
|
htmlFor={inputId}
|
||||||
className="border-2 border-dashed border-gray-300 h-40 rounded-2xl flex flex-col justify-center items-center cursor-pointer hover:bg-muted/20 transition"
|
className="border-2 border-dashed border-gray-300 h-40 rounded-2xl flex flex-col justify-center items-center cursor-pointer hover:bg-muted/20 transition"
|
||||||
@@ -97,18 +145,13 @@ export default function TicketsImagesModel({
|
|||||||
<p className="font-semibold text-white">
|
<p className="font-semibold text-white">
|
||||||
{t("Rasmlarni tanlang")}
|
{t("Rasmlarni tanlang")}
|
||||||
</p>
|
</p>
|
||||||
{multiple ? (
|
<p className="text-sm text-white">
|
||||||
<p className="text-sm text-white">
|
{multiple
|
||||||
{t("Bir nechta rasm yuklashingiz mumkin")}
|
? t("Bir nechta rasm yuklashingiz mumkin")
|
||||||
</p>
|
: t("Faqat bitta rasm yuklash mumkin")}
|
||||||
) : (
|
</p>
|
||||||
<p className="text-sm text-white">
|
|
||||||
{t("Faqat bitta rasm yuklash mumkin")}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
{/* Preview Images */}
|
|
||||||
{previews.length > 0 && (
|
{previews.length > 0 && (
|
||||||
<div className="flex flex-wrap gap-3">
|
<div className="flex flex-wrap gap-3">
|
||||||
{previews.map((src, i) => (
|
{previews.map((src, i) => (
|
||||||
@@ -121,28 +164,9 @@ export default function TicketsImagesModel({
|
|||||||
alt={`preview-${i}`}
|
alt={`preview-${i}`}
|
||||||
className="object-cover w-full h-full"
|
className="object-cover w-full h-full"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Delete Button */}
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onClick={() => handleDelete(i)}
|
||||||
if (multiple) {
|
|
||||||
// ✅ Ko‘p rasm holati
|
|
||||||
const currentFiles = form.getValues(name) || [];
|
|
||||||
const newFiles = currentFiles.filter(
|
|
||||||
(_: File, idx: number) => idx !== i,
|
|
||||||
);
|
|
||||||
form.setValue(name, newFiles);
|
|
||||||
const newPreviews = previews.filter(
|
|
||||||
(_: string, idx: number) => idx !== i,
|
|
||||||
);
|
|
||||||
setPreviews(newPreviews);
|
|
||||||
} else {
|
|
||||||
// ✅ Bitta rasm holati
|
|
||||||
form.setValue(name, null);
|
|
||||||
setPreviews([]);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
className="absolute top-1 right-1 bg-white/80 rounded-full p-1 shadow hover:bg-white transition"
|
className="absolute top-1 right-1 bg-white/80 rounded-full p-1 shadow hover:bg-white transition"
|
||||||
>
|
>
|
||||||
<XIcon className="size-4 text-destructive" />
|
<XIcon className="size-4 text-destructive" />
|
||||||
@@ -153,7 +177,6 @@ export default function TicketsImagesModel({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user