@ -3,9 +3,9 @@ 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 { useEffect , useState } from "react" ;
import { EVERY_LANGUAGE , Languages , languageStore } from "@stores" ; // Assuming these imports are correct and available
import { EVERY_LANGUAGE , Languages , languageStore } from "@stores" ;
import { LanguageSwitch } from "@/components/LanguageSwitch" ; // Assuming this import is correct and available
import { LanguageSwitch } from "@/components/LanguageSwitch" ;
import { axiosInstance , axiosInstanceForGet } from "@/providers" ; // Assuming these imports are correct and available
import { axiosInstanceForGet } from "@/providers" ;
import { useNavigate } from "react-router" ;
import { useNavigate } from "react-router" ;
import { useNotification } from "@refinedev/core" ;
import { useNotification } from "@refinedev/core" ;
@ -13,62 +13,55 @@ export const CityCreate = () => {
const { language , setLanguageAction } = languageStore ;
const { language , setLanguageAction } = languageStore ;
const navigate = useNavigate ( ) ;
const navigate = useNavigate ( ) ;
const notification = useNotification ( ) ;
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 .
// State to manage city name translations across all supported languages .
// Initializes with empty strings for each language.
const [ allLanguageNames , setAllLanguageNames ] = useState <
const [ allLanguageNames , setAllLanguageNames ] = useState <
Record < Languages , string >
Record < Languages , string >
> ( EVERY_LANGUAGE ( "" ) ) ;
> ( EVERY_LANGUAGE ( "" ) ) ;
const {
const {
saveButtonProps ,
saveButtonProps ,
refineCore : { formLoading } , // Indicates if the form is currently loading data
refineCore : { formLoading } ,
register , // Function to register input fields with react-hook-form
register ,
control , // Object to pass to Controller for controlled components
control ,
setValue , // Function to programmatically set form values
setValue ,
watch , // Function to watch for changes in form values
watch ,
handleSubmit , // Function to handle form submission, including validation
handleSubmit ,
formState : { errors } , // Object containing form validation errors
formState : { errors } ,
} = useForm < {
} = useForm < {
name : string ; // This field will hold the name for the *currently active* language
name : string ;
country_code : string ; // The code for the selected country
country_code : string ;
arms : string ; // The ID of the selected media for the city's arms (e.g., coat of arms)
arms : string ;
} > ( { } ) ;
} > ( { } ) ;
// useEffect hook to synchronize the 'name' TextField with the 'allLanguageNames' state .
// Keeps the 'name' input field synchronized with the currently active language's translation .
// W henever the active ' language' or the ' allLanguageNames' state changes,
// Updates w henever the active language or the ` allLanguageNames` state changes.
// the 'name' field in the form is updated to display the correct translation.
useEffect ( ( ) = > {
useEffect ( ( ) = > {
setValue ( "name" , allLanguageNames [ language ] ) ;
setValue ( "name" , allLanguageNames [ language ] ) ;
} , [ language , allLanguageNames , setValue ] ) ;
} , [ language , allLanguageNames , setValue ] ) ;
// Function to update the 'allLanguageNames' state with the current value from the 'name' TextField.
// Captures the current value o f the 'name' TextField and updates the `allLanguageNames` state .
// This is crucial to ensure that changes made by the user are saved before
// This is vital for preserving user input when switching languages or before form submission.
// switching languages or submitting the form.
const updateCurrentLanguageName = ( ) = > {
const updateCurrentLanguageName = ( ) = > {
const currentNameValue = watch ( "name" ) ; // Get the current value typed into the 'name' TextField
const currentNameValue = watch ( "name" ) ;
setAllLanguageNames ( ( prev ) = > ( {
setAllLanguageNames ( ( prev ) = > ( {
. . . prev ,
. . . prev ,
[ language ] : currentNameValue || "" , // Update the specific language entry in the state
[ language ] : currentNameValue || "" ,
} ) ) ;
} ) ) ;
} ;
} ;
// Handler for language switch .
// Handles language changes. It first saves the current input, then updates the active language .
// It first saves the current input for the active language, then changes the language.
const handleLanguageChange = ( lang : Languages ) = > {
const handleLanguageChange = ( lang : Languages ) = > {
updateCurrentLanguageName ( ) ; // Save the current input before switching language
updateCurrentLanguageName ( ) ;
setLanguageAction ( lang ) ; // Update the global language state
setLanguageAction ( lang ) ;
} ;
} ;
// Autocomplete prop s for Country selection .
// Autocomplete hook s for selecting a country and city arms (media) .
// '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 ( {
const { autocompleteProps : countryAutocompleteProps } = useAutocomplete ( {
resource : "country" ,
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 ( {
const { autocompleteProps : mediaAutocompleteProps } = useAutocomplete ( {
resource : "media" ,
resource : "media" ,
onSearch : ( value ) = > [
onSearch : ( value ) = > [
@ -80,60 +73,61 @@ export const CityCreate = () => {
] ,
] ,
} ) ;
} ) ;
// This function is called when the form is submitted and passes validation.
// --- Form Submission Logic ---
// It orchestrates the API calls for creating the city and updating its translations.
// Handles the form submission. It saves the current language's input,
// validates the Russian name, and then sends requests to create/update city data
// across different languages.
const onFinish = async ( data : {
const onFinish = async ( data : {
name : string ;
name : string ;
country_code : string ;
country_code : string ;
arms : 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 ( ) ;
updateCurrentLanguageName ( ) ;
// Create a final set of names including the latest value from the form
// for the current language.
const finalNames = {
const finalNames = {
. . . allLanguageNames ,
. . . allLanguageNames ,
[ language ] : data . name , // Ensure the absolute latest value from the form is included
[ language ] : data . name ,
} ;
} ;
try {
try {
// Validate that the Russian name is present, as it's used for the initial POST request.
if ( ! finalNames . ru ) {
if ( ! finalNames [ "ru" ] ) {
console . error ( "Russian name is required for initial city creation." ) ;
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.
if ( notification && typeof notification . open === "function" ) {
notification . open ( {
message : "Ошибка" ,
description : "Русское название города обязательно для создания." ,
type : "error" ,
} ) ;
}
return ;
return ;
}
}
console . log ( "Submitting with names:" , finalNames ) ;
console . log ( "Submitting with names:" , finalNames ) ;
// 1. Create the city entry in Russian .
// Create the city with the Russian name first .
// This POST request typically returns the ID of the newly created resource.
const ruResponse = await axiosInstanceForGet ( "ru" ) . post ( "/city" , {
const ruResponse = await axiosInstanceForGet ( "ru" ) . post ( "/city" , {
name : finalNames [ "ru" ] , // Russian name
name : finalNames.ru ,
country_code : data.country_code , // Country code from the form
country_code : data.country_code ,
arms : data.arms , // Arms ID from the form, included in initial creation
arms : data.arms ,
} ) ;
} ) ;
const id = ruResponse . data . id ; // Extract the ID of the newly created city
const id = ruResponse . data . id ;
// 2. Patch the city with the English name if available.
// Update the city with English and Chinese names if available.
if ( finalNames [ "en" ] ) {
if ( finalNames . en ) {
await axiosInstanceForGet ( "en" ) . patch ( ` /city/ ${ id } ` , {
await axiosInstanceForGet ( "en" ) . patch ( ` /city/ ${ id } ` , {
name : finalNames [ "en" ] , // English name
name : finalNames.en ,
country_code : data.country_code , // Country code from the form
country_code : data.country_code ,
arms : data.arms , // Arms ID from the form, included in initial creation
arms : data.arms ,
} ) ;
} ) ;
}
}
// 3. Patch the city with the Chinese name if available.
if ( finalNames . zh ) {
if ( finalNames [ "zh" ] ) {
await axiosInstanceForGet ( "zh" ) . patch ( ` /city/ ${ id } ` , {
await axiosInstanceForGet ( "zh" ) . patch ( ` /city/ ${ id } ` , {
name : finalNames [ "zh" ] , // Chinese name
name : finalNames.zh ,
country_code : data.country_code , // Country code from the form
country_code : data.country_code ,
arms : data.arms , // Arms ID from the form, included in initial creation
arms : data.arms ,
} ) ;
} ) ;
}
}
@ -144,47 +138,38 @@ export const CityCreate = () => {
type : "success" ,
type : "success" ,
} ) ;
} ) ;
}
}
window . onbeforeunload = null ;
navigate ( "/city" ) ;
// After successful creation/update, you might want to:
navigate ( "/city" , { replace : true } ) ;
// - Navigate to a different page (e.g., city list).
// - Display a success notification to the user.
} catch ( error ) {
} catch ( error ) {
console . error ( "Error creating/updating city:" , error ) ;
console . error ( "Error creating/updating city:" , error ) ;
// Display an error message to the user if the API calls fail.
}
}
} ;
} ;
return (
return (
< Create
< Create
isLoading = { formLoading } // Show loading indicator if form data is being fetched
isLoading = { formLoading }
// Pass the handleSubmit function to the save button's onClick.
// This ensures form validation runs before 'onFinish' is called.
saveButtonProps = { {
saveButtonProps = { {
. . . saveButtonProps ,
. . . saveButtonProps ,
disabled : saveButtonProps.disabled ,
onClick : handleSubmit ( onFinish as any ) ,
onClick : handleSubmit ( onFinish as any ) ,
} }
} }
>
>
< Box
< Box
component = "form"
component = "form"
sx = { { display : "flex" , flexDirection : "column" } }
sx = { { display : "flex" , flexDirection : "column" } }
autoComplete = "off" // Disable browser autocomplete
autoComplete = "off"
>
>
{ /* Language Switch component to change the active language */ }
< LanguageSwitch action = { handleLanguageChange } / >
< LanguageSwitch action = { handleLanguageChange } / >
{ /* Country Autocomplete field */ }
< Controller
< Controller
control = { control } // Pass control from useForm
control = { control }
name = "country_code" // Field name in the form state
name = "country_code"
rules = { { required : "Это поле является обязательным" } } // Validation rule
rules = { { required : "Это поле является обязательным" } }
defaultValue = { null } // Initial value
defaultValue = { null }
render = { ( { field } ) = > (
render = { ( { field } ) = > (
< Autocomplete
< Autocomplete
{ ...countryAutocompleteProps } // Spread autocomplete props from useAutocomplete hook
{ ...countryAutocompleteProps }
value = {
value = {
// Find the selected option based on the field's current value
countryAutocompleteProps . options . find (
countryAutocompleteProps . options . find (
( option : { code : string ; name : string ; id : string } ) = >
( option : { code : string ; name : string ; id : string } ) = >
option . code === field . value
option . code === field . value
@ -194,7 +179,6 @@ export const CityCreate = () => {
_ ,
_ ,
value : { code : string ; name : string ; id : string } | null
value : { code : string ; name : string ; id : string } | null
) = > {
) = > {
// Update the form field's value when an option is selected
field . onChange ( value ? . code || "" ) ;
field . onChange ( value ? . code || "" ) ;
} }
} }
getOptionLabel = { ( item : {
getOptionLabel = { ( item : {
@ -202,14 +186,12 @@ export const CityCreate = () => {
name : string ;
name : string ;
id : string ;
id : string ;
} ) = > {
} ) = > {
// Define how to display the label for each option
return item ? item . name : "" ;
return item ? item . name : "" ;
} }
} }
isOptionEqualToValue = { (
isOptionEqualToValue = { (
option : { code : string ; name : string ; id : string } ,
option : { code : string ; name : string ; id : string } ,
value : { 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 ;
return option . id === value ? . id ;
} }
} }
renderInput = { ( params ) = > (
renderInput = { ( params ) = > (
@ -218,39 +200,38 @@ export const CityCreate = () => {
label = "Выберите страну"
label = "Выберите страну"
margin = "normal"
margin = "normal"
variant = "outlined"
variant = "outlined"
error = { ! ! errors . country_code } // Show error state if validation fails
error = { ! ! errors . country_code }
required // Mark as required in UI
helperText = { errors . country_code ? . message }
required
/ >
/ >
) }
) }
/ >
/ >
) }
) }
/ >
/ >
{ /* City Name TextField for the current language */ }
< TextField
< TextField
{ ...register ( "name" , {
{ ...register ( "name" , {
required : "Это поле является обязательным" , // Validation rule
required : "Это поле является обязательным" ,
onBlur : updateCurrentLanguageName , // Update translations when the field loses focus
onBlur : updateCurrentLanguageName ,
} ) }
} ) }
error = { ! ! errors . name } // Show error state if validation fails
error = { ! ! errors . name }
helperText = { errors . name ? . message }
margin = "normal"
margin = "normal"
fullWidth
fullWidth
InputLabelProps = { { shrink : true } }
InputLabelProps = { { shrink : true } }
type = "text"
type = "text"
label = { "Название *" }
label = { "Название *" }
name = "name" // HTML name attribute, matches register key
name = "name"
/ >
/ >
{ /* Arms Autocomplete field */ }
< Controller
< Controller
control = { control } // Pass control from useForm
control = { control }
name = "arms" // Field name in the form state
name = "arms"
defaultValue = { null } // Initial value
defaultValue = { null }
render = { ( { field } ) = > (
render = { ( { field } ) = > (
< Autocomplete
< Autocomplete
{ ...mediaAutocompleteProps } // Spread autocomplete props from useAutocomplete hook
{ ...mediaAutocompleteProps }
value = {
value = {
// Find the selected option based on the field's current value
mediaAutocompleteProps . options . find (
mediaAutocompleteProps . options . find (
( option : { id : string ; media_name : string } ) = >
( option : { id : string ; media_name : string } ) = >
option . id === field . value
option . id === field . value
@ -260,25 +241,21 @@ export const CityCreate = () => {
_ ,
_ ,
value : { id : string ; media_name : string } | null
value : { id : string ; media_name : string } | null
) = > {
) = > {
// Update the form field's value when an option is selected
field . onChange ( value ? . id || "" ) ;
field . onChange ( value ? . id || "" ) ;
} }
} }
getOptionLabel = { ( item : { id : string ; media_name : string } ) = > {
getOptionLabel = { ( item : { id : string ; media_name : string } ) = > {
// Define how to display the label for each option
return item ? item . media_name : "" ;
return item ? item . media_name : "" ;
} }
} }
isOptionEqualToValue = { (
isOptionEqualToValue = { (
option : { id : string ; media_name : string } ,
option : { id : string ; media_name : string } ,
value : { id : string ; media_name : string }
value : { id : string ; media_name : string }
) = > {
) = > {
// Define how to compare options for equality
return option . id === value ? . id ;
return option . id === value ? . id ;
} }
} }
filterOptions = { (
filterOptions = { (
options : { id : string ; media_name : string } [ ] ,
options : { id : string ; media_name : string } [ ] ,
{ inputValue }
{ inputValue }
) = > {
) = > {
// Custom filter for options based on input value
return options . filter ( ( option ) = >
return options . filter ( ( option ) = >
option . media_name
option . media_name
. toLowerCase ( )
. toLowerCase ( )
@ -291,7 +268,8 @@ export const CityCreate = () => {
label = "Выберите г е р б "
label = "Выберите г е р б "
margin = "normal"
margin = "normal"
variant = "outlined"
variant = "outlined"
error = { ! ! errors . arms } // Show error state if validation fails
error = { ! ! errors . arms }
helperText = { errors . arms ? . message }
/ >
/ >
) }
) }
/ >
/ >