Продайте телефон за сутки

Партнёры нашей площадки выкупят без долгих переписок и торга

Подробнее
Продайте телефон за сутки

Рекомендации для вас

"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>   ); };

"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 ₽

г Калининград

"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>   ); };

"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> ); };

22 ₽