This commit is contained in:
parent
28826123ec
commit
16640cb116
@ -51,6 +51,7 @@
|
|||||||
"react-simple-maps": "^3.0.0",
|
"react-simple-maps": "^3.0.0",
|
||||||
"react-simplemde-editor": "^5.2.0",
|
"react-simplemde-editor": "^5.2.0",
|
||||||
"react-swipeable": "^7.0.2",
|
"react-swipeable": "^7.0.2",
|
||||||
|
"react-toastify": "^11.0.5",
|
||||||
"rehype-raw": "^7.0.0",
|
"rehype-raw": "^7.0.0",
|
||||||
"three": "^0.175.0",
|
"three": "^0.175.0",
|
||||||
"vite-plugin-svgr": "^4.3.0"
|
"vite-plugin-svgr": "^4.3.0"
|
||||||
|
@ -19,6 +19,7 @@ import {
|
|||||||
ALLOWED_VIDEO_TYPES,
|
ALLOWED_VIDEO_TYPES,
|
||||||
} from "../components/media/MediaFormUtils";
|
} from "../components/media/MediaFormUtils";
|
||||||
import { EVERY_LANGUAGE, Languages } from "@stores";
|
import { EVERY_LANGUAGE, Languages } from "@stores";
|
||||||
|
import { useNotification } from "@refinedev/core";
|
||||||
|
|
||||||
const MemoizedSimpleMDE = React.memo(MarkdownEditor);
|
const MemoizedSimpleMDE = React.memo(MarkdownEditor);
|
||||||
|
|
||||||
@ -30,14 +31,16 @@ type MediaFile = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
parentId: string | number;
|
parentId?: string | number;
|
||||||
parentResource: string;
|
parentResource: string;
|
||||||
childResource: string;
|
childResource: string;
|
||||||
title: string;
|
title: string;
|
||||||
left?: boolean;
|
left?: boolean;
|
||||||
language: Languages,
|
language: Languages;
|
||||||
setHeadingParent?: (heading: string) => void,
|
setHeadingParent?: (heading: string) => void;
|
||||||
setBodyParent?: (body: string) => void,
|
setBodyParent?: (body: string) => void;
|
||||||
|
onSave?: (something: any) => void;
|
||||||
|
noReset?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CreateSightArticle = ({
|
export const CreateSightArticle = ({
|
||||||
@ -48,8 +51,11 @@ export const CreateSightArticle = ({
|
|||||||
left,
|
left,
|
||||||
language,
|
language,
|
||||||
setHeadingParent,
|
setHeadingParent,
|
||||||
setBodyParent
|
setBodyParent,
|
||||||
|
onSave,
|
||||||
|
noReset,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
|
const notification = useNotification();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const [mediaFiles, setMediaFiles] = useState<MediaFile[]>([]);
|
const [mediaFiles, setMediaFiles] = useState<MediaFile[]>([]);
|
||||||
const [workingLanguage, setWorkingLanguage] = useState<Languages>(language);
|
const [workingLanguage, setWorkingLanguage] = useState<Languages>(language);
|
||||||
@ -69,10 +75,9 @@ export const CreateSightArticle = ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const [articleData, setArticleData] = useState({
|
const [articleData, setArticleData] = useState({
|
||||||
heading: EVERY_LANGUAGE(""),
|
heading: EVERY_LANGUAGE(""),
|
||||||
body: EVERY_LANGUAGE("")
|
body: EVERY_LANGUAGE(""),
|
||||||
});
|
});
|
||||||
|
|
||||||
function updateTranslations() {
|
function updateTranslations() {
|
||||||
@ -85,8 +90,8 @@ export const CreateSightArticle = ({
|
|||||||
body: {
|
body: {
|
||||||
...articleData.body,
|
...articleData.body,
|
||||||
[workingLanguage]: watch("body") ?? "",
|
[workingLanguage]: watch("body") ?? "",
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
setArticleData(newArticleData);
|
setArticleData(newArticleData);
|
||||||
return newArticleData;
|
return newArticleData;
|
||||||
}
|
}
|
||||||
@ -126,8 +131,12 @@ export const CreateSightArticle = ({
|
|||||||
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
||||||
onDrop,
|
onDrop,
|
||||||
accept: {
|
accept: {
|
||||||
"image/*": ALLOWED_IMAGE_TYPES,
|
"image/jpeg": [".jpeg", ".jpg"],
|
||||||
"video/*": ALLOWED_VIDEO_TYPES,
|
"image/png": [".png"],
|
||||||
|
"image/webp": [".webp"],
|
||||||
|
"video/mp4": [".mp4"],
|
||||||
|
"video/webm": [".webm"],
|
||||||
|
"video/ogg": [".ogg"],
|
||||||
},
|
},
|
||||||
multiple: true,
|
multiple: true,
|
||||||
});
|
});
|
||||||
@ -153,24 +162,29 @@ export const CreateSightArticle = ({
|
|||||||
try {
|
try {
|
||||||
// Создаем статью
|
// Создаем статью
|
||||||
const response = await axiosInstance.post(
|
const response = await axiosInstance.post(
|
||||||
`${import.meta.env.VITE_KRBL_API}/${childResource}`, {
|
`${import.meta.env.VITE_KRBL_API}/${childResource}`,
|
||||||
|
{
|
||||||
...data,
|
...data,
|
||||||
translations: updateTranslations()
|
translations: updateTranslations(),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
const itemId = response.data.id;
|
const itemId = response.data.id;
|
||||||
|
|
||||||
|
if (parentId) {
|
||||||
// Получаем существующие статьи для определения порядкового номера
|
// Получаем существующие статьи для определения порядкового номера
|
||||||
const existingItemsResponse = await axiosInstance.get(
|
const existingItemsResponse = await axiosInstance.get(
|
||||||
`${import.meta.env.VITE_KRBL_API}/${parentResource}/${parentId}/${childResource}`
|
`${
|
||||||
|
import.meta.env.VITE_KRBL_API
|
||||||
|
}/${parentResource}/${parentId}/${childResource}`
|
||||||
);
|
);
|
||||||
const existingItems = existingItemsResponse.data ?? [];
|
const existingItems = existingItemsResponse.data ?? [];
|
||||||
const nextPageNum = existingItems.length + 1;
|
const nextPageNum = existingItems.length + 1;
|
||||||
|
|
||||||
if (!left) {
|
if (!left) {
|
||||||
// Привязываем статью к достопримечательности если она не левая
|
|
||||||
await axiosInstance.post(
|
await axiosInstance.post(
|
||||||
`${import.meta.env.VITE_KRBL_API}/${parentResource}/${parentId}/${childResource}/`,
|
`${
|
||||||
|
import.meta.env.VITE_KRBL_API
|
||||||
|
}/${parentResource}/${parentId}/${childResource}/`,
|
||||||
{
|
{
|
||||||
[`${childResource}_id`]: itemId,
|
[`${childResource}_id`]: itemId,
|
||||||
page_num: nextPageNum,
|
page_num: nextPageNum,
|
||||||
@ -186,11 +200,12 @@ export const CreateSightArticle = ({
|
|||||||
`${import.meta.env.VITE_KRBL_API}/${parentResource}/${parentId}/`,
|
`${import.meta.env.VITE_KRBL_API}/${parentResource}/${parentId}/`,
|
||||||
{
|
{
|
||||||
...data,
|
...data,
|
||||||
left_article: itemId
|
left_article: itemId,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Загружаем все медиа файлы и получаем их ID
|
// Загружаем все медиа файлы и получаем их ID
|
||||||
const mediaIds = await Promise.all(
|
const mediaIds = await Promise.all(
|
||||||
@ -211,10 +226,21 @@ export const CreateSightArticle = ({
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
if (noReset) {
|
||||||
|
setValue("heading", "");
|
||||||
|
setValue("body", "");
|
||||||
|
} else {
|
||||||
resetItem();
|
resetItem();
|
||||||
setMediaFiles([]);
|
}
|
||||||
|
if (onSave) {
|
||||||
|
onSave(response.data);
|
||||||
|
notification.open({
|
||||||
|
message: "Статья успешно создана",
|
||||||
|
type: "success",
|
||||||
|
});
|
||||||
|
} else {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error("Error creating item:", err);
|
console.error("Error creating item:", err);
|
||||||
}
|
}
|
||||||
@ -345,7 +371,12 @@ export const CreateSightArticle = ({
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box sx={{ mt: 2, display: "flex", gap: 2 }}>
|
<Box sx={{ mt: 2, display: "flex", gap: 2 }}>
|
||||||
<Button variant="contained" color="primary" onClick={handleSubmitItem(handleCreate)}>
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
type="submit"
|
||||||
|
onClick={handleSubmitItem(handleCreate)}
|
||||||
|
>
|
||||||
Создать
|
Создать
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
@ -38,7 +38,7 @@ const style = {
|
|||||||
bgcolor: "background.paper",
|
bgcolor: "background.paper",
|
||||||
border: "2px solid #000",
|
border: "2px solid #000",
|
||||||
boxShadow: 24,
|
boxShadow: 24,
|
||||||
p: 4
|
p: 4,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ArticleEditModal = observer(() => {
|
export const ArticleEditModal = observer(() => {
|
||||||
@ -62,13 +62,11 @@ export const ArticleEditModal = observer(() => {
|
|||||||
|
|
||||||
// Load existing media files when editing an article
|
// Load existing media files when editing an article
|
||||||
const loadExistingMedia = async () => {
|
const loadExistingMedia = async () => {
|
||||||
console.log("Called loadExistingMedia")
|
console.log("Called loadExistingMedia");
|
||||||
if (selectedArticleId) {
|
if (selectedArticleId) {
|
||||||
try {
|
try {
|
||||||
const response = await axiosInstance.get(
|
const response = await axiosInstance.get(
|
||||||
`${
|
`${import.meta.env.VITE_KRBL_API}/article/${selectedArticleId}/media`
|
||||||
import.meta.env.VITE_KRBL_API
|
|
||||||
}/article/${selectedArticleId}/media`
|
|
||||||
);
|
);
|
||||||
const existingMedia = response.data;
|
const existingMedia = response.data;
|
||||||
|
|
||||||
@ -125,7 +123,9 @@ export const ArticleEditModal = observer(() => {
|
|||||||
try {
|
try {
|
||||||
// Upload new media files
|
// Upload new media files
|
||||||
const newMediaFiles = mediaFiles.filter((file) => !file.media_id);
|
const newMediaFiles = mediaFiles.filter((file) => !file.media_id);
|
||||||
const existingMediaAmount = mediaFiles.filter((file) => file.media_id).length;
|
const existingMediaAmount = mediaFiles.filter(
|
||||||
|
(file) => file.media_id
|
||||||
|
).length;
|
||||||
const mediaIds = await Promise.all(
|
const mediaIds = await Promise.all(
|
||||||
newMediaFiles.map(async (mediaFile) => {
|
newMediaFiles.map(async (mediaFile) => {
|
||||||
return await uploadMedia(mediaFile);
|
return await uploadMedia(mediaFile);
|
||||||
@ -164,10 +164,10 @@ export const ArticleEditModal = observer(() => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (articleData.heading[language]) {
|
if (articleData.heading[language]) {
|
||||||
setValue("heading", articleData.heading[language])
|
setValue("heading", articleData.heading[language]);
|
||||||
}
|
}
|
||||||
if (articleData.body[language]) {
|
if (articleData.body[language]) {
|
||||||
setValue("body", articleData.body[language])
|
setValue("body", articleData.body[language]);
|
||||||
}
|
}
|
||||||
}, [language, articleData, setValue]);
|
}, [language, articleData, setValue]);
|
||||||
|
|
||||||
@ -176,12 +176,12 @@ export const ArticleEditModal = observer(() => {
|
|||||||
...prevData,
|
...prevData,
|
||||||
heading: {
|
heading: {
|
||||||
...prevData.heading,
|
...prevData.heading,
|
||||||
[language]: watch("heading") ?? ""
|
[language]: watch("heading") ?? "",
|
||||||
},
|
},
|
||||||
body: {
|
body: {
|
||||||
...prevData.body,
|
...prevData.body,
|
||||||
[language]: watch("body") ?? ""
|
[language]: watch("body") ?? "",
|
||||||
}
|
},
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -205,8 +205,12 @@ export const ArticleEditModal = observer(() => {
|
|||||||
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
||||||
onDrop,
|
onDrop,
|
||||||
accept: {
|
accept: {
|
||||||
"image/*": ALLOWED_IMAGE_TYPES,
|
"image/jpeg": [".jpeg", ".jpg"],
|
||||||
"video/*": ALLOWED_VIDEO_TYPES,
|
"image/png": [".png"],
|
||||||
|
"image/webp": [".webp"],
|
||||||
|
"video/mp4": [".mp4"],
|
||||||
|
"video/webm": [".webm"],
|
||||||
|
"video/ogg": [".ogg"],
|
||||||
},
|
},
|
||||||
multiple: true,
|
multiple: true,
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
@import './stylesheets/hidden-functionality.css';
|
@import "./stylesheets/hidden-functionality.css";
|
||||||
@import './stylesheets/roles-functionality.css';
|
@import "./stylesheets/roles-functionality.css";
|
||||||
|
|
||||||
.limited-text {
|
.limited-text {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@ -7,3 +7,19 @@
|
|||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
-webkit-line-clamp: 2;
|
-webkit-line-clamp: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.backup-button {
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
width: 35px;
|
||||||
|
height: 35px;
|
||||||
|
color: #544044;
|
||||||
|
border-radius: 10%;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
.backup-button:hover {
|
||||||
|
background-color: rgba(84, 64, 68, 0.5);
|
||||||
|
}
|
||||||
|
@ -1,4 +1,13 @@
|
|||||||
import { Autocomplete, Box, TextField, Typography, Paper } from "@mui/material";
|
import {
|
||||||
|
Autocomplete,
|
||||||
|
Box,
|
||||||
|
TextField,
|
||||||
|
Typography,
|
||||||
|
Paper,
|
||||||
|
Accordion,
|
||||||
|
AccordionSummary,
|
||||||
|
AccordionDetails,
|
||||||
|
} from "@mui/material";
|
||||||
import { Create, useAutocomplete } from "@refinedev/mui";
|
import { Create, useAutocomplete } from "@refinedev/mui";
|
||||||
import { useForm } from "@refinedev/react-hook-form";
|
import { useForm } from "@refinedev/react-hook-form";
|
||||||
import { Controller, FieldValues } from "react-hook-form";
|
import { Controller, FieldValues } from "react-hook-form";
|
||||||
@ -8,11 +17,14 @@ import { TOKEN_KEY } from "@providers";
|
|||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { EVERY_LANGUAGE, Languages, languageStore, cityStore } from "@stores";
|
import { EVERY_LANGUAGE, Languages, languageStore, cityStore } from "@stores";
|
||||||
import { LanguageSelector } from "@ui";
|
import { LanguageSelector } from "@ui";
|
||||||
|
import { CreateSightArticle } from "@components";
|
||||||
|
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
||||||
|
|
||||||
export const SightCreate = observer(() => {
|
export const SightCreate = observer(() => {
|
||||||
const { language, setLanguageAction } = languageStore;
|
const { language, setLanguageAction } = languageStore;
|
||||||
const [sightData, setSightData] = useState({
|
const [sightData, setSightData] = useState({
|
||||||
name: EVERY_LANGUAGE(""),
|
name: EVERY_LANGUAGE(""),
|
||||||
address: EVERY_LANGUAGE("")
|
address: EVERY_LANGUAGE(""),
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -46,8 +58,8 @@ export const SightCreate = observer(() => {
|
|||||||
address: {
|
address: {
|
||||||
...sightData.address,
|
...sightData.address,
|
||||||
[language]: watch("address") ?? "",
|
[language]: watch("address") ?? "",
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
if (update) setSightData(newSightData);
|
if (update) setSightData(newSightData);
|
||||||
return newSightData;
|
return newSightData;
|
||||||
}
|
}
|
||||||
@ -62,7 +74,7 @@ export const SightCreate = observer(() => {
|
|||||||
console.log(newTranslations);
|
console.log(newTranslations);
|
||||||
return onFinish({
|
return onFinish({
|
||||||
...values,
|
...values,
|
||||||
translations: newTranslations
|
translations: newTranslations,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -71,6 +83,11 @@ export const SightCreate = observer(() => {
|
|||||||
latitude: "",
|
latitude: "",
|
||||||
longitude: "",
|
longitude: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [creatingArticleHeading, setCreatingArticleHeading] =
|
||||||
|
useState<string>("");
|
||||||
|
const [creatingArticleBody, setCreatingArticleBody] = useState<string>("");
|
||||||
|
|
||||||
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>(
|
||||||
@ -80,6 +97,8 @@ export const SightCreate = observer(() => {
|
|||||||
null
|
null
|
||||||
);
|
);
|
||||||
const [leftArticlePreview, setLeftArticlePreview] = useState("");
|
const [leftArticlePreview, setLeftArticlePreview] = useState("");
|
||||||
|
const [customOptions, setCustomOptions] = useState<any[]>([]);
|
||||||
|
|
||||||
const [previewArticlePreview, setPreviewArticlePreview] = useState("");
|
const [previewArticlePreview, setPreviewArticlePreview] = useState("");
|
||||||
|
|
||||||
const handleCoordinatesChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleCoordinatesChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
@ -122,6 +141,7 @@ export const SightCreate = observer(() => {
|
|||||||
|
|
||||||
const { autocompleteProps: articleAutocompleteProps } = useAutocomplete({
|
const { autocompleteProps: articleAutocompleteProps } = useAutocomplete({
|
||||||
resource: "article",
|
resource: "article",
|
||||||
|
|
||||||
onSearch: (value) => [
|
onSearch: (value) => [
|
||||||
{
|
{
|
||||||
field: "heading",
|
field: "heading",
|
||||||
@ -131,6 +151,8 @@ export const SightCreate = observer(() => {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const mergedOptions = [...articleAutocompleteProps.options, ...customOptions];
|
||||||
|
|
||||||
// Следим за изменениями во всех полях
|
// Следим за изменениями во всех полях
|
||||||
const nameContent = watch("name");
|
const nameContent = watch("name");
|
||||||
const addressContent = watch("address");
|
const addressContent = watch("address");
|
||||||
@ -222,10 +244,13 @@ export const SightCreate = observer(() => {
|
|||||||
}, [previewArticleContent, articleAutocompleteProps.options]);
|
}, [previewArticleContent, articleAutocompleteProps.options]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Create isLoading={formLoading} saveButtonProps={{
|
<Create
|
||||||
|
isLoading={formLoading}
|
||||||
|
saveButtonProps={{
|
||||||
...saveButtonProps,
|
...saveButtonProps,
|
||||||
onClick: handleFormSubmit
|
onClick: handleFormSubmit,
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
<Box sx={{ display: "flex", gap: 2 }}>
|
<Box sx={{ display: "flex", gap: 2 }}>
|
||||||
<Box sx={{ display: "flex", flexDirection: "column", flex: 1, gap: 2 }}>
|
<Box sx={{ display: "flex", flexDirection: "column", flex: 1, gap: 2 }}>
|
||||||
{/* Форма создания */}
|
{/* Форма создания */}
|
||||||
@ -468,27 +493,25 @@ export const SightCreate = observer(() => {
|
|||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
{...articleAutocompleteProps}
|
{...articleAutocompleteProps}
|
||||||
|
options={mergedOptions} // ← use merged options
|
||||||
value={
|
value={
|
||||||
articleAutocompleteProps.options.find(
|
mergedOptions.find((option) => option.id === field.value) ??
|
||||||
(option) => option.id === field.value
|
null
|
||||||
) ?? null
|
|
||||||
}
|
}
|
||||||
onChange={(_, value) => {
|
onChange={(_, value) => {
|
||||||
field.onChange(value?.id ?? "");
|
field.onChange(value?.id ?? "");
|
||||||
}}
|
}}
|
||||||
getOptionLabel={(item) => {
|
getOptionLabel={(item) => (item ? item.heading : "")}
|
||||||
return item ? item.heading : "";
|
isOptionEqualToValue={(option, value) =>
|
||||||
}}
|
option.id === value?.id
|
||||||
isOptionEqualToValue={(option, value) => {
|
}
|
||||||
return option.id === value?.id;
|
filterOptions={(options, { inputValue }) =>
|
||||||
}}
|
options.filter((option) =>
|
||||||
filterOptions={(options, { inputValue }) => {
|
|
||||||
return options.filter((option) =>
|
|
||||||
option.heading
|
option.heading
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.includes(inputValue.toLowerCase())
|
.includes(inputValue.toLowerCase())
|
||||||
);
|
)
|
||||||
}}
|
}
|
||||||
renderInput={(params) => (
|
renderInput={(params) => (
|
||||||
<TextField
|
<TextField
|
||||||
{...params}
|
{...params}
|
||||||
@ -503,6 +526,31 @@ export const SightCreate = observer(() => {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{!leftArticleContent && (
|
||||||
|
<Accordion>
|
||||||
|
<AccordionSummary
|
||||||
|
expandIcon={<ExpandMoreIcon />}
|
||||||
|
aria-controls="create-article-content"
|
||||||
|
id="create-article-header"
|
||||||
|
>
|
||||||
|
<Typography>Создать новую статью</Typography>
|
||||||
|
</AccordionSummary>
|
||||||
|
<AccordionDetails>
|
||||||
|
<CreateSightArticle
|
||||||
|
language={language}
|
||||||
|
parentResource="sight"
|
||||||
|
childResource="article"
|
||||||
|
title="статью"
|
||||||
|
noReset
|
||||||
|
left
|
||||||
|
onSave={(something: any) => {
|
||||||
|
setCustomOptions((prev) => [...prev, something]);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</AccordionDetails>
|
||||||
|
</Accordion>
|
||||||
|
)}
|
||||||
|
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="preview_media"
|
name="preview_media"
|
||||||
@ -516,7 +564,7 @@ export const SightCreate = observer(() => {
|
|||||||
) || null
|
) || null
|
||||||
}
|
}
|
||||||
onChange={(_, value) => {
|
onChange={(_, value) => {
|
||||||
console.log(value, _)
|
console.log(value, _);
|
||||||
field.onChange(value?.id || "");
|
field.onChange(value?.id || "");
|
||||||
}}
|
}}
|
||||||
getOptionLabel={(item) => {
|
getOptionLabel={(item) => {
|
||||||
@ -531,7 +579,7 @@ export const SightCreate = observer(() => {
|
|||||||
option.media_name
|
option.media_name
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.includes(inputValue.toLowerCase()) &&
|
.includes(inputValue.toLowerCase()) &&
|
||||||
[1,2,5,6].includes(option.media_type)
|
[1, 2, 3, 4, 5, 6].includes(option.media_type)
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
renderInput={(params) => (
|
renderInput={(params) => (
|
||||||
|
@ -19,10 +19,18 @@ import { ArticleItem, articleFields } from "./types";
|
|||||||
import { axiosInstance, TOKEN_KEY } from "@providers";
|
import { axiosInstance, TOKEN_KEY } from "@providers";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
|
|
||||||
import { Languages, languageStore, articleStore, META_LANGUAGE, EVERY_LANGUAGE } from "@stores";
|
import {
|
||||||
|
Languages,
|
||||||
|
languageStore,
|
||||||
|
articleStore,
|
||||||
|
META_LANGUAGE,
|
||||||
|
EVERY_LANGUAGE,
|
||||||
|
} from "@stores";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { LanguageSelector, MediaData, MediaView } from "@ui";
|
import { LanguageSelector, MediaData, MediaView } from "@ui";
|
||||||
import { ArticleEditModal } from "../../components/modals/ArticleEditModal/index";
|
import { ArticleEditModal } from "../../components/modals/ArticleEditModal/index";
|
||||||
|
import ReactMarkdown from "react-markdown";
|
||||||
|
import rehypeRaw from "rehype-raw";
|
||||||
|
|
||||||
function a11yProps(index: number) {
|
function a11yProps(index: number) {
|
||||||
return {
|
return {
|
||||||
@ -60,10 +68,9 @@ export const SightEdit = observer(() => {
|
|||||||
const { setArticleModalOpenAction, setArticleIdAction } = articleStore;
|
const { setArticleModalOpenAction, setArticleIdAction } = articleStore;
|
||||||
const [sightData, setSightData] = useState({
|
const [sightData, setSightData] = useState({
|
||||||
name: EVERY_LANGUAGE(""),
|
name: EVERY_LANGUAGE(""),
|
||||||
address: EVERY_LANGUAGE("")
|
address: EVERY_LANGUAGE(""),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
saveButtonProps,
|
saveButtonProps,
|
||||||
register,
|
register,
|
||||||
@ -76,8 +83,9 @@ export const SightEdit = observer(() => {
|
|||||||
formState: { errors },
|
formState: { errors },
|
||||||
} = useForm({
|
} = useForm({
|
||||||
refineCoreProps: META_LANGUAGE(language),
|
refineCoreProps: META_LANGUAGE(language),
|
||||||
warnWhenUnsavedChanges: false
|
warnWhenUnsavedChanges: false,
|
||||||
});
|
});
|
||||||
|
const name = watch("name");
|
||||||
|
|
||||||
const getMedia = async (id?: string | number) => {
|
const getMedia = async (id?: string | number) => {
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
@ -110,7 +118,7 @@ export const SightEdit = observer(() => {
|
|||||||
value,
|
value,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
...META_LANGUAGE("ru")
|
...META_LANGUAGE("ru"),
|
||||||
});
|
});
|
||||||
const [mediaFile, setMediaFile] = useState<MediaData>();
|
const [mediaFile, setMediaFile] = useState<MediaData>();
|
||||||
const [leftArticleData, setLeftArticleData] = useState<{
|
const [leftArticleData, setLeftArticleData] = useState<{
|
||||||
@ -129,7 +137,7 @@ export const SightEdit = observer(() => {
|
|||||||
operator: "contains",
|
operator: "contains",
|
||||||
value,
|
value,
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
const { autocompleteProps: articleAutocompleteProps } = useAutocomplete({
|
const { autocompleteProps: articleAutocompleteProps } = useAutocomplete({
|
||||||
@ -147,12 +155,10 @@ export const SightEdit = observer(() => {
|
|||||||
value,
|
value,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if(sightData.name[language])
|
if (sightData.name[language]) setValue("name", sightData.name[language]);
|
||||||
setValue("name", sightData.name[language]);
|
|
||||||
if (sightData.address[language])
|
if (sightData.address[language])
|
||||||
setValue("address", sightData.address[language]);
|
setValue("address", sightData.address[language]);
|
||||||
}, [language, sightData, setValue]);
|
}, [language, sightData, setValue]);
|
||||||
@ -180,7 +186,8 @@ export const SightEdit = observer(() => {
|
|||||||
|
|
||||||
// Состояния для предпросмотра
|
// Состояния для предпросмотра
|
||||||
|
|
||||||
const [creatingArticleHeading, setCreatingArticleHeading] = useState<string>("");
|
const [creatingArticleHeading, setCreatingArticleHeading] =
|
||||||
|
useState<string>("");
|
||||||
const [creatingArticleBody, setCreatingArticleBody] = useState<string>("");
|
const [creatingArticleBody, setCreatingArticleBody] = useState<string>("");
|
||||||
|
|
||||||
const [coordinatesPreview, setCoordinatesPreview] = useState({
|
const [coordinatesPreview, setCoordinatesPreview] = useState({
|
||||||
@ -188,6 +195,7 @@ export const SightEdit = observer(() => {
|
|||||||
longitude: "",
|
longitude: "",
|
||||||
});
|
});
|
||||||
const [selectedArticleIndex, setSelectedArticleIndex] = useState(-1);
|
const [selectedArticleIndex, setSelectedArticleIndex] = useState(-1);
|
||||||
|
const [previewMediaFile, setPreviewMediaFile] = useState<MediaData>();
|
||||||
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>(
|
||||||
null
|
null
|
||||||
@ -202,14 +210,15 @@ export const SightEdit = observer(() => {
|
|||||||
|
|
||||||
const previewMediaId = watch("preview_media");
|
const previewMediaId = watch("preview_media");
|
||||||
const leftArticleId = watch("left_article");
|
const leftArticleId = watch("left_article");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (previewMediaId) {
|
if (previewMediaId) {
|
||||||
const selectedMedia = mediaAutocompleteProps.options.find(
|
const selectedMedia = mediaAutocompleteProps.options.find(
|
||||||
(option) => option.id === previewMediaId
|
(option) => option.id === previewMediaId
|
||||||
);
|
);
|
||||||
console.log("Triggering", previewMediaId)
|
console.log("Triggering", previewMediaId);
|
||||||
if (!selectedMedia) return;
|
if (!selectedMedia) return;
|
||||||
setMediaFile(selectedMedia);
|
setPreviewMediaFile(selectedMedia);
|
||||||
}
|
}
|
||||||
}, [previewMediaId, mediaAutocompleteProps.options]);
|
}, [previewMediaId, mediaAutocompleteProps.options]);
|
||||||
|
|
||||||
@ -245,10 +254,9 @@ export const SightEdit = observer(() => {
|
|||||||
getMedia(linkedArticles[selectedArticleIndex].id).then((media) => {
|
getMedia(linkedArticles[selectedArticleIndex].id).then((media) => {
|
||||||
setMediaFile(media);
|
setMediaFile(media);
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
}, [selectedArticleIndex, linkedArticles]);
|
}, [selectedArticleIndex, linkedArticles]);
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const selectedThumbnail = mediaAutocompleteProps.options.find(
|
const selectedThumbnail = mediaAutocompleteProps.options.find(
|
||||||
(option) => option.id === thumbnailContent
|
(option) => option.id === thumbnailContent
|
||||||
@ -312,8 +320,8 @@ export const SightEdit = observer(() => {
|
|||||||
address: {
|
address: {
|
||||||
...sightData.address,
|
...sightData.address,
|
||||||
[language]: watch("address") ?? "",
|
[language]: watch("address") ?? "",
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
if (update) setSightData(newSightData);
|
if (update) setSightData(newSightData);
|
||||||
return newSightData;
|
return newSightData;
|
||||||
}
|
}
|
||||||
@ -328,7 +336,7 @@ export const SightEdit = observer(() => {
|
|||||||
console.log(newTranslations);
|
console.log(newTranslations);
|
||||||
await onFinish({
|
await onFinish({
|
||||||
...values,
|
...values,
|
||||||
translations: newTranslations
|
translations: newTranslations,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -338,21 +346,29 @@ export const SightEdit = observer(() => {
|
|||||||
};
|
};
|
||||||
}, [setLanguageAction]);
|
}, [setLanguageAction]);
|
||||||
|
|
||||||
const [articleAdditionMode, setArticleAdditionMode] = useState<'attaching' | 'creating'>('attaching');
|
const [articleAdditionMode, setArticleAdditionMode] = useState<
|
||||||
|
"attaching" | "creating"
|
||||||
|
>("attaching");
|
||||||
const [selectedItemId, setSelectedItemId] = useState<string>();
|
const [selectedItemId, setSelectedItemId] = useState<string>();
|
||||||
const [updatedLinkedArticles, setUpdatedLinkedArticles] = useState<ArticleItem[]>([]);
|
const [updatedLinkedArticles, setUpdatedLinkedArticles] = useState<
|
||||||
|
ArticleItem[]
|
||||||
|
>([]);
|
||||||
|
|
||||||
const linkItem = () => {
|
const linkItem = () => {
|
||||||
if (!selectedItemId) return;
|
if (!selectedItemId) return;
|
||||||
const requestData = {
|
const requestData = {
|
||||||
article_id: selectedItemId,
|
article_id: selectedItemId,
|
||||||
page_num: linkedArticles.length + 1,
|
page_num: linkedArticles.length + 1,
|
||||||
}
|
};
|
||||||
|
|
||||||
axiosInstance
|
axiosInstance
|
||||||
.post(`${import.meta.env.VITE_KRBL_API}/sight/${sightId}/article`, requestData)
|
.post(
|
||||||
|
`${import.meta.env.VITE_KRBL_API}/sight/${sightId}/article`,
|
||||||
|
requestData
|
||||||
|
)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
axiosInstance.get(`${import.meta.env.VITE_KRBL_API}/sight/${sightId}/article`)
|
axiosInstance
|
||||||
|
.get(`${import.meta.env.VITE_KRBL_API}/sight/${sightId}/article`)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
setUpdatedLinkedArticles(response?.data || []);
|
setUpdatedLinkedArticles(response?.data || []);
|
||||||
setSelectedItemId(undefined);
|
setSelectedItemId(undefined);
|
||||||
@ -381,7 +397,7 @@ export const SightEdit = observer(() => {
|
|||||||
<Edit
|
<Edit
|
||||||
saveButtonProps={{
|
saveButtonProps={{
|
||||||
...saveButtonProps,
|
...saveButtonProps,
|
||||||
onClick: handleFormSubmit
|
onClick: handleFormSubmit,
|
||||||
}}
|
}}
|
||||||
footerButtonProps={{
|
footerButtonProps={{
|
||||||
sx: {
|
sx: {
|
||||||
@ -391,12 +407,26 @@ export const SightEdit = observer(() => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box sx={{ display: "flex", flexDirection: "row", gap: 2 }}>
|
<Box sx={{ display: "flex", flexDirection: "row", gap: 2 }}>
|
||||||
<Box sx={{ display: "flex", gap: 2, position: "relative", flex:1 }}>
|
<Box
|
||||||
<Box sx={{display: "flex", flexDirection: "column", flex: 1, gap: 10, justifyContent: "space-between"}}>
|
sx={{ display: "flex", gap: 2, position: "relative", flex: 1 }}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
flex: 1,
|
||||||
|
gap: 10,
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}}
|
||||||
|
>
|
||||||
<LanguageSelector action={handleLanguageChange} />
|
<LanguageSelector action={handleLanguageChange} />
|
||||||
|
|
||||||
{/* Форма редактирования */}
|
{/* Форма редактирования */}
|
||||||
<Box component="form" sx={{ flex: 1, display: "flex", flexDirection: "column" }} autoComplete="off">
|
<Box
|
||||||
|
component="form"
|
||||||
|
sx={{ flex: 1, display: "flex", flexDirection: "column" }}
|
||||||
|
autoComplete="off"
|
||||||
|
>
|
||||||
<TextField
|
<TextField
|
||||||
{...register("name", {
|
{...register("name", {
|
||||||
//required: "Это поле является обязательным",
|
//required: "Это поле является обязательным",
|
||||||
@ -410,6 +440,19 @@ export const SightEdit = observer(() => {
|
|||||||
label={"Название"}
|
label={"Название"}
|
||||||
name="name"
|
name="name"
|
||||||
/>
|
/>
|
||||||
|
<TextField
|
||||||
|
{...register("address", {
|
||||||
|
//required: "Это поле является обязательным",
|
||||||
|
})}
|
||||||
|
error={!!(errors as any)?.address}
|
||||||
|
helperText={(errors as any)?.address?.message}
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
slotProps={{ inputLabel: { shrink: true } }}
|
||||||
|
type="text"
|
||||||
|
label={"Адрес"}
|
||||||
|
name="address"
|
||||||
|
/>
|
||||||
|
|
||||||
<Box sx={{ display: "none" }}>
|
<Box sx={{ display: "none" }}>
|
||||||
<Controller
|
<Controller
|
||||||
@ -433,14 +476,13 @@ export const SightEdit = observer(() => {
|
|||||||
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(
|
// return options.filter((option) =>
|
||||||
(option) =>
|
// option.media_name
|
||||||
option.media_name
|
// .toLowerCase()
|
||||||
.toLowerCase()
|
// .includes(inputValue.toLowerCase())
|
||||||
.includes(inputValue.toLowerCase())
|
// );
|
||||||
);
|
// }}
|
||||||
}}
|
|
||||||
renderInput={(params) => (
|
renderInput={(params) => (
|
||||||
<TextField
|
<TextField
|
||||||
{...params}
|
{...params}
|
||||||
@ -482,7 +524,7 @@ export const SightEdit = observer(() => {
|
|||||||
name="city_id"
|
name="city_id"
|
||||||
rules={{ required: "Это поле является обязательным" }}
|
rules={{ required: "Это поле является обязательным" }}
|
||||||
defaultValue={null}
|
defaultValue={null}
|
||||||
render={() => (<div/>)}
|
render={() => <div />}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Box sx={{ display: "none" }}>
|
<Box sx={{ display: "none" }}>
|
||||||
@ -685,10 +727,9 @@ export const SightEdit = observer(() => {
|
|||||||
variant="h6"
|
variant="h6"
|
||||||
gutterBottom
|
gutterBottom
|
||||||
px={2}
|
px={2}
|
||||||
py={.5}
|
py={0.5}
|
||||||
|
|
||||||
sx={{
|
sx={{
|
||||||
color: "text.primary"
|
color: "text.primary",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Создать и прикрепить новую статью:
|
Создать и прикрепить новую статью:
|
||||||
@ -767,7 +808,7 @@ export const SightEdit = observer(() => {
|
|||||||
mb: 3,
|
mb: 3,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{leftArticleId ? leftArticleData?.heading : creatingArticleHeading}
|
{name}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
{/* Адрес */}
|
{/* Адрес */}
|
||||||
@ -786,18 +827,50 @@ export const SightEdit = observer(() => {
|
|||||||
</Box>
|
</Box>
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
{/* Текст статьи */}
|
|
||||||
<Typography variant="body1" sx={{ mb: 2 }}>
|
|
||||||
<Box
|
<Box
|
||||||
component="span"
|
|
||||||
sx={{
|
sx={{
|
||||||
|
"& img": {
|
||||||
|
maxWidth: "100%",
|
||||||
|
height: "auto",
|
||||||
|
borderRadius: 1,
|
||||||
|
},
|
||||||
|
"& h1, & h2, & h3, & h4, & h5, & h6": {
|
||||||
|
color: "primary.main",
|
||||||
|
mt: 2,
|
||||||
|
mb: 1,
|
||||||
|
},
|
||||||
|
"& p": {
|
||||||
|
mb: 2,
|
||||||
color: (theme) =>
|
color: (theme) =>
|
||||||
theme.palette.mode === "dark" ? "grey.300" : "grey.800",
|
theme.palette.mode === "dark" ? "grey.300" : "grey.800",
|
||||||
|
},
|
||||||
|
"& a": {
|
||||||
|
color: "primary.main",
|
||||||
|
textDecoration: "none",
|
||||||
|
"&:hover": {
|
||||||
|
textDecoration: "underline",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"& blockquote": {
|
||||||
|
borderLeft: "4px solid",
|
||||||
|
borderColor: "primary.main",
|
||||||
|
pl: 2,
|
||||||
|
my: 2,
|
||||||
|
color: "text.secondary",
|
||||||
|
},
|
||||||
|
"& code": {
|
||||||
|
bgcolor: (theme) =>
|
||||||
|
theme.palette.mode === "dark" ? "grey.900" : "grey.100",
|
||||||
|
p: 0.5,
|
||||||
|
borderRadius: 0.5,
|
||||||
|
color: "primary.main",
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<ReactMarkdown rehypePlugins={[rehypeRaw]}>
|
||||||
{leftArticleId ? leftArticleData?.body : creatingArticleBody}
|
{leftArticleId ? leftArticleData?.body : creatingArticleBody}
|
||||||
|
</ReactMarkdown>
|
||||||
</Box>
|
</Box>
|
||||||
</Typography>
|
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
</Edit>
|
</Edit>
|
||||||
@ -807,15 +880,19 @@ export const SightEdit = observer(() => {
|
|||||||
<Edit
|
<Edit
|
||||||
saveButtonProps={{
|
saveButtonProps={{
|
||||||
...saveButtonProps,
|
...saveButtonProps,
|
||||||
onClick: handleFormSubmit
|
onClick: handleFormSubmit,
|
||||||
}}
|
}}
|
||||||
footerButtonProps={{
|
footerButtonProps={{
|
||||||
sx: { bottom: 0, left: 0 },
|
sx: { bottom: 0, left: 0 },
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
<Box sx={{ display: "flex", flexDirection: "row", gap: 2 }}>
|
<Box sx={{ display: "flex", flexDirection: "row", gap: 2 }}>
|
||||||
<Box sx={{ flex: 1, gap: 2, position: "relative" }}>
|
<Box sx={{ flex: 1, gap: 2, position: "relative" }}>
|
||||||
<Box component="form" sx={{ flex: 1, display: "flex", flexDirection: "column" }} autoComplete="off">
|
<Box
|
||||||
|
component="form"
|
||||||
|
sx={{ flex: 1, display: "flex", flexDirection: "column" }}
|
||||||
|
autoComplete="off"
|
||||||
|
>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="preview_media"
|
name="preview_media"
|
||||||
@ -829,7 +906,7 @@ export const SightEdit = observer(() => {
|
|||||||
) || null
|
) || null
|
||||||
}
|
}
|
||||||
onChange={(_, value) => {
|
onChange={(_, value) => {
|
||||||
console.log(value, _)
|
console.log(value, _);
|
||||||
field.onChange(value?.id || "");
|
field.onChange(value?.id || "");
|
||||||
}}
|
}}
|
||||||
getOptionLabel={(item) => {
|
getOptionLabel={(item) => {
|
||||||
@ -839,19 +916,17 @@ export const SightEdit = observer(() => {
|
|||||||
return option.id === value?.id;
|
return option.id === value?.id;
|
||||||
}}
|
}}
|
||||||
filterOptions={(options, { inputValue }) => {
|
filterOptions={(options, { inputValue }) => {
|
||||||
return options.filter(
|
return options.filter((option) =>
|
||||||
(option) =>
|
|
||||||
option.media_name
|
option.media_name
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.includes(inputValue.toLowerCase()) &&
|
.includes(inputValue.toLowerCase())
|
||||||
[1,2,5,6].includes(option.media_type)
|
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
renderInput={(params) => (
|
renderInput={(params) => (
|
||||||
<TextField
|
<TextField
|
||||||
{...params}
|
{...params}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
//setPreviewSelected(true);
|
setPreviewSelected(true);
|
||||||
//setSelectedMediaIndex(-1);
|
//setSelectedMediaIndex(-1);
|
||||||
}}
|
}}
|
||||||
label="Медиа-предпросмотр"
|
label="Медиа-предпросмотр"
|
||||||
@ -875,8 +950,14 @@ export const SightEdit = observer(() => {
|
|||||||
flex: 1,
|
flex: 1,
|
||||||
display: "flex",
|
display: "flex",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
bgcolor: articleAdditionMode === "attaching" ? "primary.main" : "transparent",
|
bgcolor:
|
||||||
color: articleAdditionMode === "attaching" ? "white" : "inherit",
|
articleAdditionMode === "attaching"
|
||||||
|
? "primary.main"
|
||||||
|
: "transparent",
|
||||||
|
color:
|
||||||
|
articleAdditionMode === "attaching"
|
||||||
|
? "white"
|
||||||
|
: "inherit",
|
||||||
borderRadius: 1,
|
borderRadius: 1,
|
||||||
p: 1,
|
p: 1,
|
||||||
}}
|
}}
|
||||||
@ -890,8 +971,14 @@ export const SightEdit = observer(() => {
|
|||||||
flex: 1,
|
flex: 1,
|
||||||
display: "flex",
|
display: "flex",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
bgcolor: articleAdditionMode === "creating" ? "primary.main" : "transparent",
|
bgcolor:
|
||||||
color: articleAdditionMode === "creating" ? "white" : "inherit",
|
articleAdditionMode === "creating"
|
||||||
|
? "primary.main"
|
||||||
|
: "transparent",
|
||||||
|
color:
|
||||||
|
articleAdditionMode === "creating"
|
||||||
|
? "white"
|
||||||
|
: "inherit",
|
||||||
borderRadius: 1,
|
borderRadius: 1,
|
||||||
p: 1,
|
p: 1,
|
||||||
}}
|
}}
|
||||||
@ -947,7 +1034,10 @@ export const SightEdit = observer(() => {
|
|||||||
>
|
>
|
||||||
Добавить
|
Добавить
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="outlined" onClick={() => setSelectedItemId(undefined)}>
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
onClick={() => setSelectedItemId(undefined)}
|
||||||
|
>
|
||||||
Очистить
|
Очистить
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
@ -962,7 +1052,7 @@ export const SightEdit = observer(() => {
|
|||||||
title="статью"
|
title="статью"
|
||||||
//left
|
//left
|
||||||
setHeadingParent={(heading) => {
|
setHeadingParent={(heading) => {
|
||||||
console.log("Updating", heading)
|
console.log("Updating", heading);
|
||||||
setCreatingArticleHeading(heading);
|
setCreatingArticleHeading(heading);
|
||||||
}}
|
}}
|
||||||
setBodyParent={(body) => {
|
setBodyParent={(body) => {
|
||||||
@ -970,7 +1060,11 @@ export const SightEdit = observer(() => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Typography variant="subtitle1" fontWeight="bold" sx={{mt: 4}}>
|
<Typography
|
||||||
|
variant="subtitle1"
|
||||||
|
fontWeight="bold"
|
||||||
|
sx={{ mt: 4 }}
|
||||||
|
>
|
||||||
Привязанные статьи
|
Привязанные статьи
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
@ -986,7 +1080,6 @@ export const SightEdit = observer(() => {
|
|||||||
title="статьи"
|
title="статьи"
|
||||||
updatedLinkedItems={updatedLinkedArticles}
|
updatedLinkedItems={updatedLinkedArticles}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
{/* Предпросмотр */}
|
{/* Предпросмотр */}
|
||||||
@ -1016,7 +1109,6 @@ export const SightEdit = observer(() => {
|
|||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
mb: 2,
|
mb: 2,
|
||||||
@ -1027,7 +1119,10 @@ export const SightEdit = observer(() => {
|
|||||||
gap: 2,
|
gap: 2,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{mediaFile && (
|
{previewSelected && previewMediaFile && (
|
||||||
|
<MediaView media={previewMediaFile} />
|
||||||
|
)}
|
||||||
|
{mediaFile && !previewSelected && (
|
||||||
<MediaView media={mediaFile} />
|
<MediaView media={mediaFile} />
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
@ -1045,7 +1140,6 @@ export const SightEdit = observer(() => {
|
|||||||
overflowY: "auto",
|
overflowY: "auto",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
||||||
{!previewSelected && articleAdditionMode !== "creating" && (
|
{!previewSelected && articleAdditionMode !== "creating" && (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@ -1060,14 +1154,13 @@ export const SightEdit = observer(() => {
|
|||||||
variant="h4"
|
variant="h4"
|
||||||
gutterBottom
|
gutterBottom
|
||||||
px={2}
|
px={2}
|
||||||
py={.5}
|
py={0.5}
|
||||||
|
|
||||||
sx={{
|
sx={{
|
||||||
color: "text.primary",
|
color: "text.primary",
|
||||||
background:
|
background:
|
||||||
"linear-gradient(180deg, hsla(0,0%,100%,.2), hsla(0,0%,100%,0)), hsla(29,15%,65%,.4)",
|
"linear-gradient(180deg, hsla(0,0%,100%,.2), hsla(0,0%,100%,0)), hsla(29,15%,65%,.4)",
|
||||||
boxShadow: "inset 4px 4px 12px hsla(0,0%,100%,.12)",
|
boxShadow:
|
||||||
|
"inset 4px 4px 12px hsla(0,0%,100%,.12)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{selectedArticle.heading}
|
{selectedArticle.heading}
|
||||||
@ -1075,14 +1168,56 @@ export const SightEdit = observer(() => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{selectedArticle && (
|
{selectedArticle && (
|
||||||
<Typography
|
<Box
|
||||||
variant="body1"
|
sx={{
|
||||||
gutterBottom
|
mt: -6,
|
||||||
px={2}
|
p: 2,
|
||||||
sx={{ color: "text.primary" }}
|
"& img": {
|
||||||
|
maxWidth: "100%",
|
||||||
|
height: "auto",
|
||||||
|
borderRadius: 1,
|
||||||
|
},
|
||||||
|
"& h1, & h2, & h3, & h4, & h5, & h6": {
|
||||||
|
color: "primary.main",
|
||||||
|
mt: 2,
|
||||||
|
mb: 1,
|
||||||
|
},
|
||||||
|
"& p": {
|
||||||
|
mb: 2,
|
||||||
|
color: (theme) =>
|
||||||
|
theme.palette.mode === "dark"
|
||||||
|
? "grey.300"
|
||||||
|
: "grey.800",
|
||||||
|
},
|
||||||
|
"& a": {
|
||||||
|
color: "primary.main",
|
||||||
|
textDecoration: "none",
|
||||||
|
"&:hover": {
|
||||||
|
textDecoration: "underline",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"& blockquote": {
|
||||||
|
borderLeft: "4px solid",
|
||||||
|
borderColor: "primary.main",
|
||||||
|
pl: 2,
|
||||||
|
my: 2,
|
||||||
|
color: "text.secondary",
|
||||||
|
},
|
||||||
|
"& code": {
|
||||||
|
bgcolor: (theme) =>
|
||||||
|
theme.palette.mode === "dark"
|
||||||
|
? "grey.900"
|
||||||
|
: "grey.100",
|
||||||
|
p: 0.5,
|
||||||
|
borderRadius: 0.5,
|
||||||
|
color: "primary.main",
|
||||||
|
},
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
|
<ReactMarkdown rehypePlugins={[rehypeRaw]}>
|
||||||
{selectedArticle.body}
|
{selectedArticle.body}
|
||||||
</Typography>
|
</ReactMarkdown>
|
||||||
|
</Box>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
@ -1100,14 +1235,12 @@ export const SightEdit = observer(() => {
|
|||||||
variant="h4"
|
variant="h4"
|
||||||
gutterBottom
|
gutterBottom
|
||||||
px={2}
|
px={2}
|
||||||
py={.5}
|
py={0.5}
|
||||||
|
|
||||||
sx={{
|
sx={{
|
||||||
color: "text.primary",
|
color: "text.primary",
|
||||||
background:
|
background:
|
||||||
"linear-gradient(180deg, hsla(0,0%,100%,.2), hsla(0,0%,100%,0)), hsla(29,15%,65%,.4)",
|
"linear-gradient(180deg, hsla(0,0%,100%,.2), hsla(0,0%,100%,0)), hsla(29,15%,65%,.4)",
|
||||||
boxShadow: "inset 4px 4px 12px hsla(0,0%,100%,.12)",
|
boxShadow: "inset 4px 4px 12px hsla(0,0%,100%,.12)",
|
||||||
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{creatingArticleHeading}
|
{creatingArticleHeading}
|
||||||
@ -1133,7 +1266,6 @@ export const SightEdit = observer(() => {
|
|||||||
background:
|
background:
|
||||||
"linear-gradient(180deg, hsla(0,0%,100%,.2), hsla(0,0%,100%,0)), hsla(29,15%,65%,.4)",
|
"linear-gradient(180deg, hsla(0,0%,100%,.2), hsla(0,0%,100%,0)), hsla(29,15%,65%,.4)",
|
||||||
boxShadow: "inset 4px 4px 12px hsla(0,0%,100%,.12)",
|
boxShadow: "inset 4px 4px 12px hsla(0,0%,100%,.12)",
|
||||||
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
@ -1156,8 +1288,9 @@ export const SightEdit = observer(() => {
|
|||||||
bgcolor: "transparent",
|
bgcolor: "transparent",
|
||||||
color: "inherit",
|
color: "inherit",
|
||||||
textDecoration:
|
textDecoration:
|
||||||
selectedArticleIndex === index ?
|
selectedArticleIndex === index
|
||||||
"underline" : "none",
|
? "underline"
|
||||||
|
: "none",
|
||||||
p: 1,
|
p: 1,
|
||||||
borderRadius: 1,
|
borderRadius: 1,
|
||||||
}}
|
}}
|
||||||
@ -1184,22 +1317,15 @@ export const SightEdit = observer(() => {
|
|||||||
footerButtonProps={{ sx: { bottom: 0, left: 0 } }}
|
footerButtonProps={{ sx: { bottom: 0, left: 0 } }}
|
||||||
>
|
>
|
||||||
<Box sx={{ display: "flex", flexDirection: "row", gap: 2 }}>
|
<Box sx={{ display: "flex", flexDirection: "row", gap: 2 }}>
|
||||||
<Box sx={{ display: "flex", flex: 1, gap: 2, position: "relative" }}>
|
<Box
|
||||||
<Box component="form" sx={{ flex: 1, display: "flex", flexDirection: "column" }} autoComplete="off">
|
sx={{ display: "flex", flex: 1, gap: 2, position: "relative" }}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
component="form"
|
||||||
|
sx={{ flex: 1, display: "flex", flexDirection: "column" }}
|
||||||
|
autoComplete="off"
|
||||||
|
>
|
||||||
<LanguageSelector action={handleLanguageChange} />
|
<LanguageSelector action={handleLanguageChange} />
|
||||||
<TextField
|
|
||||||
{...register("address", {
|
|
||||||
//required: "Это поле является обязательным",
|
|
||||||
})}
|
|
||||||
error={!!(errors as any)?.address}
|
|
||||||
helperText={(errors as any)?.address?.message}
|
|
||||||
margin="normal"
|
|
||||||
fullWidth
|
|
||||||
slotProps={{inputLabel: {shrink: true}}}
|
|
||||||
type="text"
|
|
||||||
label={"Адрес"}
|
|
||||||
name="address"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
@ -1221,7 +1347,7 @@ export const SightEdit = observer(() => {
|
|||||||
return item ? item.name : "";
|
return item ? item.name : "";
|
||||||
}}
|
}}
|
||||||
isOptionEqualToValue={(option, value) => {
|
isOptionEqualToValue={(option, value) => {
|
||||||
console.log(cityAutocompleteProps.options)
|
console.log(cityAutocompleteProps.options);
|
||||||
return option.id === value?.id;
|
return option.id === value?.id;
|
||||||
}}
|
}}
|
||||||
filterOptions={(options, { inputValue }) => {
|
filterOptions={(options, { inputValue }) => {
|
||||||
@ -1425,7 +1551,9 @@ export const SightEdit = observer(() => {
|
|||||||
component="span"
|
component="span"
|
||||||
sx={{
|
sx={{
|
||||||
color: (theme) =>
|
color: (theme) =>
|
||||||
theme.palette.mode === "dark" ? "grey.300" : "grey.800",
|
theme.palette.mode === "dark"
|
||||||
|
? "grey.300"
|
||||||
|
: "grey.800",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{`${addressContent}`}
|
{`${addressContent}`}
|
||||||
@ -1523,7 +1651,9 @@ export const SightEdit = observer(() => {
|
|||||||
component="span"
|
component="span"
|
||||||
sx={{
|
sx={{
|
||||||
color: (theme) =>
|
color: (theme) =>
|
||||||
theme.palette.mode === "dark" ? "grey.300" : "grey.800",
|
theme.palette.mode === "dark"
|
||||||
|
? "grey.300"
|
||||||
|
: "grey.800",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{`${coordinatesPreview.latitude}, ${coordinatesPreview.longitude}`}
|
{`${coordinatesPreview.latitude}, ${coordinatesPreview.longitude}`}
|
||||||
|
@ -12,8 +12,14 @@ import { CustomDataGrid } from "@components";
|
|||||||
import { localeText } from "../../locales/ru/localeText";
|
import { localeText } from "../../locales/ru/localeText";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useMany } from "@refinedev/core";
|
import { useMany } from "@refinedev/core";
|
||||||
|
import { DatabaseBackup } from "lucide-react";
|
||||||
|
import axios from "axios";
|
||||||
|
import { TOKEN_KEY } from "../../providers/authProvider";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import { useNotification } from "@refinedev/core";
|
||||||
|
|
||||||
export const SnapshotList = observer(() => {
|
export const SnapshotList = observer(() => {
|
||||||
|
const notification = useNotification();
|
||||||
const { dataGridProps } = useDataGrid({
|
const { dataGridProps } = useDataGrid({
|
||||||
resource: "snapshots",
|
resource: "snapshots",
|
||||||
hasPagination: false,
|
hasPagination: false,
|
||||||
@ -47,6 +53,30 @@ export const SnapshotList = observer(() => {
|
|||||||
return map;
|
return map;
|
||||||
}, [parentsData]);
|
}, [parentsData]);
|
||||||
|
|
||||||
|
const handleBackup = async (id: number) => {
|
||||||
|
try {
|
||||||
|
const response = await axios.post(
|
||||||
|
`${import.meta.env.VITE_KRBL_API}/snapshots/${id}/restore`,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${localStorage.getItem(TOKEN_KEY)}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
notification?.open({
|
||||||
|
message: "Cнапшот восстановлен",
|
||||||
|
type: "success",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
notification?.open({
|
||||||
|
message: "Ошибка при восстановлении снимка",
|
||||||
|
type: "error",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const columns = React.useMemo<GridColDef[]>(
|
const columns = React.useMemo<GridColDef[]>(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
@ -80,6 +110,12 @@ export const SnapshotList = observer(() => {
|
|||||||
renderCell: function render({ row }) {
|
renderCell: function render({ row }) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<button
|
||||||
|
className="backup-button"
|
||||||
|
onClick={() => handleBackup(row.ID)}
|
||||||
|
>
|
||||||
|
<DatabaseBackup />
|
||||||
|
</button>
|
||||||
<ShowButton hideText recordItemId={row.ID} />
|
<ShowButton hideText recordItemId={row.ID} />
|
||||||
<DeleteButton
|
<DeleteButton
|
||||||
hideText
|
hideText
|
||||||
|
@ -19,8 +19,6 @@ axiosInstance.interceptors.request.use((config) => {
|
|||||||
|
|
||||||
config.headers["X-Language"] = config.headers["Accept-Language"];
|
config.headers["X-Language"] = config.headers["Accept-Language"];
|
||||||
|
|
||||||
console.log("Request headers:", config.headers);
|
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -5703,6 +5703,13 @@ react-swipeable@^7.0.2:
|
|||||||
resolved "https://registry.npmjs.org/react-swipeable/-/react-swipeable-7.0.2.tgz"
|
resolved "https://registry.npmjs.org/react-swipeable/-/react-swipeable-7.0.2.tgz"
|
||||||
integrity sha512-v1Qx1l+aC2fdxKa9aKJiaU/ZxmJ5o98RMoFwUqAAzVWUcxgfHFXDDruCKXhw6zIYXm6V64JiHgP9f6mlME5l8w==
|
integrity sha512-v1Qx1l+aC2fdxKa9aKJiaU/ZxmJ5o98RMoFwUqAAzVWUcxgfHFXDDruCKXhw6zIYXm6V64JiHgP9f6mlME5l8w==
|
||||||
|
|
||||||
|
react-toastify@^11.0.5:
|
||||||
|
version "11.0.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-toastify/-/react-toastify-11.0.5.tgz#ce4c42d10eeb433988ab2264d3e445c4e9d13313"
|
||||||
|
integrity sha512-EpqHBGvnSTtHYhCPLxML05NLY2ZX0JURbAdNYa6BUkk+amz4wbKBQvoKQAB0ardvSarUBuY4Q4s1sluAzZwkmA==
|
||||||
|
dependencies:
|
||||||
|
clsx "^2.1.1"
|
||||||
|
|
||||||
react-transition-group@^4.4.5:
|
react-transition-group@^4.4.5:
|
||||||
version "4.4.5"
|
version "4.4.5"
|
||||||
resolved "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz"
|
resolved "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz"
|
||||||
|
Loading…
Reference in New Issue
Block a user