station edit in the route edit page
This commit is contained in:
parent
a1a2264758
commit
03829aacc6
@ -3,17 +3,20 @@ import {
|
|||||||
type DataGridProps,
|
type DataGridProps,
|
||||||
type GridColumnVisibilityModel,
|
type GridColumnVisibilityModel,
|
||||||
} from "@mui/x-data-grid";
|
} from "@mui/x-data-grid";
|
||||||
import { Stack, Button, Typography } from "@mui/material";
|
import { Stack, Button, Typography, Box } from "@mui/material";
|
||||||
import { ExportButton } from "@refinedev/mui";
|
import { ExportButton } from "@refinedev/mui";
|
||||||
import { useExport } from "@refinedev/core";
|
import { useExport } from "@refinedev/core";
|
||||||
import React, { useState, useEffect, useMemo } from "react";
|
import React, { useState, useEffect, useMemo } from "react";
|
||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
|
|
||||||
import { localeText } from "../locales/ru/localeText";
|
import { localeText } from "../locales/ru/localeText";
|
||||||
|
import { languageStore } from "../store/LanguageStore";
|
||||||
|
import { LanguageSwitch } from "./LanguageSwitch";
|
||||||
|
|
||||||
interface CustomDataGridProps extends DataGridProps {
|
interface CustomDataGridProps extends DataGridProps {
|
||||||
hasCoordinates?: boolean;
|
hasCoordinates?: boolean;
|
||||||
resource?: string; // Add this prop
|
resource?: string; // Add this prop
|
||||||
|
languageEnabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEV_FIELDS = [
|
const DEV_FIELDS = [
|
||||||
@ -46,6 +49,7 @@ const DEV_FIELDS = [
|
|||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export const CustomDataGrid = ({
|
export const CustomDataGrid = ({
|
||||||
|
languageEnabled = false,
|
||||||
hasCoordinates = false,
|
hasCoordinates = false,
|
||||||
columns = [],
|
columns = [],
|
||||||
resource,
|
resource,
|
||||||
@ -130,6 +134,9 @@ export const CustomDataGrid = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack spacing={2}>
|
<Stack spacing={2}>
|
||||||
|
<Box sx={{ visibility: languageEnabled ? "visible" : "hidden" }}>
|
||||||
|
<LanguageSwitch />
|
||||||
|
</Box>
|
||||||
<DataGrid
|
<DataGrid
|
||||||
{...props}
|
{...props}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
@ -149,7 +156,6 @@ export const CustomDataGrid = ({
|
|||||||
}}
|
}}
|
||||||
pageSizeOptions={[10, 25, 50, 100]}
|
pageSizeOptions={[10, 25, 50, 100]}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Stack direction="row" spacing={2} justifyContent="space-between" mb={2}>
|
<Stack direction="row" spacing={2} justifyContent="space-between" mb={2}>
|
||||||
<Stack direction="row" spacing={2} sx={{ mb: 2 }}>
|
<Stack direction="row" spacing={2} sx={{ mb: 2 }}>
|
||||||
{hasCoordinates && (
|
{hasCoordinates && (
|
||||||
|
70
src/components/LanguageSwitch/index.tsx
Normal file
70
src/components/LanguageSwitch/index.tsx
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import { Box } from "@mui/material";
|
||||||
|
import { languageStore } from "../../store/LanguageStore";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
|
||||||
|
export const LanguageSwitch = observer(({ action }: any) => {
|
||||||
|
const { language, setLanguageAction } = languageStore;
|
||||||
|
|
||||||
|
const handleLanguageChange = (lang: string) => {
|
||||||
|
if (action) {
|
||||||
|
action();
|
||||||
|
}
|
||||||
|
setLanguageAction(lang);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
flex: 1,
|
||||||
|
display: "flex",
|
||||||
|
gap: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
cursor: "pointer",
|
||||||
|
flex: 1,
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
bgcolor: language === "ru" ? "primary.main" : "transparent",
|
||||||
|
color: language === "ru" ? "white" : "inherit",
|
||||||
|
borderRadius: 1,
|
||||||
|
p: 1,
|
||||||
|
}}
|
||||||
|
onClick={() => handleLanguageChange("ru")}
|
||||||
|
>
|
||||||
|
RU
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
cursor: "pointer",
|
||||||
|
flex: 1,
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
bgcolor: language === "en" ? "primary.main" : "transparent",
|
||||||
|
color: language === "en" ? "white" : "inherit",
|
||||||
|
borderRadius: 1,
|
||||||
|
p: 1,
|
||||||
|
}}
|
||||||
|
onClick={() => handleLanguageChange("en")}
|
||||||
|
>
|
||||||
|
EN
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
cursor: "pointer",
|
||||||
|
flex: 1,
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
bgcolor: language === "zh" ? "primary.main" : "transparent",
|
||||||
|
color: language === "zh" ? "white" : "inherit",
|
||||||
|
borderRadius: 1,
|
||||||
|
p: 1,
|
||||||
|
}}
|
||||||
|
onClick={() => handleLanguageChange("zh")}
|
||||||
|
>
|
||||||
|
ZH
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
});
|
@ -1,4 +1,5 @@
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
|
import { Close } from "@mui/icons-material";
|
||||||
import {
|
import {
|
||||||
Stack,
|
Stack,
|
||||||
Typography,
|
Typography,
|
||||||
@ -20,14 +21,19 @@ import {
|
|||||||
Paper,
|
Paper,
|
||||||
TableBody,
|
TableBody,
|
||||||
IconButton,
|
IconButton,
|
||||||
|
Collapse,
|
||||||
|
Modal,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
||||||
import DragIndicatorIcon from "@mui/icons-material/DragIndicator";
|
import DragIndicatorIcon from "@mui/icons-material/DragIndicator";
|
||||||
import { axiosInstance } from "../providers/data";
|
import { axiosInstance } from "../providers/data";
|
||||||
|
|
||||||
import { DragDropContext, Droppable, Draggable } from "@hello-pangea/dnd";
|
import { DragDropContext, Droppable, Draggable } from "@hello-pangea/dnd";
|
||||||
import axios from "axios";
|
|
||||||
|
|
||||||
|
import { articleStore } from "../store/ArticleStore";
|
||||||
|
import { ArticleEditModal } from "./modals/ArticleEditModal";
|
||||||
|
import { StationEditModal } from "./modals/StationEditModal";
|
||||||
|
import { stationStore } from "../store/StationStore";
|
||||||
function insertAtPosition<T>(arr: T[], pos: number, value: T): T[] {
|
function insertAtPosition<T>(arr: T[], pos: number, value: T): T[] {
|
||||||
const index = pos - 1;
|
const index = pos - 1;
|
||||||
if (index >= arr.length) {
|
if (index >= arr.length) {
|
||||||
@ -82,41 +88,8 @@ export const LinkedItems = <T extends { id: number; [key: string]: any }>({
|
|||||||
type,
|
type,
|
||||||
onSave,
|
onSave,
|
||||||
}: LinkedItemsProps<T>) => {
|
}: LinkedItemsProps<T>) => {
|
||||||
const [articleLanguages, setArticleLanguages] = useState<
|
const { setArticleModalOpenAction, setArticleIdAction } = articleStore;
|
||||||
Record<number, string>
|
const { setStationModalOpenAction, setStationIdAction } = stationStore;
|
||||||
>({});
|
|
||||||
|
|
||||||
const handleArticleLanguageChange = (
|
|
||||||
articleId: number,
|
|
||||||
languageCode: string
|
|
||||||
) => {
|
|
||||||
setArticleLanguages((prev) => ({ ...prev, [articleId]: languageCode }));
|
|
||||||
console.log(articleId, languageCode);
|
|
||||||
// Отправка запроса на сервер для сохранения языка
|
|
||||||
axios
|
|
||||||
.get(
|
|
||||||
`${import.meta.env.VITE_KRBL_API}/article/${articleId}/`, // Пример эндпоинта
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${localStorage.getItem("refine-auth")}`,
|
|
||||||
"X-language": languageCode.toLowerCase(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.then((response) => {
|
|
||||||
setLinkedItems(
|
|
||||||
linkedItems.map((item) => {
|
|
||||||
if (item.id == articleId) {
|
|
||||||
console.log(response.data);
|
|
||||||
return { ...response.data, language: languageCode };
|
|
||||||
} else {
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const [position, setPosition] = useState<number>(1);
|
const [position, setPosition] = useState<number>(1);
|
||||||
const [items, setItems] = useState<T[]>([]);
|
const [items, setItems] = useState<T[]>([]);
|
||||||
const [linkedItems, setLinkedItems] = useState<T[]>([]);
|
const [linkedItems, setLinkedItems] = useState<T[]>([]);
|
||||||
@ -143,22 +116,6 @@ export const LinkedItems = <T extends { id: number; [key: string]: any }>({
|
|||||||
}
|
}
|
||||||
}, [linkedItems, setItemsParent]);
|
}, [linkedItems, setItemsParent]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// При загрузке linkedItems можно запросить текущие языки для статей
|
|
||||||
if (childResource === "article" && linkedItems.length > 0) {
|
|
||||||
const initialLanguages: Record<number, string> = {};
|
|
||||||
linkedItems.forEach((article) => {
|
|
||||||
// Предполагается, что у объекта article есть свойство language
|
|
||||||
if (article.language) {
|
|
||||||
initialLanguages[article.id] = article.language;
|
|
||||||
} else {
|
|
||||||
initialLanguages[article.id] = "RU"; // Или другой язык по умолчанию
|
|
||||||
}
|
|
||||||
});
|
|
||||||
setArticleLanguages(initialLanguages);
|
|
||||||
}
|
|
||||||
}, [linkedItems, childResource]);
|
|
||||||
|
|
||||||
const onDragEnd = (result: any) => {
|
const onDragEnd = (result: any) => {
|
||||||
if (!result.destination) return;
|
if (!result.destination) return;
|
||||||
|
|
||||||
@ -294,6 +251,7 @@ export const LinkedItems = <T extends { id: number; [key: string]: any }>({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<Accordion>
|
<Accordion>
|
||||||
<AccordionSummary
|
<AccordionSummary
|
||||||
expandIcon={<ExpandMoreIcon />}
|
expandIcon={<ExpandMoreIcon />}
|
||||||
@ -347,7 +305,24 @@ export const LinkedItems = <T extends { id: number; [key: string]: any }>({
|
|||||||
isDragDisabled={type !== "edit" || !dragAllowed}
|
isDragDisabled={type !== "edit" || !dragAllowed}
|
||||||
>
|
>
|
||||||
{(provided) => (
|
{(provided) => (
|
||||||
|
<>
|
||||||
<TableRow
|
<TableRow
|
||||||
|
sx={{
|
||||||
|
cursor:
|
||||||
|
childResource === "article"
|
||||||
|
? "pointer"
|
||||||
|
: "default",
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
if (childResource === "article") {
|
||||||
|
setArticleModalOpenAction(true);
|
||||||
|
setArticleIdAction(item.id);
|
||||||
|
}
|
||||||
|
if (childResource === "station") {
|
||||||
|
setStationModalOpenAction(true);
|
||||||
|
setStationIdAction(item.id);
|
||||||
|
}
|
||||||
|
}}
|
||||||
ref={provided.innerRef}
|
ref={provided.innerRef}
|
||||||
{...provided.draggableProps}
|
{...provided.draggableProps}
|
||||||
{...provided.dragHandleProps}
|
{...provided.dragHandleProps}
|
||||||
@ -386,9 +361,11 @@ export const LinkedItems = <T extends { id: number; [key: string]: any }>({
|
|||||||
</TableCell>
|
</TableCell>
|
||||||
)}
|
)}
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</Draggable>
|
</Draggable>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{provided.placeholder}
|
{provided.placeholder}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
)}
|
)}
|
||||||
@ -409,8 +386,9 @@ export const LinkedItems = <T extends { id: number; [key: string]: any }>({
|
|||||||
<Autocomplete
|
<Autocomplete
|
||||||
fullWidth
|
fullWidth
|
||||||
value={
|
value={
|
||||||
availableItems?.find((item) => item.id === selectedItemId) ||
|
availableItems?.find(
|
||||||
null
|
(item) => item.id === selectedItemId
|
||||||
|
) || null
|
||||||
}
|
}
|
||||||
onChange={(_, newValue) =>
|
onChange={(_, newValue) =>
|
||||||
setSelectedItemId(newValue?.id || null)
|
setSelectedItemId(newValue?.id || null)
|
||||||
@ -512,5 +490,8 @@ export const LinkedItems = <T extends { id: number; [key: string]: any }>({
|
|||||||
</Stack>
|
</Stack>
|
||||||
</AccordionDetails>
|
</AccordionDetails>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
<ArticleEditModal />
|
||||||
|
<StationEditModal />
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1 +0,0 @@
|
|||||||
export {Header} from './header'
|
|
171
src/components/modals/ArticleEditModal/index.tsx
Normal file
171
src/components/modals/ArticleEditModal/index.tsx
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
import { Modal, Box, Button, TextField, Typography } from "@mui/material";
|
||||||
|
import { articleStore } from "../../../store/ArticleStore";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
import { useForm } from "@refinedev/react-hook-form";
|
||||||
|
import { Controller } from "react-hook-form";
|
||||||
|
import "easymde/dist/easymde.min.css";
|
||||||
|
import { memo, useMemo, useEffect } from "react";
|
||||||
|
import { MarkdownEditor } from "../../MarkdownEditor";
|
||||||
|
import { Edit } from "@refinedev/mui";
|
||||||
|
import { languageStore } from "../../../store/LanguageStore";
|
||||||
|
import { LanguageSwitch } from "../../LanguageSwitch/index";
|
||||||
|
import { useNavigate } from "react-router";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
const MemoizedSimpleMDE = memo(MarkdownEditor);
|
||||||
|
|
||||||
|
const style = {
|
||||||
|
position: "absolute",
|
||||||
|
top: "50%",
|
||||||
|
left: "50%",
|
||||||
|
transform: "translate(-50%, -50%)",
|
||||||
|
width: "60%",
|
||||||
|
bgcolor: "background.paper",
|
||||||
|
border: "2px solid #000",
|
||||||
|
boxShadow: 24,
|
||||||
|
p: 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ArticleEditModal = observer(() => {
|
||||||
|
const [articleData, setArticleData] = useState({
|
||||||
|
ru: {
|
||||||
|
heading: "",
|
||||||
|
body: "",
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
heading: "",
|
||||||
|
body: "",
|
||||||
|
},
|
||||||
|
zh: {
|
||||||
|
heading: "",
|
||||||
|
body: "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const { articleModalOpen, setArticleModalOpenAction, selectedArticleId } =
|
||||||
|
articleStore;
|
||||||
|
const { language } = languageStore;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
setArticleModalOpenAction(false);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
control,
|
||||||
|
formState: { errors },
|
||||||
|
saveButtonProps,
|
||||||
|
reset,
|
||||||
|
setValue,
|
||||||
|
watch,
|
||||||
|
} = useForm({
|
||||||
|
refineCoreProps: {
|
||||||
|
resource: "article",
|
||||||
|
id: selectedArticleId ?? undefined,
|
||||||
|
action: "edit",
|
||||||
|
redirect: false,
|
||||||
|
|
||||||
|
onMutationSuccess: () => {
|
||||||
|
setArticleModalOpenAction(false);
|
||||||
|
reset();
|
||||||
|
window.location.reload();
|
||||||
|
},
|
||||||
|
meta: {
|
||||||
|
headers: {
|
||||||
|
"Accept-Language": language,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (articleData[language as keyof typeof articleData]?.heading) {
|
||||||
|
setValue(
|
||||||
|
"heading",
|
||||||
|
articleData[language as keyof typeof articleData]?.heading || ""
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (articleData[language as keyof typeof articleData]?.body) {
|
||||||
|
setValue(
|
||||||
|
"body",
|
||||||
|
articleData[language as keyof typeof articleData]?.body || ""
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [language, articleData, setValue]);
|
||||||
|
|
||||||
|
const handleLanguageChange = () => {
|
||||||
|
setArticleData((prevData) => ({
|
||||||
|
...prevData,
|
||||||
|
[language]: {
|
||||||
|
heading: watch("heading") || "",
|
||||||
|
body: watch("body") || "",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const simpleMDEOptions = useMemo(
|
||||||
|
() => ({
|
||||||
|
placeholder: "Введите контент в формате Markdown...",
|
||||||
|
spellChecker: false,
|
||||||
|
}),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
open={articleModalOpen}
|
||||||
|
onClose={() => setArticleModalOpenAction(false)}
|
||||||
|
aria-labelledby="modal-modal-title"
|
||||||
|
aria-describedby="modal-modal-description"
|
||||||
|
>
|
||||||
|
<Box sx={style}>
|
||||||
|
<Edit
|
||||||
|
title={<Typography variant="h5">Редактирование статьи</Typography>}
|
||||||
|
headerProps={{
|
||||||
|
sx: {
|
||||||
|
fontSize: "50px",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
saveButtonProps={saveButtonProps}
|
||||||
|
>
|
||||||
|
<LanguageSwitch action={handleLanguageChange} />
|
||||||
|
<Box
|
||||||
|
component="form"
|
||||||
|
sx={{ display: "flex", flexDirection: "column" }}
|
||||||
|
autoComplete="off"
|
||||||
|
>
|
||||||
|
<TextField
|
||||||
|
{...register("heading", {
|
||||||
|
required: "Это поле является обязательным",
|
||||||
|
})}
|
||||||
|
error={!!errors.heading}
|
||||||
|
helperText={errors.heading?.message as string}
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
InputLabelProps={{ shrink: true }}
|
||||||
|
type="text"
|
||||||
|
name="heading"
|
||||||
|
label="Заголовок *"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="body"
|
||||||
|
rules={{ required: "Это поле является обязательным" }}
|
||||||
|
defaultValue=""
|
||||||
|
render={({ field: { onChange, value } }) => (
|
||||||
|
<MemoizedSimpleMDE
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
options={simpleMDEOptions}
|
||||||
|
className="my-markdown-editor"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Edit>
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
});
|
164
src/components/modals/StationEditModal/index.tsx
Normal file
164
src/components/modals/StationEditModal/index.tsx
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
import {
|
||||||
|
Modal,
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
TextField,
|
||||||
|
Typography,
|
||||||
|
Grid,
|
||||||
|
Paper,
|
||||||
|
} from "@mui/material";
|
||||||
|
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
import { useForm } from "@refinedev/react-hook-form";
|
||||||
|
import { Controller } from "react-hook-form";
|
||||||
|
import "easymde/dist/easymde.min.css";
|
||||||
|
import { memo, useMemo, useEffect } from "react";
|
||||||
|
import { MarkdownEditor } from "../../MarkdownEditor";
|
||||||
|
import { Edit } from "@refinedev/mui";
|
||||||
|
import { languageStore } from "../../../store/LanguageStore";
|
||||||
|
import { LanguageSwitch } from "../../LanguageSwitch/index";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
|
import { stationStore } from "../../../store/StationStore";
|
||||||
|
const MemoizedSimpleMDE = memo(MarkdownEditor);
|
||||||
|
|
||||||
|
const TRANSFER_FIELDS = [
|
||||||
|
{ name: "bus", label: "Автобус" },
|
||||||
|
{ name: "metro_blue", label: "Метро (синяя)" },
|
||||||
|
{ name: "metro_green", label: "Метро (зеленая)" },
|
||||||
|
{ name: "metro_orange", label: "Метро (оранжевая)" },
|
||||||
|
{ name: "metro_purple", label: "Метро (фиолетовая)" },
|
||||||
|
{ name: "metro_red", label: "Метро (красная)" },
|
||||||
|
{ name: "train", label: "Электричка" },
|
||||||
|
{ name: "tram", label: "Трамвай" },
|
||||||
|
{ name: "trolleybus", label: "Троллейбус" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const style = {
|
||||||
|
position: "absolute",
|
||||||
|
top: "50%",
|
||||||
|
left: "50%",
|
||||||
|
transform: "translate(-50%, -50%)",
|
||||||
|
width: "60%",
|
||||||
|
bgcolor: "background.paper",
|
||||||
|
border: "2px solid #000",
|
||||||
|
boxShadow: 24,
|
||||||
|
p: 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const StationEditModal = observer(() => {
|
||||||
|
const { stationModalOpen, setStationModalOpenAction, selectedStationId } =
|
||||||
|
stationStore;
|
||||||
|
const { language } = languageStore;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
setStationModalOpenAction(false);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
control,
|
||||||
|
formState: { errors },
|
||||||
|
saveButtonProps,
|
||||||
|
reset,
|
||||||
|
setValue,
|
||||||
|
watch,
|
||||||
|
} = useForm({
|
||||||
|
refineCoreProps: {
|
||||||
|
resource: "station",
|
||||||
|
id: selectedStationId ?? undefined,
|
||||||
|
action: "edit",
|
||||||
|
redirect: false,
|
||||||
|
|
||||||
|
onMutationSuccess: () => {
|
||||||
|
setStationModalOpenAction(false);
|
||||||
|
reset();
|
||||||
|
window.location.reload();
|
||||||
|
},
|
||||||
|
meta: {
|
||||||
|
headers: {
|
||||||
|
"Accept-Language": language,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
open={stationModalOpen}
|
||||||
|
onClose={() => setStationModalOpenAction(false)}
|
||||||
|
aria-labelledby="modal-modal-title"
|
||||||
|
aria-describedby="modal-modal-description"
|
||||||
|
>
|
||||||
|
<Box sx={style}>
|
||||||
|
<Edit
|
||||||
|
title={<Typography variant="h5">Редактирование станции</Typography>}
|
||||||
|
saveButtonProps={saveButtonProps}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
component="form"
|
||||||
|
sx={{ display: "flex", flexDirection: "column" }}
|
||||||
|
autoComplete="off"
|
||||||
|
>
|
||||||
|
<TextField
|
||||||
|
{...register("offset_x", {
|
||||||
|
setValueAs: (value) => parseFloat(value),
|
||||||
|
})}
|
||||||
|
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: "Это поле является обязательным",
|
||||||
|
setValueAs: (value) => parseFloat(value),
|
||||||
|
})}
|
||||||
|
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>
|
||||||
|
</Edit>
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
});
|
@ -58,18 +58,22 @@ export const ArticleEdit = observer(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (articleData[language as keyof typeof articleData]?.heading) {
|
||||||
setValue(
|
setValue(
|
||||||
"heading",
|
"heading",
|
||||||
|
articleData[language as keyof typeof articleData]?.heading
|
||||||
|
);
|
||||||
|
setHeadingPreview(
|
||||||
articleData[language as keyof typeof articleData]?.heading || ""
|
articleData[language as keyof typeof articleData]?.heading || ""
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
if (articleData[language as keyof typeof articleData]?.body) {
|
||||||
setValue(
|
setValue(
|
||||||
"body",
|
"body",
|
||||||
articleData[language as keyof typeof articleData]?.body || ""
|
articleData[language as keyof typeof articleData]?.body || ""
|
||||||
);
|
);
|
||||||
setPreview(articleData[language as keyof typeof articleData]?.body || "");
|
setPreview(articleData[language as keyof typeof articleData]?.body || "");
|
||||||
setHeadingPreview(
|
}
|
||||||
articleData[language as keyof typeof articleData]?.heading || ""
|
|
||||||
);
|
|
||||||
}, [language, articleData, setValue]);
|
}, [language, articleData, setValue]);
|
||||||
|
|
||||||
const handleLanguageChange = (lang: string) => {
|
const handleLanguageChange = (lang: string) => {
|
||||||
|
@ -1,34 +1,49 @@
|
|||||||
import {type GridColDef} from '@mui/x-data-grid'
|
import { type GridColDef } from "@mui/x-data-grid";
|
||||||
import {CustomDataGrid} from '../../components/CustomDataGrid'
|
import { CustomDataGrid } from "../../components/CustomDataGrid";
|
||||||
import {DeleteButton, EditButton, List, ShowButton, useDataGrid} from '@refinedev/mui'
|
import {
|
||||||
import React from 'react'
|
DeleteButton,
|
||||||
|
EditButton,
|
||||||
|
List,
|
||||||
|
ShowButton,
|
||||||
|
useDataGrid,
|
||||||
|
} from "@refinedev/mui";
|
||||||
|
import React, { useEffect } from "react";
|
||||||
|
|
||||||
import {localeText} from '../../locales/ru/localeText'
|
import { localeText } from "../../locales/ru/localeText";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
import { languageStore } from "../../store/LanguageStore";
|
||||||
|
|
||||||
|
export const ArticleList = observer(() => {
|
||||||
|
const { language } = languageStore;
|
||||||
|
|
||||||
export const ArticleList = () => {
|
|
||||||
const { dataGridProps } = useDataGrid({
|
const { dataGridProps } = useDataGrid({
|
||||||
resource: 'article/',
|
resource: "article/",
|
||||||
})
|
meta: {
|
||||||
|
headers: {
|
||||||
|
"Accept-Language": language,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const columns = React.useMemo<GridColDef[]>(
|
const columns = React.useMemo<GridColDef[]>(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
field: 'id',
|
field: "id",
|
||||||
headerName: 'ID',
|
headerName: "ID",
|
||||||
type: 'number',
|
type: "number",
|
||||||
minWidth: 70,
|
minWidth: 70,
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
align: 'left',
|
align: "left",
|
||||||
headerAlign: 'left',
|
headerAlign: "left",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'heading',
|
field: "heading",
|
||||||
headerName: 'Заголовок',
|
headerName: "Заголовок",
|
||||||
type: 'string',
|
type: "string",
|
||||||
minWidth: 300,
|
minWidth: 300,
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
align: 'left',
|
align: "left",
|
||||||
headerAlign: 'left',
|
headerAlign: "left",
|
||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
@ -41,12 +56,12 @@ export const ArticleList = () => {
|
|||||||
// flex: 1,
|
// flex: 1,
|
||||||
// },
|
// },
|
||||||
{
|
{
|
||||||
field: 'actions',
|
field: "actions",
|
||||||
headerName: 'Действия',
|
headerName: "Действия",
|
||||||
align: 'right',
|
align: "right",
|
||||||
headerAlign: 'center',
|
headerAlign: "center",
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
sortable: false,
|
sortable: false,
|
||||||
filterable: false,
|
filterable: false,
|
||||||
disableColumnMenu: true,
|
disableColumnMenu: true,
|
||||||
@ -57,16 +72,22 @@ export const ArticleList = () => {
|
|||||||
<ShowButton hideText recordItemId={row.id} />
|
<ShowButton hideText recordItemId={row.id} />
|
||||||
<DeleteButton hideText recordItemId={row.id} />
|
<DeleteButton hideText recordItemId={row.id} />
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[],
|
[]
|
||||||
)
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<List>
|
<List>
|
||||||
<CustomDataGrid {...dataGridProps} columns={columns} localeText={localeText} getRowId={(row: any) => row.id} />
|
<CustomDataGrid
|
||||||
|
{...dataGridProps}
|
||||||
|
languageEnabled
|
||||||
|
columns={columns}
|
||||||
|
localeText={localeText}
|
||||||
|
getRowId={(row: any) => row.id}
|
||||||
|
/>
|
||||||
</List>
|
</List>
|
||||||
)
|
);
|
||||||
}
|
});
|
||||||
|
@ -1,71 +1,101 @@
|
|||||||
import {Autocomplete, Box, TextField} from '@mui/material'
|
import { Autocomplete, Box, TextField } 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} from 'react-hook-form'
|
import { Controller } from "react-hook-form";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
export const CarrierCreate = () => {
|
import { languageStore } from "../../store/LanguageStore";
|
||||||
|
export const CarrierCreate = observer(() => {
|
||||||
|
const { language } = languageStore;
|
||||||
const {
|
const {
|
||||||
saveButtonProps,
|
saveButtonProps,
|
||||||
refineCore: { formLoading },
|
refineCore: { formLoading },
|
||||||
register,
|
register,
|
||||||
control,
|
control,
|
||||||
formState: { errors },
|
formState: { errors },
|
||||||
} = useForm({})
|
} = useForm({
|
||||||
|
refineCoreProps: {
|
||||||
|
meta: {
|
||||||
|
headers: {
|
||||||
|
"Accept-Language": language,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const { autocompleteProps: cityAutocompleteProps } = useAutocomplete({
|
const { autocompleteProps: cityAutocompleteProps } = useAutocomplete({
|
||||||
resource: 'city',
|
resource: "city",
|
||||||
onSearch: (value) => [
|
onSearch: (value) => [
|
||||||
{
|
{
|
||||||
field: 'name',
|
field: "name",
|
||||||
operator: 'contains',
|
operator: "contains",
|
||||||
value,
|
value,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
});
|
||||||
|
|
||||||
const { autocompleteProps: mediaAutocompleteProps } = useAutocomplete({
|
const { autocompleteProps: mediaAutocompleteProps } = useAutocomplete({
|
||||||
resource: 'media',
|
resource: "media",
|
||||||
onSearch: (value) => [
|
onSearch: (value) => [
|
||||||
{
|
{
|
||||||
field: 'media_name',
|
field: "media_name",
|
||||||
operator: 'contains',
|
operator: "contains",
|
||||||
value,
|
value,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Create isLoading={formLoading} saveButtonProps={saveButtonProps}>
|
<Create isLoading={formLoading} saveButtonProps={saveButtonProps}>
|
||||||
<Box component="form" sx={{display: 'flex', flexDirection: 'column'}} autoComplete="off">
|
<Box
|
||||||
|
component="form"
|
||||||
|
sx={{ display: "flex", flexDirection: "column" }}
|
||||||
|
autoComplete="off"
|
||||||
|
>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="city_id"
|
name="city_id"
|
||||||
rules={{required: 'Это поле является обязательным'}}
|
rules={{ required: "Это поле является обязательным" }}
|
||||||
defaultValue={null}
|
defaultValue={null}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
{...cityAutocompleteProps}
|
{...cityAutocompleteProps}
|
||||||
value={cityAutocompleteProps.options.find((option) => option.id === field.value) || null}
|
value={
|
||||||
|
cityAutocompleteProps.options.find(
|
||||||
|
(option) => option.id === field.value
|
||||||
|
) || null
|
||||||
|
}
|
||||||
onChange={(_, value) => {
|
onChange={(_, value) => {
|
||||||
field.onChange(value?.id || '')
|
field.onChange(value?.id || "");
|
||||||
}}
|
}}
|
||||||
getOptionLabel={(item) => {
|
getOptionLabel={(item) => {
|
||||||
return item ? item.name : ''
|
return item ? item.name : "";
|
||||||
}}
|
}}
|
||||||
isOptionEqualToValue={(option, value) => {
|
isOptionEqualToValue={(option, value) => {
|
||||||
return option.id === value?.id
|
return option.id === value?.id;
|
||||||
}}
|
}}
|
||||||
filterOptions={(options, { inputValue }) => {
|
filterOptions={(options, { inputValue }) => {
|
||||||
return options.filter((option) => option.name.toLowerCase().includes(inputValue.toLowerCase()))
|
return options.filter((option) =>
|
||||||
|
option.name.toLowerCase().includes(inputValue.toLowerCase())
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
renderInput={(params) => <TextField {...params} label="Выберите город" margin="normal" variant="outlined" error={!!errors.city_id} helperText={(errors as any)?.city_id?.message} required />}
|
renderInput={(params) => (
|
||||||
|
<TextField
|
||||||
|
{...params}
|
||||||
|
label="Выберите город"
|
||||||
|
margin="normal"
|
||||||
|
variant="outlined"
|
||||||
|
error={!!errors.city_id}
|
||||||
|
helperText={(errors as any)?.city_id?.message}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
{...register('full_name', {
|
{...register("full_name", {
|
||||||
required: 'Это поле является обязательным',
|
required: "Это поле является обязательным",
|
||||||
})}
|
})}
|
||||||
error={!!(errors as any)?.full_name}
|
error={!!(errors as any)?.full_name}
|
||||||
helperText={(errors as any)?.full_name?.message}
|
helperText={(errors as any)?.full_name?.message}
|
||||||
@ -73,13 +103,13 @@ export const CarrierCreate = () => {
|
|||||||
fullWidth
|
fullWidth
|
||||||
InputLabelProps={{ shrink: true }}
|
InputLabelProps={{ shrink: true }}
|
||||||
type="text"
|
type="text"
|
||||||
label={'Полное имя *'}
|
label={"Полное имя *"}
|
||||||
name="full_name"
|
name="full_name"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
{...register('short_name', {
|
{...register("short_name", {
|
||||||
required: 'Это поле является обязательным',
|
required: "Это поле является обязательным",
|
||||||
})}
|
})}
|
||||||
error={!!(errors as any)?.short_name}
|
error={!!(errors as any)?.short_name}
|
||||||
helperText={(errors as any)?.short_name?.message}
|
helperText={(errors as any)?.short_name?.message}
|
||||||
@ -87,12 +117,12 @@ export const CarrierCreate = () => {
|
|||||||
fullWidth
|
fullWidth
|
||||||
InputLabelProps={{ shrink: true }}
|
InputLabelProps={{ shrink: true }}
|
||||||
type="text"
|
type="text"
|
||||||
label={'Короткое имя *'}
|
label={"Короткое имя *"}
|
||||||
name="short_name"
|
name="short_name"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
{...register('main_color', {
|
{...register("main_color", {
|
||||||
// required: 'Это поле является обязательным',
|
// required: 'Это поле является обязательным',
|
||||||
})}
|
})}
|
||||||
error={!!(errors as any)?.main_color}
|
error={!!(errors as any)?.main_color}
|
||||||
@ -101,20 +131,20 @@ export const CarrierCreate = () => {
|
|||||||
fullWidth
|
fullWidth
|
||||||
InputLabelProps={{ shrink: true }}
|
InputLabelProps={{ shrink: true }}
|
||||||
type="color"
|
type="color"
|
||||||
label={'Основной цвет'}
|
label={"Основной цвет"}
|
||||||
name="main_color"
|
name="main_color"
|
||||||
sx={{
|
sx={{
|
||||||
'& input': {
|
"& input": {
|
||||||
height: '50px',
|
height: "50px",
|
||||||
paddingBlock: '14px',
|
paddingBlock: "14px",
|
||||||
paddingInline: '14px',
|
paddingInline: "14px",
|
||||||
cursor: 'pointer',
|
cursor: "pointer",
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
{...register('left_color', {
|
{...register("left_color", {
|
||||||
// required: 'Это поле является обязательным',
|
// required: 'Это поле является обязательным',
|
||||||
})}
|
})}
|
||||||
error={!!(errors as any)?.left_color}
|
error={!!(errors as any)?.left_color}
|
||||||
@ -123,19 +153,19 @@ export const CarrierCreate = () => {
|
|||||||
fullWidth
|
fullWidth
|
||||||
InputLabelProps={{ shrink: true }}
|
InputLabelProps={{ shrink: true }}
|
||||||
type="color"
|
type="color"
|
||||||
label={'Цвет левого виджета'}
|
label={"Цвет левого виджета"}
|
||||||
name="left_color"
|
name="left_color"
|
||||||
sx={{
|
sx={{
|
||||||
'& input': {
|
"& input": {
|
||||||
height: '50px',
|
height: "50px",
|
||||||
paddingBlock: '14px',
|
paddingBlock: "14px",
|
||||||
paddingInline: '14px',
|
paddingInline: "14px",
|
||||||
cursor: 'pointer',
|
cursor: "pointer",
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
{...register('right_color', {
|
{...register("right_color", {
|
||||||
// required: 'Это поле является обязательным',
|
// required: 'Это поле является обязательным',
|
||||||
})}
|
})}
|
||||||
error={!!(errors as any)?.right_color}
|
error={!!(errors as any)?.right_color}
|
||||||
@ -144,20 +174,20 @@ export const CarrierCreate = () => {
|
|||||||
fullWidth
|
fullWidth
|
||||||
InputLabelProps={{ shrink: true }}
|
InputLabelProps={{ shrink: true }}
|
||||||
type="color"
|
type="color"
|
||||||
label={'Цвет правого виджета'}
|
label={"Цвет правого виджета"}
|
||||||
name="right_color"
|
name="right_color"
|
||||||
sx={{
|
sx={{
|
||||||
'& input': {
|
"& input": {
|
||||||
height: '50px',
|
height: "50px",
|
||||||
paddingBlock: '14px',
|
paddingBlock: "14px",
|
||||||
paddingInline: '14px',
|
paddingInline: "14px",
|
||||||
cursor: 'pointer',
|
cursor: "pointer",
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
{...register('slogan', {
|
{...register("slogan", {
|
||||||
// required: 'Это поле является обязательным',
|
// required: 'Это поле является обязательным',
|
||||||
})}
|
})}
|
||||||
error={!!(errors as any)?.slogan}
|
error={!!(errors as any)?.slogan}
|
||||||
@ -166,7 +196,7 @@ export const CarrierCreate = () => {
|
|||||||
fullWidth
|
fullWidth
|
||||||
InputLabelProps={{ shrink: true }}
|
InputLabelProps={{ shrink: true }}
|
||||||
type="text"
|
type="text"
|
||||||
label={'Слоган'}
|
label={"Слоган"}
|
||||||
name="slogan"
|
name="slogan"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -178,24 +208,41 @@ export const CarrierCreate = () => {
|
|||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
{...mediaAutocompleteProps}
|
{...mediaAutocompleteProps}
|
||||||
value={mediaAutocompleteProps.options.find((option) => option.id === field.value) || null}
|
value={
|
||||||
|
mediaAutocompleteProps.options.find(
|
||||||
|
(option) => option.id === field.value
|
||||||
|
) || null
|
||||||
|
}
|
||||||
onChange={(_, value) => {
|
onChange={(_, value) => {
|
||||||
field.onChange(value?.id || '')
|
field.onChange(value?.id || "");
|
||||||
}}
|
}}
|
||||||
getOptionLabel={(item) => {
|
getOptionLabel={(item) => {
|
||||||
return item ? item.media_name : ''
|
return item ? item.media_name : "";
|
||||||
}}
|
}}
|
||||||
isOptionEqualToValue={(option, value) => {
|
isOptionEqualToValue={(option, value) => {
|
||||||
return option.id === value?.id
|
return option.id === value?.id;
|
||||||
}}
|
}}
|
||||||
filterOptions={(options, { inputValue }) => {
|
filterOptions={(options, { inputValue }) => {
|
||||||
return options.filter((option) => option.media_name.toLowerCase().includes(inputValue.toLowerCase()))
|
return options.filter((option) =>
|
||||||
|
option.media_name
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(inputValue.toLowerCase())
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
renderInput={(params) => <TextField {...params} label="Выберите логотип" margin="normal" variant="outlined" error={!!errors.logo} helperText={(errors as any)?.logo?.message} />}
|
renderInput={(params) => (
|
||||||
|
<TextField
|
||||||
|
{...params}
|
||||||
|
label="Выберите логотип"
|
||||||
|
margin="normal"
|
||||||
|
variant="outlined"
|
||||||
|
error={!!errors.logo}
|
||||||
|
helperText={(errors as any)?.logo?.message}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Create>
|
</Create>
|
||||||
)
|
);
|
||||||
}
|
});
|
||||||
|
@ -10,11 +10,19 @@ import {
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { cityStore } from "../../store/CityStore";
|
import { cityStore } from "../../store/CityStore";
|
||||||
|
import { languageStore } from "../../store/LanguageStore";
|
||||||
|
|
||||||
export const CarrierList = observer(() => {
|
export const CarrierList = observer(() => {
|
||||||
const { city_id } = cityStore;
|
const { city_id } = cityStore;
|
||||||
|
const { language } = languageStore;
|
||||||
|
|
||||||
const { dataGridProps } = useDataGrid({
|
const { dataGridProps } = useDataGrid({
|
||||||
resource: "carrier",
|
resource: "carrier",
|
||||||
|
meta: {
|
||||||
|
headers: {
|
||||||
|
"Accept-Language": language,
|
||||||
|
},
|
||||||
|
},
|
||||||
filters: {
|
filters: {
|
||||||
permanent: [
|
permanent: [
|
||||||
{
|
{
|
||||||
@ -167,7 +175,7 @@ export const CarrierList = observer(() => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<List>
|
<List>
|
||||||
<CustomDataGrid {...dataGridProps} columns={columns} />
|
<CustomDataGrid {...dataGridProps} languageEnabled columns={columns} />
|
||||||
</List>
|
</List>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1,58 +1,76 @@
|
|||||||
import {type GridColDef} from '@mui/x-data-grid'
|
import { type GridColDef } from "@mui/x-data-grid";
|
||||||
import {CustomDataGrid} from '../../components/CustomDataGrid'
|
import { CustomDataGrid } from "../../components/CustomDataGrid";
|
||||||
import {DeleteButton, EditButton, List, ShowButton, useDataGrid} from '@refinedev/mui'
|
import {
|
||||||
import React from 'react'
|
DeleteButton,
|
||||||
|
EditButton,
|
||||||
|
List,
|
||||||
|
ShowButton,
|
||||||
|
useDataGrid,
|
||||||
|
} from "@refinedev/mui";
|
||||||
|
import React, { useEffect } from "react";
|
||||||
|
|
||||||
export const CityList = () => {
|
import { observer } from "mobx-react-lite";
|
||||||
const {dataGridProps} = useDataGrid({})
|
import { languageStore } from "../../store/LanguageStore";
|
||||||
|
|
||||||
|
export const CityList = observer(() => {
|
||||||
|
const { language } = languageStore;
|
||||||
|
|
||||||
|
const { dataGridProps } = useDataGrid({
|
||||||
|
resource: "city",
|
||||||
|
meta: {
|
||||||
|
headers: {
|
||||||
|
"Accept-Language": language,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const columns = React.useMemo<GridColDef[]>(
|
const columns = React.useMemo<GridColDef[]>(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
field: 'id',
|
field: "id",
|
||||||
headerName: 'ID',
|
headerName: "ID",
|
||||||
type: 'number',
|
type: "number",
|
||||||
minWidth: 50,
|
minWidth: 50,
|
||||||
align: 'left',
|
align: "left",
|
||||||
headerAlign: 'left',
|
headerAlign: "left",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'country_code',
|
field: "country_code",
|
||||||
headerName: 'Код страны',
|
headerName: "Код страны",
|
||||||
type: 'string',
|
type: "string",
|
||||||
minWidth: 150,
|
minWidth: 150,
|
||||||
align: 'left',
|
align: "left",
|
||||||
headerAlign: 'left',
|
headerAlign: "left",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'country',
|
field: "country",
|
||||||
headerName: 'Cтрана',
|
headerName: "Cтрана",
|
||||||
type: 'string',
|
type: "string",
|
||||||
minWidth: 150,
|
minWidth: 150,
|
||||||
align: 'left',
|
align: "left",
|
||||||
headerAlign: 'left',
|
headerAlign: "left",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'name',
|
field: "name",
|
||||||
headerName: 'Название',
|
headerName: "Название",
|
||||||
type: 'string',
|
type: "string",
|
||||||
minWidth: 150,
|
minWidth: 150,
|
||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'arms',
|
field: "arms",
|
||||||
headerName: 'Герб',
|
headerName: "Герб",
|
||||||
type: 'string',
|
type: "string",
|
||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'actions',
|
field: "actions",
|
||||||
headerName: 'Действия',
|
headerName: "Действия",
|
||||||
cellClassName: 'city-actions',
|
cellClassName: "city-actions",
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
align: 'right',
|
align: "right",
|
||||||
headerAlign: 'center',
|
headerAlign: "center",
|
||||||
sortable: false,
|
sortable: false,
|
||||||
filterable: false,
|
filterable: false,
|
||||||
disableColumnMenu: true,
|
disableColumnMenu: true,
|
||||||
@ -61,18 +79,22 @@ export const CityList = () => {
|
|||||||
<>
|
<>
|
||||||
<EditButton hideText recordItemId={row.id} />
|
<EditButton hideText recordItemId={row.id} />
|
||||||
<ShowButton hideText recordItemId={row.id} />
|
<ShowButton hideText recordItemId={row.id} />
|
||||||
<DeleteButton hideText confirmTitle="Вы уверены?" recordItemId={row.id} />
|
<DeleteButton
|
||||||
|
hideText
|
||||||
|
confirmTitle="Вы уверены?"
|
||||||
|
recordItemId={row.id}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[],
|
[]
|
||||||
)
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<List>
|
<List>
|
||||||
<CustomDataGrid {...dataGridProps} columns={columns} />
|
<CustomDataGrid {...dataGridProps} columns={columns} languageEnabled />
|
||||||
</List>
|
</List>
|
||||||
)
|
);
|
||||||
}
|
});
|
||||||
|
@ -1,36 +1,53 @@
|
|||||||
import {type GridColDef} from '@mui/x-data-grid'
|
import { type GridColDef } from "@mui/x-data-grid";
|
||||||
import {CustomDataGrid} from '../../components/CustomDataGrid'
|
import { CustomDataGrid } from "../../components/CustomDataGrid";
|
||||||
import {DeleteButton, EditButton, List, ShowButton, useDataGrid} from '@refinedev/mui'
|
import {
|
||||||
import React from 'react'
|
DeleteButton,
|
||||||
|
EditButton,
|
||||||
|
List,
|
||||||
|
ShowButton,
|
||||||
|
useDataGrid,
|
||||||
|
} from "@refinedev/mui";
|
||||||
|
import React from "react";
|
||||||
|
import { languageStore } from "../../store/LanguageStore";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
|
||||||
export const CountryList = () => {
|
export const CountryList = observer(() => {
|
||||||
const {dataGridProps} = useDataGrid({})
|
const { language } = languageStore;
|
||||||
|
|
||||||
|
const { dataGridProps } = useDataGrid({
|
||||||
|
resource: "country",
|
||||||
|
meta: {
|
||||||
|
headers: {
|
||||||
|
"Accept-Language": language,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const columns = React.useMemo<GridColDef[]>(
|
const columns = React.useMemo<GridColDef[]>(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
field: 'code',
|
field: "code",
|
||||||
headerName: 'Код',
|
headerName: "Код",
|
||||||
type: 'string',
|
type: "string",
|
||||||
minWidth: 150,
|
minWidth: 150,
|
||||||
align: 'left',
|
align: "left",
|
||||||
headerAlign: 'left',
|
headerAlign: "left",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'name',
|
field: "name",
|
||||||
headerName: 'Название',
|
headerName: "Название",
|
||||||
type: 'string',
|
type: "string",
|
||||||
minWidth: 150,
|
minWidth: 150,
|
||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'actions',
|
field: "actions",
|
||||||
headerName: 'Действия',
|
headerName: "Действия",
|
||||||
cellClassName: 'country-actions',
|
cellClassName: "country-actions",
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
align: 'right',
|
align: "right",
|
||||||
headerAlign: 'center',
|
headerAlign: "center",
|
||||||
sortable: false,
|
sortable: false,
|
||||||
filterable: false,
|
filterable: false,
|
||||||
disableColumnMenu: true,
|
disableColumnMenu: true,
|
||||||
@ -39,18 +56,27 @@ export const CountryList = () => {
|
|||||||
<>
|
<>
|
||||||
<EditButton hideText recordItemId={row.code} />
|
<EditButton hideText recordItemId={row.code} />
|
||||||
<ShowButton hideText recordItemId={row.code} />
|
<ShowButton hideText recordItemId={row.code} />
|
||||||
<DeleteButton hideText confirmTitle="Вы уверены?" recordItemId={row.code} />
|
<DeleteButton
|
||||||
|
hideText
|
||||||
|
confirmTitle="Вы уверены?"
|
||||||
|
recordItemId={row.code}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[],
|
[]
|
||||||
)
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<List>
|
<List>
|
||||||
<CustomDataGrid {...dataGridProps} columns={columns} getRowId={(row: any) => row.code} />
|
<CustomDataGrid
|
||||||
|
{...dataGridProps}
|
||||||
|
languageEnabled
|
||||||
|
columns={columns}
|
||||||
|
getRowId={(row: any) => row.code}
|
||||||
|
/>
|
||||||
</List>
|
</List>
|
||||||
)
|
);
|
||||||
}
|
});
|
||||||
|
@ -8,24 +8,36 @@ import { TOKEN_KEY } from "../../authProvider";
|
|||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
import { useLocation } from "react-router";
|
import { useLocation } from "react-router";
|
||||||
|
import { languageStore } from "../../store/LanguageStore";
|
||||||
export const SightCreate = observer(() => {
|
export const SightCreate = observer(() => {
|
||||||
const [language, setLanguage] = useState(Cookies.get("lang") || "ru");
|
const { language, setLanguageAction } = languageStore;
|
||||||
|
const [sightData, setSightData] = useState({
|
||||||
|
ru: {
|
||||||
|
name: "",
|
||||||
|
address: "",
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
name: "",
|
||||||
|
address: "",
|
||||||
|
},
|
||||||
|
zh: {
|
||||||
|
name: "",
|
||||||
|
address: "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// Состояния для предпросмотра
|
// Состояния для предпросмотра
|
||||||
const handleLanguageChange = (lang: string) => {
|
const handleLanguageChange = (lang: string) => {
|
||||||
setLanguage(lang);
|
setSightData((prevData) => ({
|
||||||
Cookies.set("lang", lang);
|
...prevData,
|
||||||
|
[language]: {
|
||||||
|
name: watch("name") ?? "",
|
||||||
|
address: watch("address") ?? "",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
setLanguageAction(lang);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const lang = Cookies.get("lang")!;
|
|
||||||
Cookies.set("lang", language);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
Cookies.set("lang", lang);
|
|
||||||
};
|
|
||||||
}, [language]);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
saveButtonProps,
|
saveButtonProps,
|
||||||
refineCore: { formLoading },
|
refineCore: { formLoading },
|
||||||
@ -41,6 +53,17 @@ export const SightCreate = observer(() => {
|
|||||||
});
|
});
|
||||||
const { city_id } = cityStore;
|
const { city_id } = cityStore;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (sightData[language as keyof typeof sightData]?.name) {
|
||||||
|
setValue("name", sightData[language as keyof typeof sightData]?.name);
|
||||||
|
}
|
||||||
|
if (sightData[language as keyof typeof sightData]?.address) {
|
||||||
|
setValue(
|
||||||
|
"address",
|
||||||
|
sightData[language as keyof typeof sightData]?.address
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [sightData, language, setValue]);
|
||||||
const [namePreview, setNamePreview] = useState("");
|
const [namePreview, setNamePreview] = useState("");
|
||||||
const [coordinatesPreview, setCoordinatesPreview] = useState({
|
const [coordinatesPreview, setCoordinatesPreview] = useState({
|
||||||
latitude: "",
|
latitude: "",
|
||||||
|
@ -19,6 +19,8 @@ import { TOKEN_KEY } from "../../authProvider";
|
|||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
|
|
||||||
import { languageStore } from "../../store/LanguageStore";
|
import { languageStore } from "../../store/LanguageStore";
|
||||||
|
import axios from "axios";
|
||||||
|
import { LanguageSwitch } from "../../components/LanguageSwitch/index";
|
||||||
|
|
||||||
function a11yProps(index: number) {
|
function a11yProps(index: number) {
|
||||||
return {
|
return {
|
||||||
@ -53,6 +55,21 @@ export const SightEdit = observer(() => {
|
|||||||
const { id: sightId } = useParams<{ id: string }>();
|
const { id: sightId } = useParams<{ id: string }>();
|
||||||
const { language, setLanguageAction } = languageStore;
|
const { language, setLanguageAction } = languageStore;
|
||||||
|
|
||||||
|
const [sightData, setSightData] = useState({
|
||||||
|
ru: {
|
||||||
|
name: "",
|
||||||
|
address: "",
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
name: "",
|
||||||
|
address: "",
|
||||||
|
},
|
||||||
|
zh: {
|
||||||
|
name: "",
|
||||||
|
address: "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
saveButtonProps,
|
saveButtonProps,
|
||||||
register,
|
register,
|
||||||
@ -71,6 +88,10 @@ export const SightEdit = observer(() => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setLanguageAction("ru");
|
||||||
|
}, []);
|
||||||
|
|
||||||
const { autocompleteProps: cityAutocompleteProps } = useAutocomplete({
|
const { autocompleteProps: cityAutocompleteProps } = useAutocomplete({
|
||||||
resource: "city",
|
resource: "city",
|
||||||
onSearch: (value) => [
|
onSearch: (value) => [
|
||||||
@ -80,7 +101,20 @@ export const SightEdit = observer(() => {
|
|||||||
value,
|
value,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
meta: {
|
||||||
|
headers: {
|
||||||
|
"Accept-Language": "ru",
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
const [mediaFile, setMediaFile] = useState<{
|
||||||
|
src: string;
|
||||||
|
filename: string;
|
||||||
|
}>({
|
||||||
|
src: "",
|
||||||
|
filename: "",
|
||||||
|
});
|
||||||
|
|
||||||
const [tabValue, setTabValue] = useState(0);
|
const [tabValue, setTabValue] = useState(0);
|
||||||
const { autocompleteProps: mediaAutocompleteProps } = useAutocomplete({
|
const { autocompleteProps: mediaAutocompleteProps } = useAutocomplete({
|
||||||
resource: "media",
|
resource: "media",
|
||||||
@ -112,6 +146,18 @@ export const SightEdit = observer(() => {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (sightData[language as keyof typeof sightData]?.name) {
|
||||||
|
setValue("name", sightData[language as keyof typeof sightData]?.name);
|
||||||
|
}
|
||||||
|
if (sightData[language as keyof typeof sightData]?.address) {
|
||||||
|
setValue(
|
||||||
|
"address",
|
||||||
|
sightData[language as keyof typeof sightData]?.address || ""
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [language, sightData, setValue]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const latitude = getValues("latitude");
|
const latitude = getValues("latitude");
|
||||||
const longitude = getValues("longitude");
|
const longitude = getValues("longitude");
|
||||||
@ -183,6 +229,46 @@ export const SightEdit = observer(() => {
|
|||||||
});
|
});
|
||||||
}, [latitudeContent, longitudeContent]);
|
}, [latitudeContent, longitudeContent]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const getMedia = async () => {
|
||||||
|
if (!linkedArticles[selectedArticleIndex]?.id) return;
|
||||||
|
try {
|
||||||
|
const response = await axios.get(
|
||||||
|
`${import.meta.env.VITE_KRBL_API}/article/${
|
||||||
|
linkedArticles[selectedArticleIndex].id
|
||||||
|
}/media`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${localStorage.getItem(TOKEN_KEY)}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const media = response.data[0];
|
||||||
|
if (media) {
|
||||||
|
setMediaFile({
|
||||||
|
src: `${import.meta.env.VITE_KRBL_MEDIA}${
|
||||||
|
media.id
|
||||||
|
}/download?token=${localStorage.getItem(TOKEN_KEY)}`,
|
||||||
|
filename: media.filename,
|
||||||
|
});
|
||||||
|
console.log(media);
|
||||||
|
} else {
|
||||||
|
setMediaFile({
|
||||||
|
src: "",
|
||||||
|
filename: "",
|
||||||
|
}); // или другой дефолт
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
setMediaFile({
|
||||||
|
src: "",
|
||||||
|
filename: "",
|
||||||
|
}); // или обработка ошибки
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
getMedia();
|
||||||
|
}, [selectedArticleIndex, linkedArticles]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const selectedCity = cityAutocompleteProps.options.find(
|
const selectedCity = cityAutocompleteProps.options.find(
|
||||||
(option) => option.id === cityContent
|
(option) => option.id === cityContent
|
||||||
@ -243,6 +329,22 @@ export const SightEdit = observer(() => {
|
|||||||
setPreviewArticlePreview(selectedPreviewArticle?.heading || "");
|
setPreviewArticlePreview(selectedPreviewArticle?.heading || "");
|
||||||
}, [previewArticleContent, articleAutocompleteProps.options]);
|
}, [previewArticleContent, articleAutocompleteProps.options]);
|
||||||
|
|
||||||
|
const handleLanguageChange = (lang: string) => {
|
||||||
|
setSightData((prevData) => ({
|
||||||
|
...prevData,
|
||||||
|
[language]: {
|
||||||
|
name: watch("name") ?? "",
|
||||||
|
address: watch("address") ?? "",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
setLanguageAction(lang);
|
||||||
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
setLanguageAction("ru");
|
||||||
|
};
|
||||||
|
}, [setLanguageAction]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ display: "flex", flexDirection: "column", gap: 2 }}>
|
<Box sx={{ display: "flex", flexDirection: "column", gap: 2 }}>
|
||||||
<Box sx={{ borderBottom: 1, borderColor: "divider" }}>
|
<Box sx={{ borderBottom: 1, borderColor: "divider" }}>
|
||||||
@ -251,13 +353,167 @@ export const SightEdit = observer(() => {
|
|||||||
onChange={(_, newValue) => setTabValue(newValue)}
|
onChange={(_, newValue) => setTabValue(newValue)}
|
||||||
aria-label="basic tabs example"
|
aria-label="basic tabs example"
|
||||||
>
|
>
|
||||||
<Tab label="Основная информация" {...a11yProps(1)} />
|
<Tab label="Левый виджет" {...a11yProps(1)} />
|
||||||
<Tab label="Левый виджет" {...a11yProps(2)} />
|
<Tab label="Правый виджет" {...a11yProps(2)} />
|
||||||
<Tab label="Правый информация" {...a11yProps(3)} />
|
<Tab label="Основная информация" {...a11yProps(3)} />
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<CustomTabPanel value={tabValue} index={0}>
|
<CustomTabPanel value={tabValue} index={0}>
|
||||||
|
<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>
|
||||||
|
<LanguageSwitch />
|
||||||
|
<Box sx={{ mt: 3 }}>
|
||||||
|
<LinkedItems<ArticleItem>
|
||||||
|
type="edit"
|
||||||
|
parentId={sightId!}
|
||||||
|
dragAllowed={true}
|
||||||
|
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 }}>
|
||||||
|
{mediaFile.src && (
|
||||||
|
<>
|
||||||
|
{mediaFile.filename.endsWith(".mp4") ? (
|
||||||
|
<video
|
||||||
|
style={{ width: "100%", height: "100%" }}
|
||||||
|
src={mediaFile.src}
|
||||||
|
controls
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<img
|
||||||
|
style={{ width: "100%", height: "100%" }}
|
||||||
|
src={mediaFile.src}
|
||||||
|
alt="Предпросмотр"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<p style={{ fontSize: "12px", color: "white" }}>
|
||||||
|
{mediaFile.filename}
|
||||||
|
</p>
|
||||||
|
</Box>
|
||||||
|
{/* Водяные знаки */}
|
||||||
|
<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={1}>
|
||||||
<Edit
|
<Edit
|
||||||
saveButtonProps={saveButtonProps}
|
saveButtonProps={saveButtonProps}
|
||||||
footerButtonProps={{
|
footerButtonProps={{
|
||||||
@ -297,7 +553,7 @@ export const SightEdit = observer(() => {
|
|||||||
p: 1,
|
p: 1,
|
||||||
borderRadius: 1,
|
borderRadius: 1,
|
||||||
}}
|
}}
|
||||||
onClick={() => setLanguageAction("ru")}
|
onClick={() => handleLanguageChange("ru")}
|
||||||
>
|
>
|
||||||
RU
|
RU
|
||||||
</Box>
|
</Box>
|
||||||
@ -312,7 +568,7 @@ export const SightEdit = observer(() => {
|
|||||||
p: 1,
|
p: 1,
|
||||||
borderRadius: 1,
|
borderRadius: 1,
|
||||||
}}
|
}}
|
||||||
onClick={() => setLanguageAction("en")}
|
onClick={() => handleLanguageChange("en")}
|
||||||
>
|
>
|
||||||
EN
|
EN
|
||||||
</Box>
|
</Box>
|
||||||
@ -327,7 +583,7 @@ export const SightEdit = observer(() => {
|
|||||||
p: 1,
|
p: 1,
|
||||||
borderRadius: 1,
|
borderRadius: 1,
|
||||||
}}
|
}}
|
||||||
onClick={() => setLanguageAction("zh")}
|
onClick={() => handleLanguageChange("zh")}
|
||||||
>
|
>
|
||||||
ZH
|
ZH
|
||||||
</Box>
|
</Box>
|
||||||
@ -776,135 +1032,6 @@ export const SightEdit = observer(() => {
|
|||||||
</Box>
|
</Box>
|
||||||
</Edit>
|
</Edit>
|
||||||
</CustomTabPanel>
|
</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}>
|
<CustomTabPanel value={tabValue} index={2}>
|
||||||
<Edit
|
<Edit
|
||||||
saveButtonProps={saveButtonProps}
|
saveButtonProps={saveButtonProps}
|
||||||
@ -928,6 +1055,7 @@ export const SightEdit = observer(() => {
|
|||||||
sx={{ flex: 1, display: "flex", flexDirection: "column" }}
|
sx={{ flex: 1, display: "flex", flexDirection: "column" }}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
>
|
>
|
||||||
|
<LanguageSwitch />
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="watermark_lu"
|
name="watermark_lu"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React from "react";
|
import React, { useEffect } from "react";
|
||||||
import { type GridColDef } from "@mui/x-data-grid";
|
import { type GridColDef } from "@mui/x-data-grid";
|
||||||
import {
|
import {
|
||||||
DeleteButton,
|
DeleteButton,
|
||||||
@ -12,11 +12,21 @@ 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";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
|
import { languageStore } from "../../store/LanguageStore";
|
||||||
|
|
||||||
export const SightList = observer(() => {
|
export const SightList = observer(() => {
|
||||||
const { city_id } = cityStore;
|
const { city_id } = cityStore;
|
||||||
|
const { language } = languageStore;
|
||||||
|
|
||||||
const { dataGridProps } = useDataGrid({
|
const { dataGridProps } = useDataGrid({
|
||||||
resource: "sight/",
|
resource: "sight",
|
||||||
|
|
||||||
|
meta: {
|
||||||
|
headers: {
|
||||||
|
"Accept-Language": language,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
filters: {
|
filters: {
|
||||||
permanent: [
|
permanent: [
|
||||||
{
|
{
|
||||||
@ -147,6 +157,7 @@ export const SightList = observer(() => {
|
|||||||
<Stack gap={2.5}>
|
<Stack gap={2.5}>
|
||||||
<CustomDataGrid
|
<CustomDataGrid
|
||||||
{...dataGridProps}
|
{...dataGridProps}
|
||||||
|
languageEnabled
|
||||||
columns={columns}
|
columns={columns}
|
||||||
localeText={localeText}
|
localeText={localeText}
|
||||||
getRowId={(row: any) => row.id}
|
getRowId={(row: any) => row.id}
|
||||||
|
@ -15,6 +15,10 @@ import { Controller } from "react-hook-form";
|
|||||||
import { useParams } from "react-router";
|
import { useParams } from "react-router";
|
||||||
import { LinkedItems } from "../../components/LinkedItems";
|
import { LinkedItems } from "../../components/LinkedItems";
|
||||||
import { type SightItem, sightFields } from "./types";
|
import { type SightItem, sightFields } from "./types";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
import { languageStore } from "../../store/LanguageStore";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { LanguageSwitch } from "../../components/LanguageSwitch/index";
|
||||||
|
|
||||||
const TRANSFER_FIELDS = [
|
const TRANSFER_FIELDS = [
|
||||||
{ name: "bus", label: "Автобус" },
|
{ name: "bus", label: "Автобус" },
|
||||||
@ -28,16 +32,126 @@ const TRANSFER_FIELDS = [
|
|||||||
{ name: "trolleybus", label: "Троллейбус" },
|
{ name: "trolleybus", label: "Троллейбус" },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const StationEdit = () => {
|
export const StationEdit = observer(() => {
|
||||||
|
const { language, setLanguageAction } = languageStore;
|
||||||
|
const [stationData, setStationData] = useState({
|
||||||
|
ru: {
|
||||||
|
name: "",
|
||||||
|
system_name: "",
|
||||||
|
description: "",
|
||||||
|
address: "",
|
||||||
|
latitude: "",
|
||||||
|
longitude: "",
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
name: "",
|
||||||
|
system_name: "",
|
||||||
|
description: "",
|
||||||
|
address: "",
|
||||||
|
latitude: "",
|
||||||
|
longitude: "",
|
||||||
|
},
|
||||||
|
zh: {
|
||||||
|
name: "",
|
||||||
|
system_name: "",
|
||||||
|
description: "",
|
||||||
|
address: "",
|
||||||
|
latitude: "",
|
||||||
|
longitude: "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleLanguageChange = () => {
|
||||||
|
setStationData((prevData) => ({
|
||||||
|
...prevData,
|
||||||
|
[language]: {
|
||||||
|
name: watch("name") ?? "",
|
||||||
|
system_name: watch("system_name") ?? "",
|
||||||
|
description: watch("description") ?? "",
|
||||||
|
address: watch("address") ?? "",
|
||||||
|
latitude: watch("latitude") ?? "",
|
||||||
|
longitude: watch("longitude") ?? "",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const [coordinatesPreview, setCoordinatesPreview] = useState({
|
||||||
|
latitude: "",
|
||||||
|
longitude: "",
|
||||||
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
saveButtonProps,
|
saveButtonProps,
|
||||||
register,
|
register,
|
||||||
control,
|
control,
|
||||||
|
getValues,
|
||||||
|
setValue,
|
||||||
|
watch,
|
||||||
formState: { errors },
|
formState: { errors },
|
||||||
} = useForm({});
|
} = useForm({
|
||||||
|
refineCoreProps: {
|
||||||
|
meta: {
|
||||||
|
headers: { "Accept-Language": language },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
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) {
|
||||||
|
setValue(
|
||||||
|
"system_name",
|
||||||
|
stationData[language as keyof typeof stationData]?.system_name || ""
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (stationData[language as keyof typeof stationData]?.description) {
|
||||||
|
setValue(
|
||||||
|
"description",
|
||||||
|
stationData[language as keyof typeof stationData]?.description || ""
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (stationData[language as keyof typeof stationData]?.latitude) {
|
||||||
|
setValue(
|
||||||
|
"latitude",
|
||||||
|
stationData[language as keyof typeof stationData]?.latitude || ""
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (stationData[language as keyof typeof stationData]?.longitude) {
|
||||||
|
setValue(
|
||||||
|
"longitude",
|
||||||
|
stationData[language as keyof typeof stationData]?.longitude || ""
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [language, stationData, setValue]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setLanguageAction("ru");
|
||||||
|
}, []);
|
||||||
|
|
||||||
const { id: stationId } = useParams<{ id: string }>();
|
const { id: stationId } = useParams<{ id: string }>();
|
||||||
|
|
||||||
|
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 latitudeContent = watch("latitude");
|
||||||
|
const longitudeContent = watch("longitude");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setCoordinatesPreview({
|
||||||
|
latitude: latitudeContent || "",
|
||||||
|
longitude: longitudeContent || "",
|
||||||
|
});
|
||||||
|
}, [latitudeContent, longitudeContent]);
|
||||||
|
|
||||||
const { autocompleteProps: cityAutocompleteProps } = useAutocomplete({
|
const { autocompleteProps: cityAutocompleteProps } = useAutocomplete({
|
||||||
resource: "city",
|
resource: "city",
|
||||||
onSearch: (value) => [
|
onSearch: (value) => [
|
||||||
@ -47,8 +161,28 @@ export const StationEdit = () => {
|
|||||||
value,
|
value,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
|
meta: {
|
||||||
|
headers: {
|
||||||
|
"Accept-Language": "ru",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
queryOptions: {
|
||||||
|
queryKey: ["city"],
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const latitude = getValues("latitude");
|
||||||
|
const longitude = getValues("longitude");
|
||||||
|
if (latitude && longitude) {
|
||||||
|
setCoordinatesPreview({
|
||||||
|
latitude: latitude,
|
||||||
|
longitude: longitude,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [getValues]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Edit saveButtonProps={saveButtonProps}>
|
<Edit saveButtonProps={saveButtonProps}>
|
||||||
<Box
|
<Box
|
||||||
@ -56,6 +190,7 @@ export const StationEdit = () => {
|
|||||||
sx={{ display: "flex", flexDirection: "column" }}
|
sx={{ display: "flex", flexDirection: "column" }}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
>
|
>
|
||||||
|
<LanguageSwitch action={handleLanguageChange} />
|
||||||
<TextField
|
<TextField
|
||||||
{...register("name", {
|
{...register("name", {
|
||||||
required: "Это поле является обязательным",
|
required: "Это поле является обязательным",
|
||||||
@ -125,33 +260,27 @@ export const StationEdit = () => {
|
|||||||
label={"Адрес"}
|
label={"Адрес"}
|
||||||
name="address"
|
name="address"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
{...register("latitude", {
|
value={`${coordinatesPreview.latitude}, ${coordinatesPreview.longitude}`}
|
||||||
required: "Это поле является обязательным",
|
onChange={handleCoordinatesChange}
|
||||||
valueAsNumber: true,
|
|
||||||
})}
|
|
||||||
error={!!(errors as any)?.latitude}
|
error={!!(errors as any)?.latitude}
|
||||||
helperText={(errors as any)?.latitude?.message}
|
helperText={(errors as any)?.latitude?.message}
|
||||||
margin="normal"
|
margin="normal"
|
||||||
fullWidth
|
fullWidth
|
||||||
InputLabelProps={{ shrink: true }}
|
InputLabelProps={{ shrink: true }}
|
||||||
type="number"
|
type="text"
|
||||||
label={"Широта *"}
|
label={"Координаты *"}
|
||||||
name="latitude"
|
|
||||||
/>
|
/>
|
||||||
<TextField
|
<input
|
||||||
{...register("longitude", {
|
type="hidden"
|
||||||
required: "Это поле является обязательным",
|
{...register("latitude", {
|
||||||
valueAsNumber: true,
|
value: coordinatesPreview.latitude,
|
||||||
})}
|
})}
|
||||||
error={!!(errors as any)?.longitude}
|
/>
|
||||||
helperText={(errors as any)?.longitude?.message}
|
<input
|
||||||
margin="normal"
|
type="hidden"
|
||||||
fullWidth
|
{...register("longitude", { value: coordinatesPreview.longitude })}
|
||||||
InputLabelProps={{ shrink: true }}
|
|
||||||
type="number"
|
|
||||||
label={"Долгота *"}
|
|
||||||
name="longitude"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Controller
|
<Controller
|
||||||
@ -210,4 +339,4 @@ export const StationEdit = () => {
|
|||||||
)}
|
)}
|
||||||
</Edit>
|
</Edit>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useMemo } from "react";
|
import React, { useEffect, useMemo } from "react";
|
||||||
import { type GridColDef } from "@mui/x-data-grid";
|
import { type GridColDef } from "@mui/x-data-grid";
|
||||||
import {
|
import {
|
||||||
DeleteButton,
|
DeleteButton,
|
||||||
@ -12,12 +12,19 @@ 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";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
|
import { languageStore } from "../../store/LanguageStore";
|
||||||
|
|
||||||
export const StationList = observer(() => {
|
export const StationList = observer(() => {
|
||||||
const { city_id } = cityStore;
|
const { city_id } = cityStore;
|
||||||
|
const { language } = languageStore;
|
||||||
|
|
||||||
const { dataGridProps } = useDataGrid({
|
const { dataGridProps } = useDataGrid({
|
||||||
resource: "station",
|
resource: "station",
|
||||||
|
meta: {
|
||||||
|
headers: {
|
||||||
|
"Accept-Language": language,
|
||||||
|
},
|
||||||
|
},
|
||||||
filters: {
|
filters: {
|
||||||
permanent: [
|
permanent: [
|
||||||
{
|
{
|
||||||
@ -160,6 +167,7 @@ export const StationList = observer(() => {
|
|||||||
<CustomDataGrid
|
<CustomDataGrid
|
||||||
{...dataGridProps}
|
{...dataGridProps}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
|
languageEnabled
|
||||||
localeText={localeText}
|
localeText={localeText}
|
||||||
getRowId={(row: any) => row.id}
|
getRowId={(row: any) => row.id}
|
||||||
hasCoordinates
|
hasCoordinates
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
import {Autocomplete, Box, TextField} from '@mui/material'
|
import { Autocomplete, Box, TextField } from "@mui/material";
|
||||||
import {Edit, useAutocomplete} from '@refinedev/mui'
|
import { Edit, useAutocomplete } from "@refinedev/mui";
|
||||||
import {useForm} from '@refinedev/react-hook-form'
|
import { useForm } from "@refinedev/react-hook-form";
|
||||||
import {Controller} from 'react-hook-form'
|
import { Controller } from "react-hook-form";
|
||||||
|
|
||||||
import {VEHICLE_TYPES} from '../../lib/constants'
|
import { VEHICLE_TYPES } from "../../lib/constants";
|
||||||
|
|
||||||
type VehicleFormValues = {
|
type VehicleFormValues = {
|
||||||
tail_number: number
|
tail_number: number;
|
||||||
type: number
|
type: number;
|
||||||
city_id: number
|
city_id: number;
|
||||||
}
|
};
|
||||||
|
|
||||||
export const VehicleEdit = () => {
|
export const VehicleEdit = () => {
|
||||||
const {
|
const {
|
||||||
@ -17,25 +17,29 @@ export const VehicleEdit = () => {
|
|||||||
register,
|
register,
|
||||||
control,
|
control,
|
||||||
formState: { errors },
|
formState: { errors },
|
||||||
} = useForm<VehicleFormValues>({})
|
} = useForm<VehicleFormValues>({});
|
||||||
|
|
||||||
const { autocompleteProps: carrierAutocompleteProps } = useAutocomplete({
|
const { autocompleteProps: carrierAutocompleteProps } = useAutocomplete({
|
||||||
resource: 'carrier',
|
resource: "carrier",
|
||||||
onSearch: (value) => [
|
onSearch: (value) => [
|
||||||
{
|
{
|
||||||
field: 'short_name',
|
field: "short_name",
|
||||||
operator: 'contains',
|
operator: "contains",
|
||||||
value,
|
value,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Edit saveButtonProps={saveButtonProps}>
|
<Edit saveButtonProps={saveButtonProps}>
|
||||||
<Box component="form" sx={{display: 'flex', flexDirection: 'column'}} autoComplete="off">
|
<Box
|
||||||
|
component="form"
|
||||||
|
sx={{ display: "flex", flexDirection: "column" }}
|
||||||
|
autoComplete="off"
|
||||||
|
>
|
||||||
<TextField
|
<TextField
|
||||||
{...register('tail_number', {
|
{...register("tail_number", {
|
||||||
required: 'Это поле является обязательным',
|
required: "Это поле является обязательным",
|
||||||
valueAsNumber: true,
|
valueAsNumber: true,
|
||||||
})}
|
})}
|
||||||
error={!!(errors as any)?.tail_number}
|
error={!!(errors as any)?.tail_number}
|
||||||
@ -52,23 +56,36 @@ export const VehicleEdit = () => {
|
|||||||
control={control}
|
control={control}
|
||||||
name="type"
|
name="type"
|
||||||
rules={{
|
rules={{
|
||||||
required: 'Это поле является обязательным',
|
required: "Это поле является обязательным",
|
||||||
}}
|
}}
|
||||||
defaultValue={null}
|
defaultValue={null}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
options={VEHICLE_TYPES}
|
options={VEHICLE_TYPES}
|
||||||
value={VEHICLE_TYPES.find((option) => option.value === field.value) || null}
|
value={
|
||||||
|
VEHICLE_TYPES.find((option) => option.value === field.value) ||
|
||||||
|
null
|
||||||
|
}
|
||||||
onChange={(_, value) => {
|
onChange={(_, value) => {
|
||||||
field.onChange(value?.value || null)
|
field.onChange(value?.value || null);
|
||||||
}}
|
}}
|
||||||
getOptionLabel={(item) => {
|
getOptionLabel={(item) => {
|
||||||
return item ? item.label : ''
|
return item ? item.label : "";
|
||||||
}}
|
}}
|
||||||
isOptionEqualToValue={(option, value) => {
|
isOptionEqualToValue={(option, value) => {
|
||||||
return option.value === value?.value
|
return option.value === value?.value;
|
||||||
}}
|
}}
|
||||||
renderInput={(params) => <TextField {...params} label="Выберите тип" margin="normal" variant="outlined" error={!!errors.type} helperText={(errors as any)?.type?.message} required />}
|
renderInput={(params) => (
|
||||||
|
<TextField
|
||||||
|
{...params}
|
||||||
|
label="Выберите тип"
|
||||||
|
margin="normal"
|
||||||
|
variant="outlined"
|
||||||
|
error={!!errors.type}
|
||||||
|
helperText={(errors as any)?.type?.message}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@ -76,29 +93,47 @@ export const VehicleEdit = () => {
|
|||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="carrier_id"
|
name="carrier_id"
|
||||||
rules={{required: 'Это поле является обязательным'}}
|
rules={{ required: "Это поле является обязательным" }}
|
||||||
defaultValue={null}
|
defaultValue={null}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
{...carrierAutocompleteProps}
|
{...carrierAutocompleteProps}
|
||||||
value={carrierAutocompleteProps.options.find((option) => option.id === field.value) || null}
|
value={
|
||||||
|
carrierAutocompleteProps.options.find(
|
||||||
|
(option) => option.id === field.value
|
||||||
|
) || null
|
||||||
|
}
|
||||||
onChange={(_, value) => {
|
onChange={(_, value) => {
|
||||||
field.onChange(value?.id || '')
|
field.onChange(value?.id || "");
|
||||||
}}
|
}}
|
||||||
getOptionLabel={(item) => {
|
getOptionLabel={(item) => {
|
||||||
return item ? item.short_name : ''
|
return item ? item.short_name : "";
|
||||||
}}
|
}}
|
||||||
isOptionEqualToValue={(option, value) => {
|
isOptionEqualToValue={(option, value) => {
|
||||||
return option.id === value?.id
|
return option.id === value?.id;
|
||||||
}}
|
}}
|
||||||
filterOptions={(options, { inputValue }) => {
|
filterOptions={(options, { inputValue }) => {
|
||||||
return options.filter((option) => option.short_name.toLowerCase().includes(inputValue.toLowerCase()))
|
return options.filter((option) =>
|
||||||
|
option.short_name
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(inputValue.toLowerCase())
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
renderInput={(params) => <TextField {...params} label="Выберите перевозчика" margin="normal" variant="outlined" error={!!errors.city_id} helperText={(errors as any)?.city_id?.message} required />}
|
renderInput={(params) => (
|
||||||
|
<TextField
|
||||||
|
{...params}
|
||||||
|
label="Выберите перевозчика"
|
||||||
|
margin="normal"
|
||||||
|
variant="outlined"
|
||||||
|
error={!!errors.city_id}
|
||||||
|
helperText={(errors as any)?.city_id?.message}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Edit>
|
</Edit>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
@ -1,71 +1,90 @@
|
|||||||
import {type GridColDef} from '@mui/x-data-grid'
|
import { type GridColDef } from "@mui/x-data-grid";
|
||||||
import {CustomDataGrid} from '../../components/CustomDataGrid'
|
import { CustomDataGrid } from "../../components/CustomDataGrid";
|
||||||
import {DeleteButton, EditButton, List, ShowButton, useDataGrid} from '@refinedev/mui'
|
import {
|
||||||
import React from 'react'
|
DeleteButton,
|
||||||
import {VEHICLE_TYPES} from '../../lib/constants'
|
EditButton,
|
||||||
|
List,
|
||||||
|
ShowButton,
|
||||||
|
useDataGrid,
|
||||||
|
} from "@refinedev/mui";
|
||||||
|
import React, { useEffect } from "react";
|
||||||
|
import { VEHICLE_TYPES } from "../../lib/constants";
|
||||||
|
|
||||||
import {localeText} from '../../locales/ru/localeText'
|
import { localeText } from "../../locales/ru/localeText";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
import { languageStore } from "../../store/LanguageStore";
|
||||||
|
|
||||||
export const VehicleList = () => {
|
export const VehicleList = observer(() => {
|
||||||
const {dataGridProps} = useDataGrid({})
|
const { language } = languageStore;
|
||||||
|
|
||||||
|
const { dataGridProps } = useDataGrid({
|
||||||
|
resource: "vehicle",
|
||||||
|
meta: {
|
||||||
|
headers: {
|
||||||
|
"Accept-Language": language,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const columns = React.useMemo<GridColDef[]>(
|
const columns = React.useMemo<GridColDef[]>(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
field: 'id',
|
field: "id",
|
||||||
headerName: 'ID',
|
headerName: "ID",
|
||||||
type: 'number',
|
type: "number",
|
||||||
minWidth: 70,
|
minWidth: 70,
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
align: 'left',
|
align: "left",
|
||||||
headerAlign: 'left',
|
headerAlign: "left",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'carrier_id',
|
field: "carrier_id",
|
||||||
headerName: 'ID перевозчика',
|
headerName: "ID перевозчика",
|
||||||
type: 'string',
|
type: "string",
|
||||||
minWidth: 150,
|
minWidth: 150,
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
align: 'left',
|
align: "left",
|
||||||
headerAlign: 'left',
|
headerAlign: "left",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'tail_number',
|
field: "tail_number",
|
||||||
headerName: 'Бортовой номер',
|
headerName: "Бортовой номер",
|
||||||
type: 'number',
|
type: "number",
|
||||||
minWidth: 150,
|
minWidth: 150,
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
align: 'left',
|
align: "left",
|
||||||
headerAlign: 'left',
|
headerAlign: "left",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'type',
|
field: "type",
|
||||||
headerName: 'Тип',
|
headerName: "Тип",
|
||||||
type: 'string',
|
type: "string",
|
||||||
minWidth: 200,
|
minWidth: 200,
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
align: 'left',
|
align: "left",
|
||||||
headerAlign: 'left',
|
headerAlign: "left",
|
||||||
renderCell: (params) => {
|
renderCell: (params) => {
|
||||||
const value = params.row.type
|
const value = params.row.type;
|
||||||
return VEHICLE_TYPES.find((type) => type.value === value)?.label || value
|
return (
|
||||||
|
VEHICLE_TYPES.find((type) => type.value === value)?.label || value
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'city',
|
field: "city",
|
||||||
headerName: 'Город',
|
headerName: "Город",
|
||||||
type: 'string',
|
type: "string",
|
||||||
align: 'left',
|
align: "left",
|
||||||
headerAlign: 'left',
|
headerAlign: "left",
|
||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'actions',
|
field: "actions",
|
||||||
headerName: 'Действия',
|
headerName: "Действия",
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
align: 'right',
|
align: "right",
|
||||||
headerAlign: 'center',
|
headerAlign: "center",
|
||||||
sortable: false,
|
sortable: false,
|
||||||
filterable: false,
|
filterable: false,
|
||||||
disableColumnMenu: true,
|
disableColumnMenu: true,
|
||||||
@ -74,18 +93,28 @@ export const VehicleList = () => {
|
|||||||
<>
|
<>
|
||||||
<EditButton hideText recordItemId={row.id} />
|
<EditButton hideText recordItemId={row.id} />
|
||||||
<ShowButton hideText recordItemId={row.id} />
|
<ShowButton hideText recordItemId={row.id} />
|
||||||
<DeleteButton hideText confirmTitle="Вы уверены?" recordItemId={row.id} />
|
<DeleteButton
|
||||||
|
hideText
|
||||||
|
confirmTitle="Вы уверены?"
|
||||||
|
recordItemId={row.id}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[],
|
[]
|
||||||
)
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<List>
|
<List>
|
||||||
<CustomDataGrid {...dataGridProps} columns={columns} localeText={localeText} getRowId={(row: any) => row.id} />
|
<CustomDataGrid
|
||||||
|
{...dataGridProps}
|
||||||
|
languageEnabled
|
||||||
|
columns={columns}
|
||||||
|
localeText={localeText}
|
||||||
|
getRowId={(row: any) => row.id}
|
||||||
|
/>
|
||||||
</List>
|
</List>
|
||||||
)
|
);
|
||||||
}
|
});
|
||||||
|
20
src/store/ArticleStore.ts
Normal file
20
src/store/ArticleStore.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { makeAutoObservable } from "mobx";
|
||||||
|
|
||||||
|
class ArticleStore {
|
||||||
|
articleModalOpen: boolean = false;
|
||||||
|
selectedArticleId: number | null = null;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
makeAutoObservable(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
setArticleIdAction = (id: number) => {
|
||||||
|
this.selectedArticleId = id;
|
||||||
|
};
|
||||||
|
|
||||||
|
setArticleModalOpenAction = (open: boolean) => {
|
||||||
|
this.articleModalOpen = open;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const articleStore = new ArticleStore();
|
20
src/store/StationStore.ts
Normal file
20
src/store/StationStore.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { makeAutoObservable } from "mobx";
|
||||||
|
|
||||||
|
class StationStore {
|
||||||
|
stationModalOpen: boolean = false;
|
||||||
|
selectedStationId: number | null = null;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
makeAutoObservable(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
setStationIdAction = (id: number) => {
|
||||||
|
this.selectedStationId = id;
|
||||||
|
};
|
||||||
|
|
||||||
|
setStationModalOpenAction = (open: boolean) => {
|
||||||
|
this.stationModalOpen = open;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const stationStore = new StationStore();
|
Loading…
Reference in New Issue
Block a user