From 03829aacc66d2d8b2835b6d23cc6ba24214fac8f Mon Sep 17 00:00:00 2001 From: itoshi <kkzemeow@gmail.com> Date: Tue, 29 Apr 2025 21:16:53 +0300 Subject: [PATCH] station edit in the route edit page --- src/components/CustomDataGrid.tsx | 10 +- src/components/LanguageSwitch/index.tsx | 70 +++ src/components/LinkedItems.tsx | 511 +++++++++--------- src/components/index.ts | 1 - .../modals/ArticleEditModal/index.tsx | 171 ++++++ .../modals/StationEditModal/index.tsx | 164 ++++++ src/pages/article/edit.tsx | 28 +- src/pages/article/list.tsx | 87 +-- src/pages/carrier/create.tsx | 195 ++++--- src/pages/carrier/list.tsx | 10 +- src/pages/city/list.tsx | 104 ++-- src/pages/country/list.tsx | 82 ++- src/pages/sight/create.tsx | 49 +- src/pages/sight/edit.tsx | 398 +++++++++----- src/pages/sight/list.tsx | 15 +- src/pages/station/edit.tsx | 173 +++++- src/pages/station/list.tsx | 10 +- src/pages/vehicle/edit.tsx | 111 ++-- src/pages/vehicle/list.tsx | 133 +++-- src/store/ArticleStore.ts | 20 + src/store/StationStore.ts | 20 + 21 files changed, 1642 insertions(+), 720 deletions(-) create mode 100644 src/components/LanguageSwitch/index.tsx delete mode 100644 src/components/index.ts create mode 100644 src/components/modals/ArticleEditModal/index.tsx create mode 100644 src/components/modals/StationEditModal/index.tsx create mode 100644 src/store/ArticleStore.ts create mode 100644 src/store/StationStore.ts diff --git a/src/components/CustomDataGrid.tsx b/src/components/CustomDataGrid.tsx index 88594cf..888dfc9 100644 --- a/src/components/CustomDataGrid.tsx +++ b/src/components/CustomDataGrid.tsx @@ -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 && ( diff --git a/src/components/LanguageSwitch/index.tsx b/src/components/LanguageSwitch/index.tsx new file mode 100644 index 0000000..c216097 --- /dev/null +++ b/src/components/LanguageSwitch/index.tsx @@ -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> + ); +}); diff --git a/src/components/LinkedItems.tsx b/src/components/LinkedItems.tsx index 614eb3f..bf25d84 100644 --- a/src/components/LinkedItems.tsx +++ b/src/components/LinkedItems.tsx @@ -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 /> + </> ); }; diff --git a/src/components/index.ts b/src/components/index.ts deleted file mode 100644 index 7c8ead2..0000000 --- a/src/components/index.ts +++ /dev/null @@ -1 +0,0 @@ -export {Header} from './header' diff --git a/src/components/modals/ArticleEditModal/index.tsx b/src/components/modals/ArticleEditModal/index.tsx new file mode 100644 index 0000000..a843dc7 --- /dev/null +++ b/src/components/modals/ArticleEditModal/index.tsx @@ -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> + ); +}); diff --git a/src/components/modals/StationEditModal/index.tsx b/src/components/modals/StationEditModal/index.tsx new file mode 100644 index 0000000..473a421 --- /dev/null +++ b/src/components/modals/StationEditModal/index.tsx @@ -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> + ); +}); diff --git a/src/pages/article/edit.tsx b/src/pages/article/edit.tsx index 6dde00f..bbf4204 100644 --- a/src/pages/article/edit.tsx +++ b/src/pages/article/edit.tsx @@ -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) => { diff --git a/src/pages/article/list.tsx b/src/pages/article/list.tsx index 16b9d2b..168a63f 100644 --- a/src/pages/article/list.tsx +++ b/src/pages/article/list.tsx @@ -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> - ) -} + ); +}); diff --git a/src/pages/carrier/create.tsx b/src/pages/carrier/create.tsx index fc692c9..891ea3e 100644 --- a/src/pages/carrier/create.tsx +++ b/src/pages/carrier/create.tsx @@ -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> - ) -} + ); +}); diff --git a/src/pages/carrier/list.tsx b/src/pages/carrier/list.tsx index 2eddc30..bc9b0eb 100644 --- a/src/pages/carrier/list.tsx +++ b/src/pages/carrier/list.tsx @@ -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> ); }); diff --git a/src/pages/city/list.tsx b/src/pages/city/list.tsx index d477288..32682a7 100644 --- a/src/pages/city/list.tsx +++ b/src/pages/city/list.tsx @@ -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> - ) -} + ); +}); diff --git a/src/pages/country/list.tsx b/src/pages/country/list.tsx index 7bfe487..a592e95 100644 --- a/src/pages/country/list.tsx +++ b/src/pages/country/list.tsx @@ -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> - ) -} + ); +}); diff --git a/src/pages/sight/create.tsx b/src/pages/sight/create.tsx index 2c7e52a..7c0b76a 100644 --- a/src/pages/sight/create.tsx +++ b/src/pages/sight/create.tsx @@ -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: "", diff --git a/src/pages/sight/edit.tsx b/src/pages/sight/edit.tsx index c19d99e..a71c7f2 100644 --- a/src/pages/sight/edit.tsx +++ b/src/pages/sight/edit.tsx @@ -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" diff --git a/src/pages/sight/list.tsx b/src/pages/sight/list.tsx index f75bf7f..6b50585 100644 --- a/src/pages/sight/list.tsx +++ b/src/pages/sight/list.tsx @@ -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} diff --git a/src/pages/station/edit.tsx b/src/pages/station/edit.tsx index 96a4fa6..cd9933d 100644 --- a/src/pages/station/edit.tsx +++ b/src/pages/station/edit.tsx @@ -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> ); -}; +}); diff --git a/src/pages/station/list.tsx b/src/pages/station/list.tsx index bb831aa..2713010 100644 --- a/src/pages/station/list.tsx +++ b/src/pages/station/list.tsx @@ -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 diff --git a/src/pages/vehicle/edit.tsx b/src/pages/vehicle/edit.tsx index 5c612b9..af57990 100644 --- a/src/pages/vehicle/edit.tsx +++ b/src/pages/vehicle/edit.tsx @@ -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> - ) -} + ); +}; diff --git a/src/pages/vehicle/list.tsx b/src/pages/vehicle/list.tsx index 74d03c5..b3e3722 100644 --- a/src/pages/vehicle/list.tsx +++ b/src/pages/vehicle/list.tsx @@ -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> - ) -} + ); +}); diff --git a/src/store/ArticleStore.ts b/src/store/ArticleStore.ts new file mode 100644 index 0000000..f844e28 --- /dev/null +++ b/src/store/ArticleStore.ts @@ -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(); diff --git a/src/store/StationStore.ts b/src/store/StationStore.ts new file mode 100644 index 0000000..5304821 --- /dev/null +++ b/src/store/StationStore.ts @@ -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();