From ffe75f36701f46c59c9d05c631bd1adbeaa5e3f4 Mon Sep 17 00:00:00 2001 From: itoshi Date: Wed, 28 May 2025 20:54:35 +0300 Subject: [PATCH] fix: Fix `service_name` for select list in sight page --- src/pages/city/create.tsx | 176 ++++++++++++++++--------------------- src/pages/sight/create.tsx | 4 +- src/pages/sight/edit.tsx | 8 +- 3 files changed, 83 insertions(+), 105 deletions(-) diff --git a/src/pages/city/create.tsx b/src/pages/city/create.tsx index 647c1ea..a6e6717 100644 --- a/src/pages/city/create.tsx +++ b/src/pages/city/create.tsx @@ -3,9 +3,9 @@ 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 { EVERY_LANGUAGE, Languages, languageStore } from "@stores"; +import { LanguageSwitch } from "@/components/LanguageSwitch"; +import { axiosInstanceForGet } from "@/providers"; import { useNavigate } from "react-router"; import { useNotification } from "@refinedev/core"; @@ -13,62 +13,55 @@ 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. + + // State to manage city name translations across all supported languages. + // Initializes with empty strings for each language. const [allLanguageNames, setAllLanguageNames] = useState< Record >(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 + refineCore: { formLoading }, + register, + control, + setValue, + watch, + handleSubmit, + formState: { 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) + name: string; + country_code: string; + arms: string; }>({}); - // 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. + // Keeps the 'name' input field synchronized with the currently active language's translation. + // Updates whenever the active language or the `allLanguageNames` state changes. 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. + // Captures the current value of the 'name' TextField and updates the `allLanguageNames` state. + // This is vital for preserving user input when switching languages or before form submission. const updateCurrentLanguageName = () => { - const currentNameValue = watch("name"); // Get the current value typed into the 'name' TextField + const currentNameValue = watch("name"); setAllLanguageNames((prev) => ({ ...prev, - [language]: currentNameValue || "", // Update the specific language entry in the state + [language]: currentNameValue || "", })); }; - // Handler for language switch. - // It first saves the current input for the active language, then changes the language. + // Handles language changes. It first saves the current input, then updates the active language. const handleLanguageChange = (lang: Languages) => { - updateCurrentLanguageName(); // Save the current input before switching language - setLanguageAction(lang); // Update the global language state + updateCurrentLanguageName(); + setLanguageAction(lang); }; - // 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. + // Autocomplete hooks for selecting a country and city arms (media). 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) => [ @@ -80,60 +73,61 @@ export const CityCreate = () => { ], }); - // 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. + // --- Form Submission Logic --- + + // 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: { 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 + [language]: data.name, }; 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."); - // 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; } 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. + // Create the city with the Russian name first. 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 + name: finalNames.ru, + country_code: data.country_code, + 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. - if (finalNames["en"]) { + // Update the city with English and Chinese names 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 + name: finalNames.en, + country_code: data.country_code, + 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}`, { - 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 + name: finalNames.zh, + country_code: data.country_code, + arms: data.arms, }); } @@ -144,47 +138,38 @@ export const CityCreate = () => { 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. + navigate("/city", { replace: true }); } catch (error) { console.error("Error creating/updating city:", error); - // Display an error message to the user if the API calls fail. } }; return ( - {/* Language Switch component to change the active language */} - {/* Country Autocomplete field */} ( option.code === field.value @@ -194,7 +179,6 @@ export const CityCreate = () => { _, 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: { @@ -202,14 +186,12 @@ export const CityCreate = () => { 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) => ( @@ -218,39 +200,38 @@ export const CityCreate = () => { label="Выберите страну" margin="normal" variant="outlined" - error={!!errors.country_code} // Show error state if validation fails - required // Mark as required in UI + error={!!errors.country_code} + helperText={errors.country_code?.message} + required /> )} /> )} /> - {/* City Name TextField for the current language */} - {/* Arms Autocomplete field */} ( option.id === field.value @@ -260,25 +241,21 @@ export const CityCreate = () => { _, 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() @@ -291,7 +268,8 @@ export const CityCreate = () => { label="Выберите герб" margin="normal" variant="outlined" - error={!!errors.arms} // Show error state if validation fails + error={!!errors.arms} + helperText={errors.arms?.message} /> )} /> diff --git a/src/pages/sight/create.tsx b/src/pages/sight/create.tsx index 2be4644..1f19e45 100644 --- a/src/pages/sight/create.tsx +++ b/src/pages/sight/create.tsx @@ -499,13 +499,13 @@ export const SightCreate = observer(() => { onChange={(_, value) => { field.onChange(value?.id ?? ""); }} - getOptionLabel={(item) => (item ? item.heading : "")} + getOptionLabel={(item) => (item ? item.service_name : "")} isOptionEqualToValue={(option, value) => option.id === value?.id } filterOptions={(options, { inputValue }) => options.filter((option) => - option.heading + option.service_name .toLowerCase() .includes(inputValue.toLowerCase()) ) diff --git a/src/pages/sight/edit.tsx b/src/pages/sight/edit.tsx index 2a81981..d009bb4 100644 --- a/src/pages/sight/edit.tsx +++ b/src/pages/sight/edit.tsx @@ -668,14 +668,14 @@ export const SightEdit = observer(() => { setLeftArticleData(undefined); }} getOptionLabel={(item) => { - return item ? item.heading : ""; + return item ? item.service_name : ""; }} isOptionEqualToValue={(option, value) => { return option.id === value?.id; }} filterOptions={(options, { inputValue }) => { return options.filter((option) => - option.heading + option.service_name .toLowerCase() .includes(inputValue.toLowerCase()) ); @@ -978,14 +978,14 @@ export const SightEdit = observer(() => { setLeftArticleData(undefined); }} getOptionLabel={(item) => { - return item ? item.heading : ""; + return item ? item.service_name : ""; }} isOptionEqualToValue={(option, value) => { return option.id === value?.id; }} filterOptions={(options, { inputValue }) => { return options.filter((option) => - option.heading + option.service_name .toLowerCase() .includes(inputValue.toLowerCase()) );