sight edit update
This commit is contained in:
@ -1,72 +1,196 @@
|
||||
import {Box, TextField, Typography, Paper} from '@mui/material'
|
||||
import {Create} from '@refinedev/mui'
|
||||
import {useForm} from '@refinedev/react-hook-form'
|
||||
import {Controller} from 'react-hook-form'
|
||||
import React, {useState, useEffect} from 'react'
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
import { Box, TextField, Typography, Paper } from "@mui/material";
|
||||
import { Create } from "@refinedev/mui";
|
||||
import { useForm } from "@refinedev/react-hook-form";
|
||||
import { Controller } from "react-hook-form";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import Cookies from "js-cookie";
|
||||
import { MarkdownEditor } from "../../components/MarkdownEditor";
|
||||
import "easymde/dist/easymde.min.css";
|
||||
|
||||
import {MarkdownEditor} from '../../components/MarkdownEditor'
|
||||
import 'easymde/dist/easymde.min.css'
|
||||
|
||||
const MemoizedSimpleMDE = React.memo(MarkdownEditor)
|
||||
const MemoizedSimpleMDE = React.memo(MarkdownEditor);
|
||||
|
||||
export const ArticleCreate = () => {
|
||||
const [language, setLanguage] = useState(Cookies.get("lang")!);
|
||||
const [articleData, setArticleData] = useState<{
|
||||
ru: { heading: string; body: string };
|
||||
en: { heading: string; body: string };
|
||||
zh: { heading: string; body: string };
|
||||
}>({
|
||||
ru: { heading: "", body: "" },
|
||||
en: { heading: "", body: "" },
|
||||
zh: { heading: "", body: "" },
|
||||
});
|
||||
|
||||
const {
|
||||
saveButtonProps,
|
||||
refineCore: {formLoading},
|
||||
refineCore: { formLoading },
|
||||
register,
|
||||
control,
|
||||
watch,
|
||||
formState: {errors},
|
||||
formState: { errors },
|
||||
setValue,
|
||||
} = useForm({
|
||||
refineCoreProps: {
|
||||
resource: 'article/',
|
||||
resource: "article/",
|
||||
meta: {
|
||||
headers: {
|
||||
"Accept-Language": language,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
const [preview, setPreview] = useState('')
|
||||
const [headingPreview, setHeadingPreview] = useState('')
|
||||
useEffect(() => {
|
||||
const lang = Cookies.get("lang")!;
|
||||
Cookies.set("lang", language);
|
||||
return () => {
|
||||
Cookies.set("lang", lang);
|
||||
};
|
||||
}, [language]);
|
||||
|
||||
useEffect(() => {
|
||||
setValue(
|
||||
"heading",
|
||||
articleData[language as keyof typeof articleData]?.heading || ""
|
||||
);
|
||||
setValue(
|
||||
"body",
|
||||
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]);
|
||||
|
||||
const handleLanguageChange = (lang: string) => {
|
||||
setArticleData((prevData) => ({
|
||||
...prevData,
|
||||
[language]: {
|
||||
heading: watch("heading") || "",
|
||||
body: watch("body") || "",
|
||||
},
|
||||
}));
|
||||
setLanguage(lang);
|
||||
Cookies.set("lang", lang);
|
||||
};
|
||||
|
||||
const [preview, setPreview] = useState("");
|
||||
const [headingPreview, setHeadingPreview] = useState("");
|
||||
|
||||
// Следим за изменениями в полях body и heading
|
||||
const bodyContent = watch('body')
|
||||
const headingContent = watch('heading')
|
||||
const bodyContent = watch("body");
|
||||
const headingContent = watch("heading");
|
||||
|
||||
useEffect(() => {
|
||||
setPreview(bodyContent || '')
|
||||
}, [bodyContent])
|
||||
setPreview(bodyContent || "");
|
||||
}, [bodyContent]);
|
||||
|
||||
useEffect(() => {
|
||||
setHeadingPreview(headingContent || '')
|
||||
}, [headingContent])
|
||||
setHeadingPreview(headingContent || "");
|
||||
}, [headingContent]);
|
||||
|
||||
const simpleMDEOptions = React.useMemo(
|
||||
() => ({
|
||||
placeholder: 'Введите контент в формате Markdown...',
|
||||
placeholder: "Введите контент в формате Markdown...",
|
||||
spellChecker: false,
|
||||
}),
|
||||
[],
|
||||
)
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<Create isLoading={formLoading} saveButtonProps={saveButtonProps}>
|
||||
<Box sx={{display: 'flex', gap: 2}}>
|
||||
<Box sx={{ display: "flex", flex: 1, gap: 2 }}>
|
||||
{/* Форма создания */}
|
||||
<Box component="form" sx={{flex: 1, display: 'flex', flexDirection: 'column'}} autoComplete="off">
|
||||
<TextField
|
||||
{...register('heading', {
|
||||
required: 'Это поле является обязательным',
|
||||
})}
|
||||
error={!!(errors as any)?.heading}
|
||||
helperText={(errors as any)?.heading?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{shrink: true}}
|
||||
type="text"
|
||||
label="Заголовок *"
|
||||
name="heading"
|
||||
/>
|
||||
<Box sx={{ display: "flex", flex: 1, flexDirection: "column", gap: 2 }}>
|
||||
<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",
|
||||
p: 1,
|
||||
borderRadius: 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",
|
||||
p: 1,
|
||||
borderRadius: 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",
|
||||
p: 1,
|
||||
borderRadius: 1,
|
||||
}}
|
||||
onClick={() => handleLanguageChange("zh")}
|
||||
>
|
||||
ZH
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
component="form"
|
||||
sx={{ flex: 1, display: "flex", flexDirection: "column" }}
|
||||
autoComplete="off"
|
||||
>
|
||||
<TextField
|
||||
{...register("heading", {
|
||||
required: "Это поле является обязательным",
|
||||
})}
|
||||
error={!!(errors as any)?.heading}
|
||||
helperText={(errors as any)?.heading?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="text"
|
||||
label="Заголовок *"
|
||||
name="heading"
|
||||
/>
|
||||
|
||||
<Controller control={control} name="body" rules={{required: 'Это поле является обязательным'}} defaultValue="" render={({field: {onChange, value}}) => <MemoizedSimpleMDE value={value} onChange={onChange} options={simpleMDEOptions} className="my-markdown-editor" />} />
|
||||
<Controller
|
||||
control={control}
|
||||
name="body"
|
||||
rules={{ required: "Это поле является обязательным" }}
|
||||
defaultValue=""
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<MemoizedSimpleMDE
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
options={simpleMDEOptions}
|
||||
className="my-markdown-editor"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Блок предпросмотра */}
|
||||
@ -74,14 +198,15 @@ export const ArticleCreate = () => {
|
||||
sx={{
|
||||
flex: 1,
|
||||
p: 2,
|
||||
maxHeight: 'calc(100vh - 200px)',
|
||||
overflowY: 'auto',
|
||||
position: 'sticky',
|
||||
maxHeight: "calc(100vh - 200px)",
|
||||
overflowY: "auto",
|
||||
position: "sticky",
|
||||
top: 16,
|
||||
borderRadius: 2,
|
||||
border: '1px solid',
|
||||
borderColor: 'primary.main',
|
||||
bgcolor: (theme) => (theme.palette.mode === 'dark' ? 'background.paper' : '#fff'),
|
||||
border: "1px solid",
|
||||
borderColor: "primary.main",
|
||||
bgcolor: (theme) =>
|
||||
theme.palette.mode === "dark" ? "background.paper" : "#fff",
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6" gutterBottom color="primary">
|
||||
@ -93,7 +218,8 @@ export const ArticleCreate = () => {
|
||||
variant="h4"
|
||||
gutterBottom
|
||||
sx={{
|
||||
color: (theme) => (theme.palette.mode === 'dark' ? 'grey.300' : 'grey.800'),
|
||||
color: (theme) =>
|
||||
theme.palette.mode === "dark" ? "grey.300" : "grey.800",
|
||||
mb: 3,
|
||||
}}
|
||||
>
|
||||
@ -103,39 +229,41 @@ export const ArticleCreate = () => {
|
||||
{/* Markdown контент */}
|
||||
<Box
|
||||
sx={{
|
||||
'& img': {
|
||||
maxWidth: '100%',
|
||||
height: 'auto',
|
||||
"& img": {
|
||||
maxWidth: "100%",
|
||||
height: "auto",
|
||||
borderRadius: 1,
|
||||
},
|
||||
'& h1, & h2, & h3, & h4, & h5, & h6': {
|
||||
color: 'primary.main',
|
||||
"& h1, & h2, & h3, & h4, & h5, & h6": {
|
||||
color: "primary.main",
|
||||
mt: 2,
|
||||
mb: 1,
|
||||
},
|
||||
'& p': {
|
||||
"& p": {
|
||||
mb: 2,
|
||||
color: (theme) => (theme.palette.mode === 'dark' ? 'grey.300' : 'grey.800'),
|
||||
color: (theme) =>
|
||||
theme.palette.mode === "dark" ? "grey.300" : "grey.800",
|
||||
},
|
||||
'& a': {
|
||||
color: 'primary.main',
|
||||
textDecoration: 'none',
|
||||
'&:hover': {
|
||||
textDecoration: 'underline',
|
||||
"& a": {
|
||||
color: "primary.main",
|
||||
textDecoration: "none",
|
||||
"&:hover": {
|
||||
textDecoration: "underline",
|
||||
},
|
||||
},
|
||||
'& blockquote': {
|
||||
borderLeft: '4px solid',
|
||||
borderColor: 'primary.main',
|
||||
"& blockquote": {
|
||||
borderLeft: "4px solid",
|
||||
borderColor: "primary.main",
|
||||
pl: 2,
|
||||
my: 2,
|
||||
color: 'text.secondary',
|
||||
color: "text.secondary",
|
||||
},
|
||||
'& code': {
|
||||
bgcolor: (theme) => (theme.palette.mode === 'dark' ? 'grey.900' : 'grey.100'),
|
||||
"& code": {
|
||||
bgcolor: (theme) =>
|
||||
theme.palette.mode === "dark" ? "grey.900" : "grey.100",
|
||||
p: 0.5,
|
||||
borderRadius: 0.5,
|
||||
color: 'primary.main',
|
||||
color: "primary.main",
|
||||
},
|
||||
}}
|
||||
>
|
||||
@ -144,5 +272,5 @@ export const ArticleCreate = () => {
|
||||
</Paper>
|
||||
</Box>
|
||||
</Create>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@ -2,7 +2,7 @@ import { Box, TextField, Typography, Paper } from "@mui/material";
|
||||
import { Edit } from "@refinedev/mui";
|
||||
import { useForm } from "@refinedev/react-hook-form";
|
||||
import { Controller } from "react-hook-form";
|
||||
import { useParams } from "react-router";
|
||||
import { useLocation, useParams } from "react-router";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import { useList } from "@refinedev/core";
|
||||
@ -12,31 +12,90 @@ import { LinkedItems } from "../../components/LinkedItems";
|
||||
import { MediaItem, mediaFields } from "./types";
|
||||
import { TOKEN_KEY } from "../../authProvider";
|
||||
import "easymde/dist/easymde.min.css";
|
||||
|
||||
import Cookies from "js-cookie";
|
||||
const MemoizedSimpleMDE = React.memo(MarkdownEditor);
|
||||
|
||||
export const ArticleEdit = () => {
|
||||
// const [initialLanguage] = useState(Cookies.get("lang")!);
|
||||
// const { pathname } = useLocation();
|
||||
|
||||
// useEffect(() => {
|
||||
// Cookies.set("lang", initialLanguage);
|
||||
// }, [pathname]);
|
||||
const [language, setLanguage] = useState(Cookies.get("lang")!);
|
||||
const [articleData, setArticleData] = useState<{
|
||||
ru: { heading: string; body: string };
|
||||
en: { heading: string; body: string };
|
||||
zh: { heading: string; body: string };
|
||||
}>({
|
||||
ru: { heading: "", body: "" },
|
||||
en: { heading: "", body: "" },
|
||||
zh: { heading: "", body: "" },
|
||||
});
|
||||
const { id: articleId } = useParams<{ id: string }>();
|
||||
const [preview, setPreview] = useState("");
|
||||
const [headingPreview, setHeadingPreview] = useState("");
|
||||
const simpleMDEOptions = React.useMemo(
|
||||
() => ({
|
||||
placeholder: "Введите контент в формате Markdown...",
|
||||
spellChecker: false,
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
const {
|
||||
saveButtonProps,
|
||||
register,
|
||||
control,
|
||||
handleSubmit,
|
||||
watch,
|
||||
formState: { errors },
|
||||
} = useForm();
|
||||
|
||||
const { id: articleId } = useParams<{ id: string }>();
|
||||
const [preview, setPreview] = useState("");
|
||||
const [headingPreview, setHeadingPreview] = useState("");
|
||||
|
||||
// Получаем привязанные медиа
|
||||
const { data: mediaData } = useList<MediaItem>({
|
||||
resource: `article/${articleId}/media`,
|
||||
queryOptions: {
|
||||
enabled: !!articleId,
|
||||
setValue,
|
||||
} = useForm({
|
||||
refineCoreProps: {
|
||||
meta: {
|
||||
headers: {
|
||||
"Accept-Language": language,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Следим за изменениями в полях body и heading
|
||||
useEffect(() => {
|
||||
const lang = Cookies.get("lang")!;
|
||||
Cookies.set("lang", language);
|
||||
return () => {
|
||||
Cookies.set("lang", lang);
|
||||
};
|
||||
}, [language]);
|
||||
|
||||
useEffect(() => {
|
||||
setValue(
|
||||
"heading",
|
||||
articleData[language as keyof typeof articleData]?.heading || ""
|
||||
);
|
||||
setValue(
|
||||
"body",
|
||||
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]);
|
||||
|
||||
const handleLanguageChange = (lang: string) => {
|
||||
setArticleData((prevData) => ({
|
||||
...prevData,
|
||||
[language]: {
|
||||
heading: watch("heading") || "",
|
||||
body: watch("body") || "",
|
||||
},
|
||||
}));
|
||||
setLanguage(lang);
|
||||
Cookies.set("lang", lang);
|
||||
};
|
||||
|
||||
const bodyContent = watch("body");
|
||||
const headingContent = watch("heading");
|
||||
|
||||
@ -48,62 +107,124 @@ export const ArticleEdit = () => {
|
||||
setHeadingPreview(headingContent || "");
|
||||
}, [headingContent]);
|
||||
|
||||
const simpleMDEOptions = React.useMemo(
|
||||
() => ({
|
||||
placeholder: "Введите контент в формате Markdown...",
|
||||
spellChecker: false,
|
||||
}),
|
||||
[]
|
||||
);
|
||||
const onSubmit = (data: { heading: string; body: string }) => {
|
||||
// Здесь вы будете отправлять данные на сервер,
|
||||
// учитывая текущий язык (language)
|
||||
console.log("Данные для сохранения:", data, language);
|
||||
// ... ваша логика сохранения ...
|
||||
};
|
||||
|
||||
const { data: mediaData } = useList<MediaItem>({
|
||||
resource: `article/${articleId}/media`,
|
||||
queryOptions: {
|
||||
enabled: !!articleId,
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Edit saveButtonProps={saveButtonProps}>
|
||||
<Box sx={{ display: "flex", gap: 2 }}>
|
||||
{/* Форма редактирования */}
|
||||
<Box
|
||||
component="form"
|
||||
sx={{ flex: 1, display: "flex", flexDirection: "column" }}
|
||||
autoComplete="off"
|
||||
>
|
||||
<TextField
|
||||
{...register("heading", {
|
||||
required: "Это поле является обязательным",
|
||||
})}
|
||||
error={!!(errors as any)?.heading}
|
||||
helperText={(errors as any)?.heading?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="text"
|
||||
label="Заголовок *"
|
||||
name="heading"
|
||||
/>
|
||||
{/* Форма создания */}
|
||||
<Box sx={{ display: "flex", flex: 1, flexDirection: "column", gap: 2 }}>
|
||||
<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",
|
||||
p: 1,
|
||||
borderRadius: 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",
|
||||
p: 1,
|
||||
borderRadius: 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",
|
||||
p: 1,
|
||||
borderRadius: 1,
|
||||
}}
|
||||
onClick={() => handleLanguageChange("zh")}
|
||||
>
|
||||
ZH
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
component="form"
|
||||
sx={{ flex: 1, display: "flex", flexDirection: "column" }}
|
||||
autoComplete="off"
|
||||
>
|
||||
<TextField
|
||||
{...register("heading", {
|
||||
required: "Это поле является обязательным",
|
||||
})}
|
||||
error={!!(errors as any)?.heading}
|
||||
helperText={(errors as any)?.heading?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="text"
|
||||
label="Заголовок *"
|
||||
name="heading"
|
||||
/>
|
||||
|
||||
<Controller
|
||||
control={control}
|
||||
name="body"
|
||||
rules={{ required: "Это поле является обязательным" }}
|
||||
defaultValue=""
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<MemoizedSimpleMDE
|
||||
value={value} // markdown
|
||||
onChange={onChange}
|
||||
options={simpleMDEOptions}
|
||||
className="my-markdown-editor"
|
||||
<Controller
|
||||
control={control}
|
||||
name="body"
|
||||
rules={{ required: "Это поле является обязательным" }}
|
||||
defaultValue=""
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<MemoizedSimpleMDE
|
||||
value={value} // markdown
|
||||
onChange={onChange}
|
||||
options={simpleMDEOptions}
|
||||
className="my-markdown-editor"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
{articleId && (
|
||||
<LinkedItems<MediaItem>
|
||||
type="edit"
|
||||
parentId={articleId}
|
||||
parentResource="article"
|
||||
childResource="media"
|
||||
fields={mediaFields}
|
||||
title="медиа"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
{articleId && (
|
||||
<LinkedItems<MediaItem>
|
||||
type="edit"
|
||||
parentId={articleId}
|
||||
parentResource="article"
|
||||
childResource="media"
|
||||
fields={mediaFields}
|
||||
title="медиа"
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Блок предпросмотра */}
|
||||
|
@ -1,171 +1,191 @@
|
||||
import {Autocomplete, Box, TextField} from '@mui/material'
|
||||
import {Edit, useAutocomplete} from '@refinedev/mui'
|
||||
import {useForm} from '@refinedev/react-hook-form'
|
||||
import {Controller} from 'react-hook-form'
|
||||
import { Autocomplete, Box, TextField } from "@mui/material";
|
||||
import { Edit, useAutocomplete } from "@refinedev/mui";
|
||||
import { useForm } from "@refinedev/react-hook-form";
|
||||
import { Controller } from "react-hook-form";
|
||||
|
||||
export const CarrierEdit = () => {
|
||||
const {
|
||||
saveButtonProps,
|
||||
register,
|
||||
control,
|
||||
formState: {errors},
|
||||
} = useForm()
|
||||
formState: { errors },
|
||||
} = useForm();
|
||||
|
||||
const {autocompleteProps: cityAutocompleteProps} = useAutocomplete({
|
||||
resource: 'city',
|
||||
const { autocompleteProps: cityAutocompleteProps } = useAutocomplete({
|
||||
resource: "city",
|
||||
onSearch: (value) => [
|
||||
{
|
||||
field: 'name',
|
||||
operator: 'contains',
|
||||
field: "name",
|
||||
operator: "contains",
|
||||
value,
|
||||
},
|
||||
],
|
||||
})
|
||||
});
|
||||
|
||||
const {autocompleteProps: mediaAutocompleteProps} = useAutocomplete({
|
||||
resource: 'media',
|
||||
const { autocompleteProps: mediaAutocompleteProps } = useAutocomplete({
|
||||
resource: "media",
|
||||
onSearch: (value) => [
|
||||
{
|
||||
field: 'media_name',
|
||||
operator: 'contains',
|
||||
field: "media_name",
|
||||
operator: "contains",
|
||||
value,
|
||||
},
|
||||
],
|
||||
})
|
||||
});
|
||||
|
||||
return (
|
||||
<Edit saveButtonProps={saveButtonProps}>
|
||||
<Box component="form" sx={{display: 'flex', flexDirection: 'column'}} autoComplete="off">
|
||||
<Box
|
||||
component="form"
|
||||
sx={{ display: "flex", flexDirection: "column" }}
|
||||
autoComplete="off"
|
||||
>
|
||||
<Controller
|
||||
control={control}
|
||||
name="city_id"
|
||||
rules={{required: 'Это поле является обязательным'}}
|
||||
rules={{ required: "Это поле является обязательным" }}
|
||||
defaultValue={null}
|
||||
render={({field}) => (
|
||||
render={({ field }) => (
|
||||
<Autocomplete
|
||||
{...cityAutocompleteProps}
|
||||
value={cityAutocompleteProps.options.find((option) => option.id === field.value) || null}
|
||||
value={
|
||||
cityAutocompleteProps.options.find(
|
||||
(option) => option.id === field.value
|
||||
) || null
|
||||
}
|
||||
onChange={(_, value) => {
|
||||
field.onChange(value?.id || '')
|
||||
field.onChange(value?.id || "");
|
||||
}}
|
||||
getOptionLabel={(item) => {
|
||||
return item ? item.name : ''
|
||||
return item ? item.name : "";
|
||||
}}
|
||||
isOptionEqualToValue={(option, value) => {
|
||||
return option.id === value?.id
|
||||
return option.id === value?.id;
|
||||
}}
|
||||
filterOptions={(options, {inputValue}) => {
|
||||
return options.filter((option) => option.name.toLowerCase().includes(inputValue.toLowerCase()))
|
||||
filterOptions={(options, { inputValue }) => {
|
||||
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
|
||||
{...register('full_name', {
|
||||
required: 'Это поле является обязательным',
|
||||
{...register("full_name", {
|
||||
required: "Это поле является обязательным",
|
||||
})}
|
||||
error={!!(errors as any)?.full_name}
|
||||
helperText={(errors as any)?.full_name?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{shrink: true}}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="text"
|
||||
label={'Полное имя *'}
|
||||
label={"Полное имя *"}
|
||||
name="full_name"
|
||||
/>
|
||||
|
||||
<TextField
|
||||
{...register('short_name', {
|
||||
required: 'Это поле является обязательным',
|
||||
{...register("short_name", {
|
||||
required: "Это поле является обязательным",
|
||||
})}
|
||||
error={!!(errors as any)?.short_name}
|
||||
helperText={(errors as any)?.short_name?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{shrink: true}}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="text"
|
||||
label={'Короткое имя *'}
|
||||
label={"Короткое имя *"}
|
||||
name="short_name"
|
||||
/>
|
||||
|
||||
<TextField
|
||||
{...register('main_color', {
|
||||
{...register("main_color", {
|
||||
// required: 'Это поле является обязательным',
|
||||
})}
|
||||
error={!!(errors as any)?.main_color}
|
||||
helperText={(errors as any)?.main_color?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{shrink: true}}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="color"
|
||||
label={'Основной цвет'}
|
||||
label={"Основной цвет"}
|
||||
name="main_color"
|
||||
sx={{
|
||||
'& input': {
|
||||
height: '50px',
|
||||
paddingBlock: '14px',
|
||||
paddingInline: '14px',
|
||||
cursor: 'pointer',
|
||||
"& input": {
|
||||
height: "50px",
|
||||
paddingBlock: "14px",
|
||||
paddingInline: "14px",
|
||||
cursor: "pointer",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
{...register('left_color', {
|
||||
{...register("left_color", {
|
||||
// required: 'Это поле является обязательным',
|
||||
})}
|
||||
error={!!(errors as any)?.left_color}
|
||||
helperText={(errors as any)?.left_color?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{shrink: true}}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="color"
|
||||
label={'Цвет левого виджета'}
|
||||
label={"Цвет левого виджета"}
|
||||
name="left_color"
|
||||
sx={{
|
||||
'& input': {
|
||||
height: '50px',
|
||||
paddingBlock: '14px',
|
||||
paddingInline: '14px',
|
||||
cursor: 'pointer',
|
||||
"& input": {
|
||||
height: "50px",
|
||||
paddingBlock: "14px",
|
||||
paddingInline: "14px",
|
||||
cursor: "pointer",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<TextField
|
||||
{...register('right_color', {
|
||||
{...register("right_color", {
|
||||
// required: 'Это поле является обязательным',
|
||||
})}
|
||||
error={!!(errors as any)?.right_color}
|
||||
helperText={(errors as any)?.right_color?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{shrink: true}}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="color"
|
||||
label={'Цвет правого виджета'}
|
||||
label={"Цвет правого виджета"}
|
||||
name="right_color"
|
||||
sx={{
|
||||
'& input': {
|
||||
height: '50px',
|
||||
paddingBlock: '14px',
|
||||
paddingInline: '14px',
|
||||
cursor: 'pointer',
|
||||
"& input": {
|
||||
height: "50px",
|
||||
paddingBlock: "14px",
|
||||
paddingInline: "14px",
|
||||
cursor: "pointer",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
{...register('slogan', {
|
||||
{...register("slogan", {
|
||||
// required: 'Это поле является обязательным',
|
||||
})}
|
||||
error={!!(errors as any)?.slogan}
|
||||
helperText={(errors as any)?.slogan?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{shrink: true}}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="text"
|
||||
label={'Слоган'}
|
||||
label={"Слоган"}
|
||||
name="slogan"
|
||||
/>
|
||||
|
||||
@ -174,27 +194,44 @@ export const CarrierEdit = () => {
|
||||
name="logo"
|
||||
// rules={{required: 'Это поле является обязательным'}}
|
||||
defaultValue={null}
|
||||
render={({field}) => (
|
||||
render={({ field }) => (
|
||||
<Autocomplete
|
||||
{...mediaAutocompleteProps}
|
||||
value={mediaAutocompleteProps.options.find((option) => option.id === field.value) || null}
|
||||
value={
|
||||
mediaAutocompleteProps.options.find(
|
||||
(option) => option.id === field.value
|
||||
) || null
|
||||
}
|
||||
onChange={(_, value) => {
|
||||
field.onChange(value?.id || '')
|
||||
field.onChange(value?.id || "");
|
||||
}}
|
||||
getOptionLabel={(item) => {
|
||||
return item ? item.media_name : ''
|
||||
return item ? item.media_name : "";
|
||||
}}
|
||||
isOptionEqualToValue={(option, value) => {
|
||||
return option.id === value?.id
|
||||
return option.id === value?.id;
|
||||
}}
|
||||
filterOptions={(options, {inputValue}) => {
|
||||
return options.filter((option) => option.media_name.toLowerCase().includes(inputValue.toLowerCase()))
|
||||
filterOptions={(options, { inputValue }) => {
|
||||
return options.filter((option) =>
|
||||
option.media_name
|
||||
.toLowerCase()
|
||||
.includes(inputValue.toLowerCase())
|
||||
);
|
||||
}}
|
||||
renderInput={(params) => <TextField {...params} label="Выберите логотип" margin="normal" variant="outlined" error={!!errors.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>
|
||||
</Edit>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@ -1,110 +1,173 @@
|
||||
import {type GridColDef} from '@mui/x-data-grid'
|
||||
import {CustomDataGrid} from '../../components/CustomDataGrid'
|
||||
import {DeleteButton, EditButton, List, ShowButton, useDataGrid} from '@refinedev/mui'
|
||||
import React from 'react'
|
||||
import { type GridColDef } from "@mui/x-data-grid";
|
||||
import { CustomDataGrid } from "../../components/CustomDataGrid";
|
||||
import {
|
||||
DeleteButton,
|
||||
EditButton,
|
||||
List,
|
||||
ShowButton,
|
||||
useDataGrid,
|
||||
} from "@refinedev/mui";
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { cityStore } from "../../store/CityStore";
|
||||
|
||||
export const CarrierList = () => {
|
||||
const {dataGridProps} = useDataGrid({})
|
||||
export const CarrierList = observer(() => {
|
||||
const { city_id } = cityStore;
|
||||
const { dataGridProps } = useDataGrid({
|
||||
resource: "carrier",
|
||||
filters: {
|
||||
permanent: [
|
||||
{
|
||||
field: "cityID",
|
||||
operator: "eq",
|
||||
value: city_id,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const columns = React.useMemo<GridColDef[]>(
|
||||
() => [
|
||||
{
|
||||
field: 'id',
|
||||
headerName: 'ID',
|
||||
type: 'number',
|
||||
field: "id",
|
||||
headerName: "ID",
|
||||
type: "number",
|
||||
minWidth: 50,
|
||||
align: 'left',
|
||||
headerAlign: 'left',
|
||||
align: "left",
|
||||
headerAlign: "left",
|
||||
},
|
||||
{
|
||||
field: 'city_id',
|
||||
headerName: 'ID Города',
|
||||
type: 'number',
|
||||
field: "city_id",
|
||||
headerName: "ID Города",
|
||||
type: "number",
|
||||
minWidth: 100,
|
||||
align: 'left',
|
||||
headerAlign: 'left',
|
||||
align: "left",
|
||||
headerAlign: "left",
|
||||
},
|
||||
{
|
||||
field: 'full_name',
|
||||
headerName: 'Полное имя',
|
||||
type: 'string',
|
||||
field: "full_name",
|
||||
headerName: "Полное имя",
|
||||
type: "string",
|
||||
minWidth: 200,
|
||||
},
|
||||
{
|
||||
field: 'short_name',
|
||||
headerName: 'Короткое имя',
|
||||
type: 'string',
|
||||
field: "short_name",
|
||||
headerName: "Короткое имя",
|
||||
type: "string",
|
||||
minWidth: 125,
|
||||
},
|
||||
{
|
||||
field: 'city',
|
||||
headerName: 'Город',
|
||||
type: 'string',
|
||||
field: "city",
|
||||
headerName: "Город",
|
||||
type: "string",
|
||||
minWidth: 125,
|
||||
align: 'left',
|
||||
headerAlign: 'left',
|
||||
align: "left",
|
||||
headerAlign: "left",
|
||||
flex: 1,
|
||||
},
|
||||
{
|
||||
field: 'main_color',
|
||||
headerName: 'Основной цвет',
|
||||
type: 'string',
|
||||
field: "main_color",
|
||||
headerName: "Основной цвет",
|
||||
type: "string",
|
||||
minWidth: 150,
|
||||
renderCell: ({value}) => <div style={{display: 'grid', placeItems: 'center', width: '100%', height: '100%', backgroundColor: `${value}10`, borderRadius: 10}}>{value}</div>,
|
||||
renderCell: ({ value }) => (
|
||||
<div
|
||||
style={{
|
||||
display: "grid",
|
||||
placeItems: "center",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
backgroundColor: `${value}10`,
|
||||
borderRadius: 10,
|
||||
}}
|
||||
>
|
||||
{value}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
field: 'left_color',
|
||||
headerName: 'Цвет левого виджета',
|
||||
type: 'string',
|
||||
field: "left_color",
|
||||
headerName: "Цвет левого виджета",
|
||||
type: "string",
|
||||
minWidth: 150,
|
||||
renderCell: ({value}) => <div style={{display: 'grid', placeItems: 'center', width: '100%', height: '100%', backgroundColor: `${value}10`, borderRadius: 10}}>{value}</div>,
|
||||
renderCell: ({ value }) => (
|
||||
<div
|
||||
style={{
|
||||
display: "grid",
|
||||
placeItems: "center",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
backgroundColor: `${value}10`,
|
||||
borderRadius: 10,
|
||||
}}
|
||||
>
|
||||
{value}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
field: 'right_color',
|
||||
headerName: 'Цвет правого виджета',
|
||||
type: 'string',
|
||||
field: "right_color",
|
||||
headerName: "Цвет правого виджета",
|
||||
type: "string",
|
||||
minWidth: 150,
|
||||
renderCell: ({value}) => <div style={{display: 'grid', placeItems: 'center', width: '100%', height: '100%', backgroundColor: `${value}10`, borderRadius: 10}}>{value}</div>,
|
||||
renderCell: ({ value }) => (
|
||||
<div
|
||||
style={{
|
||||
display: "grid",
|
||||
placeItems: "center",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
backgroundColor: `${value}10`,
|
||||
borderRadius: 10,
|
||||
}}
|
||||
>
|
||||
{value}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
field: 'logo',
|
||||
headerName: 'Лого',
|
||||
type: 'string',
|
||||
field: "logo",
|
||||
headerName: "Лого",
|
||||
type: "string",
|
||||
minWidth: 150,
|
||||
},
|
||||
{
|
||||
field: 'slogan',
|
||||
headerName: 'Слоган',
|
||||
type: 'string',
|
||||
field: "slogan",
|
||||
headerName: "Слоган",
|
||||
type: "string",
|
||||
minWidth: 150,
|
||||
},
|
||||
{
|
||||
field: 'actions',
|
||||
headerName: 'Действия',
|
||||
field: "actions",
|
||||
headerName: "Действия",
|
||||
minWidth: 120,
|
||||
display: 'flex',
|
||||
align: 'right',
|
||||
headerAlign: 'center',
|
||||
display: "flex",
|
||||
align: "right",
|
||||
headerAlign: "center",
|
||||
sortable: false,
|
||||
filterable: false,
|
||||
disableColumnMenu: true,
|
||||
renderCell: function render({row}) {
|
||||
renderCell: function render({ row }) {
|
||||
return (
|
||||
<>
|
||||
<EditButton hideText recordItemId={row.id} />
|
||||
<ShowButton hideText recordItemId={row.id} />
|
||||
<DeleteButton hideText confirmTitle="Вы уверены?" recordItemId={row.id} />
|
||||
<DeleteButton
|
||||
hideText
|
||||
confirmTitle="Вы уверены?"
|
||||
recordItemId={row.id}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
[],
|
||||
)
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<List>
|
||||
<CustomDataGrid {...dataGridProps} columns={columns} />
|
||||
</List>
|
||||
)
|
||||
}
|
||||
);
|
||||
});
|
||||
|
@ -1,156 +1,175 @@
|
||||
import {type GridColDef} from '@mui/x-data-grid'
|
||||
import {CustomDataGrid} from '../../components/CustomDataGrid'
|
||||
import {DeleteButton, EditButton, List, ShowButton, useDataGrid} from '@refinedev/mui'
|
||||
import {Typography} from '@mui/material'
|
||||
import React from 'react'
|
||||
import { type GridColDef } from "@mui/x-data-grid";
|
||||
import { CustomDataGrid } from "../../components/CustomDataGrid";
|
||||
import {
|
||||
DeleteButton,
|
||||
EditButton,
|
||||
List,
|
||||
ShowButton,
|
||||
useDataGrid,
|
||||
} from "@refinedev/mui";
|
||||
import { Typography } from "@mui/material";
|
||||
import React from "react";
|
||||
|
||||
import {localeText} from '../../locales/ru/localeText'
|
||||
import { localeText } from "../../locales/ru/localeText";
|
||||
|
||||
export const RouteList = () => {
|
||||
const {dataGridProps} = useDataGrid({
|
||||
resource: 'route/',
|
||||
})
|
||||
const { dataGridProps } = useDataGrid({
|
||||
resource: "route/",
|
||||
});
|
||||
|
||||
const columns = React.useMemo<GridColDef[]>(
|
||||
() => [
|
||||
{
|
||||
field: 'id',
|
||||
headerName: 'ID',
|
||||
type: 'number',
|
||||
field: "id",
|
||||
headerName: "ID",
|
||||
type: "number",
|
||||
minWidth: 70,
|
||||
display: 'flex',
|
||||
align: 'left',
|
||||
headerAlign: 'left',
|
||||
display: "flex",
|
||||
align: "left",
|
||||
headerAlign: "left",
|
||||
},
|
||||
{
|
||||
field: 'carrier_id',
|
||||
headerName: 'ID перевозчика',
|
||||
type: 'number',
|
||||
field: "carrier_id",
|
||||
headerName: "ID перевозчика",
|
||||
type: "number",
|
||||
minWidth: 150,
|
||||
display: 'flex',
|
||||
align: 'left',
|
||||
headerAlign: 'left',
|
||||
display: "flex",
|
||||
align: "left",
|
||||
headerAlign: "left",
|
||||
},
|
||||
{
|
||||
field: 'carrier',
|
||||
headerName: 'Перевозчик',
|
||||
type: 'string',
|
||||
field: "carrier",
|
||||
headerName: "Перевозчик",
|
||||
type: "string",
|
||||
minWidth: 150,
|
||||
display: 'flex',
|
||||
align: 'left',
|
||||
headerAlign: 'left',
|
||||
display: "flex",
|
||||
align: "left",
|
||||
headerAlign: "left",
|
||||
},
|
||||
{
|
||||
field: 'route_number',
|
||||
headerName: 'Номер маршрута',
|
||||
type: 'string',
|
||||
field: "route_number",
|
||||
headerName: "Номер маршрута",
|
||||
type: "string",
|
||||
minWidth: 150,
|
||||
display: 'flex',
|
||||
align: 'left',
|
||||
headerAlign: 'left',
|
||||
display: "flex",
|
||||
align: "left",
|
||||
headerAlign: "left",
|
||||
},
|
||||
{
|
||||
field: 'route_sys_number',
|
||||
headerName: 'Системный номер маршрута',
|
||||
type: 'string',
|
||||
field: "route_sys_number",
|
||||
headerName: "Системный номер маршрута",
|
||||
type: "string",
|
||||
minWidth: 120,
|
||||
display: 'flex',
|
||||
align: 'left',
|
||||
headerAlign: 'left',
|
||||
display: "flex",
|
||||
align: "left",
|
||||
headerAlign: "left",
|
||||
},
|
||||
{
|
||||
field: 'governor_appeal',
|
||||
headerName: 'Обращение губернатора',
|
||||
type: 'number',
|
||||
field: "governor_appeal",
|
||||
headerName: "Обращение губернатора",
|
||||
type: "number",
|
||||
minWidth: 120,
|
||||
display: 'flex',
|
||||
align: 'left',
|
||||
headerAlign: 'left',
|
||||
display: "flex",
|
||||
align: "left",
|
||||
headerAlign: "left",
|
||||
},
|
||||
{
|
||||
field: 'scale_min',
|
||||
headerName: 'Масштаб (мин)',
|
||||
type: 'number',
|
||||
field: "scale_min",
|
||||
headerName: "Масштаб (мин)",
|
||||
type: "number",
|
||||
minWidth: 120,
|
||||
display: 'flex',
|
||||
align: 'left',
|
||||
headerAlign: 'left',
|
||||
display: "flex",
|
||||
align: "left",
|
||||
headerAlign: "left",
|
||||
},
|
||||
{
|
||||
field: 'scale_max',
|
||||
headerName: 'Масштаб (макс)',
|
||||
type: 'number',
|
||||
field: "scale_max",
|
||||
headerName: "Масштаб (макс)",
|
||||
type: "number",
|
||||
minWidth: 120,
|
||||
display: 'flex',
|
||||
align: 'left',
|
||||
headerAlign: 'left',
|
||||
display: "flex",
|
||||
align: "left",
|
||||
headerAlign: "left",
|
||||
},
|
||||
{
|
||||
field: 'rotate',
|
||||
headerName: 'Поворот',
|
||||
type: 'number',
|
||||
field: "rotate",
|
||||
headerName: "Поворот",
|
||||
type: "number",
|
||||
minWidth: 120,
|
||||
display: 'flex',
|
||||
align: 'left',
|
||||
headerAlign: 'left',
|
||||
display: "flex",
|
||||
align: "left",
|
||||
headerAlign: "left",
|
||||
},
|
||||
{
|
||||
field: 'center_latitude',
|
||||
headerName: 'Центр. широта',
|
||||
type: 'number',
|
||||
field: "center_latitude",
|
||||
headerName: "Центр. широта",
|
||||
type: "number",
|
||||
minWidth: 120,
|
||||
display: 'flex',
|
||||
align: 'left',
|
||||
headerAlign: 'left',
|
||||
display: "flex",
|
||||
align: "left",
|
||||
headerAlign: "left",
|
||||
},
|
||||
{
|
||||
field: 'center_longitude',
|
||||
headerName: 'Центр. долгота',
|
||||
type: 'number',
|
||||
field: "center_longitude",
|
||||
headerName: "Центр. долгота",
|
||||
type: "number",
|
||||
minWidth: 120,
|
||||
display: 'flex',
|
||||
align: 'left',
|
||||
headerAlign: 'left',
|
||||
display: "flex",
|
||||
align: "left",
|
||||
headerAlign: "left",
|
||||
},
|
||||
{
|
||||
field: 'route_direction',
|
||||
headerName: 'Направление маршрута',
|
||||
type: 'boolean',
|
||||
display: 'flex',
|
||||
align: 'left',
|
||||
headerAlign: 'left',
|
||||
field: "route_direction",
|
||||
headerName: "Направление маршрута",
|
||||
type: "boolean",
|
||||
display: "flex",
|
||||
align: "left",
|
||||
headerAlign: "left",
|
||||
minWidth: 120,
|
||||
flex: 1,
|
||||
renderCell: ({value}) => <Typography style={{color: value ? '#48989f' : '#7f6b58'}}>{value ? 'прямое' : 'обратное'}</Typography>,
|
||||
renderCell: ({ value }) => (
|
||||
<Typography style={{ color: value ? "#48989f" : "#7f6b58" }}>
|
||||
{value ? "прямое" : "обратное"}
|
||||
</Typography>
|
||||
),
|
||||
},
|
||||
{
|
||||
field: 'actions',
|
||||
headerName: 'Действия',
|
||||
cellClassName: 'route-actions',
|
||||
align: 'right',
|
||||
headerAlign: 'center',
|
||||
field: "actions",
|
||||
headerName: "Действия",
|
||||
cellClassName: "route-actions",
|
||||
align: "right",
|
||||
headerAlign: "center",
|
||||
minWidth: 120,
|
||||
display: 'flex',
|
||||
display: "flex",
|
||||
sortable: false,
|
||||
filterable: false,
|
||||
disableColumnMenu: true,
|
||||
renderCell: function render({row}) {
|
||||
renderCell: function render({ row }) {
|
||||
return (
|
||||
<>
|
||||
<EditButton hideText recordItemId={row.id} />
|
||||
<ShowButton hideText recordItemId={row.id} />
|
||||
<DeleteButton hideText confirmTitle="Вы уверены?" recordItemId={row.id} />
|
||||
<DeleteButton
|
||||
hideText
|
||||
confirmTitle="Вы уверены?"
|
||||
recordItemId={row.id}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
[],
|
||||
)
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<List>
|
||||
<CustomDataGrid {...dataGridProps} columns={columns} localeText={localeText} getRowId={(row: any) => row.id} />
|
||||
<CustomDataGrid
|
||||
{...dataGridProps}
|
||||
columns={columns}
|
||||
localeText={localeText}
|
||||
getRowId={(row: any) => row.id}
|
||||
/>
|
||||
</List>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@ -1,35 +1,36 @@
|
||||
import {VEHICLE_TYPES} from '../../lib/constants'
|
||||
import { VEHICLE_TYPES } from "../../lib/constants";
|
||||
|
||||
export type StationItem = {
|
||||
id: number
|
||||
name: string
|
||||
description: string
|
||||
[key: string]: string | number
|
||||
}
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
[key: string]: string | number;
|
||||
};
|
||||
|
||||
export type VehicleItem = {
|
||||
id: number
|
||||
tail_number: number
|
||||
type: number
|
||||
[key: string]: string | number
|
||||
}
|
||||
id: number;
|
||||
tail_number: number;
|
||||
type: number;
|
||||
[key: string]: string | number;
|
||||
};
|
||||
|
||||
export type FieldType<T> = {
|
||||
label: string
|
||||
data: keyof T
|
||||
render?: (value: any) => React.ReactNode
|
||||
}
|
||||
label: string;
|
||||
data: keyof T;
|
||||
render?: (value: any) => React.ReactNode;
|
||||
};
|
||||
|
||||
export const stationFields: Array<FieldType<StationItem>> = [
|
||||
{label: 'Название', data: 'system_name'},
|
||||
{label: 'Описание', data: 'description'},
|
||||
]
|
||||
{ label: "Название", data: "name" },
|
||||
{ label: "Описание", data: "description" },
|
||||
];
|
||||
|
||||
export const vehicleFields: Array<FieldType<VehicleItem>> = [
|
||||
{label: 'Бортовой номер', data: 'tail_number'},
|
||||
{ label: "Бортовой номер", data: "tail_number" },
|
||||
{
|
||||
label: 'Тип',
|
||||
data: 'type',
|
||||
render: (value: number) => VEHICLE_TYPES.find((type) => type.value === value)?.label || value,
|
||||
label: "Тип",
|
||||
data: "type",
|
||||
render: (value: number) =>
|
||||
VEHICLE_TYPES.find((type) => type.value === value)?.label || value,
|
||||
},
|
||||
]
|
||||
];
|
||||
|
@ -2,25 +2,45 @@ import { Autocomplete, Box, TextField, Typography, Paper } from "@mui/material";
|
||||
import { Create, useAutocomplete } from "@refinedev/mui";
|
||||
import { useForm } from "@refinedev/react-hook-form";
|
||||
import { Controller } from "react-hook-form";
|
||||
import { Link } from "react-router";
|
||||
import { cityStore } from "../../store/CityStore";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { TOKEN_KEY } from "../../authProvider";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import Cookies from "js-cookie";
|
||||
import { useLocation } from "react-router";
|
||||
|
||||
export const SightCreate = observer(() => {
|
||||
const [language, setLanguage] = useState(Cookies.get("lang") || "ru");
|
||||
// Состояния для предпросмотра
|
||||
const handleLanguageChange = (lang: string) => {
|
||||
setLanguage(lang);
|
||||
Cookies.set("lang", lang);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const lang = Cookies.get("lang")!;
|
||||
Cookies.set("lang", language);
|
||||
|
||||
return () => {
|
||||
Cookies.set("lang", lang);
|
||||
};
|
||||
}, [language]);
|
||||
|
||||
export const SightCreate = () => {
|
||||
const {
|
||||
saveButtonProps,
|
||||
refineCore: { formLoading },
|
||||
register,
|
||||
control,
|
||||
watch,
|
||||
setValue,
|
||||
formState: { errors },
|
||||
} = useForm({
|
||||
refineCoreProps: {
|
||||
resource: "sight/",
|
||||
},
|
||||
});
|
||||
const { city_id } = cityStore;
|
||||
|
||||
// Состояния для предпросмотра
|
||||
const [namePreview, setNamePreview] = useState("");
|
||||
const [coordinatesPreview, setCoordinatesPreview] = useState({
|
||||
latitude: "",
|
||||
@ -37,6 +57,16 @@ export const SightCreate = () => {
|
||||
const [leftArticlePreview, setLeftArticlePreview] = useState("");
|
||||
const [previewArticlePreview, setPreviewArticlePreview] = useState("");
|
||||
|
||||
const handleCoordinatesChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const [lat, lon] = e.target.value.split(",").map((s) => s.trim());
|
||||
setCoordinatesPreview({
|
||||
latitude: lat,
|
||||
longitude: lon,
|
||||
});
|
||||
setValue("latitude", lat);
|
||||
setValue("longitude", lon);
|
||||
};
|
||||
|
||||
// Автокомплиты
|
||||
const { autocompleteProps: cityAutocompleteProps } = useAutocomplete({
|
||||
resource: "city",
|
||||
@ -47,6 +77,11 @@ export const SightCreate = () => {
|
||||
value,
|
||||
},
|
||||
],
|
||||
meta: {
|
||||
headers: {
|
||||
"Accept-Language": language,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { autocompleteProps: mediaAutocompleteProps } = useAutocomplete({
|
||||
@ -73,6 +108,7 @@ export const SightCreate = () => {
|
||||
|
||||
// Следим за изменениями во всех полях
|
||||
const nameContent = watch("name");
|
||||
const addressContent = watch("address");
|
||||
const latitudeContent = watch("latitude");
|
||||
const longitudeContent = watch("longitude");
|
||||
const cityContent = watch("city_id");
|
||||
@ -114,6 +150,12 @@ export const SightCreate = () => {
|
||||
);
|
||||
}, [thumbnailContent, mediaAutocompleteProps.options]);
|
||||
|
||||
useEffect(() => {
|
||||
if (city_id) {
|
||||
setValue("city_id", +city_id);
|
||||
}
|
||||
}, [city_id, setValue]);
|
||||
|
||||
useEffect(() => {
|
||||
const selectedWatermarkLU = mediaAutocompleteProps.options.find(
|
||||
(option) => option.id === watermarkLUContent
|
||||
@ -157,313 +199,380 @@ export const SightCreate = () => {
|
||||
return (
|
||||
<Create isLoading={formLoading} saveButtonProps={saveButtonProps}>
|
||||
<Box sx={{ display: "flex", gap: 2 }}>
|
||||
{/* Форма создания */}
|
||||
<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"
|
||||
/>
|
||||
<TextField
|
||||
{...register("latitude", {
|
||||
required: "Это поле является обязательным",
|
||||
valueAsNumber: true,
|
||||
})}
|
||||
error={!!(errors as any)?.latitude}
|
||||
helperText={(errors as any)?.latitude?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="number"
|
||||
label={"Широта *"}
|
||||
name="latitude"
|
||||
/>
|
||||
<TextField
|
||||
{...register("longitude", {
|
||||
required: "Это поле является обязательным",
|
||||
valueAsNumber: true,
|
||||
})}
|
||||
error={!!(errors as any)?.longitude}
|
||||
helperText={(errors as any)?.longitude?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="number"
|
||||
label={"Долгота *"}
|
||||
name="longitude"
|
||||
/>
|
||||
<Box sx={{ display: "flex", flexDirection: "column", flex: 1, gap: 2 }}>
|
||||
{/* Форма создания */}
|
||||
<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>
|
||||
|
||||
<Controller
|
||||
control={control}
|
||||
name="city_id"
|
||||
rules={{ required: "Это поле является обязательным" }}
|
||||
defaultValue={null}
|
||||
render={({ field }) => (
|
||||
<Autocomplete
|
||||
{...cityAutocompleteProps}
|
||||
value={
|
||||
cityAutocompleteProps.options.find(
|
||||
(option) => option.id === field.value
|
||||
) || null
|
||||
}
|
||||
onChange={(_, value) => {
|
||||
field.onChange(value?.id || "");
|
||||
}}
|
||||
getOptionLabel={(item) => {
|
||||
return item ? item.name : "";
|
||||
}}
|
||||
isOptionEqualToValue={(option, value) => {
|
||||
return option.id === value?.id;
|
||||
}}
|
||||
filterOptions={(options, { inputValue }) => {
|
||||
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
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<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"
|
||||
/>
|
||||
|
||||
<Controller
|
||||
control={control}
|
||||
name="thumbnail"
|
||||
defaultValue={null}
|
||||
render={({ field }) => (
|
||||
<Autocomplete
|
||||
{...mediaAutocompleteProps}
|
||||
value={
|
||||
mediaAutocompleteProps.options.find(
|
||||
(option) => option.id === field.value
|
||||
) || null
|
||||
}
|
||||
onChange={(_, value) => {
|
||||
field.onChange(value?.id || "");
|
||||
}}
|
||||
getOptionLabel={(item) => {
|
||||
return item ? item.media_name : "";
|
||||
}}
|
||||
isOptionEqualToValue={(option, value) => {
|
||||
return option.id === value?.id;
|
||||
}}
|
||||
filterOptions={(options, { inputValue }) => {
|
||||
return options.filter((option) =>
|
||||
option.media_name
|
||||
.toLowerCase()
|
||||
.includes(inputValue.toLowerCase())
|
||||
);
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
label="Выберите обложку"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
error={!!errors.arms}
|
||||
helperText={(errors as any)?.arms?.message}
|
||||
required
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<TextField
|
||||
value={`${coordinatesPreview.latitude}, ${coordinatesPreview.longitude}`}
|
||||
onChange={handleCoordinatesChange}
|
||||
error={!!(errors as any)?.latitude}
|
||||
helperText={(errors as any)?.latitude?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="text"
|
||||
label={"Координаты *"}
|
||||
/>
|
||||
<input
|
||||
type="hidden"
|
||||
{...register("longitude", {
|
||||
value: coordinatesPreview.longitude,
|
||||
required: "Это поле является обязательным",
|
||||
valueAsNumber: true,
|
||||
})}
|
||||
/>
|
||||
<input
|
||||
type="hidden"
|
||||
{...register("latitude", {
|
||||
value: coordinatesPreview.latitude,
|
||||
required: "Это поле является обязательным",
|
||||
valueAsNumber: true,
|
||||
})}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
control={control}
|
||||
name="watermark_lu"
|
||||
defaultValue={null}
|
||||
render={({ field }) => (
|
||||
<Autocomplete
|
||||
{...mediaAutocompleteProps}
|
||||
value={
|
||||
mediaAutocompleteProps.options.find(
|
||||
(option) => option.id === field.value
|
||||
) || null
|
||||
}
|
||||
onChange={(_, value) => {
|
||||
field.onChange(value?.id || "");
|
||||
}}
|
||||
getOptionLabel={(item) => {
|
||||
return item ? item.media_name : "";
|
||||
}}
|
||||
isOptionEqualToValue={(option, value) => {
|
||||
return option.id === value?.id;
|
||||
}}
|
||||
filterOptions={(options, { inputValue }) => {
|
||||
return options.filter((option) =>
|
||||
option.media_name
|
||||
.toLowerCase()
|
||||
.includes(inputValue.toLowerCase())
|
||||
);
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
label="Выберите водный знак (Левый верх)"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
error={!!errors.arms}
|
||||
helperText={(errors as any)?.arms?.message}
|
||||
required
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<TextField
|
||||
{...register("address", {
|
||||
required: "Это поле является обязательным",
|
||||
})}
|
||||
error={!!(errors as any)?.address}
|
||||
helperText={(errors as any)?.address?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="text"
|
||||
label={"Адрес *"}
|
||||
name="address"
|
||||
/>
|
||||
|
||||
<Controller
|
||||
control={control}
|
||||
name="watermark_rd"
|
||||
defaultValue={null}
|
||||
render={({ field }) => (
|
||||
<Autocomplete
|
||||
{...mediaAutocompleteProps}
|
||||
value={
|
||||
mediaAutocompleteProps.options.find(
|
||||
(option) => option.id === field.value
|
||||
) || null
|
||||
}
|
||||
onChange={(_, value) => {
|
||||
field.onChange(value?.id || "");
|
||||
}}
|
||||
getOptionLabel={(item) => {
|
||||
return item ? item.media_name : "";
|
||||
}}
|
||||
isOptionEqualToValue={(option, value) => {
|
||||
return option.id === value?.id;
|
||||
}}
|
||||
filterOptions={(options, { inputValue }) => {
|
||||
return options.filter((option) =>
|
||||
option.media_name
|
||||
.toLowerCase()
|
||||
.includes(inputValue.toLowerCase())
|
||||
);
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
label="Выберите водный знак (Правый низ)"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
error={!!errors.arms}
|
||||
helperText={(errors as any)?.arms?.message}
|
||||
required
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="city_id"
|
||||
rules={{ required: "Это поле является обязательным" }}
|
||||
render={({ field }) => (
|
||||
<Autocomplete
|
||||
{...cityAutocompleteProps}
|
||||
value={
|
||||
cityAutocompleteProps.options.find(
|
||||
(option) => option.id === field.value
|
||||
) || null
|
||||
}
|
||||
onChange={(_, value) => {
|
||||
field.onChange(value?.id || "");
|
||||
}}
|
||||
getOptionLabel={(item) => {
|
||||
return item ? item.name : "";
|
||||
}}
|
||||
isOptionEqualToValue={(option, value) => {
|
||||
return option.id === value?.id;
|
||||
}}
|
||||
filterOptions={(options, { inputValue }) => {
|
||||
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
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
control={control}
|
||||
name="left_article"
|
||||
defaultValue={null}
|
||||
render={({ field }) => (
|
||||
<Autocomplete
|
||||
{...articleAutocompleteProps}
|
||||
value={
|
||||
articleAutocompleteProps.options.find(
|
||||
(option) => option.id === field.value
|
||||
) || null
|
||||
}
|
||||
onChange={(_, value) => {
|
||||
field.onChange(value?.id || "");
|
||||
}}
|
||||
getOptionLabel={(item) => {
|
||||
return item ? item.heading : "";
|
||||
}}
|
||||
isOptionEqualToValue={(option, value) => {
|
||||
return option.id === value?.id;
|
||||
}}
|
||||
filterOptions={(options, { inputValue }) => {
|
||||
return options.filter((option) =>
|
||||
option.heading
|
||||
.toLowerCase()
|
||||
.includes(inputValue.toLowerCase())
|
||||
);
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
label="Левая статья"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
error={!!errors.arms}
|
||||
helperText={(errors as any)?.arms?.message}
|
||||
required
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="thumbnail"
|
||||
defaultValue={null}
|
||||
render={({ field }) => (
|
||||
<Autocomplete
|
||||
{...mediaAutocompleteProps}
|
||||
value={
|
||||
mediaAutocompleteProps.options.find(
|
||||
(option) => option.id === field.value
|
||||
) || null
|
||||
}
|
||||
onChange={(_, value) => {
|
||||
field.onChange(value?.id || "");
|
||||
}}
|
||||
getOptionLabel={(item) => {
|
||||
return item ? item.media_name : "";
|
||||
}}
|
||||
isOptionEqualToValue={(option, value) => {
|
||||
return option.id === value?.id;
|
||||
}}
|
||||
filterOptions={(options, { inputValue }) => {
|
||||
return options.filter((option) =>
|
||||
option.media_name
|
||||
.toLowerCase()
|
||||
.includes(inputValue.toLowerCase())
|
||||
);
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
label="Выберите обложку"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
error={!!errors.thumbnail}
|
||||
helperText={(errors as any)?.thumbnail?.message}
|
||||
required
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
control={control}
|
||||
name="preview_article"
|
||||
defaultValue={null}
|
||||
render={({ field }) => (
|
||||
<Autocomplete
|
||||
{...articleAutocompleteProps}
|
||||
value={
|
||||
articleAutocompleteProps.options.find(
|
||||
(option) => option.id === field.value
|
||||
) || null
|
||||
}
|
||||
onChange={(_, value) => {
|
||||
field.onChange(value?.id || "");
|
||||
}}
|
||||
getOptionLabel={(item) => {
|
||||
return item ? item.heading : "";
|
||||
}}
|
||||
isOptionEqualToValue={(option, value) => {
|
||||
return option.id === value?.id;
|
||||
}}
|
||||
filterOptions={(options, { inputValue }) => {
|
||||
return options.filter((option) =>
|
||||
option.heading
|
||||
.toLowerCase()
|
||||
.includes(inputValue.toLowerCase())
|
||||
);
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
label="Cтатья-предпросмотр"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
error={!!errors.arms}
|
||||
helperText={(errors as any)?.arms?.message}
|
||||
required
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="watermark_lu"
|
||||
defaultValue={null}
|
||||
render={({ field }) => (
|
||||
<Autocomplete
|
||||
{...mediaAutocompleteProps}
|
||||
value={
|
||||
mediaAutocompleteProps.options.find(
|
||||
(option) => option.id === field.value
|
||||
) || null
|
||||
}
|
||||
onChange={(_, value) => {
|
||||
field.onChange(value?.id || "");
|
||||
}}
|
||||
getOptionLabel={(item) => {
|
||||
return item ? item.media_name : "";
|
||||
}}
|
||||
isOptionEqualToValue={(option, value) => {
|
||||
return option.id === value?.id;
|
||||
}}
|
||||
filterOptions={(options, { inputValue }) => {
|
||||
return options.filter((option) =>
|
||||
option.media_name
|
||||
.toLowerCase()
|
||||
.includes(inputValue.toLowerCase())
|
||||
);
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
label="Выберите водный знак (Левый верх)"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
error={!!errors.watermark_lu}
|
||||
helperText={(errors as any)?.watermark_lu?.message}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
control={control}
|
||||
name="watermark_rd"
|
||||
defaultValue={null}
|
||||
render={({ field }) => (
|
||||
<Autocomplete
|
||||
{...mediaAutocompleteProps}
|
||||
value={
|
||||
mediaAutocompleteProps.options.find(
|
||||
(option) => option.id === field.value
|
||||
) || null
|
||||
}
|
||||
onChange={(_, value) => {
|
||||
field.onChange(value?.id || "");
|
||||
}}
|
||||
getOptionLabel={(item) => {
|
||||
return item ? item.media_name : "";
|
||||
}}
|
||||
isOptionEqualToValue={(option, value) => {
|
||||
return option.id === value?.id;
|
||||
}}
|
||||
filterOptions={(options, { inputValue }) => {
|
||||
return options.filter((option) =>
|
||||
option.media_name
|
||||
.toLowerCase()
|
||||
.includes(inputValue.toLowerCase())
|
||||
);
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
label="Выберите водный знак (Правый верх)"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
error={!!errors.watermark_rd}
|
||||
helperText={(errors as any)?.watermark_rd?.message}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
control={control}
|
||||
name="left_article"
|
||||
defaultValue={null}
|
||||
render={({ field }) => (
|
||||
<Autocomplete
|
||||
{...articleAutocompleteProps}
|
||||
value={
|
||||
articleAutocompleteProps.options.find(
|
||||
(option) => option.id === field.value
|
||||
) || null
|
||||
}
|
||||
onChange={(_, value) => {
|
||||
field.onChange(value?.id || "");
|
||||
}}
|
||||
getOptionLabel={(item) => {
|
||||
return item ? item.heading : "";
|
||||
}}
|
||||
isOptionEqualToValue={(option, value) => {
|
||||
return option.id === value?.id;
|
||||
}}
|
||||
filterOptions={(options, { inputValue }) => {
|
||||
return options.filter((option) =>
|
||||
option.heading
|
||||
.toLowerCase()
|
||||
.includes(inputValue.toLowerCase())
|
||||
);
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
label="Левая статья"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
error={!!errors.left_article}
|
||||
helperText={(errors as any)?.left_article?.message}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
control={control}
|
||||
name="preview_article"
|
||||
defaultValue={null}
|
||||
render={({ field }) => (
|
||||
<Autocomplete
|
||||
{...articleAutocompleteProps}
|
||||
value={
|
||||
articleAutocompleteProps.options.find(
|
||||
(option) => option.id === field.value
|
||||
) || null
|
||||
}
|
||||
onChange={(_, value) => {
|
||||
field.onChange(value?.id || "");
|
||||
}}
|
||||
getOptionLabel={(item) => {
|
||||
return item ? item.heading : "";
|
||||
}}
|
||||
isOptionEqualToValue={(option, value) => {
|
||||
return option.id === value?.id;
|
||||
}}
|
||||
filterOptions={(options, { inputValue }) => {
|
||||
return options.filter((option) =>
|
||||
option.heading
|
||||
.toLowerCase()
|
||||
.includes(inputValue.toLowerCase())
|
||||
);
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
label="Cтатья-предпросмотр"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
error={!!errors.preview_article}
|
||||
helperText={(errors as any)?.preview_article?.message}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Блок предпросмотра */}
|
||||
{/* Preview Panel */}
|
||||
<Paper
|
||||
sx={{
|
||||
flex: 1,
|
||||
@ -483,13 +592,14 @@ export const SightCreate = () => {
|
||||
Предпросмотр
|
||||
</Typography>
|
||||
|
||||
{/* Название */}
|
||||
{/* Название достопримечательности */}
|
||||
<Typography
|
||||
variant="h4"
|
||||
gutterBottom
|
||||
sx={{
|
||||
color: (theme) =>
|
||||
theme.palette.mode === "dark" ? "grey.300" : "grey.800",
|
||||
mb: 3,
|
||||
}}
|
||||
>
|
||||
{namePreview}
|
||||
@ -511,6 +621,22 @@ export const SightCreate = () => {
|
||||
</Box>
|
||||
</Typography>
|
||||
|
||||
{/* Адрес */}
|
||||
<Typography variant="body1" sx={{ mb: 2 }}>
|
||||
<Box component="span" sx={{ color: "text.secondary" }}>
|
||||
Адрес:{" "}
|
||||
</Box>
|
||||
<Box
|
||||
component="span"
|
||||
sx={{
|
||||
color: (theme) =>
|
||||
theme.palette.mode === "dark" ? "grey.300" : "grey.800",
|
||||
}}
|
||||
>
|
||||
{addressContent}
|
||||
</Box>
|
||||
</Typography>
|
||||
|
||||
{/* Координаты */}
|
||||
<Typography variant="body1" sx={{ mb: 2 }}>
|
||||
<Box component="span" sx={{ color: "text.secondary" }}>
|
||||
@ -523,7 +649,7 @@ export const SightCreate = () => {
|
||||
theme.palette.mode === "dark" ? "grey.300" : "grey.800",
|
||||
}}
|
||||
>
|
||||
{coordinatesPreview.latitude}, {coordinatesPreview.longitude}
|
||||
{`${coordinatesPreview.latitude}, ${coordinatesPreview.longitude}`}
|
||||
</Box>
|
||||
</Typography>
|
||||
|
||||
@ -543,8 +669,8 @@ export const SightCreate = () => {
|
||||
alt="Обложка"
|
||||
sx={{
|
||||
maxWidth: "100%",
|
||||
height: "auto",
|
||||
borderRadius: 1,
|
||||
height: "40vh",
|
||||
borderRadius: 2,
|
||||
border: "1px solid",
|
||||
borderColor: "primary.main",
|
||||
}}
|
||||
@ -593,7 +719,7 @@ export const SightCreate = () => {
|
||||
gutterBottom
|
||||
sx={{ color: "text.secondary" }}
|
||||
>
|
||||
Правый нижний:
|
||||
Правый верхний:
|
||||
</Typography>
|
||||
<Box
|
||||
component="img"
|
||||
@ -615,28 +741,16 @@ export const SightCreate = () => {
|
||||
|
||||
{/* Связанные статьи */}
|
||||
<Box>
|
||||
<Typography
|
||||
variant="body1"
|
||||
gutterBottom
|
||||
sx={{ color: "text.secondary" }}
|
||||
>
|
||||
Связанные статьи:
|
||||
</Typography>
|
||||
{leftArticlePreview && (
|
||||
<Typography variant="body1" gutterBottom>
|
||||
<Box component="span" sx={{ color: "text.secondary" }}>
|
||||
Левая статья:{" "}
|
||||
</Box>
|
||||
<Box
|
||||
component={Link}
|
||||
to={`/article/show/${watch("left_article")}`}
|
||||
component="span"
|
||||
sx={{
|
||||
color: (theme) =>
|
||||
theme.palette.mode === "dark" ? "grey.300" : "grey.800",
|
||||
textDecoration: "none",
|
||||
"&:hover": {
|
||||
textDecoration: "underline",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{leftArticlePreview}
|
||||
@ -649,15 +763,10 @@ export const SightCreate = () => {
|
||||
Статья-предпросмотр:{" "}
|
||||
</Box>
|
||||
<Box
|
||||
component={Link}
|
||||
to={`/article/show/${watch("preview_article")}`}
|
||||
component="span"
|
||||
sx={{
|
||||
color: (theme) =>
|
||||
theme.palette.mode === "dark" ? "grey.300" : "grey.800",
|
||||
textDecoration: "none",
|
||||
"&:hover": {
|
||||
textDecoration: "underline",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{previewArticlePreview}
|
||||
@ -669,4 +778,4 @@ export const SightCreate = () => {
|
||||
</Box>
|
||||
</Create>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,128 +1,158 @@
|
||||
import React from 'react'
|
||||
import {type GridColDef} from '@mui/x-data-grid'
|
||||
import {DeleteButton, EditButton, List, ShowButton, useDataGrid} from '@refinedev/mui'
|
||||
import {Stack} from '@mui/material'
|
||||
import {CustomDataGrid} from '../../components/CustomDataGrid'
|
||||
import {localeText} from '../../locales/ru/localeText'
|
||||
import React from "react";
|
||||
import { type GridColDef } from "@mui/x-data-grid";
|
||||
import {
|
||||
DeleteButton,
|
||||
EditButton,
|
||||
List,
|
||||
ShowButton,
|
||||
useDataGrid,
|
||||
} from "@refinedev/mui";
|
||||
import { Stack } from "@mui/material";
|
||||
import { CustomDataGrid } from "../../components/CustomDataGrid";
|
||||
import { localeText } from "../../locales/ru/localeText";
|
||||
import { cityStore } from "../../store/CityStore";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
export const SightList = () => {
|
||||
const {dataGridProps} = useDataGrid({resource: 'sight/'})
|
||||
export const SightList = observer(() => {
|
||||
const { city_id } = cityStore;
|
||||
const { dataGridProps } = useDataGrid({
|
||||
resource: "sight/",
|
||||
filters: {
|
||||
permanent: [
|
||||
{
|
||||
field: "cityID",
|
||||
operator: "eq",
|
||||
value: city_id,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const columns = React.useMemo<GridColDef[]>(
|
||||
() => [
|
||||
{
|
||||
field: 'id',
|
||||
headerName: 'ID',
|
||||
type: 'number',
|
||||
field: "id",
|
||||
headerName: "ID",
|
||||
type: "number",
|
||||
minWidth: 70,
|
||||
display: 'flex',
|
||||
align: 'left',
|
||||
headerAlign: 'left',
|
||||
display: "flex",
|
||||
align: "left",
|
||||
headerAlign: "left",
|
||||
},
|
||||
{
|
||||
field: 'name',
|
||||
headerName: 'Название',
|
||||
type: 'string',
|
||||
field: "name",
|
||||
headerName: "Название",
|
||||
type: "string",
|
||||
minWidth: 150,
|
||||
display: 'flex',
|
||||
align: 'left',
|
||||
headerAlign: 'left',
|
||||
display: "flex",
|
||||
align: "left",
|
||||
headerAlign: "left",
|
||||
},
|
||||
{
|
||||
field: 'latitude',
|
||||
headerName: 'Широта',
|
||||
type: 'number',
|
||||
field: "latitude",
|
||||
headerName: "Широта",
|
||||
type: "number",
|
||||
minWidth: 150,
|
||||
display: 'flex',
|
||||
align: 'left',
|
||||
headerAlign: 'left',
|
||||
display: "flex",
|
||||
align: "left",
|
||||
headerAlign: "left",
|
||||
},
|
||||
{
|
||||
field: 'longitude',
|
||||
headerName: 'Долгота',
|
||||
type: 'number',
|
||||
field: "longitude",
|
||||
headerName: "Долгота",
|
||||
type: "number",
|
||||
minWidth: 150,
|
||||
display: 'flex',
|
||||
align: 'left',
|
||||
headerAlign: 'left',
|
||||
display: "flex",
|
||||
align: "left",
|
||||
headerAlign: "left",
|
||||
},
|
||||
{
|
||||
field: 'city_id',
|
||||
headerName: 'ID города',
|
||||
type: 'number',
|
||||
field: "city_id",
|
||||
headerName: "ID города",
|
||||
type: "number",
|
||||
minWidth: 70,
|
||||
display: 'flex',
|
||||
align: 'left',
|
||||
headerAlign: 'left',
|
||||
display: "flex",
|
||||
align: "left",
|
||||
headerAlign: "left",
|
||||
},
|
||||
{
|
||||
field: 'city',
|
||||
headerName: 'Город',
|
||||
type: 'string',
|
||||
field: "city",
|
||||
headerName: "Город",
|
||||
type: "string",
|
||||
minWidth: 100,
|
||||
align: 'left',
|
||||
headerAlign: 'left',
|
||||
align: "left",
|
||||
headerAlign: "left",
|
||||
flex: 1,
|
||||
},
|
||||
{
|
||||
field: 'thumbnail',
|
||||
headerName: 'Карточка',
|
||||
type: 'string',
|
||||
field: "thumbnail",
|
||||
headerName: "Карточка",
|
||||
type: "string",
|
||||
minWidth: 150,
|
||||
},
|
||||
{
|
||||
field: 'watermark_lu',
|
||||
headerName: 'Вод. знак (lu)',
|
||||
type: 'string',
|
||||
field: "watermark_lu",
|
||||
headerName: "Вод. знак (lu)",
|
||||
type: "string",
|
||||
minWidth: 150,
|
||||
},
|
||||
{
|
||||
field: 'watermark_rd',
|
||||
headerName: 'Вод. знак (rd)',
|
||||
type: 'string',
|
||||
field: "watermark_rd",
|
||||
headerName: "Вод. знак (rd)",
|
||||
type: "string",
|
||||
minWidth: 150,
|
||||
},
|
||||
{
|
||||
field: 'left_article',
|
||||
headerName: 'Левая статья',
|
||||
type: 'number',
|
||||
field: "left_article",
|
||||
headerName: "Левая статья",
|
||||
type: "number",
|
||||
minWidth: 150,
|
||||
},
|
||||
{
|
||||
field: 'preview_article',
|
||||
headerName: 'Пред. просмотр статьи',
|
||||
type: 'number',
|
||||
field: "preview_article",
|
||||
headerName: "Пред. просмотр статьи",
|
||||
type: "number",
|
||||
minWidth: 150,
|
||||
},
|
||||
{
|
||||
field: 'actions',
|
||||
headerName: 'Действия',
|
||||
field: "actions",
|
||||
headerName: "Действия",
|
||||
minWidth: 120,
|
||||
display: 'flex',
|
||||
align: 'right',
|
||||
headerAlign: 'center',
|
||||
display: "flex",
|
||||
align: "right",
|
||||
headerAlign: "center",
|
||||
sortable: false,
|
||||
filterable: false,
|
||||
disableColumnMenu: true,
|
||||
renderCell: function render({row}) {
|
||||
renderCell: function render({ row }) {
|
||||
return (
|
||||
<>
|
||||
<EditButton hideText recordItemId={row.id} />
|
||||
<ShowButton hideText recordItemId={row.id} />
|
||||
<DeleteButton hideText confirmTitle="Вы уверены?" recordItemId={row.id} />
|
||||
<DeleteButton
|
||||
hideText
|
||||
confirmTitle="Вы уверены?"
|
||||
recordItemId={row.id}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
[],
|
||||
)
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<List>
|
||||
<Stack gap={2.5}>
|
||||
<CustomDataGrid {...dataGridProps} columns={columns} localeText={localeText} getRowId={(row: any) => row.id} hasCoordinates />
|
||||
<CustomDataGrid
|
||||
{...dataGridProps}
|
||||
columns={columns}
|
||||
localeText={localeText}
|
||||
getRowId={(row: any) => row.id}
|
||||
hasCoordinates
|
||||
/>
|
||||
</Stack>
|
||||
</List>
|
||||
)
|
||||
}
|
||||
);
|
||||
});
|
||||
|
@ -1,27 +1,28 @@
|
||||
import {Stack, Typography} from '@mui/material'
|
||||
import {useShow} from '@refinedev/core'
|
||||
import {Show, TextFieldComponent} from '@refinedev/mui'
|
||||
import {LinkedItems} from '../../components/LinkedItems'
|
||||
import {ArticleItem, articleFields} from './types'
|
||||
import { Stack, Typography } from "@mui/material";
|
||||
import { useShow } from "@refinedev/core";
|
||||
import { Show, TextFieldComponent } from "@refinedev/mui";
|
||||
import { LinkedItems } from "../../components/LinkedItems";
|
||||
import { ArticleItem, articleFields } from "./types";
|
||||
|
||||
export const SightShow = () => {
|
||||
const {query} = useShow({})
|
||||
const {data, isLoading} = query
|
||||
const record = data?.data
|
||||
const { query } = useShow({});
|
||||
const { data, isLoading } = query;
|
||||
const record = data?.data;
|
||||
|
||||
const fields = [
|
||||
// {label: 'ID', data: 'id'},
|
||||
{label: 'Название', data: 'name'},
|
||||
{ label: "Название", data: "name" },
|
||||
// {label: 'Широта', data: 'latitude'}, #*
|
||||
// {label: 'Долгота', data: 'longitude'}, #*
|
||||
// {label: 'ID города', data: 'city_id'},
|
||||
{label: 'Город', data: 'city'},
|
||||
]
|
||||
{ label: "Адрес", data: "address" },
|
||||
{ label: "Город", data: "city" },
|
||||
];
|
||||
|
||||
return (
|
||||
<Show isLoading={isLoading}>
|
||||
<Stack gap={4}>
|
||||
{fields.map(({label, data}) => (
|
||||
{fields.map(({ label, data }) => (
|
||||
<Stack key={data} gap={1}>
|
||||
<Typography variant="body1" fontWeight="bold">
|
||||
{label}
|
||||
@ -30,8 +31,17 @@ export const SightShow = () => {
|
||||
</Stack>
|
||||
))}
|
||||
|
||||
{record?.id && <LinkedItems<ArticleItem> type="show" parentId={record.id} parentResource="sight" childResource="article" fields={articleFields} title="статьи" />}
|
||||
{record?.id && (
|
||||
<LinkedItems<ArticleItem>
|
||||
type="show"
|
||||
parentId={record.id}
|
||||
parentResource="sight"
|
||||
childResource="article"
|
||||
fields={articleFields}
|
||||
title="статьи"
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@ -1,183 +1,253 @@
|
||||
import {Autocomplete, Box, TextField, Typography, Paper, Grid} from '@mui/material'
|
||||
import {Create, useAutocomplete} from '@refinedev/mui'
|
||||
import {useForm} from '@refinedev/react-hook-form'
|
||||
import {Controller} from 'react-hook-form'
|
||||
import {
|
||||
Autocomplete,
|
||||
Box,
|
||||
TextField,
|
||||
Typography,
|
||||
FormControlLabel,
|
||||
Checkbox,
|
||||
Grid,
|
||||
Paper,
|
||||
} from "@mui/material";
|
||||
import { Create, useAutocomplete } from "@refinedev/mui";
|
||||
import { useForm } from "@refinedev/react-hook-form";
|
||||
import { Controller } from "react-hook-form";
|
||||
|
||||
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: 'Троллейбус'},
|
||||
]
|
||||
{ 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: "Троллейбус" },
|
||||
];
|
||||
|
||||
export const StationCreate = () => {
|
||||
const {
|
||||
saveButtonProps,
|
||||
refineCore: {formLoading},
|
||||
refineCore: { formLoading },
|
||||
register,
|
||||
control,
|
||||
formState: {errors},
|
||||
formState: { errors },
|
||||
} = useForm({
|
||||
refineCoreProps: {
|
||||
resource: 'station/',
|
||||
resource: "station/",
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
const {autocompleteProps: cityAutocompleteProps} = useAutocomplete({
|
||||
resource: 'city',
|
||||
const { autocompleteProps: cityAutocompleteProps } = useAutocomplete({
|
||||
resource: "city",
|
||||
onSearch: (value) => [
|
||||
{
|
||||
field: 'name',
|
||||
operator: 'contains',
|
||||
field: "name",
|
||||
operator: "contains",
|
||||
value,
|
||||
},
|
||||
],
|
||||
})
|
||||
});
|
||||
|
||||
return (
|
||||
<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"
|
||||
>
|
||||
<TextField
|
||||
{...register('name', {
|
||||
required: 'Это поле является обязательным',
|
||||
{...register("name", {
|
||||
required: "Это поле является обязательным",
|
||||
})}
|
||||
error={!!(errors as any)?.name}
|
||||
helperText={(errors as any)?.name?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{shrink: true}}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="text"
|
||||
label={'Название *'}
|
||||
label={"Название *"}
|
||||
name="name"
|
||||
/>
|
||||
<TextField
|
||||
{...register('system_name', {
|
||||
required: 'Это поле является обязательным',
|
||||
{...register("system_name", {
|
||||
required: "Это поле является обязательным",
|
||||
})}
|
||||
error={!!(errors as any)?.system_name}
|
||||
helperText={(errors as any)?.system_name?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{shrink: true}}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="text"
|
||||
label={'Системное название *'}
|
||||
label={"Системное название *"}
|
||||
name="system_name"
|
||||
/>
|
||||
<TextField
|
||||
{...register('description', {
|
||||
{...register("address", {
|
||||
// required: 'Это поле является обязательным',
|
||||
})}
|
||||
error={!!(errors as any)?.address}
|
||||
helperText={(errors as any)?.address?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="text"
|
||||
label={"Адрес"}
|
||||
name="address"
|
||||
/>
|
||||
|
||||
<TextField
|
||||
{...register("description", {
|
||||
// required: 'Это поле является обязательным',
|
||||
})}
|
||||
error={!!(errors as any)?.description}
|
||||
helperText={(errors as any)?.description?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{shrink: true}}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="text"
|
||||
label={'Описание'}
|
||||
label={"Описание"}
|
||||
name="description"
|
||||
/>
|
||||
<Controller
|
||||
name="direction" // boolean
|
||||
control={control}
|
||||
defaultValue={false}
|
||||
render={({ field }: { field: any }) => (
|
||||
<FormControlLabel
|
||||
label="Прямой маршрут?"
|
||||
control={
|
||||
<Checkbox
|
||||
{...field}
|
||||
checked={field.value}
|
||||
onChange={(e) => field.onChange(e.target.checked)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<TextField
|
||||
{...register('latitude', {
|
||||
required: 'Это поле является обязательным',
|
||||
{...register("latitude", {
|
||||
required: "Это поле является обязательным",
|
||||
valueAsNumber: true,
|
||||
})}
|
||||
error={!!(errors as any)?.latitude}
|
||||
helperText={(errors as any)?.latitude?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{shrink: true}}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="number"
|
||||
label={'Широта *'}
|
||||
label={"Широта *"}
|
||||
name="latitude"
|
||||
/>
|
||||
<TextField
|
||||
{...register('longitude', {
|
||||
required: 'Это поле является обязательным',
|
||||
{...register("longitude", {
|
||||
required: "Это поле является обязательным",
|
||||
valueAsNumber: true,
|
||||
})}
|
||||
error={!!(errors as any)?.longitude}
|
||||
helperText={(errors as any)?.longitude?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{shrink: true}}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="number"
|
||||
label={'Долгота *'}
|
||||
label={"Долгота *"}
|
||||
name="longitude"
|
||||
/>
|
||||
|
||||
<Controller
|
||||
control={control}
|
||||
name="city_id"
|
||||
rules={{required: 'Это поле является обязательным'}}
|
||||
rules={{ required: "Это поле является обязательным" }}
|
||||
defaultValue={null}
|
||||
render={({field}) => (
|
||||
render={({ field }) => (
|
||||
<Autocomplete
|
||||
{...cityAutocompleteProps}
|
||||
value={cityAutocompleteProps.options.find((option) => option.id === field.value) || null}
|
||||
value={
|
||||
cityAutocompleteProps.options.find(
|
||||
(option) => option.id === field.value
|
||||
) || null
|
||||
}
|
||||
onChange={(_, value) => {
|
||||
field.onChange(value?.id || '')
|
||||
field.onChange(value?.id || "");
|
||||
}}
|
||||
getOptionLabel={(item) => {
|
||||
return item ? item.name : ''
|
||||
return item ? item.name : "";
|
||||
}}
|
||||
isOptionEqualToValue={(option, value) => {
|
||||
return option.id === value?.id
|
||||
return option.id === value?.id;
|
||||
}}
|
||||
filterOptions={(options, {inputValue}) => {
|
||||
return options.filter((option) => option.name.toLowerCase().includes(inputValue.toLowerCase()))
|
||||
filterOptions={(options, { inputValue }) => {
|
||||
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
|
||||
{...register('offset_x', {
|
||||
{...register("offset_x", {
|
||||
// required: 'Это поле является обязательным',
|
||||
})}
|
||||
error={!!(errors as any)?.offset_x}
|
||||
helperText={(errors as any)?.offset_x?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{shrink: true}}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="number"
|
||||
label={'Смещение (X)'}
|
||||
label={"Смещение (X)"}
|
||||
name="offset_x"
|
||||
/>
|
||||
|
||||
<TextField
|
||||
{...register('offset_y', {
|
||||
{...register("offset_y", {
|
||||
// required: 'Это поле является обязательным',
|
||||
})}
|
||||
error={!!(errors as any)?.offset_y}
|
||||
helperText={(errors as any)?.offset_y?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{shrink: true}}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="number"
|
||||
label={'Смещение (Y)'}
|
||||
label={"Смещение (Y)"}
|
||||
name="offset_y"
|
||||
/>
|
||||
|
||||
{/* Группа полей пересадок */}
|
||||
<Paper sx={{p: 2, mt: 2}}>
|
||||
<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}`} />
|
||||
<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>
|
||||
</Create>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@ -1,179 +1,248 @@
|
||||
import {Autocomplete, Box, TextField, Typography, Paper, Grid} from '@mui/material'
|
||||
import {Edit, useAutocomplete} from '@refinedev/mui'
|
||||
import {useForm} from '@refinedev/react-hook-form'
|
||||
import {Controller} from 'react-hook-form'
|
||||
import {
|
||||
Autocomplete,
|
||||
Box,
|
||||
TextField,
|
||||
Typography,
|
||||
FormControlLabel,
|
||||
Paper,
|
||||
Grid,
|
||||
Checkbox,
|
||||
} from "@mui/material";
|
||||
import { Edit, useAutocomplete } from "@refinedev/mui";
|
||||
import { useForm } from "@refinedev/react-hook-form";
|
||||
import { Controller } from "react-hook-form";
|
||||
|
||||
import {useParams} from 'react-router'
|
||||
import {LinkedItems} from '../../components/LinkedItems'
|
||||
import {type SightItem, sightFields} from './types'
|
||||
import { useParams } from "react-router";
|
||||
import { LinkedItems } from "../../components/LinkedItems";
|
||||
import { type SightItem, sightFields } from "./types";
|
||||
|
||||
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: 'Троллейбус'},
|
||||
]
|
||||
{ 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: "Троллейбус" },
|
||||
];
|
||||
|
||||
export const StationEdit = () => {
|
||||
const {
|
||||
saveButtonProps,
|
||||
register,
|
||||
control,
|
||||
formState: {errors},
|
||||
} = useForm({})
|
||||
formState: { errors },
|
||||
} = useForm({});
|
||||
|
||||
const {id: stationId} = useParams<{id: string}>()
|
||||
const { id: stationId } = useParams<{ id: string }>();
|
||||
|
||||
const {autocompleteProps: cityAutocompleteProps} = useAutocomplete({
|
||||
resource: 'city',
|
||||
const { autocompleteProps: cityAutocompleteProps } = useAutocomplete({
|
||||
resource: "city",
|
||||
onSearch: (value) => [
|
||||
{
|
||||
field: 'name',
|
||||
operator: 'contains',
|
||||
field: "name",
|
||||
operator: "contains",
|
||||
value,
|
||||
},
|
||||
],
|
||||
})
|
||||
});
|
||||
|
||||
return (
|
||||
<Edit saveButtonProps={saveButtonProps}>
|
||||
<Box component="form" sx={{display: 'flex', flexDirection: 'column'}} autoComplete="off">
|
||||
<Box
|
||||
component="form"
|
||||
sx={{ display: "flex", flexDirection: "column" }}
|
||||
autoComplete="off"
|
||||
>
|
||||
<TextField
|
||||
{...register('name', {
|
||||
required: 'Это поле является обязательным',
|
||||
{...register("name", {
|
||||
required: "Это поле является обязательным",
|
||||
})}
|
||||
error={!!(errors as any)?.name}
|
||||
helperText={(errors as any)?.name?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{shrink: true}}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="text"
|
||||
label={'Название *'}
|
||||
label={"Название *"}
|
||||
name="name"
|
||||
/>
|
||||
<TextField
|
||||
{...register('system_name', {
|
||||
required: 'Это поле является обязательным',
|
||||
{...register("system_name", {
|
||||
required: "Это поле является обязательным",
|
||||
})}
|
||||
error={!!(errors as any)?.system_name}
|
||||
helperText={(errors as any)?.system_name?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{shrink: true}}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="text"
|
||||
label={'Системное название *'}
|
||||
label={"Системное название *"}
|
||||
name="system_name"
|
||||
/>
|
||||
<Controller
|
||||
name="direction" // boolean
|
||||
control={control}
|
||||
defaultValue={false}
|
||||
render={({ field }: { field: any }) => (
|
||||
<FormControlLabel
|
||||
label="Прямой маршрут?"
|
||||
control={
|
||||
<Checkbox
|
||||
{...field}
|
||||
checked={field.value}
|
||||
onChange={(e) => field.onChange(e.target.checked)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<TextField
|
||||
{...register('description', {
|
||||
{...register("description", {
|
||||
// required: 'Это поле является обязательным',
|
||||
})}
|
||||
error={!!(errors as any)?.description}
|
||||
helperText={(errors as any)?.description?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{shrink: true}}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="text"
|
||||
label={'Описание'}
|
||||
label={"Описание"}
|
||||
name="description"
|
||||
/>
|
||||
<TextField
|
||||
{...register('latitude', {
|
||||
required: 'Это поле является обязательным',
|
||||
{...register("address", {
|
||||
// required: 'Это поле является обязательным',
|
||||
})}
|
||||
error={!!(errors as any)?.address}
|
||||
helperText={(errors as any)?.address?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="text"
|
||||
label={"Адрес"}
|
||||
name="address"
|
||||
/>
|
||||
<TextField
|
||||
{...register("latitude", {
|
||||
required: "Это поле является обязательным",
|
||||
valueAsNumber: true,
|
||||
})}
|
||||
error={!!(errors as any)?.latitude}
|
||||
helperText={(errors as any)?.latitude?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{shrink: true}}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="number"
|
||||
label={'Широта *'}
|
||||
label={"Широта *"}
|
||||
name="latitude"
|
||||
/>
|
||||
<TextField
|
||||
{...register('longitude', {
|
||||
required: 'Это поле является обязательным',
|
||||
{...register("longitude", {
|
||||
required: "Это поле является обязательным",
|
||||
valueAsNumber: true,
|
||||
})}
|
||||
error={!!(errors as any)?.longitude}
|
||||
helperText={(errors as any)?.longitude?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{shrink: true}}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="number"
|
||||
label={'Долгота *'}
|
||||
label={"Долгота *"}
|
||||
name="longitude"
|
||||
/>
|
||||
|
||||
<Controller
|
||||
control={control}
|
||||
name="city_id"
|
||||
rules={{required: 'Это поле является обязательным'}}
|
||||
rules={{ required: "Это поле является обязательным" }}
|
||||
defaultValue={null}
|
||||
render={({field}) => (
|
||||
render={({ field }) => (
|
||||
<Autocomplete
|
||||
{...cityAutocompleteProps}
|
||||
value={cityAutocompleteProps.options.find((option) => option.id === field.value) || null}
|
||||
value={
|
||||
cityAutocompleteProps.options.find(
|
||||
(option) => option.id === field.value
|
||||
) || null
|
||||
}
|
||||
onChange={(_, value) => {
|
||||
field.onChange(value?.id || '')
|
||||
field.onChange(value?.id || "");
|
||||
}}
|
||||
getOptionLabel={(item) => {
|
||||
return item ? item.name : ''
|
||||
return item ? item.name : "";
|
||||
}}
|
||||
isOptionEqualToValue={(option, value) => {
|
||||
return option.id === value?.id
|
||||
return option.id === value?.id;
|
||||
}}
|
||||
filterOptions={(options, {inputValue}) => {
|
||||
return options.filter((option) => option.name.toLowerCase().includes(inputValue.toLowerCase()))
|
||||
filterOptions={(options, { inputValue }) => {
|
||||
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
|
||||
{...register('offset_x', {
|
||||
{...register("offset_x", {
|
||||
// required: 'Это поле является обязательным',
|
||||
})}
|
||||
error={!!(errors as any)?.offset_x}
|
||||
helperText={(errors as any)?.offset_x?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{shrink: true}}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="number"
|
||||
label={'Смещение (X)'}
|
||||
label={"Смещение (X)"}
|
||||
name="offset_x"
|
||||
/>
|
||||
|
||||
<TextField
|
||||
{...register('offset_y', {
|
||||
{...register("offset_y", {
|
||||
// required: 'Это поле является обязательным',
|
||||
})}
|
||||
error={!!(errors as any)?.offset_y}
|
||||
helperText={(errors as any)?.offset_y?.message}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
InputLabelProps={{shrink: true}}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
type="number"
|
||||
label={'Смещение (Y)'}
|
||||
label={"Смещение (Y)"}
|
||||
name="offset_y"
|
||||
/>
|
||||
|
||||
{/* Группа полей пересадок */}
|
||||
<Paper sx={{p: 2, mt: 2}}>
|
||||
<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}`} />
|
||||
<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>
|
||||
@ -188,8 +257,9 @@ export const StationEdit = () => {
|
||||
childResource="sight"
|
||||
fields={sightFields}
|
||||
title="достопримечательности"
|
||||
dragAllowed={false}
|
||||
/>
|
||||
)}
|
||||
</Edit>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@ -1,126 +1,157 @@
|
||||
import React from 'react'
|
||||
import {type GridColDef} from '@mui/x-data-grid'
|
||||
import {DeleteButton, EditButton, List, ShowButton, useDataGrid} from '@refinedev/mui'
|
||||
import {Stack} from '@mui/material'
|
||||
import {CustomDataGrid} from '../../components/CustomDataGrid'
|
||||
import {localeText} from '../../locales/ru/localeText'
|
||||
import React, { useMemo } from "react";
|
||||
import { type GridColDef } from "@mui/x-data-grid";
|
||||
import {
|
||||
DeleteButton,
|
||||
EditButton,
|
||||
List,
|
||||
ShowButton,
|
||||
useDataGrid,
|
||||
} from "@refinedev/mui";
|
||||
import { Stack } from "@mui/material";
|
||||
import { CustomDataGrid } from "../../components/CustomDataGrid";
|
||||
import { localeText } from "../../locales/ru/localeText";
|
||||
import { cityStore } from "../../store/CityStore";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
export const StationList = () => {
|
||||
const {dataGridProps} = useDataGrid({resource: 'station/'})
|
||||
export const StationList = observer(() => {
|
||||
const { city_id } = cityStore;
|
||||
|
||||
const { dataGridProps } = useDataGrid({
|
||||
resource: "station",
|
||||
filters: {
|
||||
permanent: [
|
||||
{
|
||||
field: "cityID",
|
||||
operator: "eq",
|
||||
value: city_id,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const columns = React.useMemo<GridColDef[]>(
|
||||
() => [
|
||||
{
|
||||
field: 'id',
|
||||
headerName: 'ID',
|
||||
type: 'number',
|
||||
field: "id",
|
||||
headerName: "ID",
|
||||
type: "number",
|
||||
minWidth: 70,
|
||||
display: 'flex',
|
||||
align: 'left',
|
||||
headerAlign: 'left',
|
||||
display: "flex",
|
||||
align: "left",
|
||||
headerAlign: "left",
|
||||
},
|
||||
{
|
||||
field: 'name',
|
||||
headerName: 'Название',
|
||||
type: 'string',
|
||||
field: "name",
|
||||
headerName: "Название",
|
||||
type: "string",
|
||||
minWidth: 300,
|
||||
display: 'flex',
|
||||
align: 'left',
|
||||
headerAlign: 'left',
|
||||
display: "flex",
|
||||
align: "left",
|
||||
headerAlign: "left",
|
||||
},
|
||||
{
|
||||
field: 'system_name',
|
||||
headerName: 'Системное название',
|
||||
type: 'string',
|
||||
field: "system_name",
|
||||
headerName: "Системное название",
|
||||
type: "string",
|
||||
minWidth: 200,
|
||||
display: 'flex',
|
||||
align: 'left',
|
||||
headerAlign: 'left',
|
||||
display: "flex",
|
||||
align: "left",
|
||||
headerAlign: "left",
|
||||
},
|
||||
{
|
||||
field: 'latitude',
|
||||
headerName: 'Широта',
|
||||
type: 'number',
|
||||
field: "latitude",
|
||||
headerName: "Широта",
|
||||
type: "number",
|
||||
minWidth: 150,
|
||||
display: 'flex',
|
||||
align: 'left',
|
||||
headerAlign: 'left',
|
||||
display: "flex",
|
||||
align: "left",
|
||||
headerAlign: "left",
|
||||
},
|
||||
{
|
||||
field: 'longitude',
|
||||
headerName: 'Долгота',
|
||||
type: 'number',
|
||||
field: "longitude",
|
||||
headerName: "Долгота",
|
||||
type: "number",
|
||||
minWidth: 150,
|
||||
display: 'flex',
|
||||
align: 'left',
|
||||
headerAlign: 'left',
|
||||
display: "flex",
|
||||
align: "left",
|
||||
headerAlign: "left",
|
||||
},
|
||||
{
|
||||
field: 'city_id',
|
||||
headerName: 'ID города',
|
||||
type: 'number',
|
||||
field: "city_id",
|
||||
headerName: "ID города",
|
||||
type: "number",
|
||||
minWidth: 120,
|
||||
display: 'flex',
|
||||
align: 'left',
|
||||
headerAlign: 'left',
|
||||
display: "flex",
|
||||
align: "left",
|
||||
headerAlign: "left",
|
||||
},
|
||||
{
|
||||
field: 'offset_x',
|
||||
headerName: 'Смещение (X)',
|
||||
type: 'number',
|
||||
field: "offset_x",
|
||||
headerName: "Смещение (X)",
|
||||
type: "number",
|
||||
minWidth: 120,
|
||||
display: 'flex',
|
||||
align: 'left',
|
||||
headerAlign: 'left',
|
||||
display: "flex",
|
||||
align: "left",
|
||||
headerAlign: "left",
|
||||
},
|
||||
{
|
||||
field: 'offset_y',
|
||||
headerName: 'Смещение (Y)',
|
||||
type: 'number',
|
||||
field: "offset_y",
|
||||
headerName: "Смещение (Y)",
|
||||
type: "number",
|
||||
minWidth: 120,
|
||||
display: 'flex',
|
||||
align: 'left',
|
||||
headerAlign: 'left',
|
||||
display: "flex",
|
||||
align: "left",
|
||||
headerAlign: "left",
|
||||
},
|
||||
{
|
||||
field: 'description',
|
||||
headerName: 'Описание',
|
||||
type: 'string',
|
||||
display: 'flex',
|
||||
align: 'left',
|
||||
headerAlign: 'left',
|
||||
field: "description",
|
||||
headerName: "Описание",
|
||||
type: "string",
|
||||
display: "flex",
|
||||
align: "left",
|
||||
headerAlign: "left",
|
||||
flex: 1,
|
||||
},
|
||||
{
|
||||
field: 'actions',
|
||||
headerName: 'Действия',
|
||||
cellClassName: 'station-actions',
|
||||
align: 'right',
|
||||
headerAlign: 'center',
|
||||
field: "actions",
|
||||
headerName: "Действия",
|
||||
cellClassName: "station-actions",
|
||||
align: "right",
|
||||
headerAlign: "center",
|
||||
minWidth: 120,
|
||||
display: 'flex',
|
||||
display: "flex",
|
||||
sortable: false,
|
||||
filterable: false,
|
||||
disableColumnMenu: true,
|
||||
renderCell: function render({row}) {
|
||||
renderCell: function render({ row }) {
|
||||
return (
|
||||
<>
|
||||
<EditButton hideText recordItemId={row.id} />
|
||||
<ShowButton hideText recordItemId={row.id} />
|
||||
<DeleteButton hideText confirmTitle="Вы уверены?" recordItemId={row.id} />
|
||||
<DeleteButton
|
||||
hideText
|
||||
confirmTitle="Вы уверены?"
|
||||
recordItemId={row.id}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
[],
|
||||
)
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<List>
|
||||
<List key={city_id}>
|
||||
<Stack gap={2.5}>
|
||||
<CustomDataGrid {...dataGridProps} columns={columns} localeText={localeText} getRowId={(row: any) => row.id} hasCoordinates />
|
||||
<CustomDataGrid
|
||||
{...dataGridProps}
|
||||
columns={columns}
|
||||
localeText={localeText}
|
||||
getRowId={(row: any) => row.id}
|
||||
hasCoordinates
|
||||
/>
|
||||
</Stack>
|
||||
</List>
|
||||
)
|
||||
}
|
||||
);
|
||||
});
|
||||
|
@ -1,23 +1,31 @@
|
||||
import {useShow} from '@refinedev/core'
|
||||
import {Show, TextFieldComponent as TextField} from '@refinedev/mui'
|
||||
import {Stack, Typography} from '@mui/material'
|
||||
import {LinkedItems} from '../../components/LinkedItems'
|
||||
import {type SightItem, sightFields, stationFields} from './types'
|
||||
import { useShow } from "@refinedev/core";
|
||||
import { Show, TextFieldComponent as TextField } from "@refinedev/mui";
|
||||
import { Box, Stack, Typography } from "@mui/material";
|
||||
import { LinkedItems } from "../../components/LinkedItems";
|
||||
import { type SightItem, sightFields, stationFields } from "./types";
|
||||
|
||||
export const StationShow = () => {
|
||||
const {query} = useShow({})
|
||||
const {data, isLoading} = query
|
||||
const record = data?.data
|
||||
const { query } = useShow({});
|
||||
const { data, isLoading } = query;
|
||||
const record = data?.data;
|
||||
|
||||
return (
|
||||
<Show isLoading={isLoading}>
|
||||
<Stack gap={4}>
|
||||
{stationFields.map(({label, data}) => (
|
||||
{stationFields.map(({ label, data }) => (
|
||||
<Stack key={data} gap={1}>
|
||||
<Typography variant="body1" fontWeight="bold">
|
||||
{label}
|
||||
{label === "Системное название" && (
|
||||
<Box>
|
||||
<TextField
|
||||
value={record?.direction ? "(Прямой)" : "(Обратный)"}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Typography>
|
||||
<TextField value={record?.[data] || ''} />
|
||||
|
||||
<TextField value={record?.[data] || ""} />
|
||||
</Stack>
|
||||
))}
|
||||
|
||||
@ -33,5 +41,5 @@ export const StationShow = () => {
|
||||
)}
|
||||
</Stack>
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@ -1,44 +1,45 @@
|
||||
import React from 'react'
|
||||
import React from "react";
|
||||
|
||||
export type StationItem = {
|
||||
id: number
|
||||
name: string
|
||||
description: string
|
||||
latitude: number
|
||||
longitude: number
|
||||
[key: string]: string | number
|
||||
}
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
[key: string]: string | number;
|
||||
};
|
||||
|
||||
export type SightItem = {
|
||||
id: number
|
||||
name: string
|
||||
latitude: number
|
||||
longitude: number
|
||||
city_id: number
|
||||
city: string
|
||||
[key: string]: string | number
|
||||
}
|
||||
id: number;
|
||||
name: string;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
city_id: number;
|
||||
city: string;
|
||||
[key: string]: string | number;
|
||||
};
|
||||
|
||||
export type FieldType<T> = {
|
||||
label: string
|
||||
data: keyof T
|
||||
render?: (value: any) => React.ReactNode
|
||||
}
|
||||
label: string;
|
||||
data: keyof T;
|
||||
render?: (value: any) => React.ReactNode;
|
||||
};
|
||||
|
||||
export const stationFields: Array<FieldType<StationItem>> = [
|
||||
// {label: 'ID', data: 'id'},
|
||||
{label: 'Название', data: 'name'},
|
||||
{label: 'Системное название', data: 'system_name'},
|
||||
{ label: "Название", data: "name" },
|
||||
{ label: "Системное название", data: "system_name" },
|
||||
{ label: "Адрес", data: "address" },
|
||||
// {label: 'Широта', data: 'latitude'},
|
||||
// {label: 'Долгота', data: 'longitude'},
|
||||
{label: 'Описание', data: 'description'},
|
||||
]
|
||||
{ label: "Описание", data: "description" },
|
||||
];
|
||||
|
||||
export const sightFields: Array<FieldType<SightItem>> = [
|
||||
// {label: 'ID', data: 'id'},
|
||||
{label: 'Название', data: 'name'},
|
||||
{ label: "Название", data: "name" },
|
||||
// {label: 'Широта', data: 'latitude'},
|
||||
// {label: 'Долгота', data: 'longitude'},
|
||||
// {label: 'ID города', data: 'city_id'},
|
||||
{label: 'Город', data: 'city'},
|
||||
]
|
||||
{ label: "Город", data: "city" },
|
||||
];
|
||||
|
Reference in New Issue
Block a user