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