Merge pull request #9 from SamandarTurgunboyev/samandar

update
This commit is contained in:
Samandar Turg'unboev
2025-11-07 17:21:06 +05:00
committed by GitHub
5 changed files with 497 additions and 406 deletions

View File

@@ -496,7 +496,7 @@ export default function Seo() {
<button
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"
>
{isPending || editPending ? (

View File

@@ -51,10 +51,10 @@ export const TourformSchema = z.object({
message: "Iltimos, visa talabliligini tanlang",
}),
departureDateTime: z.object({
date: z.date({ message: "Jonash vaqti majburiy" }),
date: z.date({ message: "Jo'nash vaqti majburiy" }),
time: z
.string()
.min(1, { message: "Jonash vaqti majburiy" })
.min(1, { message: "Jo'nash vaqti majburiy" })
.refine(
(val) => {
const parts = val.split(":");
@@ -76,10 +76,10 @@ export const TourformSchema = z.object({
),
}),
travelDateTime: z.object({
date: z.date({ message: "Jonash vaqti majburiy" }),
date: z.date({ message: "Jo'nash vaqti majburiy" }),
time: z
.string()
.min(1, { message: "Jonash vaqti majburiy" })
.min(1, { message: "Jo'nash vaqti majburiy" })
.refine(
(val) => {
const parts = val.split(":");
@@ -125,14 +125,19 @@ export const TourformSchema = z.object({
.optional(),
banner: z.any().nullable(),
images: z
.array(z.union([z.instanceof(File), z.string()]))
.min(1, { message: "Kamida bitta rasm yuklang." }),
.array(
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(),
// 🔹 Quyidagilar endi ixtiyoriy (required emas)
hotel_services: z
.array(
z.object({
id: z.number().optional(),
image: z.any().nullable(),
title: z.string().min(1, "Xizmat nomi majburiy"),
title_ru: z.string().min(1, { message: "Majburiy maydon" }),
@@ -145,6 +150,7 @@ export const TourformSchema = z.object({
hotel_meals: z
.array(
z.object({
id: z.number().optional(),
image: z.any().nullable(),
title: z.string().min(1, "Xizmat nomi majburiy"),
title_ru: z.string().min(1, "Majburiy maydon"),
@@ -157,10 +163,15 @@ export const TourformSchema = z.object({
ticket_itinerary: z
.array(
z.object({
id: z.number().optional(), // Edit uchun
ticket_itinerary_image: z.array(
z.union([
z.object({
image: z.union([z.instanceof(File), z.string()]),
}),
z.instanceof(File),
z.string(),
]),
),
title: z.string().min(1, "Sarlavha majburiy"),
title_ru: z.string().min(1, "Sarlavha (RU) majburiy"),

View File

@@ -70,11 +70,7 @@ export interface GetOneTours {
};
},
];
ticket_images: [
{
image: string;
},
];
ticket_images: [{ id: number; image: string }];
ticket_amenities: {
icon_name: string;
id: number;
@@ -84,6 +80,7 @@ export interface GetOneTours {
}[];
ticket_included_services: [
{
id: number;
image: string;
title: string;
title_ru: string;
@@ -94,6 +91,7 @@ export interface GetOneTours {
},
];
ticket_itinerary: {
id: number;
title: string;
title_ru: string;
title_uz: string;
@@ -111,8 +109,8 @@ export interface GetOneTours {
},
];
}[];
ticket_hotel_meals: [
{
ticket_hotel_meals: {
id: number;
image: string;
name: string;
name_ru: string;
@@ -120,8 +118,7 @@ export interface GetOneTours {
desc: string;
desc_ru: string;
desc_uz: string;
},
];
}[];
tariff: [
{
tariff: number;

View File

@@ -111,87 +111,33 @@ const StepOne = ({
const tour = data.data;
// 🔹 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));
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 ?? []);
// 🔹 Jonash vaqti
// 🔹 Jo'nash vaqti
let departureDateTime = undefined;
if (tour.departure_time) {
const d = new Date(tour.departure_time);
form.setValue("departureDateTime", {
departureDateTime = {
date: d,
time: d.toTimeString().slice(0, 8),
});
};
}
// 🔹 Qaytish vaqti
let travelDateTime = undefined;
if (tour.travel_time) {
const d = new Date(tour.travel_time);
form.setValue("travelDateTime", {
travelDateTime = {
date: d,
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
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
setTransportPrices(transports.map((t) => formatPrice(t.price ?? 0)));
// 🔹 Tarif
const tariffs =
@@ -199,52 +145,90 @@ const StepOne = ({
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",
form.reset({
title: tour.title_uz ?? "",
title_ru: tour.title_ru ?? "",
price: tour.price ?? 0,
passenger_count: tour.passenger_count ?? 1,
min_person: tour.min_person ?? 1,
max_person: tour.max_person ?? 1,
departure: tour.departure_uz ?? "",
departure_ru: tour.departure_ru ?? "",
destination: tour.destination_uz ?? "",
destination_ru: tour.destination_ru ?? "",
location_name: tour.location_name_uz ?? "",
location_name_ru: tour.location_name_ru ?? "",
hotel_info: tour.hotel_info_uz ?? "",
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,
})) ?? [],
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 ?? "",
item.ticket_itinerary_destinations?.map((dest) => ({
name: dest.name_uz ?? dest.name ?? "",
name_ru: dest.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",
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 ?? "",
})) ?? [],
);
// 🔹 Pullik xizmatlar (paid_extra_service)
form.setValue(
"paid_extra_service",
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
// Display price
setDisplayPrice(formatPrice(tour.price ?? 0));
// TicketStore uchun id
setId(tour.id);
}, [isEditMode, data, form, setId]);
@@ -287,6 +271,7 @@ const StepOne = ({
function onSubmit(value: z.infer<typeof TourformSchema>) {
const formData = new FormData();
// Asosiy ma'lumotlar
formData.append("title", value.title);
formData.append("location_name", value.location_name);
formData.append("location_name_ru", value.location_name_ru);
@@ -294,7 +279,6 @@ const StepOne = ({
"visa_required",
value.visa_required === "yes" ? "true" : "false",
);
formData.append("title_ru", value.title_ru);
formData.append("price", String(value.price));
formData.append("min_person", String(value.min_person));
@@ -319,61 +303,96 @@ 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"));
if (value.banner instanceof File) {
formData.append("image_banner", value.banner);
}
console.log(value.banner, "value.banner");
// Tarif va transport
value.tarif?.forEach((e, i) => {
formData.append(`tariff[${i}]tariff`, String(e.tariff));
formData.append(`tariff[${i}]price`, String(e.price));
});
value.transport?.forEach((e, i) => {
formData.append(`transports[${i}]transport`, String(e.transport));
formData.append(`transports[${i}]price`, String(e.price));
});
value.badges?.forEach((e) => {
formData.append(`badge`, String(e));
});
value.amenities?.forEach((e) => {
formData.append(`ticket_amenities`, String(e));
});
value.images.forEach((e) => {
if (e instanceof File) {
formData.append(`ticket_images`, e);
if (e.id) {
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.forEach((e, i) => {
if (e.id && typeof e.image === "string") {
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_ru`, e.title_ru);
formData.append(`ticket_included_services[${i}]desc_ru`, e.desc_ru);
formData.append(`ticket_included_services[${i}]desc`, e.description);
}
});
// 🔹 Ticket Itinerary - edit rejimida ID yuborish
value.ticket_itinerary?.forEach((itinerary, i) => {
itinerary.ticket_itinerary_image.forEach((img) => {
if (img.image instanceof File) {
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),
);
}
});
// Har bir itinerary uchun asosiy maydonlar
// 🖼 Rasmlar (faqat yangi yuklangan File-larni yuborish)
// Rasmlar
if (Array.isArray(itinerary.ticket_itinerary_image)) {
itinerary.ticket_itinerary_image.forEach((img, j) => {
// img -> File yoki { image: File | string } shaklida bolishi mumkin
const file =
img instanceof File
? img
: img?.image instanceof File
? img.image
: null;
let file: File | null = null;
if (img instanceof File) {
file = img;
} else if (
typeof img === "object" &&
"image" in img &&
img.image instanceof File
) {
file = img.image;
}
if (file) {
formData.append(
@@ -383,10 +402,9 @@ const StepOne = ({
}
});
}
itinerary.ticket_itinerary_image.forEach((img) => {
// Destinations
if (Array.isArray(itinerary.ticket_itinerary_destinations)) {
if (img.image instanceof File) {
// 📍 Destinations (yonalishlar)
itinerary.ticket_itinerary_destinations.forEach((dest, k) => {
formData.append(
`ticket_itinerary[${i}]ticket_itinerary_destinations[${k}]name`,
@@ -398,30 +416,38 @@ const StepOne = ({
);
});
}
}
});
});
value.hotel_meals.forEach((e, i) => {
if (e.id && typeof e.image === "string") {
// 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_ru`, e.title_ru);
formData.append(`ticket_hotel_meals[${i}]desc`, e.description);
formData.append(`ticket_hotel_meals[${i}]desc_ru`, e.desc_ru);
}
});
// Extra services
value.extra_service &&
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 &&
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,
@@ -1412,7 +1438,15 @@ const StepOne = ({
form={form}
name="images"
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
@@ -1963,12 +1997,43 @@ const StepOne = ({
<FormField
control={form.control}
name="ticket_itinerary"
render={() => (
render={() => {
// Helper function to get image source
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 "";
};
return (
<FormItem>
<Label className="text-md">{t("Yo'nalishlar")}</Label>
<div className="flex flex-col gap-4">
{/* mavjud yonalishlar */}
{/* mavjud yo'nalishlar */}
<div className="flex flex-wrap gap-4">
{form.watch("ticket_itinerary")?.map((item, idx) => (
<div
@@ -1976,13 +2041,7 @@ const StepOne = ({
className="relative w-48 border rounded-xl overflow-hidden shadow-sm"
>
<img
src={
item.ticket_itinerary_image[0]?.image instanceof File
? URL.createObjectURL(
item.ticket_itinerary_image[0]?.image,
)
: item.ticket_itinerary_image[0]?.image // agar serverdan kelsa
}
src={getImageSrc(item.ticket_itinerary_image[0])}
alt={item.title}
className="object-cover w-full h-32"
/>
@@ -2013,7 +2072,7 @@ const StepOne = ({
))}
</div>
{/* yangi yonalish qoshish formasi */}
{/* 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")}
@@ -2128,7 +2187,8 @@ const StepOne = ({
<FormMessage />
</FormItem>
)}
);
}}
/>
<div className="w-full flex justify-end">

View File

@@ -12,12 +12,18 @@ import { ImagePlus, XIcon } from "lucide-react";
import { useEffect, useId, useState } from "react";
import { useTranslation } from "react-i18next";
interface ImageObject {
id?: number;
image: string | File;
}
interface TicketsImagesModelProps {
form: any; // React Hook Form control
form: any;
name: string;
label?: string;
imageUrl?: string | string[] | undefined;
imageUrl?: string | File | ImageObject | (string | File | ImageObject)[];
multiple?: boolean;
includeId?: boolean; // agar true bolsa — {id, image} jonatiladi
}
export default function TicketsImagesModel({
@@ -26,20 +32,92 @@ export default function TicketsImagesModel({
label = "Rasmlar",
multiple = true,
imageUrl,
includeId = false,
}: TicketsImagesModelProps) {
const { t } = useTranslation();
const [previews, setPreviews] = useState<string[]>([]);
const inputId = useId();
useEffect(() => {
if (imageUrl) {
const urls = Array.isArray(imageUrl) ? imageUrl : [imageUrl];
setPreviews(urls);
if (!imageUrl) return;
const items = Array.isArray(imageUrl) ? imageUrl : [imageUrl];
// 🔥 form bilan sinxronlash
form.setValue(name, urls);
if (multiple) {
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);
}
}, [imageUrl, form, name]);
});
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, 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 (
<FormField
@@ -48,47 +126,17 @@ export default function TicketsImagesModel({
render={() => (
<FormItem>
<Label className="text-md">{label}</Label>
<FormControl>
<div className="flex flex-col gap-3">
{/* File Input */}
<Input
id={inputId}
type="file"
accept="image/*"
multiple={multiple}
className="hidden"
onChange={(e) => {
const newFiles = e.target.files
? Array.from(e.target.files)
: [];
const currentValue = form.getValues(name) || [];
const currentUrls = previews || [];
if (multiple) {
// ✅ eski URLlarni 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)] : [],
);
}
}}
onChange={(e) => handleFileChange(e.target.files)}
/>
{/* Upload Zone */}
<label
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"
@@ -97,18 +145,13 @@ export default function TicketsImagesModel({
<p className="font-semibold text-white">
{t("Rasmlarni tanlang")}
</p>
{multiple ? (
<p className="text-sm text-white">
{t("Bir nechta rasm yuklashingiz mumkin")}
{multiple
? t("Bir nechta rasm yuklashingiz mumkin")
: t("Faqat bitta rasm yuklash mumkin")}
</p>
) : (
<p className="text-sm text-white">
{t("Faqat bitta rasm yuklash mumkin")}
</p>
)}
</label>
{/* Preview Images */}
{previews.length > 0 && (
<div className="flex flex-wrap gap-3">
{previews.map((src, i) => (
@@ -121,28 +164,9 @@ export default function TicketsImagesModel({
alt={`preview-${i}`}
className="object-cover w-full h-full"
/>
{/* Delete Button */}
<button
type="button"
onClick={() => {
if (multiple) {
// ✅ Kop 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([]);
}
}}
onClick={() => handleDelete(i)}
className="absolute top-1 right-1 bg-white/80 rounded-full p-1 shadow hover:bg-white transition"
>
<XIcon className="size-4 text-destructive" />
@@ -153,7 +177,6 @@ export default function TicketsImagesModel({
)}
</div>
</FormControl>
<FormMessage />
</FormItem>
)}