fix: Fix service_name
for select list in sight page
Some checks failed
release-tag / release-image (push) Failing after 1m36s
Some checks failed
release-tag / release-image (push) Failing after 1m36s
This commit is contained in:
parent
9218743faf
commit
ffe75f3670
@ -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.
|
||||||
// Whenever the active 'language' or the 'allLanguageNames' state changes,
|
// Updates whenever 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 of 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 props for Country selection.
|
// Autocomplete hooks 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}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
@ -499,13 +499,13 @@ export const SightCreate = observer(() => {
|
|||||||
onChange={(_, value) => {
|
onChange={(_, value) => {
|
||||||
field.onChange(value?.id ?? "");
|
field.onChange(value?.id ?? "");
|
||||||
}}
|
}}
|
||||||
getOptionLabel={(item) => (item ? item.heading : "")}
|
getOptionLabel={(item) => (item ? item.service_name : "")}
|
||||||
isOptionEqualToValue={(option, value) =>
|
isOptionEqualToValue={(option, value) =>
|
||||||
option.id === value?.id
|
option.id === value?.id
|
||||||
}
|
}
|
||||||
filterOptions={(options, { inputValue }) =>
|
filterOptions={(options, { inputValue }) =>
|
||||||
options.filter((option) =>
|
options.filter((option) =>
|
||||||
option.heading
|
option.service_name
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.includes(inputValue.toLowerCase())
|
.includes(inputValue.toLowerCase())
|
||||||
)
|
)
|
||||||
|
@ -668,14 +668,14 @@ export const SightEdit = observer(() => {
|
|||||||
setLeftArticleData(undefined);
|
setLeftArticleData(undefined);
|
||||||
}}
|
}}
|
||||||
getOptionLabel={(item) => {
|
getOptionLabel={(item) => {
|
||||||
return item ? item.heading : "";
|
return item ? item.service_name : "";
|
||||||
}}
|
}}
|
||||||
isOptionEqualToValue={(option, value) => {
|
isOptionEqualToValue={(option, value) => {
|
||||||
return option.id === value?.id;
|
return option.id === value?.id;
|
||||||
}}
|
}}
|
||||||
filterOptions={(options, { inputValue }) => {
|
filterOptions={(options, { inputValue }) => {
|
||||||
return options.filter((option) =>
|
return options.filter((option) =>
|
||||||
option.heading
|
option.service_name
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.includes(inputValue.toLowerCase())
|
.includes(inputValue.toLowerCase())
|
||||||
);
|
);
|
||||||
@ -978,14 +978,14 @@ export const SightEdit = observer(() => {
|
|||||||
setLeftArticleData(undefined);
|
setLeftArticleData(undefined);
|
||||||
}}
|
}}
|
||||||
getOptionLabel={(item) => {
|
getOptionLabel={(item) => {
|
||||||
return item ? item.heading : "";
|
return item ? item.service_name : "";
|
||||||
}}
|
}}
|
||||||
isOptionEqualToValue={(option, value) => {
|
isOptionEqualToValue={(option, value) => {
|
||||||
return option.id === value?.id;
|
return option.id === value?.id;
|
||||||
}}
|
}}
|
||||||
filterOptions={(options, { inputValue }) => {
|
filterOptions={(options, { inputValue }) => {
|
||||||
return options.filter((option) =>
|
return options.filter((option) =>
|
||||||
option.heading
|
option.service_name
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.includes(inputValue.toLowerCase())
|
.includes(inputValue.toLowerCase())
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user