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>
</div>
</div>
<div className="flex items-center gap-3 p-3 bg-gray-700 rounded-lg">
<Phone className="w-5 h-5 text-green-400" />
<div>
<p className="text-sm text-gray-400">
{t("Phone Number")}
</p>
<p className="text-gray-100">
{data && formatPhone(data?.user.phone)}
</p>
{data?.user.phone && (
<div className="flex items-center gap-3 p-3 bg-gray-700 rounded-lg">
<Phone className="w-5 h-5 text-green-400" />
<div>
<p className="text-sm text-gray-400">
{t("Phone Number")}
</p>
<p className="text-gray-100">
{data && formatPhone(data?.user.phone)}
</p>
</div>
</div>
</div>
)}
{data?.user.email && (
<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, {
message: "Sarlavha kamida 2 ta belgidan iborat bo'lishi kerak",
}),
hotel_info: z.string().min(2, {
message: "Sarlavha kamida 2 ta belgidan iborat bo'lishi kerak",
}),
hotel_info_ru: z.string(),
hotel_meals_info: z.string().min(2, {
message: "Sarlavha kamida 2 ta belgidan iborat bo'lishi kerak",
}),
hotel_meals_info_ru: z.string(),
hotel_info: z.string().optional(),
hotel_info_ru: z.string().optional(),
hotel_meals_info: z.string().optional(),
hotel_meals_info_ru: z.string().optional(),
price: z.number().min(1000, {
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.",
}),
min_person: z.number().min(1, {
min_person: z.string().min(1, {
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.",
}),
departure: z.string().min(2, {
@@ -52,56 +48,12 @@ export const TourformSchema = z.object({
}),
departureDateTime: z.object({
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({
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" }),
duration: z.number().min(1, { message: "Kamida 1 kun bo'lishi kerak" }),
languages: z.string().optional(),
duration: z.string().min(1, { message: "Kamida 1 kun bo'lishi kerak" }),
badges: z.array(z.number()).optional(),
tarif: z
.array(
@@ -122,7 +74,7 @@ export const TourformSchema = z.object({
.min(0, { message: "Narx 0 dan kichik bo'lishi mumkin emas" }),
}),
)
.optional(),
.min(1, { message: "Majburiy maydon" }),
banner: z.any().nullable(),
images: z
.array(
@@ -158,7 +110,7 @@ export const TourformSchema = z.object({
desc_ru: z.string().min(1, "Majburiy maydon"),
}),
)
.min(1, { message: "Kamida bitta xizmat kiriting." }),
.optional(),
ticket_itinerary: z
.array(
@@ -184,7 +136,7 @@ export const TourformSchema = z.object({
),
}),
)
.min(1, { message: "Kamida bitta xizmat kiriting." }),
.optional(),
extra_service: z
.array(

View File

@@ -4,7 +4,6 @@ import {
createTours,
getAllAmenities,
hotelBadge,
hotelTarif,
hotelTransport,
updateTours,
} from "@/pages/tours/lib/api";
@@ -55,7 +54,7 @@ const StepOne = ({
isEditMode: boolean;
}) => {
const [displayPrice, setDisplayPrice] = useState("");
const [tarifdisplayPrice, setTarifDisplayPrice] = useState<string[]>([]);
// const [tarifdisplayPrice, setTarifDisplayPrice] = useState<string[]>([]);
const [transportPrices, setTransportPrices] = useState<string[]>([]);
const { t } = useTranslation();
@@ -70,7 +69,7 @@ const StepOne = ({
hotel_services: [],
price: 0,
departure: "",
tarif: [],
// tarif: [],
transport: [],
departure_ru: "",
destination: "",
@@ -79,21 +78,19 @@ const StepOne = ({
location_name_ru: "",
departureDateTime: {
date: undefined,
time: "",
},
amenities: [],
hotel_meals: [],
travelDateTime: {
date: undefined,
time: "",
},
passenger_count: 1,
min_person: 1,
max_person: 1,
passenger_count: "1",
min_person: "1",
max_person: "1",
extra_service: [],
paid_extra_service: [],
languages: "",
duration: 1,
duration: "1",
badges: [],
images: [],
hotel_info: "",
@@ -117,7 +114,6 @@ const StepOne = ({
const d = new Date(tour.departure_time);
departureDateTime = {
date: d,
time: d.toTimeString().slice(0, 8),
};
}
@@ -127,7 +123,6 @@ const StepOne = ({
const d = new Date(tour.travel_time);
travelDateTime = {
date: d,
time: d.toTimeString().slice(0, 8),
};
}
@@ -140,20 +135,20 @@ const StepOne = ({
setTransportPrices(transports.map((t) => formatPrice(t.price ?? 0)));
// 🔹 Tarif
const tariffs =
tour.tariff?.map((t) => ({
tariff: t.tariff ?? 0,
price: t.price ?? 0,
})) ?? [];
setTarifDisplayPrice(tariffs.map((t) => formatPrice(t.price ?? 0)));
// const tariffs =
// tour.tariff?.map((t) => ({
// tariff: t.tariff ?? 0,
// price: t.price ?? 0,
// })) ?? [];
// setTarifDisplayPrice(tariffs.map((t) => formatPrice(t.price ?? 0)));
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,
passenger_count: String(tour.passenger_count) ?? "1",
min_person: String(tour.min_person) ?? "1",
max_person: String(tour.max_person) ?? "1",
departure: tour.departure_uz ?? "",
departure_ru: tour.departure_ru ?? "",
destination: tour.destination_uz ?? "",
@@ -165,7 +160,7 @@ const StepOne = ({
hotel_meals_info: tour.hotel_meals_uz ?? "",
hotel_meals_info_ru: tour.hotel_meals_ru ?? "",
languages: tour.languages ?? "",
duration: tour.duration_days ?? 1,
duration: String(tour.duration_days) ?? "1",
visa_required: tour.visa_required ? "yes" : "no",
badges: tour.badge ?? [],
departureDateTime,
@@ -190,7 +185,7 @@ const StepOne = ({
desc_ru: meal.desc_ru ?? "",
})) ?? [],
transport: transports,
tarif: tariffs,
// tarif: tariffs,
ticket_itinerary:
tour.ticket_itinerary?.map((item) => ({
id: item.id,
@@ -296,11 +291,22 @@ const StepOne = ({
String(value.travelDateTime.date.toISOString()),
);
formData.append("passenger_count", String(value.passenger_count));
formData.append("languages", value.languages);
formData.append("hotel_info", value.hotel_info);
formData.append("hotel_info_ru", value.hotel_info_ru);
formData.append("hotel_meals", value.hotel_meals_info);
formData.append("hotel_meals_ru", value.hotel_meals_info_ru);
if (value.languages) {
formData.append("languages", value.languages);
}
if (value.hotel_info) {
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("rating", String("0.0"));
@@ -310,10 +316,10 @@ const StepOne = ({
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.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));
@@ -417,23 +423,23 @@ 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);
if (value.hotel_meals) {
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);
}
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) => {
@@ -468,16 +474,18 @@ const StepOne = ({
queryFn: () => getAllAmenities({ page: 1, page_size: 10 }),
});
const { data: tariff } = useQuery({
queryKey: ["all_tarif"],
queryFn: () => hotelTarif({ page: 1, page_size: 10 }),
});
// const { data: tariff } = useQuery({
// queryKey: ["all_tarif"],
// queryFn: () => hotelTarif({ page: 1, page_size: 10 }),
// });
const { data: transport } = useQuery({
queryKey: ["all_transport"],
queryFn: () => hotelTransport({ page: 1, page_size: 10 }),
});
console.log(form.formState.errors);
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
@@ -561,7 +569,7 @@ const StepOne = ({
placeholder="1"
{...field}
onChange={(e) =>
form.setValue("min_person", Number(e.target.value))
form.setValue("min_person", e.target.value)
}
className="h-12 !text-md
[&::-webkit-inner-spin-button]:appearance-none
@@ -588,7 +596,7 @@ const StepOne = ({
placeholder="1"
{...field}
onChange={(e) =>
form.setValue("max_person", Number(e.target.value))
form.setValue("max_person", e.target.value)
}
className="h-12 !text-md
[&::-webkit-inner-spin-button]:appearance-none
@@ -724,12 +732,12 @@ const StepOne = ({
placeholder="1"
{...field}
onChange={(e) =>
form.setValue("passenger_count", Number(e.target.value))
form.setValue("passenger_count", e.target.value)
}
className="h-12 !text-md
[&::-webkit-inner-spin-button]:appearance-none
[&::-webkit-outer-spin-button]:appearance-none
[appearance:textfield]"
[&::-webkit-inner-spin-button]:appearance-none
[&::-webkit-outer-spin-button]:appearance-none
[appearance:textfield]"
/>
</FormControl>
<FormMessage />
@@ -783,7 +791,7 @@ const StepOne = ({
</FormItem>
)}
/>
<FormField
{/* <FormField
control={form.control}
name="departureDateTime.time"
render={() => (
@@ -805,53 +813,51 @@ const StepOne = ({
</div>
</FormItem>
)}
/>
</div>
<FormField
control={form.control}
name="travelDateTime.date"
render={() => (
<FormItem>
<div className="grid grid-cols-2 gap-4 max-lg:grid-cols-1">
<div className="flex flex-col gap-3">
<Label htmlFor="date-picker" className="px-1 text-md">
{t("Qaytish sanasi")}
</Label>
<Popover
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"
/> */}
<FormField
control={form.control}
name="travelDateTime.date"
render={() => (
<FormItem>
<div className="grid grid-cols-1 gap-4 max-lg:grid-cols-1">
<div className="flex flex-col gap-3">
<Label htmlFor="date-picker" className="px-1 text-md">
{t("Qaytish sanasi")}
</Label>
<Popover
open={openDateTravel}
onOpenChange={setOpenDateTravel}
>
<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">
<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
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">
{t("Qaytish vaqti")}
</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"
/>
</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 />
</FormItem>
)}
/>
</div>
<div className="grid grid-cols-2 max-lg:grid-cols-1 gap-6">
<FormField
control={form.control}
name="duration"
@@ -901,13 +891,29 @@ const StepOne = ({
type="number"
inputMode="numeric"
placeholder="1"
onChange={(e) => field.onChange(Number(e.target.value))}
value={field.value}
onBlur={field.onBlur}
{...field}
onChange={(e) => form.setValue("duration", e.target.value)}
className="h-12 !text-md
[&::-webkit-inner-spin-button]:appearance-none
[&::-webkit-outer-spin-button]:appearance-none
[appearance:textfield]"
[&::-webkit-inner-spin-button]:appearance-none
[&::-webkit-outer-spin-button]:appearance-none
[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>
<FormMessage />
@@ -1127,7 +1133,7 @@ const StepOne = ({
)}
/>
<FormField
{/* <FormField
control={form.control}
name="tarif"
render={() => (
@@ -1258,7 +1264,7 @@ const StepOne = ({
<FormMessage />
</FormItem>
)}
/>
/> */}
</div>
<div className="grid grid-cols-2 max-lg:grid-cols-1 gap-6 items-end">
@@ -1287,7 +1293,7 @@ const StepOne = ({
{transportItem?.name ||
`Transport #${tr.transport}`}
</Badge>
<Input
{/* <Input
type="text"
inputMode="numeric"
placeholder="1 500 000"
@@ -1310,7 +1316,7 @@ const StepOne = ({
setTransportPrices(updatedPrices);
}}
className="h-12 !text-md"
/>
/> */}
</div>
<button
type="button"
@@ -1966,7 +1972,7 @@ const StepOne = ({
const file = imgInput.files?.[0];
if (file && titleInput.value && descInput.value) {
const current = form.getValues("hotel_meals");
const current = form.getValues("hotel_meals") ?? [];
form.setValue("hotel_meals", [
...current,
{