Compare commits
1 Commits
5440126898
...
master
Author | SHA1 | Date | |
---|---|---|---|
cf2a116ecb |
@ -2,7 +2,7 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" type="image/png" href="/favicon-ship.png" />
|
||||
<link rel="icon" type="image/png" href="/favicon_ship.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 3.8 KiB |
Binary file not shown.
Before Width: | Height: | Size: 7.2 KiB |
BIN
public/favicon_ship.png
Normal file
BIN
public/favicon_ship.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.3 KiB |
@ -15,9 +15,9 @@ export function MediaView({ media }: Readonly<{ media?: MediaData }>) {
|
||||
<Box
|
||||
sx={{
|
||||
maxHeight: "300px",
|
||||
width: "100%",
|
||||
width: "80%",
|
||||
height: "100%",
|
||||
maxWidth: "300px",
|
||||
maxWidth: "600px",
|
||||
display: "flex",
|
||||
flexGrow: 1,
|
||||
justifyContent: "center",
|
||||
|
@ -1,12 +1,11 @@
|
||||
import { Ship } from "lucide-react";
|
||||
import { ProjectIcon } from "./Icons";
|
||||
import { Logo } from "@/icons/Logo";
|
||||
|
||||
export default function SidebarTitle({ collapsed }: { collapsed: boolean }) {
|
||||
return (
|
||||
<div
|
||||
style={{ display: "flex", alignItems: "center", whiteSpace: "nowrap" }}
|
||||
>
|
||||
<Ship size={40} style={{ color: "#7f6b58" }} />
|
||||
<Logo width={40} height={40} />
|
||||
|
||||
{!collapsed && (
|
||||
<span style={{ marginLeft: 8, fontWeight: "bold" }}>Белые ночи</span>
|
||||
|
3
src/icons/124.svg
Normal file
3
src/icons/124.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 7.5 KiB |
22
src/icons/Logo.tsx
Normal file
22
src/icons/Logo.tsx
Normal file
File diff suppressed because one or more lines are too long
@ -3,19 +3,63 @@ import { Create, useAutocomplete } from "@refinedev/mui";
|
||||
import { useForm } from "@refinedev/react-hook-form";
|
||||
import { Controller } from "react-hook-form";
|
||||
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(() => {
|
||||
const { language } = languageStore;
|
||||
const { language, setLanguageAction } = languageStore;
|
||||
const {
|
||||
saveButtonProps,
|
||||
refineCore: { formLoading },
|
||||
register,
|
||||
control,
|
||||
setValue,
|
||||
watch,
|
||||
formState: { errors },
|
||||
} = 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({
|
||||
resource: "city",
|
||||
onSearch: (value) => [
|
||||
@ -45,6 +89,7 @@ export const CarrierCreate = observer(() => {
|
||||
sx={{ display: "flex", flexDirection: "column" }}
|
||||
autoComplete="off"
|
||||
>
|
||||
<LanguageSwitch action={handleLanguageChange} />
|
||||
<Controller
|
||||
control={control}
|
||||
name="city_id"
|
||||
@ -95,7 +140,7 @@ export const CarrierCreate = observer(() => {
|
||||
helperText={(errors as any)?.full_name?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
slotProps={{inputLabel: {shrink: true}}}
|
||||
slotProps={{ inputLabel: { shrink: true } }}
|
||||
type="text"
|
||||
label={"Полное имя *"}
|
||||
name="full_name"
|
||||
@ -109,16 +154,13 @@ export const CarrierCreate = observer(() => {
|
||||
helperText={(errors as any)?.short_name?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
slotProps={{inputLabel: {shrink: true}}}
|
||||
slotProps={{ inputLabel: { shrink: true } }}
|
||||
type="text"
|
||||
label={"Короткое имя"}
|
||||
name="short_name"
|
||||
/>
|
||||
|
||||
<Box component="form"
|
||||
sx={{ display: "flex" }}
|
||||
autoComplete="off"
|
||||
>
|
||||
<Box component="form" sx={{ display: "flex" }} autoComplete="off">
|
||||
<TextField
|
||||
{...register("main_color", {
|
||||
// required: 'Это поле является обязательным',
|
||||
@ -127,7 +169,7 @@ export const CarrierCreate = observer(() => {
|
||||
helperText={(errors as any)?.main_color?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
slotProps={{inputLabel: {shrink: true}}}
|
||||
slotProps={{ inputLabel: { shrink: true } }}
|
||||
type="color"
|
||||
label={"Основной цвет"}
|
||||
name="main_color"
|
||||
@ -149,7 +191,7 @@ export const CarrierCreate = observer(() => {
|
||||
helperText={(errors as any)?.left_color?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
slotProps={{inputLabel: {shrink: true}}}
|
||||
slotProps={{ inputLabel: { shrink: true } }}
|
||||
type="color"
|
||||
label={"Цвет левого виджета"}
|
||||
name="left_color"
|
||||
@ -172,7 +214,7 @@ export const CarrierCreate = observer(() => {
|
||||
helperText={(errors as any)?.right_color?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
slotProps={{inputLabel: {shrink: true}}}
|
||||
slotProps={{ inputLabel: { shrink: true } }}
|
||||
type="color"
|
||||
label={"Цвет правого виджета"}
|
||||
name="right_color"
|
||||
@ -195,7 +237,7 @@ export const CarrierCreate = observer(() => {
|
||||
helperText={(errors as any)?.slogan?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
slotProps={{inputLabel: {shrink: true}}}
|
||||
slotProps={{ inputLabel: { shrink: true } }}
|
||||
type="text"
|
||||
label={"Слоган"}
|
||||
name="slogan"
|
||||
|
@ -1,69 +1,224 @@
|
||||
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 { 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";
|
||||
import { LanguageSwitch } from "@/components/LanguageSwitch";
|
||||
import { axiosInstanceForGet } from "@/providers";
|
||||
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 manage city name translations across all supported languages.
|
||||
// Initializes with empty strings for each language.
|
||||
const [allLanguageNames, setAllLanguageNames] = useState<
|
||||
Record<Languages, string>
|
||||
>(EVERY_LANGUAGE(""));
|
||||
|
||||
const {
|
||||
saveButtonProps,
|
||||
refineCore: {formLoading},
|
||||
refineCore: { formLoading },
|
||||
register,
|
||||
control,
|
||||
formState: {errors},
|
||||
} = useForm({})
|
||||
setValue,
|
||||
watch,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
} = useForm<{
|
||||
name: string;
|
||||
country_code: string;
|
||||
arms: string;
|
||||
}>({});
|
||||
|
||||
const {autocompleteProps: countryAutocompleteProps} = useAutocomplete({
|
||||
resource: 'country',
|
||||
})
|
||||
// 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]);
|
||||
|
||||
const {autocompleteProps: mediaAutocompleteProps} = useAutocomplete({
|
||||
resource: 'media',
|
||||
// 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");
|
||||
setAllLanguageNames((prev) => ({
|
||||
...prev,
|
||||
[language]: currentNameValue || "",
|
||||
}));
|
||||
};
|
||||
|
||||
// Handles language changes. It first saves the current input, then updates the active language.
|
||||
const handleLanguageChange = (lang: Languages) => {
|
||||
updateCurrentLanguageName();
|
||||
setLanguageAction(lang);
|
||||
};
|
||||
|
||||
// Autocomplete hooks for selecting a country and city arms (media).
|
||||
const { autocompleteProps: countryAutocompleteProps } = useAutocomplete({
|
||||
resource: "country",
|
||||
});
|
||||
|
||||
const { autocompleteProps: mediaAutocompleteProps } = useAutocomplete({
|
||||
resource: "media",
|
||||
onSearch: (value) => [
|
||||
{
|
||||
field: 'media_name',
|
||||
operator: 'contains',
|
||||
field: "media_name",
|
||||
operator: "contains",
|
||||
value,
|
||||
},
|
||||
],
|
||||
})
|
||||
});
|
||||
|
||||
// --- 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;
|
||||
}) => {
|
||||
updateCurrentLanguageName();
|
||||
|
||||
const finalNames = {
|
||||
...allLanguageNames,
|
||||
[language]: data.name,
|
||||
};
|
||||
|
||||
try {
|
||||
if (!finalNames.ru) {
|
||||
console.error("Russian name is required for initial city creation.");
|
||||
if (notification && typeof notification.open === "function") {
|
||||
notification.open({
|
||||
message: "Ошибка",
|
||||
description: "Русское название города обязательно для создания.",
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Submitting with names:", finalNames);
|
||||
|
||||
// Create the city with the Russian name first.
|
||||
const ruResponse = await axiosInstanceForGet("ru").post("/city", {
|
||||
name: finalNames.ru,
|
||||
country_code: data.country_code,
|
||||
arms: data.arms,
|
||||
});
|
||||
|
||||
const id = ruResponse.data.id;
|
||||
|
||||
// Update the city with English and Chinese names if available.
|
||||
if (finalNames.en) {
|
||||
await axiosInstanceForGet("en").patch(`/city/${id}`, {
|
||||
name: finalNames.en,
|
||||
country_code: data.country_code,
|
||||
arms: data.arms,
|
||||
});
|
||||
}
|
||||
|
||||
if (finalNames.zh) {
|
||||
await axiosInstanceForGet("zh").patch(`/city/${id}`, {
|
||||
name: finalNames.zh,
|
||||
country_code: data.country_code,
|
||||
arms: data.arms,
|
||||
});
|
||||
}
|
||||
|
||||
console.log("City created/updated successfully!");
|
||||
if (notification && typeof notification.open === "function") {
|
||||
notification.open({
|
||||
message: "Город успешно создан",
|
||||
type: "success",
|
||||
});
|
||||
}
|
||||
|
||||
navigate("/city", { replace: true });
|
||||
} catch (error) {
|
||||
console.error("Error creating/updating city:", error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Create isLoading={formLoading} saveButtonProps={saveButtonProps}>
|
||||
<Box component="form" sx={{display: 'flex', flexDirection: 'column'}} autoComplete="off">
|
||||
<Create
|
||||
isLoading={formLoading}
|
||||
saveButtonProps={{
|
||||
...saveButtonProps,
|
||||
disabled: saveButtonProps.disabled,
|
||||
onClick: handleSubmit(onFinish as any),
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
component="form"
|
||||
sx={{ display: "flex", flexDirection: "column" }}
|
||||
autoComplete="off"
|
||||
>
|
||||
<LanguageSwitch action={handleLanguageChange} />
|
||||
|
||||
<Controller
|
||||
control={control}
|
||||
name="country_code"
|
||||
rules={{required: 'Это поле является обязательным'}}
|
||||
rules={{ required: "Это поле является обязательным" }}
|
||||
defaultValue={null}
|
||||
render={({field}) => (
|
||||
render={({ field }) => (
|
||||
<Autocomplete
|
||||
{...countryAutocompleteProps}
|
||||
value={countryAutocompleteProps.options.find((option) => option.code === field.value) || null}
|
||||
onChange={(_, value) => {
|
||||
field.onChange(value?.code || '')
|
||||
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
|
||||
) => {
|
||||
field.onChange(value?.code || "");
|
||||
}}
|
||||
getOptionLabel={(item) => {
|
||||
return item ? item.name : ''
|
||||
getOptionLabel={(item: {
|
||||
code: string;
|
||||
name: string;
|
||||
id: string;
|
||||
}) => {
|
||||
return item ? item.name : "";
|
||||
}}
|
||||
isOptionEqualToValue={(option, value) => {
|
||||
return option.id === value?.id
|
||||
isOptionEqualToValue={(
|
||||
option: { code: string; name: string; id: string },
|
||||
value: { code: string; name: string; id: string }
|
||||
) => {
|
||||
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}
|
||||
required
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
{...register('name', {
|
||||
required: 'Это поле является обязательным',
|
||||
{...register("name", {
|
||||
required: "Это поле является обязательным",
|
||||
onBlur: updateCurrentLanguageName,
|
||||
})}
|
||||
error={!!(errors as any)?.name}
|
||||
helperText={(errors as any)?.name?.message}
|
||||
error={!!errors.name}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{shrink: true}}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="text"
|
||||
label={'Название *'}
|
||||
label={"Название *"}
|
||||
name="name"
|
||||
/>
|
||||
|
||||
@ -71,27 +226,53 @@ export const CityCreate = () => {
|
||||
control={control}
|
||||
name="arms"
|
||||
defaultValue={null}
|
||||
render={({field}) => (
|
||||
render={({ field }) => (
|
||||
<Autocomplete
|
||||
{...mediaAutocompleteProps}
|
||||
value={mediaAutocompleteProps.options.find((option) => option.id === field.value) || null}
|
||||
onChange={(_, value) => {
|
||||
field.onChange(value?.id || '')
|
||||
value={
|
||||
mediaAutocompleteProps.options.find(
|
||||
(option: { id: string; media_name: string }) =>
|
||||
option.id === field.value
|
||||
) || null
|
||||
}
|
||||
onChange={(
|
||||
_,
|
||||
value: { id: string; media_name: string } | null
|
||||
) => {
|
||||
field.onChange(value?.id || "");
|
||||
}}
|
||||
getOptionLabel={(item) => {
|
||||
return item ? item.media_name : ''
|
||||
getOptionLabel={(item: { id: string; media_name: string }) => {
|
||||
return item ? item.media_name : "";
|
||||
}}
|
||||
isOptionEqualToValue={(option, value) => {
|
||||
return option.id === value?.id
|
||||
isOptionEqualToValue={(
|
||||
option: { id: string; media_name: string },
|
||||
value: { id: string; media_name: string }
|
||||
) => {
|
||||
return option.id === value?.id;
|
||||
}}
|
||||
filterOptions={(options, {inputValue}) => {
|
||||
return options.filter((option) => option.media_name.toLowerCase().includes(inputValue.toLowerCase()))
|
||||
filterOptions={(
|
||||
options: { id: string; media_name: string }[],
|
||||
{ inputValue }
|
||||
) => {
|
||||
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}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Box>
|
||||
</Create>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@ -1,13 +1,20 @@
|
||||
import {AuthPage, ThemedTitleV2} from '@refinedev/mui'
|
||||
import {ProjectIcon} from '../../components/ui/Icons'
|
||||
import { AuthPage, ThemedTitleV2 } from "@refinedev/mui";
|
||||
|
||||
import { Logo } from "@/icons/Logo";
|
||||
|
||||
export const Login = () => {
|
||||
return (
|
||||
<AuthPage
|
||||
type="login"
|
||||
title={<ThemedTitleV2 collapsed={false} text="Белые Ночи" icon={<ProjectIcon style={{color: '#7f6b58'}} />} />}
|
||||
title={
|
||||
<ThemedTitleV2
|
||||
collapsed={false}
|
||||
text="Белые Ночи"
|
||||
icon={<Logo width={24} height={24} />}
|
||||
/>
|
||||
}
|
||||
forgotPasswordLink={false}
|
||||
registerLink={false} // only admin can add users
|
||||
/>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@ -100,7 +100,6 @@ export const MediaList = observer(() => {
|
||||
columns={columns}
|
||||
localeText={localeText}
|
||||
getRowId={(row: any) => row.id}
|
||||
languageEnabled
|
||||
/>
|
||||
</List>
|
||||
);
|
||||
|
@ -114,10 +114,10 @@ export const StationLabel = observer(
|
||||
rotation={-rotation}
|
||||
>
|
||||
<pixiText
|
||||
anchor={{ x: 0.5, y: 0.5 }}
|
||||
anchor={{ x: 1, y: 0.5 }}
|
||||
text={station.name}
|
||||
position={{
|
||||
x: position.x / scale,
|
||||
x: position.x / scale + 24,
|
||||
y: position.y / scale,
|
||||
}}
|
||||
style={{
|
||||
@ -129,10 +129,10 @@ export const StationLabel = observer(
|
||||
|
||||
{ruLabel && (
|
||||
<pixiText
|
||||
anchor={{ x: 0.5, y: -1 }}
|
||||
anchor={{ x: 1, y: -1 }}
|
||||
text={ruLabel}
|
||||
position={{
|
||||
x: position.x / scale,
|
||||
x: position.x / scale + 24,
|
||||
y: position.y / scale,
|
||||
}}
|
||||
style={{
|
||||
|
@ -153,7 +153,11 @@ export const RouteMap = observer(() => {
|
||||
<Station
|
||||
station={obj}
|
||||
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(() => {
|
||||
const { language, setLanguageAction } = languageStore;
|
||||
const [coordinates, setCoordinates] = useState("");
|
||||
const [sightData, setSightData] = useState({
|
||||
name: EVERY_LANGUAGE(""),
|
||||
address: EVERY_LANGUAGE(""),
|
||||
@ -79,10 +80,7 @@ export const SightCreate = observer(() => {
|
||||
});
|
||||
|
||||
const [namePreview, setNamePreview] = useState("");
|
||||
const [coordinatesPreview, setCoordinatesPreview] = useState({
|
||||
latitude: "",
|
||||
longitude: "",
|
||||
});
|
||||
const [coordinatesPreview, setCoordinatesPreview] = useState("");
|
||||
|
||||
const [creatingArticleHeading, setCreatingArticleHeading] =
|
||||
useState<string>("");
|
||||
@ -102,13 +100,19 @@ export const SightCreate = observer(() => {
|
||||
const [previewArticlePreview, setPreviewArticlePreview] = useState("");
|
||||
|
||||
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);
|
||||
setCoordinates(e.target.value);
|
||||
if (e.target.value) {
|
||||
const [lat, lon] = e.target.value.split(" ").map((s) => s.trim());
|
||||
setCoordinatesPreview(
|
||||
`${lat ? Number(lat) : 0}, ${lon ? Number(lon) : 0}`
|
||||
);
|
||||
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 ?? "");
|
||||
}, [nameContent]);
|
||||
|
||||
useEffect(() => {
|
||||
setCoordinatesPreview({
|
||||
latitude: latitudeContent || "",
|
||||
longitude: longitudeContent || "",
|
||||
});
|
||||
}, [latitudeContent, longitudeContent]);
|
||||
|
||||
useEffect(() => {
|
||||
const selectedCity = cityAutocompleteProps.options.find(
|
||||
(option) => option.id === cityContent
|
||||
@ -276,7 +273,7 @@ export const SightCreate = observer(() => {
|
||||
/>
|
||||
|
||||
<TextField
|
||||
value={`${coordinatesPreview.latitude}, ${coordinatesPreview.longitude}`}
|
||||
value={coordinates}
|
||||
onChange={handleCoordinatesChange}
|
||||
error={!!(errors as any)?.latitude}
|
||||
helperText={(errors as any)?.latitude?.message}
|
||||
@ -286,10 +283,11 @@ export const SightCreate = observer(() => {
|
||||
type="text"
|
||||
label={"Координаты *"}
|
||||
/>
|
||||
|
||||
<input
|
||||
type="hidden"
|
||||
{...register("longitude", {
|
||||
value: coordinatesPreview.longitude,
|
||||
value: coordinates.split(" ")[1],
|
||||
required: "Это поле является обязательным",
|
||||
valueAsNumber: true,
|
||||
})}
|
||||
@ -297,7 +295,7 @@ export const SightCreate = observer(() => {
|
||||
<input
|
||||
type="hidden"
|
||||
{...register("latitude", {
|
||||
value: coordinatesPreview.latitude,
|
||||
value: coordinates.split(" ")[0],
|
||||
required: "Это поле является обязательным",
|
||||
valueAsNumber: true,
|
||||
})}
|
||||
@ -501,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())
|
||||
)
|
||||
@ -668,6 +666,7 @@ export const SightCreate = observer(() => {
|
||||
</Typography>
|
||||
|
||||
{/* Координаты */}
|
||||
{coordinatesPreview && (
|
||||
<Typography variant="body1" sx={{ mb: 2 }}>
|
||||
<Box component="span" sx={{ color: "text.secondary" }}>
|
||||
Координаты:{" "}
|
||||
@ -679,10 +678,10 @@ export const SightCreate = observer(() => {
|
||||
theme.palette.mode === "dark" ? "grey.300" : "grey.800",
|
||||
}}
|
||||
>
|
||||
{`${coordinatesPreview.latitude}, ${coordinatesPreview.longitude}`}
|
||||
{coordinatesPreview}
|
||||
</Box>
|
||||
</Typography>
|
||||
|
||||
)}
|
||||
{/* Обложка */}
|
||||
{thumbnailPreview && (
|
||||
<Box sx={{ mb: 2 }}>
|
||||
|
@ -70,6 +70,7 @@ export const SightEdit = observer(() => {
|
||||
name: EVERY_LANGUAGE(""),
|
||||
address: EVERY_LANGUAGE(""),
|
||||
});
|
||||
const [coordinates, setCoordinates] = useState("");
|
||||
|
||||
const {
|
||||
saveButtonProps,
|
||||
@ -163,37 +164,13 @@ export const SightEdit = observer(() => {
|
||||
setValue("address", sightData.address[language]);
|
||||
}, [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] =
|
||||
useState<string>("");
|
||||
const [creatingArticleBody, setCreatingArticleBody] = useState<string>("");
|
||||
|
||||
const [coordinatesPreview, setCoordinatesPreview] = useState({
|
||||
latitude: "",
|
||||
longitude: "",
|
||||
});
|
||||
const [coordinatesPreview, setCoordinatesPreview] = useState("");
|
||||
const [selectedArticleIndex, setSelectedArticleIndex] = useState(-1);
|
||||
const [previewMediaFile, setPreviewMediaFile] = useState<MediaData>();
|
||||
const [thumbnailPreview, setThumbnailPreview] = useState<string | null>(null);
|
||||
@ -243,12 +220,25 @@ export const SightEdit = observer(() => {
|
||||
const watermarkRDContent = watch("watermark_rd");
|
||||
|
||||
useEffect(() => {
|
||||
setCoordinatesPreview({
|
||||
latitude: latitudeContent ?? "",
|
||||
longitude: longitudeContent ?? "",
|
||||
});
|
||||
if (latitudeContent && longitudeContent) {
|
||||
setCoordinates(`${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(() => {
|
||||
if (linkedArticles[selectedArticleIndex]?.id) {
|
||||
getMedia(linkedArticles[selectedArticleIndex].id).then((media) => {
|
||||
@ -505,7 +495,6 @@ export const SightEdit = observer(() => {
|
||||
<input
|
||||
type="hidden"
|
||||
{...register("longitude", {
|
||||
value: coordinatesPreview.longitude,
|
||||
required: "Это поле является обязательным",
|
||||
valueAsNumber: true,
|
||||
})}
|
||||
@ -513,7 +502,6 @@ export const SightEdit = observer(() => {
|
||||
<input
|
||||
type="hidden"
|
||||
{...register("latitude", {
|
||||
value: coordinatesPreview.latitude,
|
||||
required: "Это поле является обязательным",
|
||||
valueAsNumber: true,
|
||||
})}
|
||||
@ -680,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())
|
||||
);
|
||||
@ -794,6 +782,7 @@ export const SightEdit = observer(() => {
|
||||
color: (theme) =>
|
||||
theme.palette.mode === "dark" ? "grey.300" : "grey.800",
|
||||
mb: 3,
|
||||
mt: 3,
|
||||
}}
|
||||
>
|
||||
{name}
|
||||
@ -990,14 +979,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())
|
||||
);
|
||||
@ -1535,7 +1524,7 @@ export const SightEdit = observer(() => {
|
||||
/>
|
||||
|
||||
<TextField
|
||||
value={`${coordinatesPreview.latitude}, ${coordinatesPreview.longitude}`}
|
||||
value={coordinates}
|
||||
onChange={handleCoordinatesChange}
|
||||
error={!!(errors as any)?.latitude}
|
||||
helperText={(errors as any)?.latitude?.message}
|
||||
@ -1687,7 +1676,7 @@ export const SightEdit = observer(() => {
|
||||
: "grey.800",
|
||||
}}
|
||||
>
|
||||
{`${coordinatesPreview.latitude}, ${coordinatesPreview.longitude}`}
|
||||
{coordinatesPreview}
|
||||
</Box>
|
||||
</Typography>
|
||||
</Box>
|
||||
|
@ -56,6 +56,7 @@ export const SightList = observer(() => {
|
||||
display: "flex",
|
||||
align: "left",
|
||||
headerAlign: "left",
|
||||
flex: 1,
|
||||
},
|
||||
{
|
||||
field: "latitude",
|
||||
|
@ -41,40 +41,19 @@ export const StationCreate = () => {
|
||||
},
|
||||
});
|
||||
|
||||
const [coordinatesPreview, setCoordinatesPreview] = useState({
|
||||
latitude: "",
|
||||
longitude: "",
|
||||
});
|
||||
const [coordinates, setCoordinates] = useState("");
|
||||
|
||||
const handleCoordinatesChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const [lat, lon] = e.target.value.split(",").map((s) => s.trim());
|
||||
setCoordinatesPreview({
|
||||
latitude: lat,
|
||||
longitude: lon,
|
||||
});
|
||||
setCoordinates(e.target.value);
|
||||
const [lat, lon] = e.target.value
|
||||
.replace(/,/g, "") // Remove all commas from the string
|
||||
.split(" ")
|
||||
.map((s) => s.trim());
|
||||
console.log(lat, lon);
|
||||
setValue("latitude", lat);
|
||||
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 = [
|
||||
{
|
||||
label: "Прямой",
|
||||
@ -188,7 +167,7 @@ export const StationCreate = () => {
|
||||
)}
|
||||
/>
|
||||
<TextField
|
||||
value={`${coordinatesPreview.latitude}, ${coordinatesPreview.longitude}`}
|
||||
value={coordinates}
|
||||
onChange={handleCoordinatesChange}
|
||||
error={!!(errors as any)?.latitude}
|
||||
helperText={(errors as any)?.latitude?.message}
|
||||
@ -198,10 +177,11 @@ export const StationCreate = () => {
|
||||
type="text"
|
||||
label={"Координаты *"}
|
||||
/>
|
||||
|
||||
<input
|
||||
type="hidden"
|
||||
{...register("latitude", {
|
||||
value: coordinatesPreview.latitude,
|
||||
value: coordinates.split(",")[0],
|
||||
setValueAs: (value) => {
|
||||
if (value === "") {
|
||||
return 0;
|
||||
@ -213,7 +193,7 @@ export const StationCreate = () => {
|
||||
<input
|
||||
type="hidden"
|
||||
{...register("longitude", {
|
||||
value: coordinatesPreview.longitude,
|
||||
value: coordinates.split(",")[1],
|
||||
setValueAs: (value) => {
|
||||
if (value === "") {
|
||||
return 0;
|
||||
|
@ -40,24 +40,24 @@ export const StationEdit = observer(() => {
|
||||
system_name: "",
|
||||
description: "",
|
||||
address: "",
|
||||
latitude: "",
|
||||
longitude: "",
|
||||
latitude: 0,
|
||||
longitude: 0,
|
||||
},
|
||||
en: {
|
||||
name: "",
|
||||
system_name: "",
|
||||
description: "",
|
||||
address: "",
|
||||
latitude: "",
|
||||
longitude: "",
|
||||
latitude: 0,
|
||||
longitude: 0,
|
||||
},
|
||||
zh: {
|
||||
name: "",
|
||||
system_name: "",
|
||||
description: "",
|
||||
address: "",
|
||||
latitude: "",
|
||||
longitude: "",
|
||||
latitude: 0,
|
||||
longitude: 0,
|
||||
},
|
||||
});
|
||||
|
||||
@ -69,8 +69,8 @@ export const StationEdit = observer(() => {
|
||||
system_name: watch("system_name") ?? "",
|
||||
description: watch("description") ?? "",
|
||||
address: watch("address") ?? "",
|
||||
latitude: watch("latitude") ?? "",
|
||||
longitude: watch("longitude") ?? "",
|
||||
latitude: Number(watch("latitude")) || 0,
|
||||
longitude: Number(watch("longitude")) || 0,
|
||||
},
|
||||
}));
|
||||
};
|
||||
@ -120,7 +120,7 @@ export const StationEdit = observer(() => {
|
||||
if (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(
|
||||
"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 || ""
|
||||
);
|
||||
}
|
||||
if (stationData[language as keyof typeof stationData]?.latitude) {
|
||||
if (
|
||||
stationData[language as keyof typeof stationData]?.latitude !== undefined
|
||||
) {
|
||||
setValue(
|
||||
"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(
|
||||
"longitude",
|
||||
stationData[language as keyof typeof stationData]?.longitude || ""
|
||||
stationData[language as keyof typeof stationData]?.longitude || 0
|
||||
);
|
||||
}
|
||||
}, [language, stationData, setValue]);
|
||||
@ -150,28 +154,36 @@ export const StationEdit = observer(() => {
|
||||
setLanguageAction("ru");
|
||||
}, []);
|
||||
|
||||
const { id: stationId } = useParams<{ id: string }>();
|
||||
const [coordinates, setCoordinates] = useState("");
|
||||
|
||||
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 { id: stationId } = useParams<{ id: string }>();
|
||||
|
||||
const latitudeContent = watch("latitude");
|
||||
const longitudeContent = watch("longitude");
|
||||
|
||||
useEffect(() => {
|
||||
setCoordinatesPreview({
|
||||
latitude: latitudeContent || "",
|
||||
longitude: longitudeContent || "",
|
||||
});
|
||||
if (latitudeContent && longitudeContent) {
|
||||
setCoordinates(`${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({
|
||||
resource: "city",
|
||||
onSearch: (value) => [
|
||||
@ -282,7 +294,7 @@ export const StationEdit = observer(() => {
|
||||
/>
|
||||
|
||||
<TextField
|
||||
value={`${coordinatesPreview.latitude}, ${coordinatesPreview.longitude}`}
|
||||
value={coordinates}
|
||||
onChange={handleCoordinatesChange}
|
||||
error={!!(errors as any)?.latitude}
|
||||
helperText={(errors as any)?.latitude?.message}
|
||||
@ -295,12 +307,28 @@ export const StationEdit = observer(() => {
|
||||
<input
|
||||
type="hidden"
|
||||
{...register("latitude", {
|
||||
value: coordinatesPreview.latitude,
|
||||
valueAsNumber: true,
|
||||
value: Number(coordinates.split(" ")[0]),
|
||||
setValueAs: (value) => {
|
||||
if (value === "") {
|
||||
return 0;
|
||||
}
|
||||
return Number(value);
|
||||
},
|
||||
})}
|
||||
/>
|
||||
<input
|
||||
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
|
||||
|
@ -104,6 +104,7 @@ export const VehicleList = observer(() => {
|
||||
type: "string",
|
||||
minWidth: 150,
|
||||
display: "flex",
|
||||
flex: 1,
|
||||
align: "left",
|
||||
headerAlign: "left",
|
||||
renderCell: (params) => {
|
||||
@ -116,6 +117,7 @@ export const VehicleList = observer(() => {
|
||||
headerName: "Город",
|
||||
type: "string",
|
||||
minWidth: 150,
|
||||
flex: 1,
|
||||
display: "flex",
|
||||
align: "left",
|
||||
headerAlign: "left",
|
||||
|
Reference in New Issue
Block a user