sight edit page update

This commit is contained in:
Илья Куприец 2025-04-27 16:18:52 +03:00
parent 0d325a3aa6
commit a1a2264758
10 changed files with 485 additions and 393 deletions

View File

@ -80,7 +80,7 @@ import { KBarProvider, RefineKbar } from "@refinedev/kbar";
function App() { function App() {
return ( return (
<LoadingProvider> <LoadingProvider>
<HashRouter> <BrowserRouter>
<ColorModeContextProvider> <ColorModeContextProvider>
<CssBaseline /> <CssBaseline />
<GlobalStyles styles={{ html: { WebkitFontSmoothing: "auto" } }} /> <GlobalStyles styles={{ html: { WebkitFontSmoothing: "auto" } }} />
@ -425,7 +425,7 @@ function App() {
</DevtoolsProvider> </DevtoolsProvider>
</RefineSnackbarProvider> </RefineSnackbarProvider>
</ColorModeContextProvider> </ColorModeContextProvider>
</HashRouter> </BrowserRouter>
</LoadingProvider> </LoadingProvider>
); );
} }

View File

@ -56,6 +56,7 @@ type LinkedItemsProps<T> = {
parentResource: string; parentResource: string;
childResource: string; childResource: string;
fields: Field<T>[]; fields: Field<T>[];
setItemsParent?: (items: T[]) => void;
title: string; title: string;
type: "show" | "edit"; type: "show" | "edit";
extraField?: ExtraFieldConfig; extraField?: ExtraFieldConfig;
@ -74,6 +75,7 @@ export const LinkedItems = <T extends { id: number; [key: string]: any }>({
parentId, parentId,
parentResource, parentResource,
childResource, childResource,
setItemsParent,
fields, fields,
title, title,
dragAllowed = false, dragAllowed = false,
@ -135,6 +137,12 @@ export const LinkedItems = <T extends { id: number; [key: string]: any }>({
} }
}, [childResource, availableItems]); }, [childResource, availableItems]);
useEffect(() => {
if (setItemsParent) {
setItemsParent(linkedItems);
}
}, [linkedItems, setItemsParent]);
useEffect(() => { useEffect(() => {
// При загрузке linkedItems можно запросить текущие языки для статей // При загрузке linkedItems можно запросить текущие языки для статей
if (childResource === "article" && linkedItems.length > 0) { if (childResource === "article" && linkedItems.length > 0) {
@ -315,9 +323,7 @@ export const LinkedItems = <T extends { id: number; [key: string]: any }>({
{field.label} {field.label}
</TableCell> </TableCell>
))} ))}
{childResource === "article" && (
<TableCell key="language">Язык</TableCell>
)}
{type === "edit" && ( {type === "edit" && (
<TableCell width="120px">Действие</TableCell> <TableCell width="120px">Действие</TableCell>
)} )}
@ -366,96 +372,7 @@ export const LinkedItems = <T extends { id: number; [key: string]: any }>({
: item[field.data]} : item[field.data]}
</TableCell> </TableCell>
))} ))}
{childResource === "article" && (
<TableCell>
<Box
display="flex"
justifyContent="center"
alignItems="center"
flexDirection="column"
gap={1}
>
<Box
sx={{
padding: "4px",
cursor: "pointer",
background:
articleLanguages[item.id] === "RU"
? theme.palette.primary.main
: "transparent",
color:
articleLanguages[item.id] === "RU"
? theme.palette.primary.contrastText
: theme.palette.text.primary,
border:
articleLanguages[item.id] !== "RU"
? `1px solid ${theme.palette.primary.main}`
: "none",
}}
onClick={() =>
handleArticleLanguageChange(
item.id,
"RU"
)
}
>
RU
</Box>
<Box
sx={{
padding: "4px",
cursor: "pointer",
background:
articleLanguages[item.id] === "EN"
? theme.palette.primary.main
: "transparent",
color:
articleLanguages[item.id] === "EN"
? theme.palette.primary.contrastText
: theme.palette.text.primary,
border:
articleLanguages[item.id] !== "EN"
? `1px solid ${theme.palette.primary.main}`
: "none",
}}
onClick={() =>
handleArticleLanguageChange(
item.id,
"EN"
)
}
>
EN
</Box>
<Box
sx={{
padding: "4px",
cursor: "pointer",
background:
articleLanguages[item.id] === "ZH"
? theme.palette.primary.main
: "transparent",
color:
articleLanguages[item.id] === "ZH"
? theme.palette.primary.contrastText
: theme.palette.text.primary,
border:
articleLanguages[item.id] !== "ZH"
? `1px solid ${theme.palette.primary.main}`
: "none",
}}
onClick={() =>
handleArticleLanguageChange(
item.id,
"ZH"
)
}
>
ZN
</Box>
</Box>
</TableCell>
)}
{type === "edit" && ( {type === "edit" && (
<TableCell> <TableCell>
<Button <Button

View File

@ -2,8 +2,8 @@ import { Box, TextField, Typography, Paper } from "@mui/material";
import { Edit } from "@refinedev/mui"; import { Edit } 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 { useLocation, useParams } from "react-router"; import { useParams } from "react-router";
import React, { useState, useEffect } from "react"; import React, { useState, useEffect, useMemo } from "react";
import ReactMarkdown from "react-markdown"; import ReactMarkdown from "react-markdown";
import { useList } from "@refinedev/core"; import { useList } from "@refinedev/core";
@ -12,17 +12,13 @@ import { LinkedItems } from "../../components/LinkedItems";
import { MediaItem, mediaFields } from "./types"; import { MediaItem, mediaFields } from "./types";
import { TOKEN_KEY } from "../../authProvider"; import { TOKEN_KEY } from "../../authProvider";
import "easymde/dist/easymde.min.css"; import "easymde/dist/easymde.min.css";
import Cookies from "js-cookie"; import { languageStore } from "../../store/LanguageStore";
import { observer } from "mobx-react-lite";
const MemoizedSimpleMDE = React.memo(MarkdownEditor); const MemoizedSimpleMDE = React.memo(MarkdownEditor);
export const ArticleEdit = () => { export const ArticleEdit = observer(() => {
// const [initialLanguage] = useState(Cookies.get("lang")!); const { language, setLanguageAction } = languageStore;
// const { pathname } = useLocation();
// useEffect(() => {
// Cookies.set("lang", initialLanguage);
// }, [pathname]);
const [language, setLanguage] = useState(Cookies.get("lang") || "ru");
const [articleData, setArticleData] = useState<{ const [articleData, setArticleData] = useState<{
ru: { heading: string; body: string }; ru: { heading: string; body: string };
en: { heading: string; body: string }; en: { heading: string; body: string };
@ -35,7 +31,7 @@ export const ArticleEdit = () => {
const { id: articleId } = useParams<{ id: string }>(); const { id: articleId } = useParams<{ id: string }>();
const [preview, setPreview] = useState(""); const [preview, setPreview] = useState("");
const [headingPreview, setHeadingPreview] = useState(""); const [headingPreview, setHeadingPreview] = useState("");
const simpleMDEOptions = React.useMemo( const simpleMDEOptions = useMemo(
() => ({ () => ({
placeholder: "Введите контент в формате Markdown...", placeholder: "Введите контент в формате Markdown...",
spellChecker: false, spellChecker: false,
@ -51,7 +47,7 @@ export const ArticleEdit = () => {
watch, watch,
formState: { errors }, formState: { errors },
setValue, setValue,
} = useForm({ } = useForm<{ heading: string; body: string }>({
refineCoreProps: { refineCoreProps: {
meta: { meta: {
headers: { headers: {
@ -61,14 +57,6 @@ export const ArticleEdit = () => {
}, },
}); });
useEffect(() => {
const lang = Cookies.get("lang")!;
Cookies.set("lang", language);
return () => {
Cookies.set("lang", lang);
};
}, [language]);
useEffect(() => { useEffect(() => {
setValue( setValue(
"heading", "heading",
@ -88,12 +76,11 @@ export const ArticleEdit = () => {
setArticleData((prevData) => ({ setArticleData((prevData) => ({
...prevData, ...prevData,
[language]: { [language]: {
heading: watch("heading") || "", heading: watch("heading") ?? "",
body: watch("body") || "", body: watch("body") ?? "",
}, },
})); }));
setLanguage(lang); setLanguageAction(lang);
Cookies.set("lang", lang);
}; };
const bodyContent = watch("body"); const bodyContent = watch("body");
@ -107,17 +94,16 @@ export const ArticleEdit = () => {
setHeadingPreview(headingContent || ""); setHeadingPreview(headingContent || "");
}, [headingContent]); }, [headingContent]);
const onSubmit = (data: { heading: string; body: string }) => {
// Здесь вы будете отправлять данные на сервер,
// учитывая текущий язык (language)
console.log("Данные для сохранения:", data, language);
// ... ваша логика сохранения ...
};
const { data: mediaData } = useList<MediaItem>({ const { data: mediaData } = useList<MediaItem>({
resource: `article/${articleId}/media`, resource: `article/${articleId}/media`,
}); });
useEffect(() => {
return () => {
setLanguageAction("ru");
};
}, [setLanguageAction]);
return ( return (
<Edit saveButtonProps={saveButtonProps}> <Edit saveButtonProps={saveButtonProps}>
<Box sx={{ display: "flex", gap: 2 }}> <Box sx={{ display: "flex", gap: 2 }}>
@ -186,8 +172,8 @@ export const ArticleEdit = () => {
{...register("heading", { {...register("heading", {
required: "Это поле является обязательным", required: "Это поле является обязательным",
})} })}
error={!!(errors as any)?.heading} error={!!errors?.heading}
helperText={(errors as any)?.heading?.message} helperText={errors?.heading?.message as string}
margin="normal" margin="normal"
fullWidth fullWidth
InputLabelProps={{ shrink: true }} InputLabelProps={{ shrink: true }}
@ -347,4 +333,4 @@ export const ArticleEdit = () => {
</Box> </Box>
</Edit> </Edit>
); );
}; });

View File

@ -390,7 +390,7 @@ export const SightCreate = observer(() => {
renderInput={(params) => ( renderInput={(params) => (
<TextField <TextField
{...params} {...params}
label="Выберите обложку" label="Выберите логотип достопримечательности"
margin="normal" margin="normal"
variant="outlined" variant="outlined"
error={!!errors.thumbnail} error={!!errors.thumbnail}
@ -559,7 +559,7 @@ export const SightCreate = observer(() => {
renderInput={(params) => ( renderInput={(params) => (
<TextField <TextField
{...params} {...params}
label="атья-предпросмотр" label="Медиа-предпросмотр"
margin="normal" margin="normal"
variant="outlined" variant="outlined"
error={!!errors.preview_article} error={!!errors.preview_article}
@ -661,12 +661,12 @@ export const SightCreate = observer(() => {
gutterBottom gutterBottom
sx={{ color: "text.secondary" }} sx={{ color: "text.secondary" }}
> >
Обложка: Логотип достопримечательности:
</Typography> </Typography>
<Box <Box
component="img" component="img"
src={thumbnailPreview} src={thumbnailPreview}
alt="Обложка" alt="Логотип"
sx={{ sx={{
maxWidth: "100%", maxWidth: "100%",
height: "40vh", height: "40vh",

View File

@ -104,6 +104,11 @@ export const SightEdit = observer(() => {
operator: "contains", operator: "contains",
value, value,
}, },
{
field: "media_type",
operator: "contains",
value,
},
], ],
}); });
@ -134,6 +139,7 @@ export const SightEdit = observer(() => {
latitude: "", latitude: "",
longitude: "", longitude: "",
}); });
const [selectedArticleIndex, setSelectedArticleIndex] = useState(0);
const [cityPreview, setCityPreview] = useState(""); const [cityPreview, setCityPreview] = useState("");
const [thumbnailPreview, setThumbnailPreview] = useState<string | null>(null); const [thumbnailPreview, setThumbnailPreview] = useState<string | null>(null);
const [watermarkLUPreview, setWatermarkLUPreview] = useState<string | null>( const [watermarkLUPreview, setWatermarkLUPreview] = useState<string | null>(
@ -144,9 +150,10 @@ export const SightEdit = observer(() => {
); );
const [leftArticlePreview, setLeftArticlePreview] = useState(""); const [leftArticlePreview, setLeftArticlePreview] = useState("");
const [previewArticlePreview, setPreviewArticlePreview] = useState(""); const [previewArticlePreview, setPreviewArticlePreview] = useState("");
const [linkedArticles, setLinkedArticles] = useState<ArticleItem[]>([]);
// Следим за изменениями во всех полях // Следим за изменениями во всех полях
const coordinatesContent = watch("coordinates"); const selectedArticle = linkedArticles[selectedArticleIndex];
const addressContent = watch("address"); const addressContent = watch("address");
const nameContent = watch("name"); const nameContent = watch("name");
const latitudeContent = watch("latitude"); const latitudeContent = watch("latitude");
@ -163,6 +170,12 @@ export const SightEdit = observer(() => {
setNamePreview(nameContent || ""); setNamePreview(nameContent || "");
}, [nameContent]); }, [nameContent]);
useEffect(() => {
return () => {
setLanguageAction("ru");
};
}, []);
useEffect(() => { useEffect(() => {
setCoordinatesPreview({ setCoordinatesPreview({
latitude: latitudeContent || "", latitude: latitudeContent || "",
@ -245,13 +258,22 @@ export const SightEdit = observer(() => {
</Box> </Box>
<CustomTabPanel value={tabValue} index={0}> <CustomTabPanel value={tabValue} index={0}>
<Edit saveButtonProps={saveButtonProps}> <Edit
<Box sx={{ display: "flex", gap: 2 }}> saveButtonProps={saveButtonProps}
footerButtonProps={{
sx: {
bottom: 0,
left: 0,
},
}}
>
<Box sx={{ display: "flex", gap: 2, position: "relative" }}>
<Box <Box
sx={{ sx={{
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
flex: 1, flex: 1,
maxWidth: "50%",
gap: 10, gap: 10,
justifyContent: "space-between", justifyContent: "space-between",
}} }}
@ -330,17 +352,7 @@ export const SightEdit = observer(() => {
label={"Название *"} label={"Название *"}
name="name" name="name"
/> />
<TextField
value={`${coordinatesPreview.latitude}, ${coordinatesPreview.longitude}`}
onChange={handleCoordinatesChange}
error={!!(errors as any)?.latitude}
helperText={(errors as any)?.latitude?.message}
margin="normal"
fullWidth
InputLabelProps={{ shrink: true }}
type="text"
label={"Координаты *"}
/>
<input <input
type="hidden" type="hidden"
{...register("longitude", { {...register("longitude", {
@ -469,16 +481,18 @@ export const SightEdit = observer(() => {
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.media_name option.media_name
.toLowerCase() .toLowerCase()
.includes(inputValue.toLowerCase()) .includes(inputValue.toLowerCase()) &&
option.media_type === 3
); );
}} }}
renderInput={(params) => ( renderInput={(params) => (
<TextField <TextField
{...params} {...params}
label="Выберите обложку" label="Выберите логотип достопримечательности"
margin="normal" margin="normal"
variant="outlined" variant="outlined"
error={!!errors.arms} error={!!errors.arms}
@ -490,6 +504,430 @@ export const SightEdit = observer(() => {
)} )}
/> />
<Box sx={{ display: "none" }}>
<Controller
control={control}
name="watermark_lu"
defaultValue={null}
render={({ field }) => (
<Autocomplete
{...mediaAutocompleteProps}
value={
mediaAutocompleteProps.options.find(
(option) => option.id === field.value
) || null
}
onChange={(_, value) => {
field.onChange(value?.id || "");
}}
getOptionLabel={(item) => {
return item ? item.media_name : "";
}}
isOptionEqualToValue={(option, value) => {
return option.id === value?.id;
}}
filterOptions={(options, { 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}
required
/>
)}
/>
)}
/>
</Box>
<Box sx={{ display: "none" }}>
<Controller
control={control}
name="watermark_rd"
defaultValue={null}
render={({ field }) => (
<Autocomplete
{...mediaAutocompleteProps}
value={
mediaAutocompleteProps.options.find(
(option) => option.id === field.value
) || null
}
onChange={(_, value) => {
field.onChange(value?.id || "");
}}
getOptionLabel={(item) => {
return item ? item.media_name : "";
}}
isOptionEqualToValue={(option, value) => {
return option.id === value?.id;
}}
filterOptions={(options, { 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}
required
/>
)}
/>
)}
/>
</Box>
<Controller
control={control}
name="left_article"
defaultValue={null}
render={({ field }) => (
<Autocomplete
{...articleAutocompleteProps}
value={
articleAutocompleteProps.options.find(
(option) => option.id === field.value
) || null
}
onChange={(_, value) => {
field.onChange(value?.id || "");
}}
getOptionLabel={(item) => {
return item ? item.heading : "";
}}
isOptionEqualToValue={(option, value) => {
return option.id === value?.id;
}}
filterOptions={(options, { inputValue }) => {
return options.filter((option) =>
option.heading
.toLowerCase()
.includes(inputValue.toLowerCase())
);
}}
renderInput={(params) => (
<TextField
{...params}
label="Левая статья"
margin="normal"
variant="outlined"
error={!!errors.arms}
helperText={(errors as any)?.arms?.message}
required
/>
)}
/>
)}
/>
<Controller
control={control}
name="preview_article"
defaultValue={null}
render={({ field }) => (
<Autocomplete
{...articleAutocompleteProps}
value={
articleAutocompleteProps.options.find(
(option) => option.id === field.value
) || null
}
onChange={(_, value) => {
field.onChange(value?.id || "");
}}
getOptionLabel={(item) => {
return item ? item.heading : "";
}}
isOptionEqualToValue={(option, value) => {
return option.id === value?.id;
}}
filterOptions={(options, { inputValue }) => {
return options.filter((option) =>
option.heading
.toLowerCase()
.includes(inputValue.toLowerCase())
);
}}
renderInput={(params) => (
<TextField
{...params}
label="Медиа-предпросмотр"
margin="normal"
variant="outlined"
error={!!errors.arms}
helperText={(errors as any)?.arms?.message}
required
/>
)}
/>
)}
/>
</Box>
</Box>
{/* Блок предпросмотра */}
<Paper
sx={{
position: "fixed",
p: 2,
width: "30%",
top: "179px",
right: 50,
zIndex: 1000,
borderRadius: 2,
border: "1px solid",
borderColor: "primary.main",
bgcolor: (theme) =>
theme.palette.mode === "dark" ? "background.paper" : "#fff",
}}
>
<Typography variant="h6" gutterBottom color="primary">
Предпросмотр
</Typography>
{/* Название достопримечательности */}
<Typography
variant="h4"
sx={{
wordWrap: "break-word",
color: (theme) =>
theme.palette.mode === "dark" ? "grey.300" : "grey.800",
mb: 3,
}}
>
{namePreview}
</Typography>
{/* Город */}
<Typography variant="body1" sx={{ mb: 2 }}>
<Box component="span" sx={{ color: "text.secondary" }}>
Город:{" "}
</Box>
<Box
component="span"
sx={{
color: (theme) =>
theme.palette.mode === "dark" ? "grey.300" : "grey.800",
}}
>
{cityPreview}
</Box>
</Typography>
{/* Адрес */}
<Typography variant="body1" sx={{ mb: 2 }}>
<Box component="span" sx={{ color: "text.secondary" }}>
Адрес:{" "}
</Box>
<Box
component="span"
sx={{
color: (theme) =>
theme.palette.mode === "dark" ? "grey.300" : "grey.800",
}}
>
{addressContent}
</Box>
</Typography>
{/* Обложка */}
{thumbnailPreview && (
<Box sx={{ mb: 2 }}>
<Typography
variant="body1"
gutterBottom
sx={{ color: "text.secondary" }}
>
Логотип достопримечательности:
</Typography>
<Box
component="img"
src={thumbnailPreview}
alt="Логотип"
sx={{
maxWidth: "100%",
height: "40vh",
borderRadius: 2,
border: "1px solid",
borderColor: "primary.main",
}}
/>
</Box>
)}
</Paper>
</Box>
</Edit>
</CustomTabPanel>
<CustomTabPanel value={tabValue} index={1}>
<Edit
saveButtonProps={saveButtonProps}
footerButtonProps={{
sx: {
bottom: 0,
left: 0,
},
}}
>
<Box
sx={{
maxWidth: "50%",
display: "flex",
flexDirection: "column",
gap: 2,
position: "relative",
}}
>
<Box
component="form"
sx={{ flex: 1, display: "flex", flexDirection: "column" }}
autoComplete="off"
>
<TextField
{...register("name", {
required: "Это поле является обязательным",
})}
error={!!(errors as any)?.name}
helperText={(errors as any)?.name?.message}
margin="normal"
fullWidth
InputLabelProps={{ shrink: true }}
type="text"
label={"Название *"}
name="name"
/>
</Box>
<Box sx={{ mt: 3 }}>
<LinkedItems<ArticleItem>
type="edit"
parentId={sightId!}
setItemsParent={setLinkedArticles}
parentResource="sight"
fields={articleFields}
childResource="article"
title="статьи"
/>
<CreateSightArticle
parentId={sightId!}
parentResource="sight"
childResource="article"
title="статью"
/>
</Box>
</Box>
</Edit>
<Paper
sx={{
position: "fixed",
p: 2,
width: "30%",
top: "178px",
right: 50,
zIndex: 1000,
borderRadius: 2,
border: "1px solid",
borderColor: "primary.main",
bgcolor: (theme) =>
theme.palette.mode === "dark" ? "background.paper" : "#fff",
}}
>
<Typography variant="h6" gutterBottom color="primary">
Предпросмотр
</Typography>
{/* Водяные знаки */}
<Box sx={{ mb: 2 }}>
<Box sx={{ display: "flex", flexDirection: "column", gap: 2 }}>
{selectedArticle && (
<Typography
variant="h4"
gutterBottom
sx={{ color: "text.primary" }}
>
{selectedArticle.heading}
</Typography>
)}
{selectedArticle && (
<Typography
variant="body1"
gutterBottom
sx={{ color: "text.primary" }}
>
{selectedArticle.body}
</Typography>
)}
</Box>
{/* Координаты */}
<Box sx={{ display: "flex", gap: 1, mt: 2 }}>
{linkedArticles.map((article, index) => (
<Box
key={article.id}
onClick={() => setSelectedArticleIndex(index)}
sx={{
cursor: "pointer",
bgcolor:
selectedArticleIndex === index
? "primary.main"
: "transparent",
color: selectedArticleIndex === index ? "white" : "inherit",
p: 1,
borderRadius: 1,
}}
>
<Typography variant="body1" gutterBottom>
{article.heading}
</Typography>
</Box>
))}
</Box>
</Box>
</Paper>
</CustomTabPanel>
<CustomTabPanel value={tabValue} index={2}>
<Edit
saveButtonProps={saveButtonProps}
footerButtonProps={{
sx: {
bottom: 0,
left: 0,
},
}}
>
<Box
sx={{
maxWidth: "50%",
display: "flex",
gap: 2,
position: "relative",
}}
>
<Box
component="form"
sx={{ flex: 1, display: "flex", flexDirection: "column" }}
autoComplete="off"
>
<Controller <Controller
control={control} control={control}
name="watermark_lu" name="watermark_lu"
@ -576,102 +1014,30 @@ export const SightEdit = observer(() => {
)} )}
/> />
<Controller
control={control}
name="left_article"
defaultValue={null}
render={({ field }) => (
<Autocomplete
{...articleAutocompleteProps}
value={
articleAutocompleteProps.options.find(
(option) => option.id === field.value
) || null
}
onChange={(_, value) => {
field.onChange(value?.id || "");
}}
getOptionLabel={(item) => {
return item ? item.heading : "";
}}
isOptionEqualToValue={(option, value) => {
return option.id === value?.id;
}}
filterOptions={(options, { inputValue }) => {
return options.filter((option) =>
option.heading
.toLowerCase()
.includes(inputValue.toLowerCase())
);
}}
renderInput={(params) => (
<TextField <TextField
{...params} value={`${coordinatesPreview.latitude}, ${coordinatesPreview.longitude}`}
label="Левая статья" onChange={handleCoordinatesChange}
error={!!(errors as any)?.latitude}
helperText={(errors as any)?.latitude?.message}
margin="normal" margin="normal"
variant="outlined" fullWidth
error={!!errors.arms} InputLabelProps={{ shrink: true }}
helperText={(errors as any)?.arms?.message} type="text"
required label={"Координаты *"}
/>
)}
/>
)}
/>
<Controller
control={control}
name="preview_article"
defaultValue={null}
render={({ field }) => (
<Autocomplete
{...articleAutocompleteProps}
value={
articleAutocompleteProps.options.find(
(option) => option.id === field.value
) || null
}
onChange={(_, value) => {
field.onChange(value?.id || "");
}}
getOptionLabel={(item) => {
return item ? item.heading : "";
}}
isOptionEqualToValue={(option, value) => {
return option.id === value?.id;
}}
filterOptions={(options, { inputValue }) => {
return options.filter((option) =>
option.heading
.toLowerCase()
.includes(inputValue.toLowerCase())
);
}}
renderInput={(params) => (
<TextField
{...params}
label="Cтатья-предпросмотр"
margin="normal"
variant="outlined"
error={!!errors.arms}
helperText={(errors as any)?.arms?.message}
required
/>
)}
/>
)}
/> />
</Box> </Box>
</Box>
{/* Блок предпросмотра */}
<Paper <Paper
sx={{ sx={{
flex: 1, position: "fixed",
p: 2, p: 2,
position: "sticky", width: "30%",
top: 16,
top: "178px",
right: 50,
zIndex: 1000,
borderRadius: 2, borderRadius: 2,
border: "1px solid", border: "1px solid",
borderColor: "primary.main", borderColor: "primary.main",
@ -683,92 +1049,6 @@ export const SightEdit = observer(() => {
Предпросмотр Предпросмотр
</Typography> </Typography>
{/* Название достопримечательности */}
<Typography
variant="h4"
gutterBottom
sx={{
color: (theme) =>
theme.palette.mode === "dark" ? "grey.300" : "grey.800",
mb: 3,
}}
>
{namePreview}
</Typography>
{/* Город */}
<Typography variant="body1" sx={{ mb: 2 }}>
<Box component="span" sx={{ color: "text.secondary" }}>
Город:{" "}
</Box>
<Box
component="span"
sx={{
color: (theme) =>
theme.palette.mode === "dark" ? "grey.300" : "grey.800",
}}
>
{cityPreview}
</Box>
</Typography>
{/* Адрес */}
<Typography variant="body1" sx={{ mb: 2 }}>
<Box component="span" sx={{ color: "text.secondary" }}>
Адрес:{" "}
</Box>
<Box
component="span"
sx={{
color: (theme) =>
theme.palette.mode === "dark" ? "grey.300" : "grey.800",
}}
>
{addressContent}
</Box>
</Typography>
{/* Координаты */}
<Typography variant="body1" sx={{ mb: 2 }}>
<Box component="span" sx={{ color: "text.secondary" }}>
Координаты:{" "}
</Box>
<Box
component="span"
sx={{
color: (theme) =>
theme.palette.mode === "dark" ? "grey.300" : "grey.800",
}}
>
{`${coordinatesPreview.latitude}, ${coordinatesPreview.longitude}`}
</Box>
</Typography>
{/* Обложка */}
{thumbnailPreview && (
<Box sx={{ mb: 2 }}>
<Typography
variant="body1"
gutterBottom
sx={{ color: "text.secondary" }}
>
Обложка:
</Typography>
<Box
component="img"
src={thumbnailPreview}
alt="Обложка"
sx={{
maxWidth: "100%",
height: "40vh",
borderRadius: 2,
border: "1px solid",
borderColor: "primary.main",
}}
/>
</Box>
)}
{/* Водяные знаки */} {/* Водяные знаки */}
<Box sx={{ mb: 2 }}> <Box sx={{ mb: 2 }}>
<Typography <Typography
@ -828,87 +1108,29 @@ export const SightEdit = observer(() => {
</Box> </Box>
)} )}
</Box> </Box>
</Box> {/* Координаты */}
<Typography
{/* Связанные статьи */} variant="body1"
<Box> sx={{ display: "flex", flexDirection: "column", mb: 2 }}
{/* <Typography variant="body1" gutterBottom sx={{color: 'text.secondary'}}> >
Связанные статьи:
</Typography> */}
{leftArticlePreview && (
<Typography variant="body1" gutterBottom>
<Box component="span" sx={{ color: "text.secondary" }}> <Box component="span" sx={{ color: "text.secondary" }}>
Левая статья:{" "} Координаты:{" "}
</Box> </Box>
<Box <Box
component={Link} component="span"
to={`/article/show/${watch("left_article")}`}
sx={{ sx={{
color: (theme) => color: (theme) =>
theme.palette.mode === "dark" theme.palette.mode === "dark" ? "grey.300" : "grey.800",
? "grey.300"
: "grey.800",
textDecoration: "none",
"&:hover": {
textDecoration: "underline",
},
}} }}
> >
{leftArticlePreview} {`${coordinatesPreview.latitude}, ${coordinatesPreview.longitude}`}
</Box> </Box>
</Typography> </Typography>
)}
{previewArticlePreview && (
<Typography variant="body1" gutterBottom>
<Box component="span" sx={{ color: "text.secondary" }}>
Статья-предпросмотр:{" "}
</Box>
<Box
component={Link}
to={`/article/show/${watch("preview_article")}`}
sx={{
color: (theme) =>
theme.palette.mode === "dark"
? "grey.300"
: "grey.800",
textDecoration: "none",
"&:hover": {
textDecoration: "underline",
},
}}
>
{previewArticlePreview}
</Box>
</Typography>
)}
</Box> </Box>
</Paper> </Paper>
</Box> </Box>
<Box sx={{ mt: 3 }}>
<LinkedItems<ArticleItem>
type="edit"
parentId={sightId!}
parentResource="sight"
childResource="article"
fields={articleFields}
title="статьи"
/>
<CreateSightArticle
parentId={sightId!}
parentResource="sight"
childResource="article"
title="статью"
/>
</Box>
</Edit> </Edit>
</CustomTabPanel> </CustomTabPanel>
<CustomTabPanel value={tabValue} index={1}>
1
</CustomTabPanel>
<CustomTabPanel value={tabValue} index={2}>
2
</CustomTabPanel>
</Box> </Box>
); );
}); });

View File

@ -195,58 +195,6 @@ export const StationEdit = () => {
/> />
)} )}
/> />
<TextField
{...register("offset_x", {
// required: 'Это поле является обязательным',
})}
error={!!(errors as any)?.offset_x}
helperText={(errors as any)?.offset_x?.message}
margin="normal"
fullWidth
InputLabelProps={{ shrink: true }}
type="number"
label={"Смещение (X)"}
name="offset_x"
/>
<TextField
{...register("offset_y", {
// required: 'Это поле является обязательным',
})}
error={!!(errors as any)?.offset_y}
helperText={(errors as any)?.offset_y?.message}
margin="normal"
fullWidth
InputLabelProps={{ shrink: true }}
type="number"
label={"Смещение (Y)"}
name="offset_y"
/>
{/* Группа полей пересадок */}
<Paper sx={{ p: 2, mt: 2 }}>
<Typography variant="h6" gutterBottom>
Пересадки
</Typography>
<Grid container spacing={2}>
{TRANSFER_FIELDS.map((field) => (
<Grid item xs={12} sm={6} md={4} key={field.name}>
<TextField
{...register(`transfers.${field.name}`)}
error={!!(errors as any)?.transfers?.[field.name]}
helperText={(errors as any)?.transfers?.[field.name]?.message}
margin="normal"
fullWidth
InputLabelProps={{ shrink: true }}
type="text"
label={field.label}
name={`transfers.${field.name}`}
/>
</Grid>
))}
</Grid>
</Paper>
</Box> </Box>
{stationId && ( {stationId && (

View File

@ -7,7 +7,7 @@ import {
ShowButton, ShowButton,
useDataGrid, useDataGrid,
} from "@refinedev/mui"; } from "@refinedev/mui";
import { Stack } from "@mui/material"; import { Stack, Typography } from "@mui/material";
import { CustomDataGrid } from "../../components/CustomDataGrid"; import { CustomDataGrid } from "../../components/CustomDataGrid";
import { localeText } from "../../locales/ru/localeText"; import { localeText } from "../../locales/ru/localeText";
import { cityStore } from "../../store/CityStore"; import { cityStore } from "../../store/CityStore";
@ -58,6 +58,19 @@ export const StationList = observer(() => {
align: "left", align: "left",
headerAlign: "left", headerAlign: "left",
}, },
{
field: "direction",
headerName: "Направление",
type: "boolean",
minWidth: 200,
display: "flex",
renderCell: ({ value }) => (
<Typography style={{ color: value ? "#48989f" : "#7f6b58" }}>
{value ? "прямой" : "обратный"}
</Typography>
),
},
{ {
field: "latitude", field: "latitude",
headerName: "Широта", headerName: "Широта",

View File

@ -29,6 +29,7 @@ export const stationFields: Array<FieldType<StationItem>> = [
// {label: 'ID', data: 'id'}, // {label: 'ID', data: 'id'},
{ label: "Название", data: "name" }, { label: "Название", data: "name" },
{ label: "Системное название", data: "system_name" }, { label: "Системное название", data: "system_name" },
// { label: "Направление", data: "direction" },
{ label: "Адрес", data: "address" }, { label: "Адрес", data: "address" },
// {label: 'Широта', data: 'latitude'}, // {label: 'Широта', data: 'latitude'},
// {label: 'Долгота', data: 'longitude'}, // {label: 'Долгота', data: 'longitude'},

View File

@ -17,7 +17,7 @@ axiosInstance.interceptors.request.use((config) => {
// Добавляем язык в кастомный заголовок // Добавляем язык в кастомный заголовок
config.headers["X-Language"] = "ru"; config.headers["X-Language"] = languageStore.language;
console.log("Request headers:", config.headers); console.log("Request headers:", config.headers);

View File

@ -1,7 +1,7 @@
import { makeAutoObservable } from "mobx"; import { makeAutoObservable } from "mobx";
class CityStore { class CityStore {
city_id: string = ""; city_id: string = "0";
constructor() { constructor() {
makeAutoObservable(this); makeAutoObservable(this);
@ -9,7 +9,12 @@ class CityStore {
} }
initialize() { initialize() {
this.city_id = localStorage.getItem("city_id") ?? ""; const id = localStorage.getItem("city_id");
if (id) {
this.city_id = id;
} else {
this.city_id = "0";
}
} }
setCityIdAction = (city_id: string) => { setCityIdAction = (city_id: string) => {