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