This commit is contained in:
Samandar Turgunboyev
2025-11-11 15:07:55 +05:00
parent 3f043fe330
commit 8a4618b454
3 changed files with 164 additions and 205 deletions

View File

@@ -366,18 +366,19 @@ export default function FinanceDetailUser() {
</p> </p>
</div> </div>
</div> </div>
{data?.user.phone && (
<div className="flex items-center gap-3 p-3 bg-gray-700 rounded-lg"> <div className="flex items-center gap-3 p-3 bg-gray-700 rounded-lg">
<Phone className="w-5 h-5 text-green-400" /> <Phone className="w-5 h-5 text-green-400" />
<div> <div>
<p className="text-sm text-gray-400"> <p className="text-sm text-gray-400">
{t("Phone Number")} {t("Phone Number")}
</p> </p>
<p className="text-gray-100"> <p className="text-gray-100">
{data && formatPhone(data?.user.phone)} {data && formatPhone(data?.user.phone)}
</p> </p>
</div>
</div> </div>
</div> )}
{data?.user.email && ( {data?.user.email && (
<div className="flex items-center gap-3 p-3 bg-gray-700 rounded-lg"> <div className="flex items-center gap-3 p-3 bg-gray-700 rounded-lg">

View File

@@ -7,24 +7,20 @@ export const TourformSchema = z.object({
title_ru: z.string().min(2, { title_ru: z.string().min(2, {
message: "Sarlavha kamida 2 ta belgidan iborat bo'lishi kerak", message: "Sarlavha kamida 2 ta belgidan iborat bo'lishi kerak",
}), }),
hotel_info: z.string().min(2, { hotel_info: z.string().optional(),
message: "Sarlavha kamida 2 ta belgidan iborat bo'lishi kerak", hotel_info_ru: z.string().optional(),
}), hotel_meals_info: z.string().optional(),
hotel_info_ru: z.string(), hotel_meals_info_ru: z.string().optional(),
hotel_meals_info: z.string().min(2, {
message: "Sarlavha kamida 2 ta belgidan iborat bo'lishi kerak",
}),
hotel_meals_info_ru: z.string(),
price: z.number().min(1000, { price: z.number().min(1000, {
message: "Narx kamida 1000 UZS bo'lishi kerak.", message: "Narx kamida 1000 UZS bo'lishi kerak.",
}), }),
passenger_count: z.number().min(1, { passenger_count: z.string().min(1, {
message: "Kamida 1 yo'lovchi bo'lishi kerak.", message: "Kamida 1 yo'lovchi bo'lishi kerak.",
}), }),
min_person: z.number().min(1, { min_person: z.string().min(1, {
message: "Kamida 1 yo'lovchi bo'lishi kerak.", message: "Kamida 1 yo'lovchi bo'lishi kerak.",
}), }),
max_person: z.number().min(1, { max_person: z.string().min(1, {
message: "Kamida 1 yo'lovchi bo'lishi kerak.", message: "Kamida 1 yo'lovchi bo'lishi kerak.",
}), }),
departure: z.string().min(2, { departure: z.string().min(2, {
@@ -52,56 +48,12 @@ export const TourformSchema = z.object({
}), }),
departureDateTime: z.object({ departureDateTime: z.object({
date: z.date({ message: "Jo'nash vaqti majburiy" }), date: z.date({ message: "Jo'nash vaqti majburiy" }),
time: z
.string()
.min(1, { message: "Jo'nash vaqti majburiy" })
.refine(
(val) => {
const parts = val.split(":");
if (parts.length !== 3) return false;
const [hour, minute, second] = parts.map(Number);
return (
!isNaN(hour) &&
!isNaN(minute) &&
!isNaN(second) &&
hour >= 0 &&
hour <= 23 &&
minute >= 0 &&
minute <= 59 &&
second >= 0 &&
second <= 59
);
},
{ message: "Yaroqli vaqt kiriting (masalan, 08:30:00)" },
),
}), }),
travelDateTime: z.object({ travelDateTime: z.object({
date: z.date({ message: "Jo'nash vaqti majburiy" }), date: z.date({ message: "Jo'nash vaqti majburiy" }),
time: z
.string()
.min(1, { message: "Jo'nash vaqti majburiy" })
.refine(
(val) => {
const parts = val.split(":");
if (parts.length !== 3) return false;
const [hour, minute, second] = parts.map(Number);
return (
!isNaN(hour) &&
!isNaN(minute) &&
!isNaN(second) &&
hour >= 0 &&
hour <= 23 &&
minute >= 0 &&
minute <= 59 &&
second >= 0 &&
second <= 59
);
},
{ message: "Yaroqli vaqt kiriting (masalan, 08:30:00)" },
),
}), }),
languages: z.string().min(1, { message: "Majburiy maydon" }), languages: z.string().optional(),
duration: z.number().min(1, { message: "Kamida 1 kun bo'lishi kerak" }), duration: z.string().min(1, { message: "Kamida 1 kun bo'lishi kerak" }),
badges: z.array(z.number()).optional(), badges: z.array(z.number()).optional(),
tarif: z tarif: z
.array( .array(
@@ -122,7 +74,7 @@ export const TourformSchema = z.object({
.min(0, { message: "Narx 0 dan kichik bo'lishi mumkin emas" }), .min(0, { message: "Narx 0 dan kichik bo'lishi mumkin emas" }),
}), }),
) )
.optional(), .min(1, { message: "Majburiy maydon" }),
banner: z.any().nullable(), banner: z.any().nullable(),
images: z images: z
.array( .array(
@@ -158,7 +110,7 @@ export const TourformSchema = z.object({
desc_ru: z.string().min(1, "Majburiy maydon"), desc_ru: z.string().min(1, "Majburiy maydon"),
}), }),
) )
.min(1, { message: "Kamida bitta xizmat kiriting." }), .optional(),
ticket_itinerary: z ticket_itinerary: z
.array( .array(
@@ -184,7 +136,7 @@ export const TourformSchema = z.object({
), ),
}), }),
) )
.min(1, { message: "Kamida bitta xizmat kiriting." }), .optional(),
extra_service: z extra_service: z
.array( .array(

View File

@@ -4,7 +4,6 @@ import {
createTours, createTours,
getAllAmenities, getAllAmenities,
hotelBadge, hotelBadge,
hotelTarif,
hotelTransport, hotelTransport,
updateTours, updateTours,
} from "@/pages/tours/lib/api"; } from "@/pages/tours/lib/api";
@@ -55,7 +54,7 @@ const StepOne = ({
isEditMode: boolean; isEditMode: boolean;
}) => { }) => {
const [displayPrice, setDisplayPrice] = useState(""); const [displayPrice, setDisplayPrice] = useState("");
const [tarifdisplayPrice, setTarifDisplayPrice] = useState<string[]>([]); // const [tarifdisplayPrice, setTarifDisplayPrice] = useState<string[]>([]);
const [transportPrices, setTransportPrices] = useState<string[]>([]); const [transportPrices, setTransportPrices] = useState<string[]>([]);
const { t } = useTranslation(); const { t } = useTranslation();
@@ -70,7 +69,7 @@ const StepOne = ({
hotel_services: [], hotel_services: [],
price: 0, price: 0,
departure: "", departure: "",
tarif: [], // tarif: [],
transport: [], transport: [],
departure_ru: "", departure_ru: "",
destination: "", destination: "",
@@ -79,21 +78,19 @@ const StepOne = ({
location_name_ru: "", location_name_ru: "",
departureDateTime: { departureDateTime: {
date: undefined, date: undefined,
time: "",
}, },
amenities: [], amenities: [],
hotel_meals: [], hotel_meals: [],
travelDateTime: { travelDateTime: {
date: undefined, date: undefined,
time: "",
}, },
passenger_count: 1, passenger_count: "1",
min_person: 1, min_person: "1",
max_person: 1, max_person: "1",
extra_service: [], extra_service: [],
paid_extra_service: [], paid_extra_service: [],
languages: "", languages: "",
duration: 1, duration: "1",
badges: [], badges: [],
images: [], images: [],
hotel_info: "", hotel_info: "",
@@ -117,7 +114,6 @@ const StepOne = ({
const d = new Date(tour.departure_time); const d = new Date(tour.departure_time);
departureDateTime = { departureDateTime = {
date: d, date: d,
time: d.toTimeString().slice(0, 8),
}; };
} }
@@ -127,7 +123,6 @@ const StepOne = ({
const d = new Date(tour.travel_time); const d = new Date(tour.travel_time);
travelDateTime = { travelDateTime = {
date: d, date: d,
time: d.toTimeString().slice(0, 8),
}; };
} }
@@ -140,20 +135,20 @@ const StepOne = ({
setTransportPrices(transports.map((t) => formatPrice(t.price ?? 0))); setTransportPrices(transports.map((t) => formatPrice(t.price ?? 0)));
// 🔹 Tarif // 🔹 Tarif
const tariffs = // const tariffs =
tour.tariff?.map((t) => ({ // tour.tariff?.map((t) => ({
tariff: t.tariff ?? 0, // tariff: t.tariff ?? 0,
price: t.price ?? 0, // price: t.price ?? 0,
})) ?? []; // })) ?? [];
setTarifDisplayPrice(tariffs.map((t) => formatPrice(t.price ?? 0))); // setTarifDisplayPrice(tariffs.map((t) => formatPrice(t.price ?? 0)));
form.reset({ form.reset({
title: tour.title_uz ?? "", title: tour.title_uz ?? "",
title_ru: tour.title_ru ?? "", title_ru: tour.title_ru ?? "",
price: tour.price ?? 0, price: tour.price ?? 0,
passenger_count: tour.passenger_count ?? 1, passenger_count: String(tour.passenger_count) ?? "1",
min_person: tour.min_person ?? 1, min_person: String(tour.min_person) ?? "1",
max_person: tour.max_person ?? 1, max_person: String(tour.max_person) ?? "1",
departure: tour.departure_uz ?? "", departure: tour.departure_uz ?? "",
departure_ru: tour.departure_ru ?? "", departure_ru: tour.departure_ru ?? "",
destination: tour.destination_uz ?? "", destination: tour.destination_uz ?? "",
@@ -165,7 +160,7 @@ const StepOne = ({
hotel_meals_info: tour.hotel_meals_uz ?? "", hotel_meals_info: tour.hotel_meals_uz ?? "",
hotel_meals_info_ru: tour.hotel_meals_ru ?? "", hotel_meals_info_ru: tour.hotel_meals_ru ?? "",
languages: tour.languages ?? "", languages: tour.languages ?? "",
duration: tour.duration_days ?? 1, duration: String(tour.duration_days) ?? "1",
visa_required: tour.visa_required ? "yes" : "no", visa_required: tour.visa_required ? "yes" : "no",
badges: tour.badge ?? [], badges: tour.badge ?? [],
departureDateTime, departureDateTime,
@@ -190,7 +185,7 @@ const StepOne = ({
desc_ru: meal.desc_ru ?? "", desc_ru: meal.desc_ru ?? "",
})) ?? [], })) ?? [],
transport: transports, transport: transports,
tarif: tariffs, // tarif: tariffs,
ticket_itinerary: ticket_itinerary:
tour.ticket_itinerary?.map((item) => ({ tour.ticket_itinerary?.map((item) => ({
id: item.id, id: item.id,
@@ -296,11 +291,22 @@ const StepOne = ({
String(value.travelDateTime.date.toISOString()), String(value.travelDateTime.date.toISOString()),
); );
formData.append("passenger_count", String(value.passenger_count)); formData.append("passenger_count", String(value.passenger_count));
formData.append("languages", value.languages); if (value.languages) {
formData.append("hotel_info", value.hotel_info); formData.append("languages", value.languages);
formData.append("hotel_info_ru", value.hotel_info_ru); }
formData.append("hotel_meals", value.hotel_meals_info); if (value.hotel_info) {
formData.append("hotel_meals_ru", value.hotel_meals_info_ru); formData.append("hotel_info", value.hotel_info);
}
if (value.hotel_info_ru) {
formData.append("hotel_info_ru", value.hotel_info_ru);
}
if (value.hotel_meals_info) {
formData.append("hotel_meals", value.hotel_meals_info);
}
if (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"));
@@ -310,10 +316,10 @@ const StepOne = ({
console.log(value.banner, "value.banner"); console.log(value.banner, "value.banner");
// Tarif va transport // 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));
@@ -417,23 +423,23 @@ const StepOne = ({
}); });
} }
}); });
if (value.hotel_meals) {
value.hotel_meals.forEach((e, i) => { value.hotel_meals.forEach((e, i) => {
if (e.id && typeof e.image === "string") { if (e.id && typeof e.image === "string") {
// Mavjud meal (o'zgartirilmagan) // Mavjud meal (o'zgartirilmagan)
formData.append(`ticket_hotel_meals_ids`, String(e.id)); formData.append(`ticket_hotel_meals_ids`, String(e.id));
} else { } else {
// Yangi meal yoki o'zgartirilgan meal // Yangi meal yoki o'zgartirilgan meal
if (e.image instanceof File) { if (e.image instanceof File) {
formData.append(`ticket_hotel_meals[${i}]image`, e.image); 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);
} }
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 // Extra services
value.extra_service && value.extra_service &&
value.extra_service.forEach((e, i) => { value.extra_service.forEach((e, i) => {
@@ -468,16 +474,18 @@ const StepOne = ({
queryFn: () => getAllAmenities({ page: 1, page_size: 10 }), queryFn: () => getAllAmenities({ page: 1, page_size: 10 }),
}); });
const { data: tariff } = useQuery({ // const { data: tariff } = useQuery({
queryKey: ["all_tarif"], // queryKey: ["all_tarif"],
queryFn: () => hotelTarif({ page: 1, page_size: 10 }), // queryFn: () => hotelTarif({ page: 1, page_size: 10 }),
}); // });
const { data: transport } = useQuery({ const { data: transport } = useQuery({
queryKey: ["all_transport"], queryKey: ["all_transport"],
queryFn: () => hotelTransport({ page: 1, page_size: 10 }), queryFn: () => hotelTransport({ page: 1, page_size: 10 }),
}); });
console.log(form.formState.errors);
return ( return (
<Form {...form}> <Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6"> <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
@@ -561,7 +569,7 @@ const StepOne = ({
placeholder="1" placeholder="1"
{...field} {...field}
onChange={(e) => onChange={(e) =>
form.setValue("min_person", Number(e.target.value)) form.setValue("min_person", e.target.value)
} }
className="h-12 !text-md className="h-12 !text-md
[&::-webkit-inner-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none
@@ -588,7 +596,7 @@ const StepOne = ({
placeholder="1" placeholder="1"
{...field} {...field}
onChange={(e) => onChange={(e) =>
form.setValue("max_person", Number(e.target.value)) form.setValue("max_person", e.target.value)
} }
className="h-12 !text-md className="h-12 !text-md
[&::-webkit-inner-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none
@@ -724,12 +732,12 @@ const StepOne = ({
placeholder="1" placeholder="1"
{...field} {...field}
onChange={(e) => onChange={(e) =>
form.setValue("passenger_count", Number(e.target.value)) form.setValue("passenger_count", e.target.value)
} }
className="h-12 !text-md className="h-12 !text-md
[&::-webkit-inner-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none
[&::-webkit-outer-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none
[appearance:textfield]" [appearance:textfield]"
/> />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@@ -783,7 +791,7 @@ const StepOne = ({
</FormItem> </FormItem>
)} )}
/> />
<FormField {/* <FormField
control={form.control} control={form.control}
name="departureDateTime.time" name="departureDateTime.time"
render={() => ( render={() => (
@@ -805,53 +813,51 @@ const StepOne = ({
</div> </div>
</FormItem> </FormItem>
)} )}
/> /> */}
</div> <FormField
control={form.control}
<FormField name="travelDateTime.date"
control={form.control} render={() => (
name="travelDateTime.date" <FormItem>
render={() => ( <div className="grid grid-cols-1 gap-4 max-lg:grid-cols-1">
<FormItem> <div className="flex flex-col gap-3">
<div className="grid grid-cols-2 gap-4 max-lg:grid-cols-1"> <Label htmlFor="date-picker" className="px-1 text-md">
<div className="flex flex-col gap-3"> {t("Qaytish sanasi")}
<Label htmlFor="date-picker" className="px-1 text-md"> </Label>
{t("Qaytish sanasi")} <Popover
</Label> open={openDateTravel}
<Popover onOpenChange={setOpenDateTravel}
open={openDateTravel}
onOpenChange={setOpenDateTravel}
>
<PopoverTrigger asChild>
<Button
variant="outline"
id="date-picker"
className="w-full justify-between font-normal h-12"
>
{selectedDateTravel
? selectedDateTravel.toLocaleDateString()
: t("Sana tanlang")}
<ChevronDownIcon />
</Button>
</PopoverTrigger>
<PopoverContent
className="w-auto overflow-hidden p-0"
align="start"
> >
<Calendar <PopoverTrigger asChild>
mode="single" <Button
selected={selectedDateTravel} variant="outline"
captionLayout="dropdown" id="date-picker"
onSelect={(date) => { className="w-full justify-between font-normal h-12"
setValue("travelDateTime.date", date!); >
setOpenDateTravel(false); {selectedDateTravel
}} ? selectedDateTravel.toLocaleDateString()
toYear={new Date().getFullYear() + 100} : t("Sana tanlang")}
/> <ChevronDownIcon />
</PopoverContent> </Button>
</Popover> </PopoverTrigger>
</div> <PopoverContent
<div className="flex flex-col gap-3"> className="w-auto overflow-hidden p-0"
align="start"
>
<Calendar
mode="single"
selected={selectedDateTravel}
captionLayout="dropdown"
onSelect={(date) => {
setValue("travelDateTime.date", date!);
setOpenDateTravel(false);
}}
toYear={new Date().getFullYear() + 100}
/>
</PopoverContent>
</Popover>
</div>
{/* <div className="flex flex-col gap-3">
<Label htmlFor="time-picker" className="px-1 text-md"> <Label htmlFor="time-picker" className="px-1 text-md">
{t("Qaytish vaqti")} {t("Qaytish vaqti")}
</Label> </Label>
@@ -865,31 +871,15 @@ const StepOne = ({
} }
className="bg-background !h-12 appearance-none [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none" className="bg-background !h-12 appearance-none [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none"
/> />
</div> */}
</div> </div>
</div>
<FormMessage />
</FormItem>
)}
/>
<div className="grid grid-cols-2 max-lg:grid-cols-1 gap-6">
<FormField
control={form.control}
name="languages"
render={({ field }) => (
<FormItem>
<Label className="text-md">{t("Tillar")}</Label>
<FormControl>
<Input
placeholder={t("Har bir tilni vergul (,) bilan ajrating")}
{...field}
className="h-12 !text-md"
/>
</FormControl>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
)} )}
/> />
</div>
<div className="grid grid-cols-2 max-lg:grid-cols-1 gap-6">
<FormField <FormField
control={form.control} control={form.control}
name="duration" name="duration"
@@ -901,13 +891,29 @@ const StepOne = ({
type="number" type="number"
inputMode="numeric" inputMode="numeric"
placeholder="1" placeholder="1"
onChange={(e) => field.onChange(Number(e.target.value))} {...field}
value={field.value} onChange={(e) => form.setValue("duration", e.target.value)}
onBlur={field.onBlur}
className="h-12 !text-md className="h-12 !text-md
[&::-webkit-inner-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none
[&::-webkit-outer-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none
[appearance:textfield]" [appearance:textfield]"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="languages"
render={({ field }) => (
<FormItem>
<Label className="text-md">{t("Tillar")}</Label>
<FormControl>
<Input
placeholder={t("Har bir tilni vergul (,) bilan ajrating")}
{...field}
className="h-12 !text-md"
/> />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@@ -1127,7 +1133,7 @@ const StepOne = ({
)} )}
/> />
<FormField {/* <FormField
control={form.control} control={form.control}
name="tarif" name="tarif"
render={() => ( render={() => (
@@ -1258,7 +1264,7 @@ const StepOne = ({
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
)} )}
/> /> */}
</div> </div>
<div className="grid grid-cols-2 max-lg:grid-cols-1 gap-6 items-end"> <div className="grid grid-cols-2 max-lg:grid-cols-1 gap-6 items-end">
@@ -1287,7 +1293,7 @@ const StepOne = ({
{transportItem?.name || {transportItem?.name ||
`Transport #${tr.transport}`} `Transport #${tr.transport}`}
</Badge> </Badge>
<Input {/* <Input
type="text" type="text"
inputMode="numeric" inputMode="numeric"
placeholder="1 500 000" placeholder="1 500 000"
@@ -1310,7 +1316,7 @@ const StepOne = ({
setTransportPrices(updatedPrices); setTransportPrices(updatedPrices);
}} }}
className="h-12 !text-md" className="h-12 !text-md"
/> /> */}
</div> </div>
<button <button
type="button" type="button"
@@ -1966,7 +1972,7 @@ const StepOne = ({
const file = imgInput.files?.[0]; const file = imgInput.files?.[0];
if (file && titleInput.value && descInput.value) { if (file && titleInput.value && descInput.value) {
const current = form.getValues("hotel_meals"); const current = form.getValues("hotel_meals") ?? [];
form.setValue("hotel_meals", [ form.setValue("hotel_meals", [
...current, ...current,
{ {