last changes, possibly
This commit is contained in:
parent
042b53e6a4
commit
ab1fd6b22a
@ -18,10 +18,7 @@ import {
|
|||||||
ALLOWED_IMAGE_TYPES,
|
ALLOWED_IMAGE_TYPES,
|
||||||
ALLOWED_VIDEO_TYPES,
|
ALLOWED_VIDEO_TYPES,
|
||||||
} from "../components/media/MediaFormUtils";
|
} from "../components/media/MediaFormUtils";
|
||||||
import { LinkedItems } from "./LinkedItems";
|
import { EVERY_LANGUAGE, Languages } from "@stores";
|
||||||
import { mediaFields, MediaItem } from "../pages/article/types";
|
|
||||||
import { LanguageSelector } from "@ui";
|
|
||||||
import { EVERY_LANGUAGE, Languages, languageStore } from "@stores";
|
|
||||||
|
|
||||||
const MemoizedSimpleMDE = React.memo(MarkdownEditor);
|
const MemoizedSimpleMDE = React.memo(MarkdownEditor);
|
||||||
|
|
||||||
@ -38,6 +35,9 @@ type Props = {
|
|||||||
childResource: string;
|
childResource: string;
|
||||||
title: string;
|
title: string;
|
||||||
left?: boolean;
|
left?: boolean;
|
||||||
|
language: Languages,
|
||||||
|
setHeadingParent?: (heading: string) => void,
|
||||||
|
setBodyParent?: (body: string) => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CreateSightArticle = ({
|
export const CreateSightArticle = ({
|
||||||
@ -46,10 +46,13 @@ export const CreateSightArticle = ({
|
|||||||
childResource,
|
childResource,
|
||||||
title,
|
title,
|
||||||
left,
|
left,
|
||||||
|
language,
|
||||||
|
setHeadingParent,
|
||||||
|
setBodyParent
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const [mediaFiles, setMediaFiles] = useState<MediaFile[]>([]);
|
const [mediaFiles, setMediaFiles] = useState<MediaFile[]>([]);
|
||||||
const { language, setLanguageAction } = languageStore;
|
const [workingLanguage, setWorkingLanguage] = useState<Languages>(language);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
register: registerItem,
|
register: registerItem,
|
||||||
@ -66,6 +69,7 @@ export const CreateSightArticle = ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const [articleData, setArticleData] = useState({
|
const [articleData, setArticleData] = useState({
|
||||||
heading: EVERY_LANGUAGE(""),
|
heading: EVERY_LANGUAGE(""),
|
||||||
body: EVERY_LANGUAGE("")
|
body: EVERY_LANGUAGE("")
|
||||||
@ -76,34 +80,31 @@ export const CreateSightArticle = ({
|
|||||||
...articleData,
|
...articleData,
|
||||||
heading: {
|
heading: {
|
||||||
...articleData.heading,
|
...articleData.heading,
|
||||||
[language]: watch("heading") ?? "",
|
[workingLanguage]: watch("heading") ?? "",
|
||||||
},
|
},
|
||||||
body: {
|
body: {
|
||||||
...articleData.body,
|
...articleData.body,
|
||||||
[language]: watch("body") ?? "",
|
[workingLanguage]: watch("body") ?? "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setArticleData(newArticleData);
|
setArticleData(newArticleData);
|
||||||
return newArticleData;
|
return newArticleData;
|
||||||
}
|
}
|
||||||
|
|
||||||
// const handleFormSubmit = handleSubmit((values: FieldValues) => {
|
useEffect(() => {
|
||||||
// const newTranslations = updateTranslations();
|
setValue("heading", articleData.heading[workingLanguage] ?? "");
|
||||||
// console.log(newTranslations);
|
setValue("body", articleData.body[workingLanguage] ?? "");
|
||||||
// return onFinish({
|
}, [workingLanguage, articleData, setValue]);
|
||||||
// translations: newTranslations
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setValue("heading", articleData.heading[language] ?? "");
|
|
||||||
setValue("body", articleData.body[language] ?? "");
|
|
||||||
}, [language, articleData, setValue]);
|
|
||||||
|
|
||||||
const handleLanguageChange = (lang: Languages) => {
|
|
||||||
updateTranslations();
|
updateTranslations();
|
||||||
setLanguageAction(lang);
|
setWorkingLanguage(language);
|
||||||
};
|
}, [language]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setHeadingParent?.(watch("heading"));
|
||||||
|
setBodyParent?.(watch("body"));
|
||||||
|
}, [watch("heading"), watch("body"), setHeadingParent, setBodyParent]);
|
||||||
|
|
||||||
const simpleMDEOptions = React.useMemo(
|
const simpleMDEOptions = React.useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
@ -152,8 +153,7 @@ export const CreateSightArticle = ({
|
|||||||
try {
|
try {
|
||||||
// Создаем статью
|
// Создаем статью
|
||||||
const response = await axiosInstance.post(
|
const response = await axiosInstance.post(
|
||||||
`${import.meta.env.VITE_KRBL_API}/${childResource}`,
|
`${import.meta.env.VITE_KRBL_API}/${childResource}`, {
|
||||||
{
|
|
||||||
...data,
|
...data,
|
||||||
translations: updateTranslations()
|
translations: updateTranslations()
|
||||||
}
|
}
|
||||||
@ -220,154 +220,135 @@ export const CreateSightArticle = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Accordion>
|
<Box component="form" onSubmit={handleSubmitItem(handleCreate)}>
|
||||||
<AccordionSummary
|
<TextField
|
||||||
expandIcon={<ExpandMoreIcon />}
|
{...registerItem("heading", {
|
||||||
|
required: "Это поле является обязательным",
|
||||||
|
})}
|
||||||
|
error={!!(itemErrors as any)?.heading}
|
||||||
|
helperText={(itemErrors as any)?.heading?.message}
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
slotProps={{inputLabel: {shrink: true}}}
|
||||||
|
type="text"
|
||||||
sx={{
|
sx={{
|
||||||
marginTop: 2,
|
backgroundColor: theme.palette.background.paper,
|
||||||
background: theme.palette.background.paper,
|
|
||||||
borderBottom: `1px solid ${theme.palette.divider}`,
|
|
||||||
zIndex: 2000,
|
|
||||||
}}
|
}}
|
||||||
>
|
label="Заголовок *"
|
||||||
<Typography variant="subtitle1" fontWeight="bold">
|
/>
|
||||||
Создать {title}
|
|
||||||
</Typography>
|
|
||||||
</AccordionSummary>
|
|
||||||
<AccordionDetails sx={{ background: theme.palette.background.paper }}>
|
|
||||||
<Box component="form" onSubmit={handleSubmitItem(handleCreate)}>
|
|
||||||
<LanguageSelector action={handleLanguageChange} />
|
|
||||||
<TextField
|
|
||||||
{...registerItem("heading", {
|
|
||||||
required: "Это поле является обязательным",
|
|
||||||
})}
|
|
||||||
error={!!(itemErrors as any)?.heading}
|
|
||||||
helperText={(itemErrors as any)?.heading?.message}
|
|
||||||
margin="normal"
|
|
||||||
fullWidth
|
|
||||||
InputLabelProps={{ shrink: true }}
|
|
||||||
type="text"
|
|
||||||
sx={{
|
|
||||||
zIndex: 2000,
|
|
||||||
backgroundColor: theme.palette.background.paper,
|
|
||||||
}}
|
|
||||||
label="Заголовок *"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Controller
|
<Controller
|
||||||
control={controlItem}
|
control={controlItem}
|
||||||
name="body"
|
name="body"
|
||||||
rules={{ required: "Это поле является обязательным" }}
|
rules={{ required: "Это поле является обязательным" }}
|
||||||
defaultValue=""
|
defaultValue=""
|
||||||
render={({ field: { onChange, value } }) => (
|
render={({ field: { onChange, value } }) => (
|
||||||
<MemoizedSimpleMDE
|
<MemoizedSimpleMDE
|
||||||
value={value}
|
value={value}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
options={simpleMDEOptions}
|
options={simpleMDEOptions}
|
||||||
className="my-markdown-editor"
|
className="my-markdown-editor"
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* Dropzone для медиа файлов */}
|
{/* Dropzone для медиа файлов */}
|
||||||
<Box sx={{ mt: 2, mb: 2 }}>
|
<Box sx={{ mt: 2, mb: 2 }}>
|
||||||
|
<Box
|
||||||
|
{...getRootProps()}
|
||||||
|
sx={{
|
||||||
|
border: "2px dashed",
|
||||||
|
borderColor: isDragActive ? "primary.main" : "grey.300",
|
||||||
|
borderRadius: 1,
|
||||||
|
p: 2,
|
||||||
|
textAlign: "center",
|
||||||
|
cursor: "pointer",
|
||||||
|
"&:hover": {
|
||||||
|
borderColor: "primary.main",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<input {...getInputProps()} />
|
||||||
|
<Typography>
|
||||||
|
{isDragActive
|
||||||
|
? "Перетащите файлы сюда..."
|
||||||
|
: "Перетащите файлы сюда или кликните для выбора"}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Превью загруженных файлов */}
|
||||||
|
<Box sx={{ mt: 2, display: "flex", flexWrap: "wrap", gap: 1 }}>
|
||||||
|
{mediaFiles.map((mediaFile, index) => (
|
||||||
<Box
|
<Box
|
||||||
{...getRootProps()}
|
key={mediaFile.preview}
|
||||||
sx={{
|
sx={{
|
||||||
border: "2px dashed",
|
position: "relative",
|
||||||
borderColor: isDragActive ? "primary.main" : "grey.300",
|
width: 100,
|
||||||
borderRadius: 1,
|
height: 100,
|
||||||
p: 2,
|
|
||||||
textAlign: "center",
|
|
||||||
cursor: "pointer",
|
|
||||||
"&:hover": {
|
|
||||||
borderColor: "primary.main",
|
|
||||||
},
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<input {...getInputProps()} />
|
{mediaFile.file.type.startsWith("image/") ? (
|
||||||
<Typography>
|
<img
|
||||||
{isDragActive
|
src={mediaFile.preview}
|
||||||
? "Перетащите файлы сюда..."
|
alt={mediaFile.file.name}
|
||||||
: "Перетащите файлы сюда или кликните для выбора"}
|
style={{
|
||||||
</Typography>
|
width: "100%",
|
||||||
</Box>
|
height: "100%",
|
||||||
|
objectFit: "cover",
|
||||||
{/* Превью загруженных файлов */}
|
}}
|
||||||
<Box sx={{ mt: 2, display: "flex", flexWrap: "wrap", gap: 1 }}>
|
/>
|
||||||
{mediaFiles.map((mediaFile, index) => (
|
) : (
|
||||||
<Box
|
<Box
|
||||||
key={mediaFile.preview}
|
|
||||||
sx={{
|
sx={{
|
||||||
position: "relative",
|
width: "100%",
|
||||||
width: 100,
|
height: "100%",
|
||||||
height: 100,
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
bgcolor: "grey.200",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{mediaFile.file.type.startsWith("image/") ? (
|
<Typography variant="caption">
|
||||||
<img
|
{mediaFile.file.name}
|
||||||
src={mediaFile.preview}
|
</Typography>
|
||||||
alt={mediaFile.file.name}
|
|
||||||
style={{
|
|
||||||
width: "100%",
|
|
||||||
height: "100%",
|
|
||||||
objectFit: "cover",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
width: "100%",
|
|
||||||
height: "100%",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
bgcolor: "grey.200",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography variant="caption">
|
|
||||||
{mediaFile.file.name}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
<Button
|
|
||||||
size="small"
|
|
||||||
color="error"
|
|
||||||
onClick={() => removeMedia(index)}
|
|
||||||
sx={{
|
|
||||||
position: "absolute",
|
|
||||||
top: 0,
|
|
||||||
right: 0,
|
|
||||||
minWidth: "auto",
|
|
||||||
width: 20,
|
|
||||||
height: 20,
|
|
||||||
p: 0,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
×
|
|
||||||
</Button>
|
|
||||||
</Box>
|
</Box>
|
||||||
))}
|
)}
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
color="error"
|
||||||
|
onClick={() => removeMedia(index)}
|
||||||
|
sx={{
|
||||||
|
position: "absolute",
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
minWidth: "auto",
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
p: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
))}
|
||||||
|
|
||||||
<Box sx={{ mt: 2, display: "flex", gap: 2 }}>
|
|
||||||
<Button variant="contained" color="primary" type="submit">
|
|
||||||
Создать
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="outlined"
|
|
||||||
onClick={() => {
|
|
||||||
resetItem();
|
|
||||||
mediaFiles.forEach((file) => URL.revokeObjectURL(file.preview));
|
|
||||||
setMediaFiles([]);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Очистить
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
</AccordionDetails>
|
</Box>
|
||||||
</Accordion>
|
|
||||||
|
<Box sx={{ mt: 2, display: "flex", gap: 2 }}>
|
||||||
|
<Button variant="contained" color="primary" type="submit">
|
||||||
|
Создать
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
onClick={() => {
|
||||||
|
resetItem();
|
||||||
|
mediaFiles.forEach((file) => URL.revokeObjectURL(file.preview));
|
||||||
|
setMediaFiles([]);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Очистить
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -5,8 +5,6 @@ import {
|
|||||||
Typography,
|
Typography,
|
||||||
Button,
|
Button,
|
||||||
FormControl,
|
FormControl,
|
||||||
Grid,
|
|
||||||
Box,
|
|
||||||
Accordion,
|
Accordion,
|
||||||
AccordionSummary,
|
AccordionSummary,
|
||||||
AccordionDetails,
|
AccordionDetails,
|
||||||
@ -21,8 +19,6 @@ import {
|
|||||||
Paper,
|
Paper,
|
||||||
TableBody,
|
TableBody,
|
||||||
IconButton,
|
IconButton,
|
||||||
Collapse,
|
|
||||||
Modal,
|
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
||||||
import DragIndicatorIcon from "@mui/icons-material/DragIndicator";
|
import DragIndicatorIcon from "@mui/icons-material/DragIndicator";
|
||||||
@ -71,6 +67,8 @@ type LinkedItemsProps<T> = {
|
|||||||
onSave?: (items: T[]) => void;
|
onSave?: (items: T[]) => void;
|
||||||
onUpdate?: () => void;
|
onUpdate?: () => void;
|
||||||
dontRecurse?: boolean;
|
dontRecurse?: boolean;
|
||||||
|
disableCreation?: boolean;
|
||||||
|
updatedLinkedItems?: T[];
|
||||||
};
|
};
|
||||||
|
|
||||||
const reorder = (list: any[], startIndex: number, endIndex: number) => {
|
const reorder = (list: any[], startIndex: number, endIndex: number) => {
|
||||||
@ -80,7 +78,44 @@ const reorder = (list: any[], startIndex: number, endIndex: number) => {
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LinkedItems = <T extends { id: number; [key: string]: any }>({
|
export const LinkedItems = <T extends { id: number; [key: string]: any }>(
|
||||||
|
props: LinkedItemsProps<T>
|
||||||
|
) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Accordion>
|
||||||
|
<AccordionSummary
|
||||||
|
expandIcon={<ExpandMoreIcon />}
|
||||||
|
sx={{
|
||||||
|
background: theme.palette.background.paper,
|
||||||
|
borderBottom: `1px solid ${theme.palette.divider}`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant="subtitle1" fontWeight="bold">
|
||||||
|
Привязанные {props.title}
|
||||||
|
</Typography>
|
||||||
|
</AccordionSummary>
|
||||||
|
|
||||||
|
<AccordionDetails sx={{ background: theme.palette.background.paper }}>
|
||||||
|
<Stack gap={2}>
|
||||||
|
<LinkedItemsContents {...props} />
|
||||||
|
</Stack>
|
||||||
|
</AccordionDetails>
|
||||||
|
</Accordion>
|
||||||
|
|
||||||
|
{!props.dontRecurse &&
|
||||||
|
<>
|
||||||
|
<ArticleEditModal />
|
||||||
|
<StationEditModal />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LinkedItemsContents = <T extends { id: number; [key: string]: any }>({
|
||||||
parentId,
|
parentId,
|
||||||
parentResource,
|
parentResource,
|
||||||
childResource,
|
childResource,
|
||||||
@ -89,9 +124,9 @@ export const LinkedItems = <T extends { id: number; [key: string]: any }>({
|
|||||||
title,
|
title,
|
||||||
dragAllowed = false,
|
dragAllowed = false,
|
||||||
type,
|
type,
|
||||||
onSave,
|
|
||||||
onUpdate,
|
onUpdate,
|
||||||
dontRecurse = false,
|
disableCreation = false,
|
||||||
|
updatedLinkedItems
|
||||||
}: LinkedItemsProps<T>) => {
|
}: LinkedItemsProps<T>) => {
|
||||||
const { language } = languageStore;
|
const { language } = languageStore;
|
||||||
const { setArticleModalOpenAction, setArticleIdAction } = articleStore;
|
const { setArticleModalOpenAction, setArticleIdAction } = articleStore;
|
||||||
@ -104,7 +139,6 @@ export const LinkedItems = <T extends { id: number; [key: string]: any }>({
|
|||||||
const [pageNum, setPageNum] = useState<number>(1);
|
const [pageNum, setPageNum] = useState<number>(1);
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||||
const [mediaOrder, setMediaOrder] = useState<number>(1);
|
const [mediaOrder, setMediaOrder] = useState<number>(1);
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
let availableItems = items.filter(
|
let availableItems = items.filter(
|
||||||
(item) => !linkedItems.some((linked) => linked.id === item.id)
|
(item) => !linkedItems.some((linked) => linked.id === item.id)
|
||||||
@ -118,9 +152,12 @@ export const LinkedItems = <T extends { id: number; [key: string]: any }>({
|
|||||||
}, [childResource, availableItems]);
|
}, [childResource, availableItems]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (setItemsParent) {
|
if(!updatedLinkedItems?.length) return;
|
||||||
setItemsParent(linkedItems);
|
setLinkedItems(updatedLinkedItems);
|
||||||
}
|
}, [updatedLinkedItems]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setItemsParent?.(linkedItems);
|
||||||
}, [linkedItems, setItemsParent]);
|
}, [linkedItems, setItemsParent]);
|
||||||
|
|
||||||
const onDragEnd = (result: any) => {
|
const onDragEnd = (result: any) => {
|
||||||
@ -272,254 +309,230 @@ export const LinkedItems = <T extends { id: number; [key: string]: any }>({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Accordion>
|
{linkedItems?.length > 0 && (
|
||||||
<AccordionSummary
|
<DragDropContext onDragEnd={onDragEnd}>
|
||||||
expandIcon={<ExpandMoreIcon />}
|
<TableContainer component={Paper}>
|
||||||
sx={{
|
<Table>
|
||||||
background: theme.palette.background.paper,
|
<TableHead>
|
||||||
borderBottom: `1px solid ${theme.palette.divider}`,
|
<TableRow>
|
||||||
}}
|
{type === "edit" && dragAllowed && (
|
||||||
>
|
<TableCell width="40px"></TableCell>
|
||||||
<Typography variant="subtitle1" fontWeight="bold">
|
)}
|
||||||
Привязанные {title}
|
<TableCell key="id">№</TableCell>
|
||||||
</Typography>
|
{fields.map((field) => (
|
||||||
</AccordionSummary>
|
<TableCell key={String(field.data)}>
|
||||||
|
{field.label}
|
||||||
|
</TableCell>
|
||||||
|
))}
|
||||||
|
|
||||||
<AccordionDetails sx={{ background: theme.palette.background.paper }}>
|
{type === "edit" && (
|
||||||
<Stack gap={2}>
|
<TableCell width="120px">Действие</TableCell>
|
||||||
{linkedItems?.length > 0 && (
|
)}
|
||||||
<DragDropContext onDragEnd={onDragEnd}>
|
</TableRow>
|
||||||
<TableContainer component={Paper}>
|
</TableHead>
|
||||||
<Table>
|
|
||||||
<TableHead>
|
|
||||||
<TableRow>
|
|
||||||
{type === "edit" && dragAllowed && (
|
|
||||||
<TableCell width="40px"></TableCell>
|
|
||||||
)}
|
|
||||||
<TableCell key="id">№</TableCell>
|
|
||||||
{fields.map((field) => (
|
|
||||||
<TableCell key={String(field.data)}>
|
|
||||||
{field.label}
|
|
||||||
</TableCell>
|
|
||||||
))}
|
|
||||||
|
|
||||||
{type === "edit" && (
|
<Droppable
|
||||||
<TableCell width="120px">Действие</TableCell>
|
droppableId="droppable"
|
||||||
)}
|
isDropDisabled={type !== "edit" || !dragAllowed}
|
||||||
</TableRow>
|
>
|
||||||
</TableHead>
|
{(provided) => (
|
||||||
|
<TableBody
|
||||||
|
ref={provided.innerRef}
|
||||||
|
{...provided.droppableProps}
|
||||||
|
>
|
||||||
|
{linkedItems.map((item, index) => (
|
||||||
|
<Draggable
|
||||||
|
key={item.id}
|
||||||
|
draggableId={"q" + String(item.id)}
|
||||||
|
index={index}
|
||||||
|
isDragDisabled={type !== "edit" || !dragAllowed}
|
||||||
|
>
|
||||||
|
{(provided) => (
|
||||||
|
<TableRow
|
||||||
|
sx={{
|
||||||
|
cursor:
|
||||||
|
childResource === "article"
|
||||||
|
? "pointer"
|
||||||
|
: "default",
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
if (childResource === "article") {
|
||||||
|
setArticleModalOpenAction(true);
|
||||||
|
setArticleIdAction(item.id);
|
||||||
|
}
|
||||||
|
if (childResource === "station") {
|
||||||
|
setStationModalOpenAction(true);
|
||||||
|
setStationIdAction(item.id);
|
||||||
|
setRouteIdAction(Number(parentId));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
ref={provided.innerRef}
|
||||||
|
{...provided.draggableProps}
|
||||||
|
{...provided.dragHandleProps}
|
||||||
|
hover
|
||||||
|
>
|
||||||
|
{type === "edit" && dragAllowed && (
|
||||||
|
<TableCell {...provided.dragHandleProps}>
|
||||||
|
<IconButton size="small">
|
||||||
|
<DragIndicatorIcon />
|
||||||
|
</IconButton>
|
||||||
|
</TableCell>
|
||||||
|
)}
|
||||||
|
<TableCell key={String(item.id)}>
|
||||||
|
{index + 1}
|
||||||
|
</TableCell>
|
||||||
|
{fields.map((field, index) => (
|
||||||
|
<TableCell
|
||||||
|
key={String(field.data) + String(index)}
|
||||||
|
>
|
||||||
|
{field.render
|
||||||
|
? field.render(item[field.data])
|
||||||
|
: item[field.data]}
|
||||||
|
</TableCell>
|
||||||
|
))}
|
||||||
|
|
||||||
<Droppable
|
{type === "edit" && (
|
||||||
droppableId="droppable"
|
<TableCell>
|
||||||
isDropDisabled={type !== "edit" || !dragAllowed}
|
<Button
|
||||||
>
|
variant="outlined"
|
||||||
{(provided) => (
|
color="error"
|
||||||
<TableBody
|
size="small"
|
||||||
ref={provided.innerRef}
|
onClick={(e) => {
|
||||||
{...provided.droppableProps}
|
e.stopPropagation();
|
||||||
>
|
deleteItem(item.id);
|
||||||
{linkedItems.map((item, index) => (
|
|
||||||
<Draggable
|
|
||||||
key={item.id}
|
|
||||||
draggableId={"q" + String(item.id)}
|
|
||||||
index={index}
|
|
||||||
isDragDisabled={type !== "edit" || !dragAllowed}
|
|
||||||
>
|
|
||||||
{(provided) => (
|
|
||||||
<TableRow
|
|
||||||
sx={{
|
|
||||||
cursor:
|
|
||||||
childResource === "article"
|
|
||||||
? "pointer"
|
|
||||||
: "default",
|
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
|
||||||
if (childResource === "article") {
|
|
||||||
setArticleModalOpenAction(true);
|
|
||||||
setArticleIdAction(item.id);
|
|
||||||
}
|
|
||||||
if (childResource === "station") {
|
|
||||||
setStationModalOpenAction(true);
|
|
||||||
setStationIdAction(item.id);
|
|
||||||
setRouteIdAction(Number(parentId));
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
ref={provided.innerRef}
|
|
||||||
{...provided.draggableProps}
|
|
||||||
{...provided.dragHandleProps}
|
|
||||||
hover
|
|
||||||
>
|
>
|
||||||
{type === "edit" && dragAllowed && (
|
Отвязать
|
||||||
<TableCell {...provided.dragHandleProps}>
|
</Button>
|
||||||
<IconButton size="small">
|
</TableCell>
|
||||||
<DragIndicatorIcon />
|
)}
|
||||||
</IconButton>
|
</TableRow>
|
||||||
</TableCell>
|
)}
|
||||||
)}
|
</Draggable>
|
||||||
<TableCell key={String(item.id)}>
|
))}
|
||||||
{index + 1}
|
|
||||||
</TableCell>
|
|
||||||
{fields.map((field, index) => (
|
|
||||||
<TableCell
|
|
||||||
key={String(field.data) + String(index)}
|
|
||||||
>
|
|
||||||
{field.render
|
|
||||||
? field.render(item[field.data])
|
|
||||||
: item[field.data]}
|
|
||||||
</TableCell>
|
|
||||||
))}
|
|
||||||
|
|
||||||
{type === "edit" && (
|
{provided.placeholder}
|
||||||
<TableCell>
|
</TableBody>
|
||||||
<Button
|
|
||||||
variant="outlined"
|
|
||||||
color="error"
|
|
||||||
size="small"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
deleteItem(item.id);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Отвязать
|
|
||||||
</Button>
|
|
||||||
</TableCell>
|
|
||||||
)}
|
|
||||||
</TableRow>
|
|
||||||
)}
|
|
||||||
</Draggable>
|
|
||||||
))}
|
|
||||||
|
|
||||||
{provided.placeholder}
|
|
||||||
</TableBody>
|
|
||||||
)}
|
|
||||||
</Droppable>
|
|
||||||
</Table>
|
|
||||||
</TableContainer>
|
|
||||||
</DragDropContext>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{linkedItems.length === 0 && !isLoading && (
|
|
||||||
<Typography color="textSecondary" textAlign="center" py={2}>
|
|
||||||
{title} не найдены
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{type === "edit" && (
|
|
||||||
<Stack gap={2} mt={2}>
|
|
||||||
<Typography variant="subtitle1">Добавить {title}</Typography>
|
|
||||||
<Autocomplete
|
|
||||||
fullWidth
|
|
||||||
value={
|
|
||||||
availableItems?.find(
|
|
||||||
(item) => item.id === selectedItemId
|
|
||||||
) || null
|
|
||||||
}
|
|
||||||
onChange={(_, newValue) =>
|
|
||||||
setSelectedItemId(newValue?.id || null)
|
|
||||||
}
|
|
||||||
options={availableItems}
|
|
||||||
getOptionLabel={(item) => String(item[fields[0].data])}
|
|
||||||
renderInput={(params) => (
|
|
||||||
<TextField
|
|
||||||
{...params}
|
|
||||||
label={`Выберите ${title}`}
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
isOptionEqualToValue={(option, value) =>
|
|
||||||
option.id === value?.id
|
|
||||||
}
|
|
||||||
filterOptions={(options, { inputValue }) => {
|
|
||||||
const searchWords = inputValue
|
|
||||||
.toLowerCase()
|
|
||||||
.split(" ")
|
|
||||||
.filter((word) => word.length > 0);
|
|
||||||
return options.filter((option) => {
|
|
||||||
const optionWords = String(option[fields[0].data])
|
|
||||||
.toLowerCase()
|
|
||||||
.split(" ");
|
|
||||||
return searchWords.every((searchWord) =>
|
|
||||||
optionWords.some((word) => word.startsWith(searchWord))
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
renderOption={(props, option) => (
|
|
||||||
<li {...props} key={option.id}>
|
|
||||||
{String(option[fields[0].data])}
|
|
||||||
</li>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* {childResource === "article" && (
|
|
||||||
<FormControl fullWidth>
|
|
||||||
<TextField
|
|
||||||
type="number"
|
|
||||||
label="Позиция добавляемой статьи"
|
|
||||||
name="page_num"
|
|
||||||
value={pageNum}
|
|
||||||
onChange={(e) => {
|
|
||||||
const newValue = Number(e.target.value);
|
|
||||||
const minValue = linkedItems.length + 1;
|
|
||||||
setPageNum(newValue < minValue ? minValue : newValue);
|
|
||||||
}}
|
|
||||||
fullWidth
|
|
||||||
InputLabelProps={{ shrink: true }}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
)} */}
|
|
||||||
|
|
||||||
{childResource === "media" && (
|
|
||||||
<FormControl fullWidth>
|
|
||||||
<TextField
|
|
||||||
type="number"
|
|
||||||
label="Порядок отображения медиа"
|
|
||||||
value={mediaOrder}
|
|
||||||
onChange={(e) => {
|
|
||||||
const newValue = Number(e.target.value);
|
|
||||||
const maxValue = linkedItems.length + 1;
|
|
||||||
const value = Math.max(1, Math.min(newValue, maxValue));
|
|
||||||
setMediaOrder(value);
|
|
||||||
}}
|
|
||||||
fullWidth
|
|
||||||
InputLabelProps={{ shrink: true }}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
)}
|
)}
|
||||||
|
</Droppable>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
</DragDropContext>
|
||||||
|
)}
|
||||||
|
|
||||||
<Button
|
{linkedItems.length === 0 && !isLoading && (
|
||||||
variant="contained"
|
<Typography color="textSecondary" textAlign="center" py={2}>
|
||||||
onClick={linkItem}
|
{title} не найдены
|
||||||
disabled={!selectedItemId}
|
</Typography>
|
||||||
sx={{ alignSelf: "flex-start" }}
|
)}
|
||||||
>
|
|
||||||
Добавить
|
{type === "edit" && !disableCreation && (
|
||||||
</Button>
|
<Stack gap={2} mt={2}>
|
||||||
{childResource == "station" && (
|
<Typography variant="subtitle1">Добавить {title}</Typography>
|
||||||
<TextField
|
<Autocomplete
|
||||||
type="text"
|
fullWidth
|
||||||
label="Позиция добавляемой остановки к маршруту"
|
value={
|
||||||
value={position}
|
availableItems?.find(
|
||||||
onChange={(e) => {
|
(item) => item.id === selectedItemId
|
||||||
const newValue = Number(e.target.value);
|
) || null
|
||||||
setPosition(
|
}
|
||||||
newValue > linkedItems.length + 1
|
onChange={(_, newValue) =>
|
||||||
? linkedItems.length + 1
|
setSelectedItemId(newValue?.id || null)
|
||||||
: newValue
|
}
|
||||||
);
|
options={availableItems}
|
||||||
}}
|
getOptionLabel={(item) => String(item[fields[0].data])}
|
||||||
></TextField>
|
renderInput={(params) => (
|
||||||
)}
|
<TextField
|
||||||
</Stack>
|
{...params}
|
||||||
|
label={`Выберите ${title}`}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
isOptionEqualToValue={(option, value) =>
|
||||||
</AccordionDetails>
|
option.id === value?.id
|
||||||
</Accordion>
|
}
|
||||||
{!dontRecurse &&
|
filterOptions={(options, { inputValue }) => {
|
||||||
<>
|
const searchWords = inputValue
|
||||||
<ArticleEditModal />
|
.toLowerCase()
|
||||||
<StationEditModal />
|
.split(" ")
|
||||||
</>
|
.filter((word) => word.length > 0);
|
||||||
}
|
return options.filter((option) => {
|
||||||
|
const optionWords = String(option[fields[0].data])
|
||||||
|
.toLowerCase()
|
||||||
|
.split(" ");
|
||||||
|
return searchWords.every((searchWord) =>
|
||||||
|
optionWords.some((word) => word.startsWith(searchWord))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
renderOption={(props, option) => (
|
||||||
|
<li {...props} key={option.id}>
|
||||||
|
{String(option[fields[0].data])}
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* {childResource === "article" && (
|
||||||
|
<FormControl fullWidth>
|
||||||
|
<TextField
|
||||||
|
type="number"
|
||||||
|
label="Позиция добавляемой статьи"
|
||||||
|
name="page_num"
|
||||||
|
value={pageNum}
|
||||||
|
onChange={(e) => {
|
||||||
|
const newValue = Number(e.target.value);
|
||||||
|
const minValue = linkedItems.length + 1;
|
||||||
|
setPageNum(newValue < minValue ? minValue : newValue);
|
||||||
|
}}
|
||||||
|
fullWidth
|
||||||
|
InputLabelProps={{ shrink: true }}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)} */}
|
||||||
|
|
||||||
|
{childResource === "media" && (
|
||||||
|
<FormControl fullWidth>
|
||||||
|
<TextField
|
||||||
|
type="number"
|
||||||
|
label="Порядок отображения медиа"
|
||||||
|
value={mediaOrder}
|
||||||
|
onChange={(e) => {
|
||||||
|
const newValue = Number(e.target.value);
|
||||||
|
const maxValue = linkedItems.length + 1;
|
||||||
|
const value = Math.max(1, Math.min(newValue, maxValue));
|
||||||
|
setMediaOrder(value);
|
||||||
|
}}
|
||||||
|
fullWidth
|
||||||
|
slotProps={{inputLabel: {shrink: true}}}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={linkItem}
|
||||||
|
disabled={!selectedItemId}
|
||||||
|
sx={{ alignSelf: "flex-start" }}
|
||||||
|
>
|
||||||
|
Добавить
|
||||||
|
</Button>
|
||||||
|
{childResource == "station" && (
|
||||||
|
<TextField
|
||||||
|
type="text"
|
||||||
|
label="Позиция добавляемой остановки к маршруту"
|
||||||
|
value={position}
|
||||||
|
onChange={(e) => {
|
||||||
|
const newValue = Number(e.target.value);
|
||||||
|
setPosition(
|
||||||
|
newValue > linkedItems.length + 1
|
||||||
|
? linkedItems.length + 1
|
||||||
|
: newValue
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
></TextField>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
5
src/components/index.ts
Normal file
5
src/components/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export * from './AdminOnly'
|
||||||
|
export * from './CreateSightArticle'
|
||||||
|
export * from './CustomDataGrid'
|
||||||
|
export * from './LinkedItems'
|
||||||
|
export * from './MarkdownEditor'
|
@ -4,17 +4,16 @@ import { ReactPhotoSphereViewer } from "react-photo-sphere-viewer";
|
|||||||
import { ModelViewer } from "./ModelViewer";
|
import { ModelViewer } from "./ModelViewer";
|
||||||
|
|
||||||
export interface MediaData {
|
export interface MediaData {
|
||||||
filename?: string;
|
|
||||||
id: string | number;
|
id: string | number;
|
||||||
media_name?: string;
|
|
||||||
media_type: number;
|
media_type: number;
|
||||||
|
filename?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MediaView({media} : Readonly<{media?: MediaData}>) {
|
export function MediaView({media} : Readonly<{media?: MediaData}>) {
|
||||||
const token = localStorage.getItem(TOKEN_KEY);
|
const token = localStorage.getItem(TOKEN_KEY);
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{maxHeight: "50vh", height: "100%", width: "100%", display: "flex", justifyContent: "center"}}
|
sx={{maxHeight: "300px", width: "100%", display: "flex", justifyContent: "center"}}
|
||||||
>
|
>
|
||||||
{media?.media_type === 1 && (
|
{media?.media_type === 1 && (
|
||||||
<img
|
<img
|
||||||
@ -23,10 +22,10 @@ export function MediaView({media} : Readonly<{media?: MediaData}>) {
|
|||||||
}/download?token=${token}`}
|
}/download?token=${token}`}
|
||||||
alt={media?.filename}
|
alt={media?.filename}
|
||||||
style={{
|
style={{
|
||||||
maxWidth: "100%",
|
maxWidth: "100%",
|
||||||
height: "100%",
|
height: "auto",
|
||||||
objectFit: "contain",
|
objectFit: "contain",
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -37,9 +36,10 @@ export function MediaView({media} : Readonly<{media?: MediaData}>) {
|
|||||||
media?.id
|
media?.id
|
||||||
}/download?token=${token}`}
|
}/download?token=${token}`}
|
||||||
style={{
|
style={{
|
||||||
|
maxWidth: "100%",
|
||||||
objectFit: "contain",
|
height: "100%",
|
||||||
borderRadius: 30,
|
objectFit: "contain",
|
||||||
|
borderRadius: 30,
|
||||||
}}
|
}}
|
||||||
controls
|
controls
|
||||||
autoPlay
|
autoPlay
|
||||||
|
@ -9,7 +9,6 @@ import "easymde/dist/easymde.min.css";
|
|||||||
import { LanguageSelector } from "@ui";
|
import { LanguageSelector } from "@ui";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { EVERY_LANGUAGE, Languages, languageStore, META_LANGUAGE } from "@stores";
|
import { EVERY_LANGUAGE, Languages, languageStore, META_LANGUAGE } from "@stores";
|
||||||
import { axiosInstance } from "@/providers/data";
|
|
||||||
|
|
||||||
const MemoizedSimpleMDE = React.memo(MarkdownEditor);
|
const MemoizedSimpleMDE = React.memo(MarkdownEditor);
|
||||||
|
|
||||||
@ -33,15 +32,14 @@ export const ArticleCreate = observer(() => {
|
|||||||
refineCoreProps: {
|
refineCoreProps: {
|
||||||
resource: "article",
|
resource: "article",
|
||||||
...META_LANGUAGE(language)
|
...META_LANGUAGE(language)
|
||||||
},
|
}
|
||||||
warnWhenUnsavedChanges: false
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Следим за изменениями в полях body и heading
|
// Следим за изменениями в полях body и heading
|
||||||
const bodyContent = watch("body");
|
const bodyContent = watch("body");
|
||||||
const headingContent = watch("heading");
|
const headingContent = watch("heading");
|
||||||
|
|
||||||
function updateTranslations() {
|
function updateTranslations(update: boolean = true) {
|
||||||
const newArticleData = {
|
const newArticleData = {
|
||||||
...articleData,
|
...articleData,
|
||||||
heading: {
|
heading: {
|
||||||
@ -53,13 +51,12 @@ export const ArticleCreate = observer(() => {
|
|||||||
[language]: watch("body") ?? "",
|
[language]: watch("body") ?? "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setArticleData(newArticleData);
|
if(update) setArticleData(newArticleData);
|
||||||
return newArticleData;
|
return newArticleData;
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleFormSubmit = handleSubmit((values: FieldValues) => {
|
const handleFormSubmit = handleSubmit((values) => {
|
||||||
const newTranslations = updateTranslations();
|
const newTranslations = updateTranslations(false);
|
||||||
console.log(newTranslations);
|
|
||||||
return onFinish({
|
return onFinish({
|
||||||
translations: newTranslations
|
translations: newTranslations
|
||||||
});
|
});
|
||||||
@ -80,7 +77,6 @@ export const ArticleCreate = observer(() => {
|
|||||||
const [preview, setPreview] = useState("");
|
const [preview, setPreview] = useState("");
|
||||||
const [headingPreview, setHeadingPreview] = useState("");
|
const [headingPreview, setHeadingPreview] = useState("");
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setPreview(bodyContent ?? "");
|
setPreview(bodyContent ?? "");
|
||||||
}, [bodyContent]);
|
}, [bodyContent]);
|
||||||
@ -89,17 +85,13 @@ export const ArticleCreate = observer(() => {
|
|||||||
setHeadingPreview(headingContent ?? "");
|
setHeadingPreview(headingContent ?? "");
|
||||||
}, [headingContent]);
|
}, [headingContent]);
|
||||||
|
|
||||||
const simpleMDEOptions = React.useMemo(
|
const simpleMDEOptions = React.useMemo(() => ({
|
||||||
() => ({
|
placeholder: "Введите контент в формате Markdown...",
|
||||||
placeholder: "Введите контент в формате Markdown...",
|
spellChecker: false,
|
||||||
spellChecker: false,
|
}), []);
|
||||||
}),
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Create isLoading={formLoading} saveButtonProps={{
|
<Create isLoading={formLoading} saveButtonProps={{
|
||||||
//...saveButtonProps,
|
|
||||||
onClick: handleFormSubmit
|
onClick: handleFormSubmit
|
||||||
}}>
|
}}>
|
||||||
<Box sx={{ display: "flex", flex: 1, gap: 2 }}>
|
<Box sx={{ display: "flex", flex: 1, gap: 2 }}>
|
||||||
@ -119,7 +111,7 @@ export const ArticleCreate = observer(() => {
|
|||||||
helperText={(errors as any)?.heading?.message}
|
helperText={(errors as any)?.heading?.message}
|
||||||
margin="normal"
|
margin="normal"
|
||||||
fullWidth
|
fullWidth
|
||||||
InputLabelProps={{ shrink: true }}
|
slotProps={{inputLabel: {shrink: true}}}
|
||||||
type="text"
|
type="text"
|
||||||
label="Заголовок *"
|
label="Заголовок *"
|
||||||
name="heading"
|
name="heading"
|
||||||
@ -128,7 +120,7 @@ export const ArticleCreate = observer(() => {
|
|||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="body"
|
name="body"
|
||||||
rules={{ required: "Это поле является обязательным" }}
|
//rules={{ required: "Это поле является обязательным" }}
|
||||||
defaultValue=""
|
defaultValue=""
|
||||||
render={({ field: { onChange, value } }) => (
|
render={({ field: { onChange, value } }) => (
|
||||||
<MemoizedSimpleMDE
|
<MemoizedSimpleMDE
|
||||||
|
@ -7,10 +7,8 @@ import React, { useState, useEffect, useMemo } from "react";
|
|||||||
import ReactMarkdown from "react-markdown";
|
import ReactMarkdown from "react-markdown";
|
||||||
import { useList } from "@refinedev/core";
|
import { useList } from "@refinedev/core";
|
||||||
|
|
||||||
import { MarkdownEditor } from "../../components/MarkdownEditor";
|
import { MarkdownEditor, LinkedItems } from "@components";
|
||||||
import { LinkedItems } from "../../components/LinkedItems";
|
|
||||||
import { MediaItem, mediaFields } from "./types";
|
import { MediaItem, mediaFields } from "./types";
|
||||||
import { TOKEN_KEY } from "@providers";
|
|
||||||
import "easymde/dist/easymde.min.css";
|
import "easymde/dist/easymde.min.css";
|
||||||
import { EVERY_LANGUAGE, Languages, languageStore, META_LANGUAGE } from "@stores";
|
import { EVERY_LANGUAGE, Languages, languageStore, META_LANGUAGE } from "@stores";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
@ -58,7 +56,7 @@ export const ArticleEdit = observer(() => {
|
|||||||
setPreview(articleData.body[language] ?? "");
|
setPreview(articleData.body[language] ?? "");
|
||||||
}, [language, articleData, setValue]);
|
}, [language, articleData, setValue]);
|
||||||
|
|
||||||
function updateTranslations() {
|
function updateTranslations(update: boolean = true) {
|
||||||
const newArticleData = {
|
const newArticleData = {
|
||||||
...articleData,
|
...articleData,
|
||||||
heading: {
|
heading: {
|
||||||
@ -70,7 +68,7 @@ export const ArticleEdit = observer(() => {
|
|||||||
[language]: watch("body") ?? "",
|
[language]: watch("body") ?? "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setArticleData(newArticleData);
|
if(update) setArticleData(newArticleData);
|
||||||
return newArticleData;
|
return newArticleData;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,7 +78,7 @@ export const ArticleEdit = observer(() => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleFormSubmit = handleSubmit((values: FieldValues) => {
|
const handleFormSubmit = handleSubmit((values: FieldValues) => {
|
||||||
const newTranslations = updateTranslations();
|
const newTranslations = updateTranslations(false);
|
||||||
console.log(newTranslations);
|
console.log(newTranslations);
|
||||||
return onFinish({
|
return onFinish({
|
||||||
translations: newTranslations
|
translations: newTranslations
|
||||||
@ -113,7 +111,6 @@ export const ArticleEdit = observer(() => {
|
|||||||
>
|
>
|
||||||
<Box sx={{ display: "flex", gap: 2 }}>
|
<Box sx={{ display: "flex", gap: 2 }}>
|
||||||
{/* Форма редактирования */}
|
{/* Форма редактирования */}
|
||||||
{/* Форма создания */}
|
|
||||||
<Box sx={{ display: "flex", flex: 1, flexDirection: "column", gap: 2 }}>
|
<Box sx={{ display: "flex", flex: 1, flexDirection: "column", gap: 2 }}>
|
||||||
|
|
||||||
<LanguageSelector action={handleLanguageChange} />
|
<LanguageSelector action={handleLanguageChange} />
|
||||||
@ -130,7 +127,7 @@ export const ArticleEdit = observer(() => {
|
|||||||
helperText={errors?.heading?.message as string}
|
helperText={errors?.heading?.message as string}
|
||||||
margin="normal"
|
margin="normal"
|
||||||
fullWidth
|
fullWidth
|
||||||
InputLabelProps={{ shrink: true }}
|
slotProps={{inputLabel: {shrink: true}}}
|
||||||
type="text"
|
type="text"
|
||||||
label="Заголовок *"
|
label="Заголовок *"
|
||||||
name="heading"
|
name="heading"
|
||||||
@ -139,7 +136,7 @@ export const ArticleEdit = observer(() => {
|
|||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="body"
|
name="body"
|
||||||
rules={{ required: "Это поле является обязательным" }}
|
//rules={{ required: "Это поле является обязательным" }}
|
||||||
defaultValue=""
|
defaultValue=""
|
||||||
render={({ field: { onChange, value } }) => (
|
render={({ field: { onChange, value } }) => (
|
||||||
<MemoizedSimpleMDE
|
<MemoizedSimpleMDE
|
||||||
|
@ -7,8 +7,7 @@ import {
|
|||||||
ShowButton,
|
ShowButton,
|
||||||
useDataGrid,
|
useDataGrid,
|
||||||
} from "@refinedev/mui";
|
} from "@refinedev/mui";
|
||||||
import React, { useEffect } from "react";
|
import React from "react";
|
||||||
import { useDelete } from "@refinedev/core";
|
|
||||||
|
|
||||||
import { localeText } from "../../locales/ru/localeText";
|
import { localeText } from "../../locales/ru/localeText";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
|
@ -62,10 +62,10 @@ export const CarrierCreate = observer(() => {
|
|||||||
value={
|
value={
|
||||||
cityAutocompleteProps.options.find(
|
cityAutocompleteProps.options.find(
|
||||||
(option) => option.id === field.value
|
(option) => option.id === field.value
|
||||||
) || null
|
) ?? null
|
||||||
}
|
}
|
||||||
onChange={(_, value) => {
|
onChange={(_, value) => {
|
||||||
field.onChange(value?.id || "");
|
field.onChange(value?.id ?? "");
|
||||||
}}
|
}}
|
||||||
getOptionLabel={(item) => {
|
getOptionLabel={(item) => {
|
||||||
return item ? item.name : "";
|
return item ? item.name : "";
|
||||||
@ -101,7 +101,7 @@ export const CarrierCreate = observer(() => {
|
|||||||
helperText={(errors as any)?.full_name?.message}
|
helperText={(errors as any)?.full_name?.message}
|
||||||
margin="normal"
|
margin="normal"
|
||||||
fullWidth
|
fullWidth
|
||||||
InputLabelProps={{ shrink: true }}
|
slotProps={{inputLabel: {shrink: true}}}
|
||||||
type="text"
|
type="text"
|
||||||
label={"Полное имя *"}
|
label={"Полное имя *"}
|
||||||
name="full_name"
|
name="full_name"
|
||||||
@ -109,82 +109,89 @@ export const CarrierCreate = observer(() => {
|
|||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
{...register("short_name", {
|
{...register("short_name", {
|
||||||
required: "Это поле является обязательным",
|
//required: "Это поле является обязательным",
|
||||||
})}
|
})}
|
||||||
error={!!(errors as any)?.short_name}
|
error={!!(errors as any)?.short_name}
|
||||||
helperText={(errors as any)?.short_name?.message}
|
helperText={(errors as any)?.short_name?.message}
|
||||||
margin="normal"
|
margin="normal"
|
||||||
fullWidth
|
fullWidth
|
||||||
InputLabelProps={{ shrink: true }}
|
slotProps={{inputLabel: {shrink: true}}}
|
||||||
type="text"
|
type="text"
|
||||||
label={"Короткое имя *"}
|
label={"Короткое имя"}
|
||||||
name="short_name"
|
name="short_name"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextField
|
<Box component="form"
|
||||||
{...register("main_color", {
|
sx={{ display: "flex" }}
|
||||||
// required: 'Это поле является обязательным',
|
autoComplete="off"
|
||||||
})}
|
>
|
||||||
error={!!(errors as any)?.main_color}
|
<TextField
|
||||||
helperText={(errors as any)?.main_color?.message}
|
{...register("main_color", {
|
||||||
margin="normal"
|
// required: 'Это поле является обязательным',
|
||||||
fullWidth
|
})}
|
||||||
InputLabelProps={{ shrink: true }}
|
error={!!(errors as any)?.main_color}
|
||||||
type="color"
|
helperText={(errors as any)?.main_color?.message}
|
||||||
label={"Основной цвет"}
|
margin="normal"
|
||||||
name="main_color"
|
fullWidth
|
||||||
sx={{
|
slotProps={{inputLabel: {shrink: true}}}
|
||||||
"& input": {
|
type="color"
|
||||||
height: "50px",
|
label={"Основной цвет"}
|
||||||
paddingBlock: "14px",
|
name="main_color"
|
||||||
paddingInline: "14px",
|
sx={{
|
||||||
cursor: "pointer",
|
"& input": {
|
||||||
},
|
height: "50px",
|
||||||
}}
|
paddingBlock: "14px",
|
||||||
/>
|
paddingInline: "14px",
|
||||||
|
cursor: "pointer",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
{...register("left_color", {
|
{...register("left_color", {
|
||||||
// required: 'Это поле является обязательным',
|
// required: 'Это поле является обязательным',
|
||||||
})}
|
})}
|
||||||
error={!!(errors as any)?.left_color}
|
error={!!(errors as any)?.left_color}
|
||||||
helperText={(errors as any)?.left_color?.message}
|
helperText={(errors as any)?.left_color?.message}
|
||||||
margin="normal"
|
margin="normal"
|
||||||
fullWidth
|
fullWidth
|
||||||
InputLabelProps={{ shrink: true }}
|
slotProps={{inputLabel: {shrink: true}}}
|
||||||
type="color"
|
type="color"
|
||||||
label={"Цвет левого виджета"}
|
label={"Цвет левого виджета"}
|
||||||
name="left_color"
|
name="left_color"
|
||||||
sx={{
|
sx={{
|
||||||
"& input": {
|
marginLeft: "16px",
|
||||||
height: "50px",
|
marginRight: "16px",
|
||||||
paddingBlock: "14px",
|
"& input": {
|
||||||
paddingInline: "14px",
|
height: "50px",
|
||||||
cursor: "pointer",
|
paddingBlock: "14px",
|
||||||
},
|
paddingInline: "14px",
|
||||||
}}
|
cursor: "pointer",
|
||||||
/>
|
},
|
||||||
<TextField
|
}}
|
||||||
{...register("right_color", {
|
/>
|
||||||
// required: 'Это поле является обязательным',
|
<TextField
|
||||||
})}
|
{...register("right_color", {
|
||||||
error={!!(errors as any)?.right_color}
|
// required: 'Это поле является обязательным',
|
||||||
helperText={(errors as any)?.right_color?.message}
|
})}
|
||||||
margin="normal"
|
error={!!(errors as any)?.right_color}
|
||||||
fullWidth
|
helperText={(errors as any)?.right_color?.message}
|
||||||
InputLabelProps={{ shrink: true }}
|
margin="normal"
|
||||||
type="color"
|
fullWidth
|
||||||
label={"Цвет правого виджета"}
|
slotProps={{inputLabel: {shrink: true}}}
|
||||||
name="right_color"
|
type="color"
|
||||||
sx={{
|
label={"Цвет правого виджета"}
|
||||||
"& input": {
|
name="right_color"
|
||||||
height: "50px",
|
sx={{
|
||||||
paddingBlock: "14px",
|
"& input": {
|
||||||
paddingInline: "14px",
|
height: "50px",
|
||||||
cursor: "pointer",
|
paddingBlock: "14px",
|
||||||
},
|
paddingInline: "14px",
|
||||||
}}
|
cursor: "pointer",
|
||||||
/>
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
{...register("slogan", {
|
{...register("slogan", {
|
||||||
@ -194,7 +201,7 @@ export const CarrierCreate = observer(() => {
|
|||||||
helperText={(errors as any)?.slogan?.message}
|
helperText={(errors as any)?.slogan?.message}
|
||||||
margin="normal"
|
margin="normal"
|
||||||
fullWidth
|
fullWidth
|
||||||
InputLabelProps={{ shrink: true }}
|
slotProps={{inputLabel: {shrink: true}}}
|
||||||
type="text"
|
type="text"
|
||||||
label={"Слоган"}
|
label={"Слоган"}
|
||||||
name="slogan"
|
name="slogan"
|
||||||
@ -211,10 +218,10 @@ export const CarrierCreate = observer(() => {
|
|||||||
value={
|
value={
|
||||||
mediaAutocompleteProps.options.find(
|
mediaAutocompleteProps.options.find(
|
||||||
(option) => option.id === field.value
|
(option) => option.id === field.value
|
||||||
) || null
|
) ?? null
|
||||||
}
|
}
|
||||||
onChange={(_, value) => {
|
onChange={(_, value) => {
|
||||||
field.onChange(value?.id || "");
|
field.onChange(value?.id ?? "");
|
||||||
}}
|
}}
|
||||||
getOptionLabel={(item) => {
|
getOptionLabel={(item) => {
|
||||||
return item ? item.media_name : "";
|
return item ? item.media_name : "";
|
||||||
|
@ -2,7 +2,7 @@ import { Autocomplete, Box, TextField } from "@mui/material";
|
|||||||
import { Edit, useAutocomplete } from "@refinedev/mui";
|
import { Edit, useAutocomplete } from "@refinedev/mui";
|
||||||
import { useForm } from "@refinedev/react-hook-form";
|
import { useForm } from "@refinedev/react-hook-form";
|
||||||
import { languageStore, META_LANGUAGE } from "@stores";
|
import { languageStore, META_LANGUAGE } from "@stores";
|
||||||
import { LanguageSelector, MediaData, MediaView } from "@ui";
|
import { LanguageSelector, MediaView } from "@ui";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { Controller } from "react-hook-form";
|
import { Controller } from "react-hook-form";
|
||||||
|
|
||||||
@ -42,6 +42,7 @@ export const CarrierEdit = observer(() => {
|
|||||||
...META_LANGUAGE(language)
|
...META_LANGUAGE(language)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Edit saveButtonProps={saveButtonProps}>
|
<Edit saveButtonProps={saveButtonProps}>
|
||||||
<Box
|
<Box
|
||||||
@ -61,10 +62,10 @@ export const CarrierEdit = observer(() => {
|
|||||||
value={
|
value={
|
||||||
cityAutocompleteProps.options.find(
|
cityAutocompleteProps.options.find(
|
||||||
(option) => option.id === field.value
|
(option) => option.id === field.value
|
||||||
) || null
|
) ?? null
|
||||||
}
|
}
|
||||||
onChange={(_, value) => {
|
onChange={(_, value) => {
|
||||||
field.onChange(value?.id || "");
|
field.onChange(value?.id ?? "");
|
||||||
}}
|
}}
|
||||||
getOptionLabel={(item) => {
|
getOptionLabel={(item) => {
|
||||||
return item ? item.name : "";
|
return item ? item.name : "";
|
||||||
@ -100,7 +101,7 @@ export const CarrierEdit = observer(() => {
|
|||||||
helperText={(errors as any)?.full_name?.message}
|
helperText={(errors as any)?.full_name?.message}
|
||||||
margin="normal"
|
margin="normal"
|
||||||
fullWidth
|
fullWidth
|
||||||
InputLabelProps={{ shrink: true }}
|
slotProps={{inputLabel: {shrink: true}}}
|
||||||
type="text"
|
type="text"
|
||||||
label={"Полное имя *"}
|
label={"Полное имя *"}
|
||||||
name="full_name"
|
name="full_name"
|
||||||
@ -108,21 +109,21 @@ export const CarrierEdit = observer(() => {
|
|||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
{...register("short_name", {
|
{...register("short_name", {
|
||||||
required: "Это поле является обязательным",
|
//required: "Это поле является обязательным",
|
||||||
})}
|
})}
|
||||||
error={!!(errors as any)?.short_name}
|
error={!!(errors as any)?.short_name}
|
||||||
helperText={(errors as any)?.short_name?.message}
|
helperText={(errors as any)?.short_name?.message}
|
||||||
margin="normal"
|
margin="normal"
|
||||||
fullWidth
|
fullWidth
|
||||||
InputLabelProps={{ shrink: true }}
|
slotProps={{inputLabel: {shrink: true}}}
|
||||||
type="text"
|
type="text"
|
||||||
label={"Короткое имя *"}
|
label={"Короткое имя"}
|
||||||
name="short_name"
|
name="short_name"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Box component="form"
|
<Box component="form"
|
||||||
sx={{ display: "flex" }}
|
sx={{ display: "flex" }}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
>
|
>
|
||||||
<TextField
|
<TextField
|
||||||
{...register("main_color", {
|
{...register("main_color", {
|
||||||
@ -132,7 +133,7 @@ export const CarrierEdit = observer(() => {
|
|||||||
helperText={(errors as any)?.main_color?.message}
|
helperText={(errors as any)?.main_color?.message}
|
||||||
margin="normal"
|
margin="normal"
|
||||||
fullWidth
|
fullWidth
|
||||||
InputLabelProps={{ shrink: true }}
|
slotProps={{inputLabel: {shrink: true}}}
|
||||||
type="color"
|
type="color"
|
||||||
label={"Основной цвет"}
|
label={"Основной цвет"}
|
||||||
name="main_color"
|
name="main_color"
|
||||||
@ -154,7 +155,7 @@ export const CarrierEdit = observer(() => {
|
|||||||
helperText={(errors as any)?.left_color?.message}
|
helperText={(errors as any)?.left_color?.message}
|
||||||
margin="normal"
|
margin="normal"
|
||||||
fullWidth
|
fullWidth
|
||||||
InputLabelProps={{ shrink: true }}
|
slotProps={{inputLabel: {shrink: true}}}
|
||||||
type="color"
|
type="color"
|
||||||
label={"Цвет левого виджета"}
|
label={"Цвет левого виджета"}
|
||||||
name="left_color"
|
name="left_color"
|
||||||
@ -177,7 +178,7 @@ export const CarrierEdit = observer(() => {
|
|||||||
helperText={(errors as any)?.right_color?.message}
|
helperText={(errors as any)?.right_color?.message}
|
||||||
margin="normal"
|
margin="normal"
|
||||||
fullWidth
|
fullWidth
|
||||||
InputLabelProps={{ shrink: true }}
|
slotProps={{inputLabel: {shrink: true}}}
|
||||||
type="color"
|
type="color"
|
||||||
label={"Цвет правого виджета"}
|
label={"Цвет правого виджета"}
|
||||||
name="right_color"
|
name="right_color"
|
||||||
@ -200,7 +201,7 @@ export const CarrierEdit = observer(() => {
|
|||||||
helperText={(errors as any)?.slogan?.message}
|
helperText={(errors as any)?.slogan?.message}
|
||||||
margin="normal"
|
margin="normal"
|
||||||
fullWidth
|
fullWidth
|
||||||
InputLabelProps={{ shrink: true }}
|
slotProps={{inputLabel: {shrink: true}}}
|
||||||
type="text"
|
type="text"
|
||||||
label={"Слоган"}
|
label={"Слоган"}
|
||||||
name="slogan"
|
name="slogan"
|
||||||
@ -217,10 +218,10 @@ export const CarrierEdit = observer(() => {
|
|||||||
value={
|
value={
|
||||||
mediaAutocompleteProps.options.find(
|
mediaAutocompleteProps.options.find(
|
||||||
(option) => option.id === field.value
|
(option) => option.id === field.value
|
||||||
) || null
|
) ?? null
|
||||||
}
|
}
|
||||||
onChange={(_, value) => {
|
onChange={(_, value) => {
|
||||||
field.onChange(value?.id || "");
|
field.onChange(value?.id ?? "");
|
||||||
}}
|
}}
|
||||||
getOptionLabel={(item) => {
|
getOptionLabel={(item) => {
|
||||||
return item ? item.media_name : "";
|
return item ? item.media_name : "";
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useCustom, useApiUrl } from "@refinedev/core";
|
import { useCustom, useApiUrl } from "@refinedev/core";
|
||||||
import { useParams } from "react-router";
|
import { useParams } from "react-router";
|
||||||
import { createContext, ReactNode, useContext, useEffect, useMemo, useState } from "react";
|
import { createContext, ReactNode, useContext, useEffect, useMemo, useState } from "react";
|
||||||
import { RouteData, SightData, StationData, StationPatchData } from "./types";
|
import { RouteData, SightData, SightPatchData, StationData, StationPatchData } from "./types";
|
||||||
import { axiosInstance } from "../../providers/data";
|
import { axiosInstance } from "../../providers/data";
|
||||||
|
|
||||||
const MapDataContext = createContext<{
|
const MapDataContext = createContext<{
|
||||||
@ -19,6 +19,7 @@ const MapDataContext = createContext<{
|
|||||||
setMapRotation: (rotation: number) => void,
|
setMapRotation: (rotation: number) => void,
|
||||||
setMapCenter: (x: number, y: number) => void,
|
setMapCenter: (x: number, y: number) => void,
|
||||||
setStationOffset: (stationId: number, x: number, y: number) => void,
|
setStationOffset: (stationId: number, x: number, y: number) => void,
|
||||||
|
setSightCoordinates: (sightId: number, latitude: number, longitude: number) => void,
|
||||||
saveChanges: () => void,
|
saveChanges: () => void,
|
||||||
}>({
|
}>({
|
||||||
originalRouteData: undefined,
|
originalRouteData: undefined,
|
||||||
@ -35,6 +36,7 @@ const MapDataContext = createContext<{
|
|||||||
setMapRotation: () => {},
|
setMapRotation: () => {},
|
||||||
setMapCenter: () => {},
|
setMapCenter: () => {},
|
||||||
setStationOffset: () => {},
|
setStationOffset: () => {},
|
||||||
|
setSightCoordinates: () => {},
|
||||||
saveChanges: () => {},
|
saveChanges: () => {},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -52,14 +54,13 @@ export function MapDataProvider({ children }: Readonly<{ children: ReactNode }>)
|
|||||||
|
|
||||||
const [routeChanges, setRouteChanges] = useState<RouteData>({} as RouteData);
|
const [routeChanges, setRouteChanges] = useState<RouteData>({} as RouteData);
|
||||||
const [stationChanges, setStationChanges] = useState<StationPatchData[]>([]);
|
const [stationChanges, setStationChanges] = useState<StationPatchData[]>([]);
|
||||||
const [sightChanges, setSightChanges] = useState<SightData[]>([]);
|
const [sightChanges, setSightChanges] = useState<SightPatchData[]>([]);
|
||||||
|
|
||||||
|
|
||||||
const { data: routeQuery, isLoading: isRouteLoading } = useCustom({
|
const { data: routeQuery, isLoading: isRouteLoading } = useCustom({
|
||||||
url: `${apiUrl}/route/${routeId}`,
|
url: `${apiUrl}/route/${routeId}`,
|
||||||
method: 'get',
|
method: 'get',
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: stationQuery, isLoading: isStationLoading } = useCustom({
|
const { data: stationQuery, isLoading: isStationLoading } = useCustom({
|
||||||
url: `${apiUrl}/route/${routeId}/station`,
|
url: `${apiUrl}/route/${routeId}/station`,
|
||||||
method: 'get'
|
method: 'get'
|
||||||
@ -110,17 +111,25 @@ export function MapDataProvider({ children }: Readonly<{ children: ReactNode }>)
|
|||||||
|
|
||||||
async function saveChanges() {
|
async function saveChanges() {
|
||||||
await axiosInstance.patch(`/route/${routeId}`, routeData);
|
await axiosInstance.patch(`/route/${routeId}`, routeData);
|
||||||
saveStationChanges();
|
await saveStationChanges();
|
||||||
|
await saveSightChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveStationChanges() {
|
async function saveStationChanges() {
|
||||||
console.log("saveStationChanges", stationChanges);
|
|
||||||
for(const station of stationChanges) {
|
for(const station of stationChanges) {
|
||||||
const response = await axiosInstance.patch(`/route/${routeId}/station`, station);
|
const response = await axiosInstance.patch(`/route/${routeId}/station`, station);
|
||||||
console.log("response", response);
|
console.log("response", response);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function saveSightChanges() {
|
||||||
|
console.log("sightChanges", sightChanges);
|
||||||
|
for(const sight of sightChanges) {
|
||||||
|
const response = await axiosInstance.patch(`/route/${routeId}/sight`, sight);
|
||||||
|
console.log("response", response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function setStationOffset(stationId: number, x: number, y: number) {
|
function setStationOffset(stationId: number, x: number, y: number) {
|
||||||
setStationChanges((prev) => {
|
setStationChanges((prev) => {
|
||||||
let found = prev.find((station) => station.station_id === stationId);
|
let found = prev.find((station) => station.station_id === stationId);
|
||||||
@ -148,9 +157,36 @@ export function MapDataProvider({ children }: Readonly<{ children: ReactNode }>)
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setSightCoordinates(sightId: number, latitude: number, longitude: number) {
|
||||||
|
setSightChanges((prev) => {
|
||||||
|
let found = prev.find((sight) => sight.sight_id === sightId);
|
||||||
|
if(found) {
|
||||||
|
found.latitude = latitude;
|
||||||
|
found.longitude = longitude;
|
||||||
|
|
||||||
|
return prev.map((sight) => {
|
||||||
|
if(sight.sight_id === sightId) {
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
return sight;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const foundSight = sightData?.find((sight) => sight.id === sightId);
|
||||||
|
if(foundSight) {
|
||||||
|
return [...prev, {
|
||||||
|
sight_id: sightId,
|
||||||
|
latitude,
|
||||||
|
longitude
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log("stationChanges", stationChanges);
|
console.log("sightChanges", sightChanges);
|
||||||
}, [stationChanges]);
|
}, [sightChanges]);
|
||||||
|
|
||||||
const value = useMemo(() => ({
|
const value = useMemo(() => ({
|
||||||
originalRouteData: originalRouteData,
|
originalRouteData: originalRouteData,
|
||||||
@ -167,6 +203,7 @@ export function MapDataProvider({ children }: Readonly<{ children: ReactNode }>)
|
|||||||
setMapCenter,
|
setMapCenter,
|
||||||
saveChanges,
|
saveChanges,
|
||||||
setStationOffset,
|
setStationOffset,
|
||||||
|
setSightCoordinates
|
||||||
}), [originalRouteData, originalStationData, originalSightData, routeData, stationData, sightData, isRouteLoading, isStationLoading, isSightLoading]);
|
}), [originalRouteData, originalStationData, originalSightData, routeData, stationData, sightData, isRouteLoading, isStationLoading, isSightLoading]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -4,7 +4,8 @@ import { SightData } from "./types";
|
|||||||
import { Assets, FederatedMouseEvent, Graphics, Texture } from "pixi.js";
|
import { Assets, FederatedMouseEvent, Graphics, Texture } from "pixi.js";
|
||||||
import { COLORS } from "../../contexts/color-mode/theme";
|
import { COLORS } from "../../contexts/color-mode/theme";
|
||||||
import { SIGHT_SIZE, UP_SCALE } from "./Constants";
|
import { SIGHT_SIZE, UP_SCALE } from "./Constants";
|
||||||
import { coordinatesToLocal } from "./utils";
|
import { coordinatesToLocal, localToCoordinates } from "./utils";
|
||||||
|
import { useMapData } from "./MapDataContext";
|
||||||
|
|
||||||
interface SightProps {
|
interface SightProps {
|
||||||
sight: SightData;
|
sight: SightData;
|
||||||
@ -15,8 +16,9 @@ export function Sight({
|
|||||||
sight, id
|
sight, id
|
||||||
}: Readonly<SightProps>) {
|
}: Readonly<SightProps>) {
|
||||||
const { rotation, scale } = useTransform();
|
const { rotation, scale } = useTransform();
|
||||||
|
const { setSightCoordinates } = useMapData();
|
||||||
|
|
||||||
const [position, setPosition] = useState({ x: 0, y: 0 });
|
const [position, setPosition] = useState(coordinatesToLocal(sight.latitude, sight.longitude));
|
||||||
const [isDragging, setIsDragging] = useState(false);
|
const [isDragging, setIsDragging] = useState(false);
|
||||||
const [startPosition, setStartPosition] = useState({ x: 0, y: 0 });
|
const [startPosition, setStartPosition] = useState({ x: 0, y: 0 });
|
||||||
const [startMousePosition, setStartMousePosition] = useState({ x: 0, y: 0 });
|
const [startMousePosition, setStartMousePosition] = useState({ x: 0, y: 0 });
|
||||||
@ -36,8 +38,8 @@ export function Sight({
|
|||||||
};
|
};
|
||||||
const handlePointerMove = (e: FederatedMouseEvent) => {
|
const handlePointerMove = (e: FederatedMouseEvent) => {
|
||||||
if (!isDragging) return;
|
if (!isDragging) return;
|
||||||
const dx = (e.globalX - startMousePosition.x) / scale;
|
const dx = (e.globalX - startMousePosition.x) / scale / UP_SCALE;
|
||||||
const dy = (e.globalY - startMousePosition.y) / scale;
|
const dy = (e.globalY - startMousePosition.y) / scale / UP_SCALE;
|
||||||
const cos = Math.cos(rotation);
|
const cos = Math.cos(rotation);
|
||||||
const sin = Math.sin(rotation);
|
const sin = Math.sin(rotation);
|
||||||
const newPosition = {
|
const newPosition = {
|
||||||
@ -45,6 +47,8 @@ export function Sight({
|
|||||||
y: startPosition.y - dx * sin + dy * cos
|
y: startPosition.y - dx * sin + dy * cos
|
||||||
};
|
};
|
||||||
setPosition(newPosition);
|
setPosition(newPosition);
|
||||||
|
const coordinates = localToCoordinates(newPosition.x, newPosition.y);
|
||||||
|
setSightCoordinates(sight.id, coordinates.latitude, coordinates.longitude);
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -85,8 +89,8 @@ export function Sight({
|
|||||||
onGlobalPointerMove={handlePointerMove}
|
onGlobalPointerMove={handlePointerMove}
|
||||||
onPointerUp={handlePointerUp}
|
onPointerUp={handlePointerUp}
|
||||||
onPointerUpOutside={handlePointerUp}
|
onPointerUpOutside={handlePointerUp}
|
||||||
x={coordinates.x * UP_SCALE - SIGHT_SIZE/2 + position.x} // Offset by half width to center
|
x={position.x * UP_SCALE - SIGHT_SIZE/2} // Offset by half width to center
|
||||||
y={coordinates.y * UP_SCALE - SIGHT_SIZE/2 + position.y} // Offset by half height to center
|
y={position.y * UP_SCALE - SIGHT_SIZE/2} // Offset by half height to center
|
||||||
>
|
>
|
||||||
<pixiSprite
|
<pixiSprite
|
||||||
texture={texture}
|
texture={texture}
|
||||||
|
@ -62,11 +62,9 @@ export function StationLabel({
|
|||||||
if (!isDragging) return;
|
if (!isDragging) return;
|
||||||
const dx = (e.globalX - startMousePosition.x);
|
const dx = (e.globalX - startMousePosition.x);
|
||||||
const dy = (e.globalY - startMousePosition.y);
|
const dy = (e.globalY - startMousePosition.y);
|
||||||
const cos = Math.cos(rotation);
|
|
||||||
const sin = Math.sin(rotation);
|
|
||||||
const newPosition = {
|
const newPosition = {
|
||||||
x: startPosition.x + dx * cos + dy * sin,
|
x: startPosition.x + dx,
|
||||||
y: startPosition.y - dx * sin + dy * cos
|
y: startPosition.y + dy
|
||||||
};
|
};
|
||||||
setPosition(newPosition);
|
setPosition(newPosition);
|
||||||
setStationOffset(station.id, newPosition.x, newPosition.y);
|
setStationOffset(station.id, newPosition.x, newPosition.y);
|
||||||
|
@ -47,6 +47,12 @@ export interface StationPatchData {
|
|||||||
transfers: StationTransferData;
|
transfers: StationTransferData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SightPatchData {
|
||||||
|
sight_id: number;
|
||||||
|
latitude: number;
|
||||||
|
longitude: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface SightData {
|
export interface SightData {
|
||||||
address: string;
|
address: string;
|
||||||
city: string;
|
city: string;
|
||||||
|
@ -19,7 +19,7 @@ export const RouteCreate = () => {
|
|||||||
formState: { errors },
|
formState: { errors },
|
||||||
} = useForm({
|
} = useForm({
|
||||||
refineCoreProps: {
|
refineCoreProps: {
|
||||||
resource: "route/",
|
resource: "route",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,66 +1,70 @@
|
|||||||
import { Autocomplete, Box, TextField, Typography, Paper } from "@mui/material";
|
import { Autocomplete, Box, TextField, Typography, Paper } from "@mui/material";
|
||||||
import { Create, useAutocomplete } from "@refinedev/mui";
|
import { Create, useAutocomplete } from "@refinedev/mui";
|
||||||
import { useForm } from "@refinedev/react-hook-form";
|
import { useForm } from "@refinedev/react-hook-form";
|
||||||
import { Controller } from "react-hook-form";
|
import { Controller, FieldValues } from "react-hook-form";
|
||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { TOKEN_KEY } from "@providers";
|
import { TOKEN_KEY } from "@providers";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { Languages, languageStore, cityStore } from "@stores";
|
import { EVERY_LANGUAGE, Languages, languageStore, cityStore } from "@stores";
|
||||||
|
import { LanguageSelector } from "@ui";
|
||||||
export const SightCreate = observer(() => {
|
export const SightCreate = observer(() => {
|
||||||
const { language, setLanguageAction } = languageStore;
|
const { language, setLanguageAction } = languageStore;
|
||||||
const [sightData, setSightData] = useState({
|
const [sightData, setSightData] = useState({
|
||||||
ru: {
|
name: EVERY_LANGUAGE(""),
|
||||||
name: "",
|
address: EVERY_LANGUAGE("")
|
||||||
address: "",
|
|
||||||
},
|
|
||||||
en: {
|
|
||||||
name: "",
|
|
||||||
address: "",
|
|
||||||
},
|
|
||||||
zh: {
|
|
||||||
name: "",
|
|
||||||
address: "",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Состояния для предпросмотра
|
|
||||||
const handleLanguageChange = (lang: Languages) => {
|
|
||||||
setSightData((prevData) => ({
|
|
||||||
...prevData,
|
|
||||||
[language]: {
|
|
||||||
name: watch("name") ?? "",
|
|
||||||
address: watch("address") ?? "",
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
setLanguageAction(lang);
|
|
||||||
};
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
saveButtonProps,
|
saveButtonProps,
|
||||||
refineCore: { formLoading },
|
refineCore: { formLoading, onFinish },
|
||||||
register,
|
register,
|
||||||
control,
|
control,
|
||||||
watch,
|
watch,
|
||||||
setValue,
|
setValue,
|
||||||
formState: { errors },
|
formState: { errors },
|
||||||
|
handleSubmit,
|
||||||
} = useForm({
|
} = useForm({
|
||||||
refineCoreProps: {
|
refineCoreProps: {
|
||||||
resource: "sight/",
|
resource: "sight",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const { city_id } = cityStore;
|
const { city_id } = cityStore;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (sightData[language as keyof typeof sightData]?.name) {
|
setValue("name", sightData.name[language]);
|
||||||
setValue("name", sightData[language as keyof typeof sightData]?.name);
|
setValue("address", sightData.address[language]);
|
||||||
}
|
|
||||||
if (sightData[language as keyof typeof sightData]?.address) {
|
|
||||||
setValue(
|
|
||||||
"address",
|
|
||||||
sightData[language as keyof typeof sightData]?.address
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}, [sightData, language, setValue]);
|
}, [sightData, language, setValue]);
|
||||||
|
|
||||||
|
function updateTranslations(update: boolean = true) {
|
||||||
|
const newSightData = {
|
||||||
|
...sightData,
|
||||||
|
name: {
|
||||||
|
...sightData.name,
|
||||||
|
[language]: watch("name") ?? "",
|
||||||
|
},
|
||||||
|
address: {
|
||||||
|
...sightData.address,
|
||||||
|
[language]: watch("address") ?? "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(update) setSightData(newSightData);
|
||||||
|
return newSightData;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleLanguageChange = (lang: Languages) => {
|
||||||
|
updateTranslations();
|
||||||
|
setLanguageAction(lang);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFormSubmit = handleSubmit((values: FieldValues) => {
|
||||||
|
const newTranslations = updateTranslations(false);
|
||||||
|
console.log(newTranslations);
|
||||||
|
return onFinish({
|
||||||
|
...values,
|
||||||
|
translations: newTranslations
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const [namePreview, setNamePreview] = useState("");
|
const [namePreview, setNamePreview] = useState("");
|
||||||
const [coordinatesPreview, setCoordinatesPreview] = useState({
|
const [coordinatesPreview, setCoordinatesPreview] = useState({
|
||||||
latitude: "",
|
latitude: "",
|
||||||
@ -140,7 +144,7 @@ export const SightCreate = observer(() => {
|
|||||||
|
|
||||||
// Обновляем состояния при изменении полей
|
// Обновляем состояния при изменении полей
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setNamePreview(nameContent || "");
|
setNamePreview(nameContent ?? "");
|
||||||
}, [nameContent]);
|
}, [nameContent]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -154,7 +158,7 @@ export const SightCreate = observer(() => {
|
|||||||
const selectedCity = cityAutocompleteProps.options.find(
|
const selectedCity = cityAutocompleteProps.options.find(
|
||||||
(option) => option.id === cityContent
|
(option) => option.id === cityContent
|
||||||
);
|
);
|
||||||
setCityPreview(selectedCity?.name || "");
|
setCityPreview(selectedCity?.name ?? "");
|
||||||
}, [cityContent, cityAutocompleteProps.options]);
|
}, [cityContent, cityAutocompleteProps.options]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -206,74 +210,25 @@ export const SightCreate = observer(() => {
|
|||||||
const selectedLeftArticle = articleAutocompleteProps.options.find(
|
const selectedLeftArticle = articleAutocompleteProps.options.find(
|
||||||
(option) => option.id === leftArticleContent
|
(option) => option.id === leftArticleContent
|
||||||
);
|
);
|
||||||
setLeftArticlePreview(selectedLeftArticle?.heading || "");
|
setLeftArticlePreview(selectedLeftArticle?.heading ?? "");
|
||||||
}, [leftArticleContent, articleAutocompleteProps.options]);
|
}, [leftArticleContent, articleAutocompleteProps.options]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const selectedPreviewArticle = articleAutocompleteProps.options.find(
|
const selectedPreviewArticle = articleAutocompleteProps.options.find(
|
||||||
(option) => option.id === previewArticleContent
|
(option) => option.id === previewArticleContent
|
||||||
);
|
);
|
||||||
setPreviewArticlePreview(selectedPreviewArticle?.heading || "");
|
setPreviewArticlePreview(selectedPreviewArticle?.heading ?? "");
|
||||||
}, [previewArticleContent, articleAutocompleteProps.options]);
|
}, [previewArticleContent, articleAutocompleteProps.options]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Create isLoading={formLoading} saveButtonProps={saveButtonProps}>
|
<Create isLoading={formLoading} saveButtonProps={{
|
||||||
|
...saveButtonProps,
|
||||||
|
onClick: handleFormSubmit
|
||||||
|
}}>
|
||||||
<Box sx={{ display: "flex", gap: 2 }}>
|
<Box sx={{ display: "flex", gap: 2 }}>
|
||||||
<Box sx={{ display: "flex", flexDirection: "column", flex: 1, gap: 2 }}>
|
<Box sx={{ display: "flex", flexDirection: "column", flex: 1, gap: 2 }}>
|
||||||
{/* Форма создания */}
|
{/* Форма создания */}
|
||||||
<Box
|
<LanguageSelector action={handleLanguageChange} />
|
||||||
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>
|
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
component="form"
|
component="form"
|
||||||
@ -288,7 +243,7 @@ export const SightCreate = observer(() => {
|
|||||||
helperText={(errors as any)?.name?.message}
|
helperText={(errors as any)?.name?.message}
|
||||||
margin="normal"
|
margin="normal"
|
||||||
fullWidth
|
fullWidth
|
||||||
InputLabelProps={{ shrink: true }}
|
slotProps={{inputLabel: {shrink: true}}}
|
||||||
type="text"
|
type="text"
|
||||||
label={"Название *"}
|
label={"Название *"}
|
||||||
name="name"
|
name="name"
|
||||||
@ -301,7 +256,7 @@ export const SightCreate = observer(() => {
|
|||||||
helperText={(errors as any)?.latitude?.message}
|
helperText={(errors as any)?.latitude?.message}
|
||||||
margin="normal"
|
margin="normal"
|
||||||
fullWidth
|
fullWidth
|
||||||
InputLabelProps={{ shrink: true }}
|
slotProps={{inputLabel: {shrink: true}}}
|
||||||
type="text"
|
type="text"
|
||||||
label={"Координаты *"}
|
label={"Координаты *"}
|
||||||
/>
|
/>
|
||||||
@ -324,15 +279,15 @@ export const SightCreate = observer(() => {
|
|||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
{...register("address", {
|
{...register("address", {
|
||||||
required: "Это поле является обязательным",
|
//required: "Это поле является обязательным",
|
||||||
})}
|
})}
|
||||||
error={!!(errors as any)?.address}
|
error={!!(errors as any)?.address}
|
||||||
helperText={(errors as any)?.address?.message}
|
helperText={(errors as any)?.address?.message}
|
||||||
margin="normal"
|
margin="normal"
|
||||||
fullWidth
|
fullWidth
|
||||||
InputLabelProps={{ shrink: true }}
|
slotProps={{inputLabel: {shrink: true}}}
|
||||||
type="text"
|
type="text"
|
||||||
label={"Адрес *"}
|
label={"Адрес"}
|
||||||
name="address"
|
name="address"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -346,10 +301,10 @@ export const SightCreate = observer(() => {
|
|||||||
value={
|
value={
|
||||||
cityAutocompleteProps.options.find(
|
cityAutocompleteProps.options.find(
|
||||||
(option) => option.id === field.value
|
(option) => option.id === field.value
|
||||||
) || null
|
) ?? null
|
||||||
}
|
}
|
||||||
onChange={(_, value) => {
|
onChange={(_, value) => {
|
||||||
field.onChange(value?.id || "");
|
field.onChange(value?.id ?? "");
|
||||||
}}
|
}}
|
||||||
getOptionLabel={(item) => {
|
getOptionLabel={(item) => {
|
||||||
return item ? item.name : "";
|
return item ? item.name : "";
|
||||||
@ -372,7 +327,6 @@ export const SightCreate = observer(() => {
|
|||||||
variant="outlined"
|
variant="outlined"
|
||||||
error={!!errors.city_id}
|
error={!!errors.city_id}
|
||||||
helperText={(errors as any)?.city_id?.message}
|
helperText={(errors as any)?.city_id?.message}
|
||||||
required
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@ -389,10 +343,10 @@ export const SightCreate = observer(() => {
|
|||||||
value={
|
value={
|
||||||
mediaAutocompleteProps.options.find(
|
mediaAutocompleteProps.options.find(
|
||||||
(option) => option.id === field.value
|
(option) => option.id === field.value
|
||||||
) || null
|
) ?? null
|
||||||
}
|
}
|
||||||
onChange={(_, value) => {
|
onChange={(_, value) => {
|
||||||
field.onChange(value?.id || "");
|
field.onChange(value?.id ?? "");
|
||||||
}}
|
}}
|
||||||
getOptionLabel={(item) => {
|
getOptionLabel={(item) => {
|
||||||
return item ? item.media_name : "";
|
return item ? item.media_name : "";
|
||||||
@ -415,7 +369,7 @@ export const SightCreate = observer(() => {
|
|||||||
variant="outlined"
|
variant="outlined"
|
||||||
error={!!errors.thumbnail}
|
error={!!errors.thumbnail}
|
||||||
helperText={(errors as any)?.thumbnail?.message}
|
helperText={(errors as any)?.thumbnail?.message}
|
||||||
required
|
// required
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@ -432,10 +386,10 @@ export const SightCreate = observer(() => {
|
|||||||
value={
|
value={
|
||||||
mediaAutocompleteProps.options.find(
|
mediaAutocompleteProps.options.find(
|
||||||
(option) => option.id === field.value
|
(option) => option.id === field.value
|
||||||
) || null
|
) ?? null
|
||||||
}
|
}
|
||||||
onChange={(_, value) => {
|
onChange={(_, value) => {
|
||||||
field.onChange(value?.id || "");
|
field.onChange(value?.id ?? "");
|
||||||
}}
|
}}
|
||||||
getOptionLabel={(item) => {
|
getOptionLabel={(item) => {
|
||||||
return item ? item.media_name : "";
|
return item ? item.media_name : "";
|
||||||
@ -474,10 +428,10 @@ export const SightCreate = observer(() => {
|
|||||||
value={
|
value={
|
||||||
mediaAutocompleteProps.options.find(
|
mediaAutocompleteProps.options.find(
|
||||||
(option) => option.id === field.value
|
(option) => option.id === field.value
|
||||||
) || null
|
) ?? null
|
||||||
}
|
}
|
||||||
onChange={(_, value) => {
|
onChange={(_, value) => {
|
||||||
field.onChange(value?.id || "");
|
field.onChange(value?.id ?? "");
|
||||||
}}
|
}}
|
||||||
getOptionLabel={(item) => {
|
getOptionLabel={(item) => {
|
||||||
return item ? item.media_name : "";
|
return item ? item.media_name : "";
|
||||||
@ -516,10 +470,10 @@ export const SightCreate = observer(() => {
|
|||||||
value={
|
value={
|
||||||
articleAutocompleteProps.options.find(
|
articleAutocompleteProps.options.find(
|
||||||
(option) => option.id === field.value
|
(option) => option.id === field.value
|
||||||
) || null
|
) ?? null
|
||||||
}
|
}
|
||||||
onChange={(_, value) => {
|
onChange={(_, value) => {
|
||||||
field.onChange(value?.id || "");
|
field.onChange(value?.id ?? "");
|
||||||
}}
|
}}
|
||||||
getOptionLabel={(item) => {
|
getOptionLabel={(item) => {
|
||||||
return item ? item.heading : "";
|
return item ? item.heading : "";
|
||||||
@ -558,10 +512,10 @@ export const SightCreate = observer(() => {
|
|||||||
value={
|
value={
|
||||||
articleAutocompleteProps.options.find(
|
articleAutocompleteProps.options.find(
|
||||||
(option) => option.id === field.value
|
(option) => option.id === field.value
|
||||||
) || null
|
) ?? null
|
||||||
}
|
}
|
||||||
onChange={(_, value) => {
|
onChange={(_, value) => {
|
||||||
field.onChange(value?.id || "");
|
field.onChange(value?.id ?? "");
|
||||||
}}
|
}}
|
||||||
getOptionLabel={(item) => {
|
getOptionLabel={(item) => {
|
||||||
return item ? item.heading : "";
|
return item ? item.heading : "";
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect } from "react";
|
import React from "react";
|
||||||
import { type GridColDef } from "@mui/x-data-grid";
|
import { type GridColDef } from "@mui/x-data-grid";
|
||||||
import {
|
import {
|
||||||
DeleteButton,
|
DeleteButton,
|
||||||
@ -8,11 +8,10 @@ import {
|
|||||||
useDataGrid,
|
useDataGrid,
|
||||||
} from "@refinedev/mui";
|
} from "@refinedev/mui";
|
||||||
import { Stack } from "@mui/material";
|
import { Stack } from "@mui/material";
|
||||||
import { CustomDataGrid } from "../../components/CustomDataGrid";
|
import { CustomDataGrid } from "@components";
|
||||||
import { localeText } from "../../locales/ru/localeText";
|
import { localeText } from "../../locales/ru/localeText";
|
||||||
import { cityStore } from "../../store/CityStore";
|
import { cityStore, languageStore } from "@stores";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { languageStore } from "../../store/LanguageStore";
|
|
||||||
|
|
||||||
export const SightList = observer(() => {
|
export const SightList = observer(() => {
|
||||||
const { city_id } = cityStore;
|
const { city_id } = cityStore;
|
||||||
|
Loading…
Reference in New Issue
Block a user