Latest version #12
| @@ -3,19 +3,63 @@ import { Create, useAutocomplete } from "@refinedev/mui"; | |||||||
| import { useForm } from "@refinedev/react-hook-form"; | import { useForm } from "@refinedev/react-hook-form"; | ||||||
| import { Controller } from "react-hook-form"; | import { Controller } from "react-hook-form"; | ||||||
| import { observer } from "mobx-react-lite"; | import { observer } from "mobx-react-lite"; | ||||||
| import { languageStore, META_LANGUAGE } from "../../store/LanguageStore"; | import { META_LANGUAGE } from "../../store/LanguageStore"; | ||||||
|  | import { LanguageSwitch } from "@/components/LanguageSwitch"; | ||||||
|  |  | ||||||
|  | import { useEffect, useState } from "react"; | ||||||
|  | import { EVERY_LANGUAGE, Languages, languageStore } from "@stores"; | ||||||
|  |  | ||||||
| export const CarrierCreate = observer(() => { | export const CarrierCreate = observer(() => { | ||||||
|   const { language } = languageStore; |   const { language, setLanguageAction } = languageStore; | ||||||
|   const { |   const { | ||||||
|     saveButtonProps, |     saveButtonProps, | ||||||
|     refineCore: { formLoading }, |     refineCore: { formLoading }, | ||||||
|     register, |     register, | ||||||
|     control, |     control, | ||||||
|  |     setValue, | ||||||
|  |     watch, | ||||||
|     formState: { errors }, |     formState: { errors }, | ||||||
|   } = useForm({ |   } = useForm({ | ||||||
|     refineCoreProps: META_LANGUAGE(language) |     refineCoreProps: META_LANGUAGE(language), | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|  |   const [carrierData, setCarrierData] = useState({ | ||||||
|  |     full_name: EVERY_LANGUAGE(""), | ||||||
|  |     short_name: EVERY_LANGUAGE(""), | ||||||
|  |     slogan: EVERY_LANGUAGE(""), | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   useEffect(() => { | ||||||
|  |     setValue("full_name", carrierData.full_name[language]); | ||||||
|  |     setValue("short_name", carrierData.short_name[language]); | ||||||
|  |     setValue("slogan", carrierData.slogan[language]); | ||||||
|  |   }, [carrierData, language, setValue]); | ||||||
|  |  | ||||||
|  |   function updateTranslations(update: boolean = true) { | ||||||
|  |     const newCarrierData = { | ||||||
|  |       ...carrierData, | ||||||
|  |       full_name: { | ||||||
|  |         ...carrierData.full_name, | ||||||
|  |         [language]: watch("full_name") ?? "", | ||||||
|  |       }, | ||||||
|  |       short_name: { | ||||||
|  |         ...carrierData.short_name, | ||||||
|  |         [language]: watch("short_name") ?? "", | ||||||
|  |       }, | ||||||
|  |       slogan: { | ||||||
|  |         ...carrierData.slogan, | ||||||
|  |         [language]: watch("slogan") ?? "", | ||||||
|  |       }, | ||||||
|  |     }; | ||||||
|  |     if (update) setCarrierData(newCarrierData); | ||||||
|  |     return newCarrierData; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   const handleLanguageChange = (lang: Languages) => { | ||||||
|  |     updateTranslations(); | ||||||
|  |     setLanguageAction(lang); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|   const { autocompleteProps: cityAutocompleteProps } = useAutocomplete({ |   const { autocompleteProps: cityAutocompleteProps } = useAutocomplete({ | ||||||
|     resource: "city", |     resource: "city", | ||||||
|     onSearch: (value) => [ |     onSearch: (value) => [ | ||||||
| @@ -45,6 +89,7 @@ export const CarrierCreate = observer(() => { | |||||||
|         sx={{ display: "flex", flexDirection: "column" }} |         sx={{ display: "flex", flexDirection: "column" }} | ||||||
|         autoComplete="off" |         autoComplete="off" | ||||||
|       > |       > | ||||||
|  |         <LanguageSwitch action={handleLanguageChange} /> | ||||||
|         <Controller |         <Controller | ||||||
|           control={control} |           control={control} | ||||||
|           name="city_id" |           name="city_id" | ||||||
| @@ -95,7 +140,7 @@ export const CarrierCreate = observer(() => { | |||||||
|           helperText={(errors as any)?.full_name?.message} |           helperText={(errors as any)?.full_name?.message} | ||||||
|           margin="normal" |           margin="normal" | ||||||
|           fullWidth |           fullWidth | ||||||
|           slotProps={{inputLabel: {shrink: true}}} |           slotProps={{ inputLabel: { shrink: true } }} | ||||||
|           type="text" |           type="text" | ||||||
|           label={"Полное имя *"} |           label={"Полное имя *"} | ||||||
|           name="full_name" |           name="full_name" | ||||||
| @@ -109,16 +154,13 @@ export const CarrierCreate = observer(() => { | |||||||
|           helperText={(errors as any)?.short_name?.message} |           helperText={(errors as any)?.short_name?.message} | ||||||
|           margin="normal" |           margin="normal" | ||||||
|           fullWidth |           fullWidth | ||||||
|           slotProps={{inputLabel: {shrink: true}}} |           slotProps={{ inputLabel: { shrink: true } }} | ||||||
|           type="text" |           type="text" | ||||||
|           label={"Короткое имя"} |           label={"Короткое имя"} | ||||||
|           name="short_name" |           name="short_name" | ||||||
|         /> |         /> | ||||||
|  |  | ||||||
|         <Box component="form" |         <Box component="form" sx={{ display: "flex" }} autoComplete="off"> | ||||||
|           sx={{ display: "flex" }} |  | ||||||
|           autoComplete="off" |  | ||||||
|         > |  | ||||||
|           <TextField |           <TextField | ||||||
|             {...register("main_color", { |             {...register("main_color", { | ||||||
|               // required: 'Это поле является обязательным', |               // required: 'Это поле является обязательным', | ||||||
| @@ -127,7 +169,7 @@ export const CarrierCreate = observer(() => { | |||||||
|             helperText={(errors as any)?.main_color?.message} |             helperText={(errors as any)?.main_color?.message} | ||||||
|             margin="normal" |             margin="normal" | ||||||
|             fullWidth |             fullWidth | ||||||
|             slotProps={{inputLabel: {shrink: true}}} |             slotProps={{ inputLabel: { shrink: true } }} | ||||||
|             type="color" |             type="color" | ||||||
|             label={"Основной цвет"} |             label={"Основной цвет"} | ||||||
|             name="main_color" |             name="main_color" | ||||||
| @@ -149,7 +191,7 @@ export const CarrierCreate = observer(() => { | |||||||
|             helperText={(errors as any)?.left_color?.message} |             helperText={(errors as any)?.left_color?.message} | ||||||
|             margin="normal" |             margin="normal" | ||||||
|             fullWidth |             fullWidth | ||||||
|             slotProps={{inputLabel: {shrink: true}}} |             slotProps={{ inputLabel: { shrink: true } }} | ||||||
|             type="color" |             type="color" | ||||||
|             label={"Цвет левого виджета"} |             label={"Цвет левого виджета"} | ||||||
|             name="left_color" |             name="left_color" | ||||||
| @@ -172,7 +214,7 @@ export const CarrierCreate = observer(() => { | |||||||
|             helperText={(errors as any)?.right_color?.message} |             helperText={(errors as any)?.right_color?.message} | ||||||
|             margin="normal" |             margin="normal" | ||||||
|             fullWidth |             fullWidth | ||||||
|             slotProps={{inputLabel: {shrink: true}}} |             slotProps={{ inputLabel: { shrink: true } }} | ||||||
|             type="color" |             type="color" | ||||||
|             label={"Цвет правого виджета"} |             label={"Цвет правого виджета"} | ||||||
|             name="right_color" |             name="right_color" | ||||||
| @@ -195,7 +237,7 @@ export const CarrierCreate = observer(() => { | |||||||
|           helperText={(errors as any)?.slogan?.message} |           helperText={(errors as any)?.slogan?.message} | ||||||
|           margin="normal" |           margin="normal" | ||||||
|           fullWidth |           fullWidth | ||||||
|           slotProps={{inputLabel: {shrink: true}}} |           slotProps={{ inputLabel: { shrink: true } }} | ||||||
|           type="text" |           type="text" | ||||||
|           label={"Слоган"} |           label={"Слоган"} | ||||||
|           name="slogan" |           name="slogan" | ||||||
|   | |||||||
| @@ -1,97 +1,303 @@ | |||||||
| import {Autocomplete, Box, TextField} from '@mui/material' | import { Autocomplete, Box, TextField } from "@mui/material"; | ||||||
| import {Create, useAutocomplete} from '@refinedev/mui' | import { Create, useAutocomplete } from "@refinedev/mui"; | ||||||
| import {useForm} from '@refinedev/react-hook-form' | import { useForm } from "@refinedev/react-hook-form"; | ||||||
| import {Controller} from 'react-hook-form' | import { Controller } from "react-hook-form"; | ||||||
|  | import { useEffect, useState } from "react"; | ||||||
|  | import { EVERY_LANGUAGE, Languages, languageStore } from "@stores"; // Assuming these imports are correct and available | ||||||
|  | import { LanguageSwitch } from "@/components/LanguageSwitch"; // Assuming this import is correct and available | ||||||
|  | import { axiosInstance, axiosInstanceForGet } from "@/providers"; // Assuming these imports are correct and available | ||||||
|  | import { useNavigate } from "react-router"; | ||||||
|  | import { useNotification } from "@refinedev/core"; | ||||||
|  |  | ||||||
| export const CityCreate = () => { | export const CityCreate = () => { | ||||||
|  |   const { language, setLanguageAction } = languageStore; | ||||||
|  |   const navigate = useNavigate(); | ||||||
|  |   const notification = useNotification(); | ||||||
|  |   // State to hold all language translations for the city name. | ||||||
|  |   // Initialized with an object where each language key maps to an empty string. | ||||||
|  |   const [allLanguageNames, setAllLanguageNames] = useState< | ||||||
|  |     Record<Languages, string> | ||||||
|  |   >(EVERY_LANGUAGE("")); | ||||||
|  |  | ||||||
|   const { |   const { | ||||||
|     saveButtonProps, |     saveButtonProps, | ||||||
|     refineCore: {formLoading}, |     refineCore: { formLoading }, // Indicates if the form is currently loading data | ||||||
|     register, |     register, // Function to register input fields with react-hook-form | ||||||
|     control, |     control, // Object to pass to Controller for controlled components | ||||||
|     formState: {errors}, |     setValue, // Function to programmatically set form values | ||||||
|   } = useForm({}) |     watch, // Function to watch for changes in form values | ||||||
|  |     handleSubmit, // Function to handle form submission, including validation | ||||||
|  |     formState: { errors }, // Object containing form validation errors | ||||||
|  |   } = useForm<{ | ||||||
|  |     name: string; // This field will hold the name for the *currently active* language | ||||||
|  |     country_code: string; // The code for the selected country | ||||||
|  |     arms: string; // The ID of the selected media for the city's arms (e.g., coat of arms) | ||||||
|  |   }>({}); | ||||||
|  |  | ||||||
|   const {autocompleteProps: countryAutocompleteProps} = useAutocomplete({ |   // useEffect hook to synchronize the 'name' TextField with the 'allLanguageNames' state. | ||||||
|     resource: 'country', |   // Whenever the active 'language' or the 'allLanguageNames' state changes, | ||||||
|   }) |   // the 'name' field in the form is updated to display the correct translation. | ||||||
|  |   useEffect(() => { | ||||||
|  |     setValue("name", allLanguageNames[language]); | ||||||
|  |   }, [language, allLanguageNames, setValue]); | ||||||
|  |  | ||||||
|   const {autocompleteProps: mediaAutocompleteProps} = useAutocomplete({ |   // Function to update the 'allLanguageNames' state with the current value from the 'name' TextField. | ||||||
|     resource: 'media', |   // This is crucial to ensure that changes made by the user are saved before | ||||||
|  |   // switching languages or submitting the form. | ||||||
|  |   const updateCurrentLanguageName = () => { | ||||||
|  |     const currentNameValue = watch("name"); // Get the current value typed into the 'name' TextField | ||||||
|  |     setAllLanguageNames((prev) => ({ | ||||||
|  |       ...prev, | ||||||
|  |       [language]: currentNameValue || "", // Update the specific language entry in the state | ||||||
|  |     })); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   // Handler for language switch. | ||||||
|  |   // It first saves the current input for the active language, then changes the language. | ||||||
|  |   const handleLanguageChange = (lang: Languages) => { | ||||||
|  |     updateCurrentLanguageName(); // Save the current input before switching language | ||||||
|  |     setLanguageAction(lang); // Update the global language state | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   // Autocomplete props for Country selection. | ||||||
|  |   // 'resource' specifies the API endpoint, and it's assumed 'code' is the value | ||||||
|  |   // to be stored and 'name' is the label to display. | ||||||
|  |   const { autocompleteProps: countryAutocompleteProps } = useAutocomplete({ | ||||||
|  |     resource: "country", | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   // Autocomplete props for Media selection (specifically for city's arms). | ||||||
|  |   // 'resource' specifies the API endpoint, and 'onSearch' provides a custom | ||||||
|  |   // search filter based on 'media_name'. | ||||||
|  |   const { autocompleteProps: mediaAutocompleteProps } = useAutocomplete({ | ||||||
|  |     resource: "media", | ||||||
|     onSearch: (value) => [ |     onSearch: (value) => [ | ||||||
|       { |       { | ||||||
|         field: 'media_name', |         field: "media_name", | ||||||
|         operator: 'contains', |         operator: "contains", | ||||||
|         value, |         value, | ||||||
|       }, |       }, | ||||||
|     ], |     ], | ||||||
|   }) |   }); | ||||||
|  |  | ||||||
|  |   // This function is called when the form is submitted and passes validation. | ||||||
|  |   // It orchestrates the API calls for creating the city and updating its translations. | ||||||
|  |   const onFinish = async (data: { | ||||||
|  |     name: string; | ||||||
|  |     country_code: string; | ||||||
|  |     arms: string; | ||||||
|  |   }) => { | ||||||
|  |     // Ensure the very latest input for the current language is captured | ||||||
|  |     // into 'allLanguageNames' before making API calls. This handles cases | ||||||
|  |     // where the user might click 'Save' without blurring the 'name' field. | ||||||
|  |     updateCurrentLanguageName(); | ||||||
|  |  | ||||||
|  |     // Create a final set of names including the latest value from the form | ||||||
|  |     // for the current language. | ||||||
|  |     const finalNames = { | ||||||
|  |       ...allLanguageNames, | ||||||
|  |       [language]: data.name, // Ensure the absolute latest value from the form is included | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     try { | ||||||
|  |       // Validate that the Russian name is present, as it's used for the initial POST request. | ||||||
|  |       if (!finalNames["ru"]) { | ||||||
|  |         console.error("Russian name is required for initial city creation."); | ||||||
|  |         // In a real application, you might want to display a user-friendly error message here. | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       console.log("Submitting with names:", finalNames); | ||||||
|  |  | ||||||
|  |       // 1. Create the city entry in Russian. | ||||||
|  |       // This POST request typically returns the ID of the newly created resource. | ||||||
|  |       const ruResponse = await axiosInstanceForGet("ru").post("/city", { | ||||||
|  |         name: finalNames["ru"], // Russian name | ||||||
|  |         country_code: data.country_code, // Country code from the form | ||||||
|  |         arms: data.arms, // Arms ID from the form, included in initial creation | ||||||
|  |       }); | ||||||
|  |  | ||||||
|  |       const id = ruResponse.data.id; // Extract the ID of the newly created city | ||||||
|  |  | ||||||
|  |       // 2. Patch the city with the English name if available. | ||||||
|  |       if (finalNames["en"]) { | ||||||
|  |         await axiosInstanceForGet("en").patch(`/city/${id}`, { | ||||||
|  |           name: finalNames["en"], // English name | ||||||
|  |           country_code: data.country_code, // Country code from the form | ||||||
|  |           arms: data.arms, // Arms ID from the form, included in initial creation | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       // 3. Patch the city with the Chinese name if available. | ||||||
|  |       if (finalNames["zh"]) { | ||||||
|  |         await axiosInstanceForGet("zh").patch(`/city/${id}`, { | ||||||
|  |           name: finalNames["zh"], // Chinese name | ||||||
|  |           country_code: data.country_code, // Country code from the form | ||||||
|  |           arms: data.arms, // Arms ID from the form, included in initial creation | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       console.log("City created/updated successfully!"); | ||||||
|  |       if (notification && typeof notification.open === "function") { | ||||||
|  |         notification.open({ | ||||||
|  |           message: "Город успешно создан", | ||||||
|  |           type: "success", | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |       window.onbeforeunload = null; | ||||||
|  |       navigate("/city"); | ||||||
|  |  | ||||||
|  |       // After successful creation/update, you might want to: | ||||||
|  |       // - Navigate to a different page (e.g., city list). | ||||||
|  |       // - Display a success notification to the user. | ||||||
|  |     } catch (error) { | ||||||
|  |       console.error("Error creating/updating city:", error); | ||||||
|  |       // Display an error message to the user if the API calls fail. | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|     <Create isLoading={formLoading} saveButtonProps={saveButtonProps}> |     <Create | ||||||
|       <Box component="form" sx={{display: 'flex', flexDirection: 'column'}} autoComplete="off"> |       isLoading={formLoading} // Show loading indicator if form data is being fetched | ||||||
|  |       // Pass the handleSubmit function to the save button's onClick. | ||||||
|  |       // This ensures form validation runs before 'onFinish' is called. | ||||||
|  |       saveButtonProps={{ | ||||||
|  |         ...saveButtonProps, | ||||||
|  |         onClick: handleSubmit(onFinish as any), | ||||||
|  |       }} | ||||||
|  |     > | ||||||
|  |       <Box | ||||||
|  |         component="form" | ||||||
|  |         sx={{ display: "flex", flexDirection: "column" }} | ||||||
|  |         autoComplete="off" // Disable browser autocomplete | ||||||
|  |       > | ||||||
|  |         {/* Language Switch component to change the active language */} | ||||||
|  |         <LanguageSwitch action={handleLanguageChange} /> | ||||||
|  |  | ||||||
|  |         {/* Country Autocomplete field */} | ||||||
|         <Controller |         <Controller | ||||||
|           control={control} |           control={control} // Pass control from useForm | ||||||
|           name="country_code" |           name="country_code" // Field name in the form state | ||||||
|           rules={{required: 'Это поле является обязательным'}} |           rules={{ required: "Это поле является обязательным" }} // Validation rule | ||||||
|           defaultValue={null} |           defaultValue={null} // Initial value | ||||||
|           render={({field}) => ( |           render={({ field }) => ( | ||||||
|             <Autocomplete |             <Autocomplete | ||||||
|               {...countryAutocompleteProps} |               {...countryAutocompleteProps} // Spread autocomplete props from useAutocomplete hook | ||||||
|               value={countryAutocompleteProps.options.find((option) => option.code === field.value) || null} |               value={ | ||||||
|               onChange={(_, value) => { |                 // Find the selected option based on the field's current value | ||||||
|                 field.onChange(value?.code || '') |                 countryAutocompleteProps.options.find( | ||||||
|  |                   (option: { code: string; name: string; id: string }) => | ||||||
|  |                     option.code === field.value | ||||||
|  |                 ) || null | ||||||
|  |               } | ||||||
|  |               onChange={( | ||||||
|  |                 _, | ||||||
|  |                 value: { code: string; name: string; id: string } | null | ||||||
|  |               ) => { | ||||||
|  |                 // Update the form field's value when an option is selected | ||||||
|  |                 field.onChange(value?.code || ""); | ||||||
|               }} |               }} | ||||||
|               getOptionLabel={(item) => { |               getOptionLabel={(item: { | ||||||
|                 return item ? item.name : '' |                 code: string; | ||||||
|  |                 name: string; | ||||||
|  |                 id: string; | ||||||
|  |               }) => { | ||||||
|  |                 // Define how to display the label for each option | ||||||
|  |                 return item ? item.name : ""; | ||||||
|               }} |               }} | ||||||
|               isOptionEqualToValue={(option, value) => { |               isOptionEqualToValue={( | ||||||
|                 return option.id === value?.id |                 option: { code: string; name: string; id: string }, | ||||||
|  |                 value: { code: string; name: string; id: string } | ||||||
|  |               ) => { | ||||||
|  |                 // Define how to compare options for equality | ||||||
|  |                 return option.id === value?.id; | ||||||
|               }} |               }} | ||||||
|               renderInput={(params) => <TextField {...params} label="Выберите страну" margin="normal" variant="outlined" error={!!errors.country_code} helperText={(errors as any)?.country_code?.message} required />} |               renderInput={(params) => ( | ||||||
|  |                 <TextField | ||||||
|  |                   {...params} | ||||||
|  |                   label="Выберите страну" | ||||||
|  |                   margin="normal" | ||||||
|  |                   variant="outlined" | ||||||
|  |                   error={!!errors.country_code} // Show error state if validation fails | ||||||
|  |                   required // Mark as required in UI | ||||||
|  |                 /> | ||||||
|  |               )} | ||||||
|             /> |             /> | ||||||
|           )} |           )} | ||||||
|         /> |         /> | ||||||
|  |  | ||||||
|  |         {/* City Name TextField for the current language */} | ||||||
|         <TextField |         <TextField | ||||||
|           {...register('name', { |           {...register("name", { | ||||||
|             required: 'Это поле является обязательным', |             required: "Это поле является обязательным", // Validation rule | ||||||
|  |             onBlur: updateCurrentLanguageName, // Update translations when the field loses focus | ||||||
|           })} |           })} | ||||||
|           error={!!(errors as any)?.name} |           error={!!errors.name} // Show error state if validation fails | ||||||
|           helperText={(errors as any)?.name?.message} |  | ||||||
|           margin="normal" |           margin="normal" | ||||||
|           fullWidth |           fullWidth | ||||||
|           InputLabelProps={{shrink: true}} |           InputLabelProps={{ shrink: true }} | ||||||
|           type="text" |           type="text" | ||||||
|           label={'Название *'} |           label={"Название *"} | ||||||
|           name="name" |           name="name" // HTML name attribute, matches register key | ||||||
|         /> |         /> | ||||||
|  |  | ||||||
|  |         {/* Arms Autocomplete field */} | ||||||
|         <Controller |         <Controller | ||||||
|           control={control} |           control={control} // Pass control from useForm | ||||||
|           name="arms" |           name="arms" // Field name in the form state | ||||||
|           defaultValue={null} |           defaultValue={null} // Initial value | ||||||
|           render={({field}) => ( |           render={({ field }) => ( | ||||||
|             <Autocomplete |             <Autocomplete | ||||||
|               {...mediaAutocompleteProps} |               {...mediaAutocompleteProps} // Spread autocomplete props from useAutocomplete hook | ||||||
|               value={mediaAutocompleteProps.options.find((option) => option.id === field.value) || null} |               value={ | ||||||
|               onChange={(_, value) => { |                 // Find the selected option based on the field's current value | ||||||
|                 field.onChange(value?.id || '') |                 mediaAutocompleteProps.options.find( | ||||||
|  |                   (option: { id: string; media_name: string }) => | ||||||
|  |                     option.id === field.value | ||||||
|  |                 ) || null | ||||||
|  |               } | ||||||
|  |               onChange={( | ||||||
|  |                 _, | ||||||
|  |                 value: { id: string; media_name: string } | null | ||||||
|  |               ) => { | ||||||
|  |                 // Update the form field's value when an option is selected | ||||||
|  |                 field.onChange(value?.id || ""); | ||||||
|               }} |               }} | ||||||
|               getOptionLabel={(item) => { |               getOptionLabel={(item: { id: string; media_name: string }) => { | ||||||
|                 return item ? item.media_name : '' |                 // Define how to display the label for each option | ||||||
|  |                 return item ? item.media_name : ""; | ||||||
|               }} |               }} | ||||||
|               isOptionEqualToValue={(option, value) => { |               isOptionEqualToValue={( | ||||||
|                 return option.id === value?.id |                 option: { id: string; media_name: string }, | ||||||
|  |                 value: { id: string; media_name: string } | ||||||
|  |               ) => { | ||||||
|  |                 // Define how to compare options for equality | ||||||
|  |                 return option.id === value?.id; | ||||||
|               }} |               }} | ||||||
|               filterOptions={(options, {inputValue}) => { |               filterOptions={( | ||||||
|                 return options.filter((option) => option.media_name.toLowerCase().includes(inputValue.toLowerCase())) |                 options: { id: string; media_name: string }[], | ||||||
|  |                 { inputValue } | ||||||
|  |               ) => { | ||||||
|  |                 // Custom filter for options based on input value | ||||||
|  |                 return options.filter((option) => | ||||||
|  |                   option.media_name | ||||||
|  |                     .toLowerCase() | ||||||
|  |                     .includes(inputValue.toLowerCase()) | ||||||
|  |                 ); | ||||||
|               }} |               }} | ||||||
|               renderInput={(params) => <TextField {...params} label="Выберите герб" margin="normal" variant="outlined" error={!!errors.arms} helperText={(errors as any)?.arms?.message} />} |               renderInput={(params) => ( | ||||||
|  |                 <TextField | ||||||
|  |                   {...params} | ||||||
|  |                   label="Выберите герб" | ||||||
|  |                   margin="normal" | ||||||
|  |                   variant="outlined" | ||||||
|  |                   error={!!errors.arms} // Show error state if validation fails | ||||||
|  |                 /> | ||||||
|  |               )} | ||||||
|             /> |             /> | ||||||
|           )} |           )} | ||||||
|         /> |         /> | ||||||
|       </Box> |       </Box> | ||||||
|     </Create> |     </Create> | ||||||
|   ) |   ); | ||||||
| } | }; | ||||||
|   | |||||||
| @@ -100,7 +100,6 @@ export const MediaList = observer(() => { | |||||||
|         columns={columns} |         columns={columns} | ||||||
|         localeText={localeText} |         localeText={localeText} | ||||||
|         getRowId={(row: any) => row.id} |         getRowId={(row: any) => row.id} | ||||||
|         languageEnabled |  | ||||||
|       /> |       /> | ||||||
|     </List> |     </List> | ||||||
|   ); |   ); | ||||||
|   | |||||||
| @@ -114,10 +114,10 @@ export const StationLabel = observer( | |||||||
|         rotation={-rotation} |         rotation={-rotation} | ||||||
|       > |       > | ||||||
|         <pixiText |         <pixiText | ||||||
|           anchor={{ x: 0.5, y: 0.5 }} |           anchor={{ x: 1, y: 0.5 }} | ||||||
|           text={station.name} |           text={station.name} | ||||||
|           position={{ |           position={{ | ||||||
|             x: position.x / scale, |             x: position.x / scale + 24, | ||||||
|             y: position.y / scale, |             y: position.y / scale, | ||||||
|           }} |           }} | ||||||
|           style={{ |           style={{ | ||||||
| @@ -129,10 +129,10 @@ export const StationLabel = observer( | |||||||
|  |  | ||||||
|         {ruLabel && ( |         {ruLabel && ( | ||||||
|           <pixiText |           <pixiText | ||||||
|             anchor={{ x: 0.5, y: -1 }} |             anchor={{ x: 1, y: -1 }} | ||||||
|             text={ruLabel} |             text={ruLabel} | ||||||
|             position={{ |             position={{ | ||||||
|               x: position.x / scale, |               x: position.x / scale + 24, | ||||||
|               y: position.y / scale, |               y: position.y / scale, | ||||||
|             }} |             }} | ||||||
|             style={{ |             style={{ | ||||||
|   | |||||||
| @@ -153,7 +153,11 @@ export const RouteMap = observer(() => { | |||||||
|             <Station |             <Station | ||||||
|               station={obj} |               station={obj} | ||||||
|               key={obj.id} |               key={obj.id} | ||||||
|               ruLabel={language === "ru" ? null : stationData.ru[index].name} |               ruLabel={ | ||||||
|  |                 language === "ru" | ||||||
|  |                   ? stationData.en[index].name | ||||||
|  |                   : stationData.ru[index].name | ||||||
|  |               } | ||||||
|             /> |             /> | ||||||
|           ))} |           ))} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -22,6 +22,7 @@ import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; | |||||||
|  |  | ||||||
| export const SightCreate = observer(() => { | export const SightCreate = observer(() => { | ||||||
|   const { language, setLanguageAction } = languageStore; |   const { language, setLanguageAction } = languageStore; | ||||||
|  |   const [coordinates, setCoordinates] = useState(""); | ||||||
|   const [sightData, setSightData] = useState({ |   const [sightData, setSightData] = useState({ | ||||||
|     name: EVERY_LANGUAGE(""), |     name: EVERY_LANGUAGE(""), | ||||||
|     address: EVERY_LANGUAGE(""), |     address: EVERY_LANGUAGE(""), | ||||||
| @@ -79,10 +80,7 @@ export const SightCreate = observer(() => { | |||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   const [namePreview, setNamePreview] = useState(""); |   const [namePreview, setNamePreview] = useState(""); | ||||||
|   const [coordinatesPreview, setCoordinatesPreview] = useState({ |   const [coordinatesPreview, setCoordinatesPreview] = useState(""); | ||||||
|     latitude: "", |  | ||||||
|     longitude: "", |  | ||||||
|   }); |  | ||||||
|  |  | ||||||
|   const [creatingArticleHeading, setCreatingArticleHeading] = |   const [creatingArticleHeading, setCreatingArticleHeading] = | ||||||
|     useState<string>(""); |     useState<string>(""); | ||||||
| @@ -102,13 +100,19 @@ export const SightCreate = observer(() => { | |||||||
|   const [previewArticlePreview, setPreviewArticlePreview] = useState(""); |   const [previewArticlePreview, setPreviewArticlePreview] = useState(""); | ||||||
|  |  | ||||||
|   const handleCoordinatesChange = (e: React.ChangeEvent<HTMLInputElement>) => { |   const handleCoordinatesChange = (e: React.ChangeEvent<HTMLInputElement>) => { | ||||||
|     const [lat, lon] = e.target.value.split(",").map((s) => s.trim()); |     setCoordinates(e.target.value); | ||||||
|     setCoordinatesPreview({ |     if (e.target.value) { | ||||||
|       latitude: lat, |       const [lat, lon] = e.target.value.split(" ").map((s) => s.trim()); | ||||||
|       longitude: lon, |       setCoordinatesPreview( | ||||||
|     }); |         `${lat ? Number(lat) : 0}, ${lon ? Number(lon) : 0}` | ||||||
|     setValue("latitude", lat); |       ); | ||||||
|     setValue("longitude", lon); |       setValue("latitude", lat ? Number(lat) : 0); | ||||||
|  |       setValue("longitude", lon ? Number(lon) : 0); | ||||||
|  |     } else { | ||||||
|  |       setCoordinatesPreview(""); | ||||||
|  |       setValue("latitude", ""); | ||||||
|  |       setValue("longitude", ""); | ||||||
|  |     } | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   // Автокомплиты |   // Автокомплиты | ||||||
| @@ -170,13 +174,6 @@ export const SightCreate = observer(() => { | |||||||
|     setNamePreview(nameContent ?? ""); |     setNamePreview(nameContent ?? ""); | ||||||
|   }, [nameContent]); |   }, [nameContent]); | ||||||
|  |  | ||||||
|   useEffect(() => { |  | ||||||
|     setCoordinatesPreview({ |  | ||||||
|       latitude: latitudeContent || "", |  | ||||||
|       longitude: longitudeContent || "", |  | ||||||
|     }); |  | ||||||
|   }, [latitudeContent, longitudeContent]); |  | ||||||
|  |  | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     const selectedCity = cityAutocompleteProps.options.find( |     const selectedCity = cityAutocompleteProps.options.find( | ||||||
|       (option) => option.id === cityContent |       (option) => option.id === cityContent | ||||||
| @@ -276,7 +273,7 @@ export const SightCreate = observer(() => { | |||||||
|             /> |             /> | ||||||
|  |  | ||||||
|             <TextField |             <TextField | ||||||
|               value={`${coordinatesPreview.latitude}, ${coordinatesPreview.longitude}`} |               value={coordinates} | ||||||
|               onChange={handleCoordinatesChange} |               onChange={handleCoordinatesChange} | ||||||
|               error={!!(errors as any)?.latitude} |               error={!!(errors as any)?.latitude} | ||||||
|               helperText={(errors as any)?.latitude?.message} |               helperText={(errors as any)?.latitude?.message} | ||||||
| @@ -286,10 +283,11 @@ export const SightCreate = observer(() => { | |||||||
|               type="text" |               type="text" | ||||||
|               label={"Координаты *"} |               label={"Координаты *"} | ||||||
|             /> |             /> | ||||||
|  |  | ||||||
|             <input |             <input | ||||||
|               type="hidden" |               type="hidden" | ||||||
|               {...register("longitude", { |               {...register("longitude", { | ||||||
|                 value: coordinatesPreview.longitude, |                 value: coordinates.split(" ")[1], | ||||||
|                 required: "Это поле является обязательным", |                 required: "Это поле является обязательным", | ||||||
|                 valueAsNumber: true, |                 valueAsNumber: true, | ||||||
|               })} |               })} | ||||||
| @@ -297,7 +295,7 @@ export const SightCreate = observer(() => { | |||||||
|             <input |             <input | ||||||
|               type="hidden" |               type="hidden" | ||||||
|               {...register("latitude", { |               {...register("latitude", { | ||||||
|                 value: coordinatesPreview.latitude, |                 value: coordinates.split(" ")[0], | ||||||
|                 required: "Это поле является обязательным", |                 required: "Это поле является обязательным", | ||||||
|                 valueAsNumber: true, |                 valueAsNumber: true, | ||||||
|               })} |               })} | ||||||
| @@ -668,21 +666,22 @@ export const SightCreate = observer(() => { | |||||||
|           </Typography> |           </Typography> | ||||||
|  |  | ||||||
|           {/* Координаты */} |           {/* Координаты */} | ||||||
|           <Typography variant="body1" sx={{ mb: 2 }}> |           {coordinatesPreview && ( | ||||||
|             <Box component="span" sx={{ color: "text.secondary" }}> |             <Typography variant="body1" sx={{ mb: 2 }}> | ||||||
|               Координаты:{" "} |               <Box component="span" sx={{ color: "text.secondary" }}> | ||||||
|             </Box> |                 Координаты:{" "} | ||||||
|             <Box |               </Box> | ||||||
|               component="span" |               <Box | ||||||
|               sx={{ |                 component="span" | ||||||
|                 color: (theme) => |                 sx={{ | ||||||
|                   theme.palette.mode === "dark" ? "grey.300" : "grey.800", |                   color: (theme) => | ||||||
|               }} |                     theme.palette.mode === "dark" ? "grey.300" : "grey.800", | ||||||
|             > |                 }} | ||||||
|               {`${coordinatesPreview.latitude}, ${coordinatesPreview.longitude}`} |               > | ||||||
|             </Box> |                 {coordinatesPreview} | ||||||
|           </Typography> |               </Box> | ||||||
|  |             </Typography> | ||||||
|  |           )} | ||||||
|           {/* Обложка */} |           {/* Обложка */} | ||||||
|           {thumbnailPreview && ( |           {thumbnailPreview && ( | ||||||
|             <Box sx={{ mb: 2 }}> |             <Box sx={{ mb: 2 }}> | ||||||
|   | |||||||
| @@ -70,6 +70,7 @@ export const SightEdit = observer(() => { | |||||||
|     name: EVERY_LANGUAGE(""), |     name: EVERY_LANGUAGE(""), | ||||||
|     address: EVERY_LANGUAGE(""), |     address: EVERY_LANGUAGE(""), | ||||||
|   }); |   }); | ||||||
|  |   const [coordinates, setCoordinates] = useState(""); | ||||||
|  |  | ||||||
|   const { |   const { | ||||||
|     saveButtonProps, |     saveButtonProps, | ||||||
| @@ -163,37 +164,13 @@ export const SightEdit = observer(() => { | |||||||
|       setValue("address", sightData.address[language]); |       setValue("address", sightData.address[language]); | ||||||
|   }, [language, sightData, setValue]); |   }, [language, sightData, setValue]); | ||||||
|  |  | ||||||
|   useEffect(() => { |  | ||||||
|     const latitude = getValues("latitude"); |  | ||||||
|     const longitude = getValues("longitude"); |  | ||||||
|     if (latitude && longitude) { |  | ||||||
|       setCoordinatesPreview({ |  | ||||||
|         latitude: latitude, |  | ||||||
|         longitude: longitude, |  | ||||||
|       }); |  | ||||||
|     } |  | ||||||
|   }, [getValues]); |  | ||||||
|  |  | ||||||
|   const handleCoordinatesChange = (e: React.ChangeEvent<HTMLInputElement>) => { |  | ||||||
|     const [lat, lon] = e.target.value.split(",").map((s) => s.trim()); |  | ||||||
|     setCoordinatesPreview({ |  | ||||||
|       latitude: lat, |  | ||||||
|       longitude: lon, |  | ||||||
|     }); |  | ||||||
|     setValue("latitude", lat); |  | ||||||
|     setValue("longitude", lon); |  | ||||||
|   }; |  | ||||||
|  |  | ||||||
|   // Состояния для предпросмотра |   // Состояния для предпросмотра | ||||||
|  |  | ||||||
|   const [creatingArticleHeading, setCreatingArticleHeading] = |   const [creatingArticleHeading, setCreatingArticleHeading] = | ||||||
|     useState<string>(""); |     useState<string>(""); | ||||||
|   const [creatingArticleBody, setCreatingArticleBody] = useState<string>(""); |   const [creatingArticleBody, setCreatingArticleBody] = useState<string>(""); | ||||||
|  |  | ||||||
|   const [coordinatesPreview, setCoordinatesPreview] = useState({ |   const [coordinatesPreview, setCoordinatesPreview] = useState(""); | ||||||
|     latitude: "", |  | ||||||
|     longitude: "", |  | ||||||
|   }); |  | ||||||
|   const [selectedArticleIndex, setSelectedArticleIndex] = useState(-1); |   const [selectedArticleIndex, setSelectedArticleIndex] = useState(-1); | ||||||
|   const [previewMediaFile, setPreviewMediaFile] = useState<MediaData>(); |   const [previewMediaFile, setPreviewMediaFile] = useState<MediaData>(); | ||||||
|   const [thumbnailPreview, setThumbnailPreview] = useState<string | null>(null); |   const [thumbnailPreview, setThumbnailPreview] = useState<string | null>(null); | ||||||
| @@ -243,12 +220,25 @@ export const SightEdit = observer(() => { | |||||||
|   const watermarkRDContent = watch("watermark_rd"); |   const watermarkRDContent = watch("watermark_rd"); | ||||||
|  |  | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     setCoordinatesPreview({ |     if (latitudeContent && longitudeContent) { | ||||||
|       latitude: latitudeContent ?? "", |       setCoordinates(`${latitudeContent} ${longitudeContent}`); | ||||||
|       longitude: longitudeContent ?? "", |     } | ||||||
|     }); |  | ||||||
|   }, [latitudeContent, longitudeContent]); |   }, [latitudeContent, longitudeContent]); | ||||||
|  |  | ||||||
|  |   const handleCoordinatesChange = (e: React.ChangeEvent<HTMLInputElement>) => { | ||||||
|  |     setCoordinates(e.target.value); | ||||||
|  |     if (e.target.value) { | ||||||
|  |       const [lat, lon] = e.target.value.split(" ").map((s) => s.trim()); | ||||||
|  |       setCoordinatesPreview(`${lat ?? "0"}, ${lon ?? "0"}`); | ||||||
|  |       setValue("latitude", lat ?? ""); | ||||||
|  |       setValue("longitude", lon ?? ""); | ||||||
|  |     } else { | ||||||
|  |       setCoordinatesPreview(""); | ||||||
|  |       setValue("latitude", ""); | ||||||
|  |       setValue("longitude", ""); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     if (linkedArticles[selectedArticleIndex]?.id) { |     if (linkedArticles[selectedArticleIndex]?.id) { | ||||||
|       getMedia(linkedArticles[selectedArticleIndex].id).then((media) => { |       getMedia(linkedArticles[selectedArticleIndex].id).then((media) => { | ||||||
| @@ -505,7 +495,6 @@ export const SightEdit = observer(() => { | |||||||
|                   <input |                   <input | ||||||
|                     type="hidden" |                     type="hidden" | ||||||
|                     {...register("longitude", { |                     {...register("longitude", { | ||||||
|                       value: coordinatesPreview.longitude, |  | ||||||
|                       required: "Это поле является обязательным", |                       required: "Это поле является обязательным", | ||||||
|                       valueAsNumber: true, |                       valueAsNumber: true, | ||||||
|                     })} |                     })} | ||||||
| @@ -513,7 +502,6 @@ export const SightEdit = observer(() => { | |||||||
|                   <input |                   <input | ||||||
|                     type="hidden" |                     type="hidden" | ||||||
|                     {...register("latitude", { |                     {...register("latitude", { | ||||||
|                       value: coordinatesPreview.latitude, |  | ||||||
|                       required: "Это поле является обязательным", |                       required: "Это поле является обязательным", | ||||||
|                       valueAsNumber: true, |                       valueAsNumber: true, | ||||||
|                     })} |                     })} | ||||||
| @@ -1535,7 +1523,7 @@ export const SightEdit = observer(() => { | |||||||
|                 /> |                 /> | ||||||
|  |  | ||||||
|                 <TextField |                 <TextField | ||||||
|                   value={`${coordinatesPreview.latitude}, ${coordinatesPreview.longitude}`} |                   value={coordinates} | ||||||
|                   onChange={handleCoordinatesChange} |                   onChange={handleCoordinatesChange} | ||||||
|                   error={!!(errors as any)?.latitude} |                   error={!!(errors as any)?.latitude} | ||||||
|                   helperText={(errors as any)?.latitude?.message} |                   helperText={(errors as any)?.latitude?.message} | ||||||
| @@ -1687,7 +1675,7 @@ export const SightEdit = observer(() => { | |||||||
|                             : "grey.800", |                             : "grey.800", | ||||||
|                       }} |                       }} | ||||||
|                     > |                     > | ||||||
|                       {`${coordinatesPreview.latitude}, ${coordinatesPreview.longitude}`} |                       {coordinatesPreview} | ||||||
|                     </Box> |                     </Box> | ||||||
|                   </Typography> |                   </Typography> | ||||||
|                 </Box> |                 </Box> | ||||||
|   | |||||||
| @@ -56,6 +56,7 @@ export const SightList = observer(() => { | |||||||
|         display: "flex", |         display: "flex", | ||||||
|         align: "left", |         align: "left", | ||||||
|         headerAlign: "left", |         headerAlign: "left", | ||||||
|  |         flex: 1, | ||||||
|       }, |       }, | ||||||
|       { |       { | ||||||
|         field: "latitude", |         field: "latitude", | ||||||
|   | |||||||
| @@ -41,40 +41,19 @@ export const StationCreate = () => { | |||||||
|     }, |     }, | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   const [coordinatesPreview, setCoordinatesPreview] = useState({ |   const [coordinates, setCoordinates] = useState(""); | ||||||
|     latitude: "", |  | ||||||
|     longitude: "", |  | ||||||
|   }); |  | ||||||
|  |  | ||||||
|   const handleCoordinatesChange = (e: React.ChangeEvent<HTMLInputElement>) => { |   const handleCoordinatesChange = (e: React.ChangeEvent<HTMLInputElement>) => { | ||||||
|     const [lat, lon] = e.target.value.split(",").map((s) => s.trim()); |     setCoordinates(e.target.value); | ||||||
|     setCoordinatesPreview({ |     const [lat, lon] = e.target.value | ||||||
|       latitude: lat, |       .replace(/,/g, "") // Remove all commas from the string | ||||||
|       longitude: lon, |       .split(" ") | ||||||
|     }); |       .map((s) => s.trim()); | ||||||
|  |     console.log(lat, lon); | ||||||
|     setValue("latitude", lat); |     setValue("latitude", lat); | ||||||
|     setValue("longitude", lon); |     setValue("longitude", lon); | ||||||
|   }; |   }; | ||||||
|   const latitudeContent = watch("latitude"); |  | ||||||
|   const longitudeContent = watch("longitude"); |  | ||||||
|  |  | ||||||
|   useEffect(() => { |  | ||||||
|     setCoordinatesPreview({ |  | ||||||
|       latitude: latitudeContent || "", |  | ||||||
|       longitude: longitudeContent || "", |  | ||||||
|     }); |  | ||||||
|   }, [latitudeContent, longitudeContent]); |  | ||||||
|  |  | ||||||
|   useEffect(() => { |  | ||||||
|     const latitude = getValues("latitude"); |  | ||||||
|     const longitude = getValues("longitude"); |  | ||||||
|     if (latitude && longitude) { |  | ||||||
|       setCoordinatesPreview({ |  | ||||||
|         latitude: latitude, |  | ||||||
|         longitude: longitude, |  | ||||||
|       }); |  | ||||||
|     } |  | ||||||
|   }, [getValues]); |  | ||||||
|   const directions = [ |   const directions = [ | ||||||
|     { |     { | ||||||
|       label: "Прямой", |       label: "Прямой", | ||||||
| @@ -188,7 +167,7 @@ export const StationCreate = () => { | |||||||
|           )} |           )} | ||||||
|         /> |         /> | ||||||
|         <TextField |         <TextField | ||||||
|           value={`${coordinatesPreview.latitude}, ${coordinatesPreview.longitude}`} |           value={coordinates} | ||||||
|           onChange={handleCoordinatesChange} |           onChange={handleCoordinatesChange} | ||||||
|           error={!!(errors as any)?.latitude} |           error={!!(errors as any)?.latitude} | ||||||
|           helperText={(errors as any)?.latitude?.message} |           helperText={(errors as any)?.latitude?.message} | ||||||
| @@ -198,10 +177,11 @@ export const StationCreate = () => { | |||||||
|           type="text" |           type="text" | ||||||
|           label={"Координаты *"} |           label={"Координаты *"} | ||||||
|         /> |         /> | ||||||
|  |  | ||||||
|         <input |         <input | ||||||
|           type="hidden" |           type="hidden" | ||||||
|           {...register("latitude", { |           {...register("latitude", { | ||||||
|             value: coordinatesPreview.latitude, |             value: coordinates.split(",")[0], | ||||||
|             setValueAs: (value) => { |             setValueAs: (value) => { | ||||||
|               if (value === "") { |               if (value === "") { | ||||||
|                 return 0; |                 return 0; | ||||||
| @@ -213,7 +193,7 @@ export const StationCreate = () => { | |||||||
|         <input |         <input | ||||||
|           type="hidden" |           type="hidden" | ||||||
|           {...register("longitude", { |           {...register("longitude", { | ||||||
|             value: coordinatesPreview.longitude, |             value: coordinates.split(",")[1], | ||||||
|             setValueAs: (value) => { |             setValueAs: (value) => { | ||||||
|               if (value === "") { |               if (value === "") { | ||||||
|                 return 0; |                 return 0; | ||||||
|   | |||||||
| @@ -40,24 +40,24 @@ export const StationEdit = observer(() => { | |||||||
|       system_name: "", |       system_name: "", | ||||||
|       description: "", |       description: "", | ||||||
|       address: "", |       address: "", | ||||||
|       latitude: "", |       latitude: 0, | ||||||
|       longitude: "", |       longitude: 0, | ||||||
|     }, |     }, | ||||||
|     en: { |     en: { | ||||||
|       name: "", |       name: "", | ||||||
|       system_name: "", |       system_name: "", | ||||||
|       description: "", |       description: "", | ||||||
|       address: "", |       address: "", | ||||||
|       latitude: "", |       latitude: 0, | ||||||
|       longitude: "", |       longitude: 0, | ||||||
|     }, |     }, | ||||||
|     zh: { |     zh: { | ||||||
|       name: "", |       name: "", | ||||||
|       system_name: "", |       system_name: "", | ||||||
|       description: "", |       description: "", | ||||||
|       address: "", |       address: "", | ||||||
|       latitude: "", |       latitude: 0, | ||||||
|       longitude: "", |       longitude: 0, | ||||||
|     }, |     }, | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
| @@ -69,8 +69,8 @@ export const StationEdit = observer(() => { | |||||||
|         system_name: watch("system_name") ?? "", |         system_name: watch("system_name") ?? "", | ||||||
|         description: watch("description") ?? "", |         description: watch("description") ?? "", | ||||||
|         address: watch("address") ?? "", |         address: watch("address") ?? "", | ||||||
|         latitude: watch("latitude") ?? "", |         latitude: Number(watch("latitude")) || 0, | ||||||
|         longitude: watch("longitude") ?? "", |         longitude: Number(watch("longitude")) || 0, | ||||||
|       }, |       }, | ||||||
|     })); |     })); | ||||||
|   }; |   }; | ||||||
| @@ -120,7 +120,7 @@ export const StationEdit = observer(() => { | |||||||
|     if (stationData[language as keyof typeof stationData]?.name) { |     if (stationData[language as keyof typeof stationData]?.name) { | ||||||
|       setValue("name", stationData[language as keyof typeof stationData]?.name); |       setValue("name", stationData[language as keyof typeof stationData]?.name); | ||||||
|     } |     } | ||||||
|     if (stationData[language as keyof typeof stationData]?.address) { |     if (stationData[language as keyof typeof stationData]?.system_name) { | ||||||
|       setValue( |       setValue( | ||||||
|         "system_name", |         "system_name", | ||||||
|         stationData[language as keyof typeof stationData]?.system_name || "" |         stationData[language as keyof typeof stationData]?.system_name || "" | ||||||
| @@ -132,16 +132,20 @@ export const StationEdit = observer(() => { | |||||||
|         stationData[language as keyof typeof stationData]?.description || "" |         stationData[language as keyof typeof stationData]?.description || "" | ||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
|     if (stationData[language as keyof typeof stationData]?.latitude) { |     if ( | ||||||
|  |       stationData[language as keyof typeof stationData]?.latitude !== undefined | ||||||
|  |     ) { | ||||||
|       setValue( |       setValue( | ||||||
|         "latitude", |         "latitude", | ||||||
|         stationData[language as keyof typeof stationData]?.latitude || "" |         stationData[language as keyof typeof stationData]?.latitude || 0 | ||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
|     if (stationData[language as keyof typeof stationData]?.longitude) { |     if ( | ||||||
|  |       stationData[language as keyof typeof stationData]?.longitude !== undefined | ||||||
|  |     ) { | ||||||
|       setValue( |       setValue( | ||||||
|         "longitude", |         "longitude", | ||||||
|         stationData[language as keyof typeof stationData]?.longitude || "" |         stationData[language as keyof typeof stationData]?.longitude || 0 | ||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
|   }, [language, stationData, setValue]); |   }, [language, stationData, setValue]); | ||||||
| @@ -150,28 +154,36 @@ export const StationEdit = observer(() => { | |||||||
|     setLanguageAction("ru"); |     setLanguageAction("ru"); | ||||||
|   }, []); |   }, []); | ||||||
|  |  | ||||||
|   const { id: stationId } = useParams<{ id: string }>(); |   const [coordinates, setCoordinates] = useState(""); | ||||||
|  |  | ||||||
|   const handleCoordinatesChange = (e: React.ChangeEvent<HTMLInputElement>) => { |   const { id: stationId } = useParams<{ id: string }>(); | ||||||
|     const [lat, lon] = e.target.value.split(",").map((s) => s.trim()); |  | ||||||
|     setCoordinatesPreview({ |  | ||||||
|       latitude: lat, |  | ||||||
|       longitude: lon, |  | ||||||
|     }); |  | ||||||
|     setValue("latitude", lat); |  | ||||||
|     setValue("longitude", lon); |  | ||||||
|   }; |  | ||||||
|  |  | ||||||
|   const latitudeContent = watch("latitude"); |   const latitudeContent = watch("latitude"); | ||||||
|   const longitudeContent = watch("longitude"); |   const longitudeContent = watch("longitude"); | ||||||
|  |  | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     setCoordinatesPreview({ |     if (latitudeContent && longitudeContent) { | ||||||
|       latitude: latitudeContent || "", |       setCoordinates(`${latitudeContent} ${longitudeContent}`); | ||||||
|       longitude: longitudeContent || "", |     } | ||||||
|     }); |  | ||||||
|   }, [latitudeContent, longitudeContent]); |   }, [latitudeContent, longitudeContent]); | ||||||
|  |  | ||||||
|  |   const handleCoordinatesChange = (e: React.ChangeEvent<HTMLInputElement>) => { | ||||||
|  |     setCoordinates(e.target.value); | ||||||
|  |     if (e.target.value) { | ||||||
|  |       const [lat, lon] = e.target.value | ||||||
|  |         .replace(/,/g, "") // Remove all commas from the string | ||||||
|  |         .split(" ") | ||||||
|  |         .map((s) => s.trim()); | ||||||
|  |       setCoordinates(`${lat ?? 0} ${lon ?? 0}`); | ||||||
|  |       setValue("latitude", lat ?? 0); | ||||||
|  |       setValue("longitude", lon ?? 0); | ||||||
|  |     } else { | ||||||
|  |       setCoordinates(""); | ||||||
|  |       setValue("latitude", ""); | ||||||
|  |       setValue("longitude", ""); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|   const { autocompleteProps: cityAutocompleteProps } = useAutocomplete({ |   const { autocompleteProps: cityAutocompleteProps } = useAutocomplete({ | ||||||
|     resource: "city", |     resource: "city", | ||||||
|     onSearch: (value) => [ |     onSearch: (value) => [ | ||||||
| @@ -282,7 +294,7 @@ export const StationEdit = observer(() => { | |||||||
|         /> |         /> | ||||||
|  |  | ||||||
|         <TextField |         <TextField | ||||||
|           value={`${coordinatesPreview.latitude}, ${coordinatesPreview.longitude}`} |           value={coordinates} | ||||||
|           onChange={handleCoordinatesChange} |           onChange={handleCoordinatesChange} | ||||||
|           error={!!(errors as any)?.latitude} |           error={!!(errors as any)?.latitude} | ||||||
|           helperText={(errors as any)?.latitude?.message} |           helperText={(errors as any)?.latitude?.message} | ||||||
| @@ -295,12 +307,28 @@ export const StationEdit = observer(() => { | |||||||
|         <input |         <input | ||||||
|           type="hidden" |           type="hidden" | ||||||
|           {...register("latitude", { |           {...register("latitude", { | ||||||
|             value: coordinatesPreview.latitude, |             valueAsNumber: true, | ||||||
|  |             value: Number(coordinates.split(" ")[0]), | ||||||
|  |             setValueAs: (value) => { | ||||||
|  |               if (value === "") { | ||||||
|  |                 return 0; | ||||||
|  |               } | ||||||
|  |               return Number(value); | ||||||
|  |             }, | ||||||
|           })} |           })} | ||||||
|         /> |         /> | ||||||
|         <input |         <input | ||||||
|           type="hidden" |           type="hidden" | ||||||
|           {...register("longitude", { value: coordinatesPreview.longitude })} |           {...register("longitude", { | ||||||
|  |             valueAsNumber: true, | ||||||
|  |             value: Number(coordinates.split(" ")[1]), | ||||||
|  |             setValueAs: (value) => { | ||||||
|  |               if (value === "") { | ||||||
|  |                 return 0; | ||||||
|  |               } | ||||||
|  |               return Number(value); | ||||||
|  |             }, | ||||||
|  |           })} | ||||||
|         /> |         /> | ||||||
|  |  | ||||||
|         <Controller |         <Controller | ||||||
|   | |||||||
| @@ -104,6 +104,7 @@ export const VehicleList = observer(() => { | |||||||
|         type: "string", |         type: "string", | ||||||
|         minWidth: 150, |         minWidth: 150, | ||||||
|         display: "flex", |         display: "flex", | ||||||
|  |         flex: 1, | ||||||
|         align: "left", |         align: "left", | ||||||
|         headerAlign: "left", |         headerAlign: "left", | ||||||
|         renderCell: (params) => { |         renderCell: (params) => { | ||||||
| @@ -116,6 +117,7 @@ export const VehicleList = observer(() => { | |||||||
|         headerName: "Город", |         headerName: "Город", | ||||||
|         type: "string", |         type: "string", | ||||||
|         minWidth: 150, |         minWidth: 150, | ||||||
|  |         flex: 1, | ||||||
|         display: "flex", |         display: "flex", | ||||||
|         align: "left", |         align: "left", | ||||||
|         headerAlign: "left", |         headerAlign: "left", | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user