
продам кофту
23 000 ₽

продам кофту
23 000 ₽

Пиджак lime
2 000 ₽

Платье 2
2 000 ₽

Тряпка на тело
600 ₽

вфыфвыф
2 ₽
Вологодская обл
!["use client"; import { ReviewColumns } from "@/entities/review"; import { ImageInput } from "@/features/image-input"; import { AdvertisementStatus, PropertyTypeEnum, type ImageModel } from "@/shared/api"; import { serverErrorToast, useGetProductsDynamicById, usePutProductsDynamicUpdateAdvertisement, usePutProductsUpdateStatus, } from "@/shared/lib"; import { Button, Headline, Input, RadioGroup, Select, Textarea } from "@/shared/ui"; import { useForm } from "@tanstack/react-form"; import { useRouter } from "next/navigation"; import { FC, Fragment, useEffect, useState } from "react"; import { toast } from "react-toastify"; import { MapInput } from "./map-input"; import { Dispatch, SetStateAction } from "react"; interface PhotoItem { id: number; url: string; isNew?: boolean; } export interface ImageInputProps { photos: PhotoItem[]; setPhotos: Dispatch<SetStateAction<PhotoItem[]>>; } interface DynamicProperty { propertyId?: number; propertyType: PropertyTypeEnum; propertyValue: unknown; enumValues?: Array<{ id: number; value: string }>; name?: string; } interface DynamicPropertyGroup { name?: string; properties?: DynamicProperty[]; } interface ProductDynamic { name?: string; description?: string; price?: number; images?: ImageModel[]; properties?: DynamicPropertyGroup[]; } export interface EditProductFormProps { productId: number; categoryId: number; } export const EditProductForm: FC<EditProductFormProps> = ({ productId }) => { const productsUpdateStatusMutation = usePutProductsUpdateStatus(); const updateAdvertisementMutation = usePutProductsDynamicUpdateAdvertisement(); const productDynamicQuery = useGetProductsDynamicById({ path: { id: productId } }); const router = useRouter(); const [photos, setPhotos] = useState<PhotoItem[]>([]); const [mapAddress, setMapAddress] = useState(""); const [advertisementProperties, setAdvertisementProperties] = useState<Record<number, unknown>>({}); const productDynamic = productDynamicQuery.data?.payload; const advertisementFields = productDynamic?.properties; useEffect(() => { if (productDynamic?.images) { const initialPhotos = productDynamic.images.map((img) => ({ id: img.id, url: img.link, isNew: false })); setPhotos(initialPhotos); } }, [productDynamic?.images, productId]); interface FormValues { name: string; description: string; price: string; } const defaultValues: FormValues = { name: productDynamic?.name ?? "", description: productDynamic?.description ?? "", price: productDynamic?.price?.toString() ?? "", }; const form = useForm({ defaultValues, onSubmit: async ({ value }) => { const { name, price, description } = value; try { await updateAdvertisementMutation.mutateAsync({ body: { productId, name, price: price ? +price : null, description, images: photos.map((photo) => ({ id: photo.isNew ? undefined : photo.id, link: photo.url, })), inputValues: Object.entries(advertisementProperties).map( ([propertyIdStr, propertyValue]) => ({ propertyId: +propertyIdStr, propertyValue, }), ), address: mapAddress, }, }); await productsUpdateStatusMutation.mutateAsync({ body: { productId, statusId: AdvertisementStatus.ON_MODERATION }, }); toast("Объявление отправлено на модерацию", { type: "success" }); router.push(`/item/${productId}`); } catch { serverErrorToast(); } }, }); return ( <form className="flex flex-col gap-8" onSubmit={(e) => { e.preventDefault(); e.stopPropagation(); form.handleSubmit(); }} > <ReviewColumns variant="md" label="Название объявления"> <form.Field name="name" children={(field) => ( <Input size="md" name={field.name} id={field.name} value={field.state.value} onBlur={field.handleBlur} onChange={(e) => field.handleChange(e.target.value)} /> )} /> <p className="text-color-gray-medium mt-2.5 text-base"> Например, «Комбинезон зимний Reima 104 см» или «Apple Watch 3 стальной ремешок» </p> </ReviewColumns> <ReviewColumns label="Описание объявления" variant="ml"> <div className="flex flex-col gap-8"> <div> <form.Field name="description" children={(field) => ( <Textarea id={field.name} name={field.name} value={field.state.value} onBlur={field.handleBlur} onChange={(e) => field.handleChange(e.target.value)} rows={8} /> )} /> <p className="text-color-gray-medium mt-2.5 text-base"> Не указывайте в описании телефон и e-mail — для этого есть отдельные поля </p> </div> </div> </ReviewColumns> <ReviewColumns label="Фотографии" variant="lg"> <ImageInput photos={photos} setPhotos={setPhotos} /> </ReviewColumns> {advertisementFields?.map((dynamicPropertyGroup, groupIdx) => { const groupName = (dynamicPropertyGroup.properties?.length ?? 0) > 1 ? dynamicPropertyGroup.name : void 0; return ( <Fragment key={groupIdx}> {groupName && <Headline title={groupName} size="mm" />} {dynamicPropertyGroup.properties?.map((dynamicProperty) => { const renderControl = () => { switch (dynamicProperty.propertyType) { case PropertyTypeEnum.ENUM: return ( <Select defaultValue={`${dynamicProperty.propertyValue}`} onValueChange={(value) => { setAdvertisementProperties((prev) => ({ ...prev, [dynamicProperty.propertyId!]: +value, })); }} items={ dynamicProperty.enumValues?.map((it) => ({ label: it.value, value: `${it.id}`, })) ?? [] } /> ); case PropertyTypeEnum.BOOL: return ( <RadioGroup name="" items={[ { label: "Да", value: "true" }, { label: "Нет", value: "false" }, ]} defaultValue={(dynamicProperty.propertyValue as boolean) ? "true" : "false"} onValueChange={(value) => { setAdvertisementProperties((prev) => ({ ...prev, [dynamicProperty.propertyId!]: value === "true", })); }} /> ); case PropertyTypeEnum.DATE: return ( <Input size="lg" type="date" defaultValue={(dynamicProperty.propertyValue as string | null)?.substring( 0, 10, )} onChange={(e) => { const { valueAsDate } = e.currentTarget; setAdvertisementProperties((prev) => ({ ...prev, [dynamicProperty.propertyId!]: valueAsDate?.toISOString(), })); }} /> ); case PropertyTypeEnum.DOUBLE: return ( <Input size="lg" type="number" step={0.01} defaultValue={String(dynamicProperty.propertyValue)} onChange={(e) => { const { valueAsNumber } = e.currentTarget; setAdvertisementProperties((prev) => ({ ...prev, [dynamicProperty.propertyId!]: valueAsNumber, })); }} /> ); case PropertyTypeEnum.INT32: case PropertyTypeEnum.INT64: return ( <Input size="lg" type="number" defaultValue={String(dynamicProperty.propertyValue)} onChange={(e) => { const { valueAsNumber } = e.currentTarget; setAdvertisementProperties((prev) => ({ ...prev, [dynamicProperty.propertyId!]: valueAsNumber, })); }} /> ); default: return ( <Input size="lg" defaultValue={String(dynamicProperty.propertyValue)} onChange={(e) => { const { value } = e.currentTarget; setAdvertisementProperties((prev) => ({ ...prev, [dynamicProperty.propertyId!]: value, })); }} /> ); } }; return ( <ReviewColumns label={dynamicProperty?.name ?? ""} variant="ml" key={dynamicProperty.propertyId + `${dynamicProperty.propertyValue}`} > <div className="flex flex-col gap-8"> <div> {renderControl()} </div> </div> </ReviewColumns> ); })} </Fragment> ); })} <Headline title="Место сделки" size="mm" /> <MapInput value={mapAddress} setValue={setMapAddress} /> <ReviewColumns variant="md" label="Цена"> <form.Field name="price" children={(field) => ( <Input autoComplete="off" size="md" name={field.name} id={field.name} value={field.state.value} onBlur={field.handleBlur} onChange={(e) => field.handleChange(e.target.value)} /> )} /> </ReviewColumns> <div className="flex gap-2"> <Button size="lg" variant="yellow" type="submit"> Сохранить изменения </Button> <Button size="lg" variant="gray"> Отменить и выйти </Button> </div> </form> ); };](/_next/image?url=https%3A%2F%2Fbloshinyj-rynok-api-dev.spaceapp.ru%2Fv1%2FfilesS3%2Fdownload%2F24cea8c0-b2dd-4750-9452-627f3338637b.png&w=3840&q=75)
"use client"; import { ReviewColumns } from "@/entities/review"; import { ImageInput } from "@/features/image-input"; import { AdvertisementStatus, PropertyTypeEnum, type ImageModel } from "@/shared/api"; import { serverErrorToast, useGetProductsDynamicById, usePutProductsDynamicUpdateAdvertisement, usePutProductsUpdateStatus, } from "@/shared/lib"; import { Button, Headline, Input, RadioGroup, Select, Textarea } from "@/shared/ui"; import { useForm } from "@tanstack/react-form"; import { useRouter } from "next/navigation"; import { FC, Fragment, useEffect, useState } from "react"; import { toast } from "react-toastify"; import { MapInput } from "./map-input"; import { Dispatch, SetStateAction } from "react"; interface PhotoItem { id: number; url: string; isNew?: boolean; } export interface ImageInputProps { photos: PhotoItem[]; setPhotos: Dispatch<SetStateAction<PhotoItem[]>>; } interface DynamicProperty { propertyId?: number; propertyType: PropertyTypeEnum; propertyValue: unknown; enumValues?: Array<{ id: number; value: string }>; name?: string; } interface DynamicPropertyGroup { name?: string; properties?: DynamicProperty[]; } interface ProductDynamic { name?: string; description?: string; price?: number; images?: ImageModel[]; properties?: DynamicPropertyGroup[]; } export interface EditProductFormProps { productId: number; categoryId: number; } export const EditProductForm: FC<EditProductFormProps> = ({ productId }) => { const productsUpdateStatusMutation = usePutProductsUpdateStatus(); const updateAdvertisementMutation = usePutProductsDynamicUpdateAdvertisement(); const productDynamicQuery = useGetProductsDynamicById({ path: { id: productId } }); const router = useRouter(); const [photos, setPhotos] = useState<PhotoItem[]>([]); const [mapAddress, setMapAddress] = useState(""); const [advertisementProperties, setAdvertisementProperties] = useState<Record<number, unknown>>({}); const productDynamic = productDynamicQuery.data?.payload; const advertisementFields = productDynamic?.properties; useEffect(() => { if (productDynamic?.images) { const initialPhotos = productDynamic.images.map((img) => ({ id: img.id, url: img.link, isNew: false })); setPhotos(initialPhotos); } }, [productDynamic?.images, productId]); interface FormValues { name: string; description: string; price: string; } const defaultValues: FormValues = { name: productDynamic?.name ?? "", description: productDynamic?.description ?? "", price: productDynamic?.price?.toString() ?? "", }; const form = useForm({ defaultValues, onSubmit: async ({ value }) => { const { name, price, description } = value; try { await updateAdvertisementMutation.mutateAsync({ body: { productId, name, price: price ? +price : null, description, images: photos.map((photo) => ({ id: photo.isNew ? undefined : photo.id, link: photo.url, })), inputValues: Object.entries(advertisementProperties).map( ([propertyIdStr, propertyValue]) => ({ propertyId: +propertyIdStr, propertyValue, }), ), address: mapAddress, }, }); await productsUpdateStatusMutation.mutateAsync({ body: { productId, statusId: AdvertisementStatus.ON_MODERATION }, }); toast("Объявление отправлено на модерацию", { type: "success" }); router.push(`/item/${productId}`); } catch { serverErrorToast(); } }, }); return ( <form className="flex flex-col gap-8" onSubmit={(e) => { e.preventDefault(); e.stopPropagation(); form.handleSubmit(); }} > <ReviewColumns variant="md" label="Название объявления"> <form.Field name="name" children={(field) => ( <Input size="md" name={field.name} id={field.name} value={field.state.value} onBlur={field.handleBlur} onChange={(e) => field.handleChange(e.target.value)} /> )} /> <p className="text-color-gray-medium mt-2.5 text-base"> Например, «Комбинезон зимний Reima 104 см» или «Apple Watch 3 стальной ремешок» </p> </ReviewColumns> <ReviewColumns label="Описание объявления" variant="ml"> <div className="flex flex-col gap-8"> <div> <form.Field name="description" children={(field) => ( <Textarea id={field.name} name={field.name} value={field.state.value} onBlur={field.handleBlur} onChange={(e) => field.handleChange(e.target.value)} rows={8} /> )} /> <p className="text-color-gray-medium mt-2.5 text-base"> Не указывайте в описании телефон и e-mail — для этого есть отдельные поля </p> </div> </div> </ReviewColumns> <ReviewColumns label="Фотографии" variant="lg"> <ImageInput photos={photos} setPhotos={setPhotos} /> </ReviewColumns> {advertisementFields?.map((dynamicPropertyGroup, groupIdx) => { const groupName = (dynamicPropertyGroup.properties?.length ?? 0) > 1 ? dynamicPropertyGroup.name : void 0; return ( <Fragment key={groupIdx}> {groupName && <Headline title={groupName} size="mm" />} {dynamicPropertyGroup.properties?.map((dynamicProperty) => { const renderControl = () => { switch (dynamicProperty.propertyType) { case PropertyTypeEnum.ENUM: return ( <Select defaultValue={`${dynamicProperty.propertyValue}`} onValueChange={(value) => { setAdvertisementProperties((prev) => ({ ...prev, [dynamicProperty.propertyId!]: +value, })); }} items={ dynamicProperty.enumValues?.map((it) => ({ label: it.value, value: `${it.id}`, })) ?? [] } /> ); case PropertyTypeEnum.BOOL: return ( <RadioGroup name="" items={[ { label: "Да", value: "true" }, { label: "Нет", value: "false" }, ]} defaultValue={(dynamicProperty.propertyValue as boolean) ? "true" : "false"} onValueChange={(value) => { setAdvertisementProperties((prev) => ({ ...prev, [dynamicProperty.propertyId!]: value === "true", })); }} /> ); case PropertyTypeEnum.DATE: return ( <Input size="lg" type="date" defaultValue={(dynamicProperty.propertyValue as string | null)?.substring( 0, 10, )} onChange={(e) => { const { valueAsDate } = e.currentTarget; setAdvertisementProperties((prev) => ({ ...prev, [dynamicProperty.propertyId!]: valueAsDate?.toISOString(), })); }} /> ); case PropertyTypeEnum.DOUBLE: return ( <Input size="lg" type="number" step={0.01} defaultValue={String(dynamicProperty.propertyValue)} onChange={(e) => { const { valueAsNumber } = e.currentTarget; setAdvertisementProperties((prev) => ({ ...prev, [dynamicProperty.propertyId!]: valueAsNumber, })); }} /> ); case PropertyTypeEnum.INT32: case PropertyTypeEnum.INT64: return ( <Input size="lg" type="number" defaultValue={String(dynamicProperty.propertyValue)} onChange={(e) => { const { valueAsNumber } = e.currentTarget; setAdvertisementProperties((prev) => ({ ...prev, [dynamicProperty.propertyId!]: valueAsNumber, })); }} /> ); default: return ( <Input size="lg" defaultValue={String(dynamicProperty.propertyValue)} onChange={(e) => { const { value } = e.currentTarget; setAdvertisementProperties((prev) => ({ ...prev, [dynamicProperty.propertyId!]: value, })); }} /> ); } }; return ( <ReviewColumns label={dynamicProperty?.name ?? ""} variant="ml" key={dynamicProperty.propertyId + `${dynamicProperty.propertyValue}`} > <div className="flex flex-col gap-8"> <div> {renderControl()} </div> </div> </ReviewColumns> ); })} </Fragment> ); })} <Headline title="Место сделки" size="mm" /> <MapInput value={mapAddress} setValue={setMapAddress} /> <ReviewColumns variant="md" label="Цена"> <form.Field name="price" children={(field) => ( <Input autoComplete="off" size="md" name={field.name} id={field.name} value={field.state.value} onBlur={field.handleBlur} onChange={(e) => field.handleChange(e.target.value)} /> )} /> </ReviewColumns> <div className="flex gap-2"> <Button size="lg" variant="yellow" type="submit"> Сохранить изменения </Button> <Button size="lg" variant="gray"> Отменить и выйти </Button> </div> </form> ); };
2 ₽
г Калининград

Тряпка на тело
2 ₽

выфвыффыв
22 ₽
Волгоградская обл

ddddd
22 ₽

dsa
2 222 ₽
ddd

Кроссовки New Balance 530
8 500 ₽
г Краснодар, ул Семеновская 140

кроссовки найк мужские
2 000 ₽

кроссовки мужские
5 000 ₽
г Саратов, ул Большая Казачья, д 18

Фрак
25 000 ₽
г Саратов, тер Саратов-63

Кеды Vans
3 500 ₽
г Саратов, ул Большая Казачья, д 17

Платье Zara
2 500 ₽
Саратовская обл, г Энгельс, ул Тельмана, д 13

fggag
15 000 ₽
г Ярославль

кроссовки Nike — самовывоз Казань
108 509 ₽
Казань, район Западный

платье на выход — самовывоз Нижний Новгород
26 452 ₽
Нижний Новгород, район Южный

джинсы Levi's (с коробкой)
246 872 ₽
Санкт-Петербург, район Южный