WhiteNightsAdminPanel/src/pages/city/create.tsx
itoshi 9218743faf
All checks were successful
release-tag / release-image (push) Successful in 2m16s
fix: Update language usage with review coordinates
2025-05-28 19:15:23 +03:00

304 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Autocomplete, Box, TextField } from "@mui/material";
import { Create, useAutocomplete } from "@refinedev/mui";
import { useForm } from "@refinedev/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 = () => {
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 {
saveButtonProps,
refineCore: { formLoading }, // Indicates if the form is currently loading data
register, // Function to register input fields with react-hook-form
control, // Object to pass to Controller for controlled components
setValue, // Function to programmatically set form values
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)
}>({});
// useEffect hook to synchronize the 'name' TextField with the 'allLanguageNames' state.
// 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]);
// Function to update the 'allLanguageNames' state with the current value from the 'name' TextField.
// 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) => [
{
field: "media_name",
operator: "contains",
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 (
<Create
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
control={control} // Pass control from useForm
name="country_code" // Field name in the form state
rules={{ required: "Это поле является обязательным" }} // Validation rule
defaultValue={null} // Initial value
render={({ field }) => (
<Autocomplete
{...countryAutocompleteProps} // Spread autocomplete props from useAutocomplete hook
value={
// Find the selected option based on the field's current value
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: {
code: string;
name: string;
id: string;
}) => {
// Define how to display the label for each option
return item ? item.name : "";
}}
isOptionEqualToValue={(
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} // Show error state if validation fails
required // Mark as required in UI
/>
)}
/>
)}
/>
{/* City Name TextField for the current language */}
<TextField
{...register("name", {
required: "Это поле является обязательным", // Validation rule
onBlur: updateCurrentLanguageName, // Update translations when the field loses focus
})}
error={!!errors.name} // Show error state if validation fails
margin="normal"
fullWidth
InputLabelProps={{ shrink: true }}
type="text"
label={"Название *"}
name="name" // HTML name attribute, matches register key
/>
{/* Arms Autocomplete field */}
<Controller
control={control} // Pass control from useForm
name="arms" // Field name in the form state
defaultValue={null} // Initial value
render={({ field }) => (
<Autocomplete
{...mediaAutocompleteProps} // Spread autocomplete props from useAutocomplete hook
value={
// Find the selected option based on the field's current value
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: { id: string; media_name: string }) => {
// Define how to display the label for each option
return item ? item.media_name : "";
}}
isOptionEqualToValue={(
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: { 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} // Show error state if validation fails
/>
)}
/>
)}
/>
</Box>
</Create>
);
};