feat: Add carriers
translation on 3 languages
This commit is contained in:
@ -39,6 +39,7 @@ import {
|
||||
RouteCreatePage,
|
||||
RoutePreview,
|
||||
RouteEditPage,
|
||||
ArticlePreviewPage,
|
||||
} from "@pages";
|
||||
import { authStore, createSightStore, editSightStore } from "@shared";
|
||||
import { Layout } from "@widgets";
|
||||
@ -170,7 +171,7 @@ const router = createBrowserRouter([
|
||||
// { path: "vehicle/:id/edit", element: <VehicleEditPage /> },
|
||||
// Article
|
||||
{ path: "article", element: <ArticleListPage /> },
|
||||
// { path: "article/:id", element: <ArticlePreviewPage /> },
|
||||
{ path: "article/:id", element: <ArticlePreviewPage /> },
|
||||
// { path: "media/create", element: <CreateMediaPage /> },
|
||||
],
|
||||
},
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useState } from "react";
|
||||
import React from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { ArrowLeft } from "lucide-react";
|
||||
import { LanguageSwitcher } from "@widgets";
|
||||
|
@ -1,14 +1,14 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useEffect } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { ArrowLeft } from "lucide-react";
|
||||
import { LanguageSwitcher } from "@widgets";
|
||||
import { articlesStore, languageStore } from "@shared";
|
||||
import { articlesStore } from "@shared";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
const ArticleEditPage: React.FC = observer(() => {
|
||||
const navigate = useNavigate();
|
||||
const { id } = useParams();
|
||||
const { language } = languageStore;
|
||||
|
||||
const { articleData, getArticle } = articlesStore;
|
||||
|
||||
useEffect(() => {
|
||||
|
85
src/pages/Article/ArticlePreviewPage/PreviewLeftWidget.tsx
Normal file
85
src/pages/Article/ArticlePreviewPage/PreviewLeftWidget.tsx
Normal file
@ -0,0 +1,85 @@
|
||||
import { Paper, Box, Typography } from "@mui/material";
|
||||
import { MediaViewer, ReactMarkdownComponent } from "@widgets";
|
||||
import { articlesStore, languageStore } from "@shared";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
export const PreviewLeftWidget = observer(() => {
|
||||
const { articleMedia, articleData } = articlesStore;
|
||||
const { language } = languageStore;
|
||||
|
||||
return (
|
||||
<Paper
|
||||
elevation={3}
|
||||
sx={{
|
||||
width: "100%",
|
||||
minWidth: 320,
|
||||
background:
|
||||
"#806c59 linear-gradient(90deg, rgba(255, 255, 255, 0.2) 12.5%, rgba(255, 255, 255, 0.2) 100%)",
|
||||
overflowY: "auto",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
borderRadius: "10px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
overflow: "hidden",
|
||||
width: "100%",
|
||||
minHeight: 100,
|
||||
padding: "3px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
"& img": {
|
||||
borderTopLeftRadius: "10px",
|
||||
borderTopRightRadius: "10px",
|
||||
width: "100%",
|
||||
height: "auto",
|
||||
objectFit: "contain",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{articleMedia && <MediaViewer media={articleMedia} fullWidth />}
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
background:
|
||||
"#806c59 linear-gradient(90deg, rgba(255, 255, 255, 0.2) 12.5%, rgba(255, 255, 255, 0.2) 100%)",
|
||||
color: "white",
|
||||
margin: "5px 0px 5px 0px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 1,
|
||||
padding: 1,
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="h5"
|
||||
component="h2"
|
||||
sx={{
|
||||
wordBreak: "break-word",
|
||||
fontSize: "24px",
|
||||
fontWeight: 700,
|
||||
lineHeight: "120%",
|
||||
}}
|
||||
>
|
||||
{articleData?.[language]?.heading || "Название информации"}
|
||||
</Typography>
|
||||
</Box>
|
||||
{articleData?.[language]?.body && (
|
||||
<Box
|
||||
sx={{
|
||||
padding: 1,
|
||||
maxHeight: "300px",
|
||||
overflowY: "scroll",
|
||||
background:
|
||||
"#806c59 linear-gradient(90deg, rgba(255, 255, 255, 0.2) 12.5%, rgba(255, 255, 255, 0.2) 100%)",
|
||||
flexGrow: 1,
|
||||
}}
|
||||
>
|
||||
<ReactMarkdownComponent value={articleData?.[language]?.body} />
|
||||
</Box>
|
||||
)}
|
||||
</Paper>
|
||||
);
|
||||
});
|
139
src/pages/Article/ArticlePreviewPage/PreviewRightWidget.tsx
Normal file
139
src/pages/Article/ArticlePreviewPage/PreviewRightWidget.tsx
Normal file
@ -0,0 +1,139 @@
|
||||
import { Paper, Box, Typography } from "@mui/material";
|
||||
import { MediaViewer, ReactMarkdownComponent } from "@widgets";
|
||||
import { articlesStore, languageStore } from "@shared";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { ImagePlus } from "lucide-react";
|
||||
|
||||
export const PreviewRightWidget = observer(() => {
|
||||
const { articleData, articleMedia } = articlesStore;
|
||||
const { language } = languageStore;
|
||||
const article = articleData?.[language];
|
||||
if (!article) return null;
|
||||
|
||||
return (
|
||||
<Paper
|
||||
className="flex-1 flex flex-col max-w-[500px]"
|
||||
sx={{
|
||||
borderRadius: "10px",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
elevation={2}
|
||||
>
|
||||
<Box
|
||||
className="overflow-hidden"
|
||||
sx={{
|
||||
width: "100%",
|
||||
background: "#877361",
|
||||
borderColor: "grey.300",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
{articleMedia ? (
|
||||
<Box
|
||||
sx={{
|
||||
overflow: "hidden",
|
||||
width: "100%",
|
||||
padding: "2px 2px 0px 2px",
|
||||
"& img": {
|
||||
borderTopLeftRadius: "10px",
|
||||
borderTopRightRadius: "10px",
|
||||
width: "100%",
|
||||
height: "auto",
|
||||
objectFit: "contain",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<MediaViewer media={articleMedia} fullWidth />
|
||||
</Box>
|
||||
) : (
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: 200,
|
||||
flexShrink: 0,
|
||||
backgroundColor: "rgba(0,0,0,0.1)",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<ImagePlus size={48} color="white" />
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
p: 1,
|
||||
wordBreak: "break-word",
|
||||
fontSize: "24px",
|
||||
fontWeight: 700,
|
||||
lineHeight: "120%",
|
||||
backdropFilter: "blur(12px)",
|
||||
borderBottom: "1px solid #A89F90",
|
||||
boxShadow: "inset 4px 4px 12px 0 rgba(255,255,255,0.12)",
|
||||
background:
|
||||
"#806c59 linear-gradient(90deg, rgba(255, 255, 255, 0.2) 12.5%, rgba(255, 255, 255, 0.2) 100%)",
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6" color="white">
|
||||
{article.heading || "Выберите статью"}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
padding: 1,
|
||||
minHeight: "200px",
|
||||
maxHeight: "300px",
|
||||
overflowY: "scroll",
|
||||
background:
|
||||
"rgba(179, 165, 152, 0.4), linear-gradient(180deg, rgba(255, 255, 255, 0.2) 0%, rgba(255, 255, 255, 0.2) 100%)",
|
||||
flexGrow: 1,
|
||||
}}
|
||||
>
|
||||
{article.body ? (
|
||||
<ReactMarkdownComponent value={article.body} />
|
||||
) : (
|
||||
<Typography
|
||||
color="rgba(255,255,255,0.7)"
|
||||
sx={{ textAlign: "center", mt: 4 }}
|
||||
>
|
||||
Предпросмотр статьи появится здесь
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* @ts-ignore */}
|
||||
{articleData?.right && articleData?.right.length > 1 && (
|
||||
<Box
|
||||
sx={{
|
||||
p: 2,
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
fontSize: "24px",
|
||||
fontWeight: 700,
|
||||
lineHeight: "120%",
|
||||
flexWrap: "wrap",
|
||||
gap: 1,
|
||||
backdropFilter: "blur(12px)",
|
||||
boxShadow: "inset 4px 4px 12px 0 rgba(255,255,255,0.12)",
|
||||
background:
|
||||
"#806c59 linear-gradient(90deg, rgba(255, 255, 255, 0.2) 12.5%, rgba(255, 255, 255, 0.2) 100%)",
|
||||
}}
|
||||
>
|
||||
{/* @ts-ignore */}
|
||||
{articleData.right.map((a, idx) => (
|
||||
<button
|
||||
key={idx}
|
||||
className="inline-block text-left text-xs text-white"
|
||||
>
|
||||
{a.heading}
|
||||
</button>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</Paper>
|
||||
);
|
||||
});
|
57
src/pages/Article/ArticlePreviewPage/index.tsx
Normal file
57
src/pages/Article/ArticlePreviewPage/index.tsx
Normal file
@ -0,0 +1,57 @@
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { useEffect } from "react";
|
||||
import { Box } from "@mui/material";
|
||||
import { PreviewLeftWidget } from "./PreviewLeftWidget";
|
||||
import { PreviewRightWidget } from "./PreviewRightWidget";
|
||||
import { articlesStore, languageStore } from "@shared";
|
||||
import { ArrowLeft } from "lucide-react";
|
||||
|
||||
export const ArticlePreviewPage = () => {
|
||||
const navigate = useNavigate();
|
||||
const { id } = useParams();
|
||||
const { getArticle, getArticleMedia, getArticlePreview } = articlesStore;
|
||||
const { language } = languageStore;
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
if (id) {
|
||||
await getArticle(Number(id), language);
|
||||
await getArticleMedia(Number(id));
|
||||
await getArticlePreview(Number(id));
|
||||
}
|
||||
};
|
||||
fetchData();
|
||||
}, [id, language]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center gap-4 mb-10">
|
||||
<button
|
||||
className="flex items-center gap-2"
|
||||
onClick={() => navigate(-1)}
|
||||
>
|
||||
<ArrowLeft size={20} />
|
||||
Назад
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: 2,
|
||||
p: 2,
|
||||
justifyContent: "center",
|
||||
margin: "0 auto",
|
||||
}}
|
||||
>
|
||||
<Box sx={{ width: "320px" }}>
|
||||
<PreviewLeftWidget />
|
||||
</Box>
|
||||
|
||||
<Box sx={{ width: "500px" }}>
|
||||
<PreviewRightWidget />
|
||||
</Box>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
@ -1 +1,2 @@
|
||||
export * from "./ArticleListPage";
|
||||
export * from "./ArticlePreviewPage";
|
||||
|
@ -12,9 +12,9 @@ import { ArrowLeft, Save } from "lucide-react";
|
||||
import { Loader2 } from "lucide-react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { toast } from "react-toastify";
|
||||
import { carrierStore, cityStore, mediaStore, languageStore } from "@shared";
|
||||
import { carrierStore, cityStore, mediaStore } from "@shared";
|
||||
import { useState, useEffect } from "react";
|
||||
import { MediaViewer, ImageUploadCard, LanguageSwitcher } from "@widgets";
|
||||
import { ImageUploadCard, LanguageSwitcher } from "@widgets";
|
||||
import {
|
||||
SelectMediaDialog,
|
||||
UploadMediaDialog,
|
||||
@ -23,7 +23,7 @@ import {
|
||||
|
||||
export const CarrierCreatePage = observer(() => {
|
||||
const navigate = useNavigate();
|
||||
const { language } = languageStore;
|
||||
|
||||
const [fullName, setFullName] = useState("");
|
||||
const [shortName, setShortName] = useState("");
|
||||
const [cityId, setCityId] = useState<number | null>(null);
|
||||
|
@ -12,9 +12,9 @@ import { ArrowLeft, Save } from "lucide-react";
|
||||
import { Loader2 } from "lucide-react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { toast } from "react-toastify";
|
||||
import { carrierStore, cityStore, mediaStore } from "@shared";
|
||||
import { carrierStore, cityStore, mediaStore, languageStore } from "@shared";
|
||||
import { useState, useEffect } from "react";
|
||||
import { MediaViewer, ImageUploadCard } from "@widgets";
|
||||
import { ImageUploadCard, LanguageSwitcher } from "@widgets";
|
||||
import {
|
||||
SelectMediaDialog,
|
||||
UploadMediaDialog,
|
||||
@ -24,8 +24,8 @@ import {
|
||||
export const CarrierEditPage = observer(() => {
|
||||
const navigate = useNavigate();
|
||||
const { id } = useParams();
|
||||
const { carrier, getCarrier, setEditCarrierData, editCarrierData } =
|
||||
carrierStore;
|
||||
const { getCarrier, setEditCarrierData, editCarrierData } = carrierStore;
|
||||
const { language } = languageStore;
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isSelectMediaOpen, setIsSelectMediaOpen] = useState(false);
|
||||
@ -41,16 +41,34 @@ export const CarrierEditPage = observer(() => {
|
||||
await cityStore.getCities("ru");
|
||||
await cityStore.getCities("en");
|
||||
await cityStore.getCities("zh");
|
||||
await getCarrier(Number(id));
|
||||
const carrierData = await getCarrier(Number(id));
|
||||
|
||||
setEditCarrierData(
|
||||
carrier?.[Number(id)]?.full_name as string,
|
||||
carrier?.[Number(id)]?.short_name as string,
|
||||
|
||||
carrier?.[Number(id)]?.city_id as number,
|
||||
carrier?.[Number(id)]?.slogan as string,
|
||||
carrier?.[Number(id)]?.logo as string
|
||||
);
|
||||
if (carrierData) {
|
||||
setEditCarrierData(
|
||||
carrierData.ru?.full_name || "",
|
||||
carrierData.ru?.short_name || "",
|
||||
carrierData.ru?.city_id || 0,
|
||||
carrierData.ru?.slogan || "",
|
||||
carrierData.ru?.logo || "",
|
||||
"ru"
|
||||
);
|
||||
setEditCarrierData(
|
||||
carrierData.en?.full_name || "",
|
||||
carrierData.en?.short_name || "",
|
||||
carrierData.en?.city_id || 0,
|
||||
carrierData.en?.slogan || "",
|
||||
carrierData.en?.logo || "",
|
||||
"en"
|
||||
);
|
||||
setEditCarrierData(
|
||||
carrierData.zh?.full_name || "",
|
||||
carrierData.zh?.short_name || "",
|
||||
carrierData.zh?.city_id || 0,
|
||||
carrierData.zh?.slogan || "",
|
||||
carrierData.zh?.logo || "",
|
||||
"zh"
|
||||
);
|
||||
}
|
||||
|
||||
mediaStore.getMedia();
|
||||
})();
|
||||
@ -76,12 +94,12 @@ export const CarrierEditPage = observer(() => {
|
||||
media_type: number;
|
||||
}) => {
|
||||
setEditCarrierData(
|
||||
editCarrierData.full_name,
|
||||
editCarrierData.short_name,
|
||||
|
||||
editCarrierData[language].full_name,
|
||||
editCarrierData[language].short_name,
|
||||
editCarrierData.city_id,
|
||||
editCarrierData.slogan,
|
||||
media.id
|
||||
editCarrierData[language].slogan,
|
||||
media.id,
|
||||
language
|
||||
);
|
||||
};
|
||||
|
||||
@ -91,6 +109,7 @@ export const CarrierEditPage = observer(() => {
|
||||
|
||||
return (
|
||||
<Paper className="w-full h-full p-3 flex flex-col gap-10">
|
||||
<LanguageSwitcher />
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
className="flex items-center gap-2"
|
||||
@ -101,6 +120,9 @@ export const CarrierEditPage = observer(() => {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-10 items-center mb-5 max-w-[80%] self-start">
|
||||
<h1 className="text-3xl break-words">{editCarrierData.ru.full_name}</h1>
|
||||
</div>
|
||||
<div className="flex flex-col gap-10 w-full items-end">
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Город</InputLabel>
|
||||
@ -110,15 +132,16 @@ export const CarrierEditPage = observer(() => {
|
||||
required
|
||||
onChange={(e) =>
|
||||
setEditCarrierData(
|
||||
editCarrierData.full_name,
|
||||
editCarrierData.short_name,
|
||||
editCarrierData[language].full_name,
|
||||
editCarrierData[language].short_name,
|
||||
Number(e.target.value),
|
||||
editCarrierData.slogan,
|
||||
editCarrierData.logo
|
||||
editCarrierData[language].slogan,
|
||||
editCarrierData.logo,
|
||||
language
|
||||
)
|
||||
}
|
||||
>
|
||||
{cityStore.cities.ru.data?.map((city) => (
|
||||
{cityStore.cities[language].data?.map((city) => (
|
||||
<MenuItem key={city.id} value={city.id}>
|
||||
{city.name}
|
||||
</MenuItem>
|
||||
@ -129,16 +152,16 @@ export const CarrierEditPage = observer(() => {
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Полное название"
|
||||
value={editCarrierData.full_name}
|
||||
value={editCarrierData[language].full_name}
|
||||
required
|
||||
onChange={(e) =>
|
||||
setEditCarrierData(
|
||||
e.target.value,
|
||||
editCarrierData.short_name,
|
||||
|
||||
editCarrierData[language].short_name,
|
||||
editCarrierData.city_id,
|
||||
editCarrierData.slogan,
|
||||
editCarrierData.logo
|
||||
editCarrierData[language].slogan,
|
||||
editCarrierData.logo,
|
||||
language
|
||||
)
|
||||
}
|
||||
/>
|
||||
@ -146,16 +169,16 @@ export const CarrierEditPage = observer(() => {
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Короткое название"
|
||||
value={editCarrierData.short_name}
|
||||
value={editCarrierData[language].short_name}
|
||||
required
|
||||
onChange={(e) =>
|
||||
setEditCarrierData(
|
||||
editCarrierData.full_name,
|
||||
editCarrierData[language].full_name,
|
||||
e.target.value,
|
||||
|
||||
editCarrierData.city_id,
|
||||
editCarrierData.slogan,
|
||||
editCarrierData.logo
|
||||
editCarrierData[language].slogan,
|
||||
editCarrierData.logo,
|
||||
language
|
||||
)
|
||||
}
|
||||
/>
|
||||
@ -163,15 +186,15 @@ export const CarrierEditPage = observer(() => {
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Слоган"
|
||||
value={editCarrierData.slogan}
|
||||
value={editCarrierData[language].slogan}
|
||||
onChange={(e) =>
|
||||
setEditCarrierData(
|
||||
editCarrierData.full_name,
|
||||
editCarrierData.short_name,
|
||||
|
||||
editCarrierData[language].full_name,
|
||||
editCarrierData[language].short_name,
|
||||
editCarrierData.city_id,
|
||||
e.target.value,
|
||||
editCarrierData.logo
|
||||
editCarrierData.logo,
|
||||
language
|
||||
)
|
||||
}
|
||||
/>
|
||||
@ -187,12 +210,12 @@ export const CarrierEditPage = observer(() => {
|
||||
}}
|
||||
onDeleteImageClick={() => {
|
||||
setEditCarrierData(
|
||||
editCarrierData.full_name,
|
||||
editCarrierData.short_name,
|
||||
|
||||
editCarrierData[language].full_name,
|
||||
editCarrierData[language].short_name,
|
||||
editCarrierData.city_id,
|
||||
editCarrierData.slogan,
|
||||
""
|
||||
editCarrierData[language].slogan,
|
||||
"",
|
||||
language
|
||||
);
|
||||
setActiveMenuType(null);
|
||||
}}
|
||||
@ -214,8 +237,8 @@ export const CarrierEditPage = observer(() => {
|
||||
onClick={handleEdit}
|
||||
disabled={
|
||||
isLoading ||
|
||||
!editCarrierData.full_name ||
|
||||
!editCarrierData.short_name ||
|
||||
!editCarrierData[language].full_name ||
|
||||
!editCarrierData[language].short_name ||
|
||||
!editCarrierData.city_id ||
|
||||
!editCarrierData.logo
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { DataGrid, GridColDef, GridRenderCellParams } from "@mui/x-data-grid";
|
||||
import { carrierStore } from "@shared";
|
||||
import { carrierStore, languageStore } from "@shared";
|
||||
import { useEffect, useState } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Eye, Pencil, Trash2, Minus } from "lucide-react";
|
||||
import { Pencil, Trash2, Minus } from "lucide-react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { CreateButton, DeleteModal } from "@widgets";
|
||||
import { CreateButton, DeleteModal, LanguageSwitcher } from "@widgets";
|
||||
|
||||
export const CarrierListPage = observer(() => {
|
||||
const { carriers, getCarriers, deleteCarrier } = carrierStore;
|
||||
@ -13,10 +13,13 @@ export const CarrierListPage = observer(() => {
|
||||
const [isBulkDeleteModalOpen, setIsBulkDeleteModalOpen] = useState(false);
|
||||
const [rowId, setRowId] = useState<number | null>(null);
|
||||
const [ids, setIds] = useState<number[]>([]);
|
||||
const { language } = languageStore;
|
||||
|
||||
useEffect(() => {
|
||||
getCarriers();
|
||||
}, []);
|
||||
(async () => {
|
||||
await getCarriers(language);
|
||||
})();
|
||||
}, [language]);
|
||||
|
||||
const columns: GridColDef[] = [
|
||||
{
|
||||
@ -96,7 +99,7 @@ export const CarrierListPage = observer(() => {
|
||||
},
|
||||
];
|
||||
|
||||
const rows = carriers.data?.map((carrier) => ({
|
||||
const rows = carriers[language].data?.map((carrier) => ({
|
||||
id: carrier.id,
|
||||
full_name: carrier.full_name,
|
||||
short_name: carrier.short_name,
|
||||
@ -105,6 +108,7 @@ export const CarrierListPage = observer(() => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<LanguageSwitcher />
|
||||
<div className="w-full">
|
||||
<div className="flex justify-between items-center mb-10">
|
||||
<h1 className="text-2xl">Перевозчики</h1>
|
||||
@ -155,7 +159,7 @@ export const CarrierListPage = observer(() => {
|
||||
open={isBulkDeleteModalOpen}
|
||||
onDelete={async () => {
|
||||
await Promise.all(ids.map((id) => deleteCarrier(id)));
|
||||
getCarriers();
|
||||
await getCarriers(language);
|
||||
setIsBulkDeleteModalOpen(false);
|
||||
setIds([]);
|
||||
}}
|
||||
|
@ -1,116 +0,0 @@
|
||||
import { Paper } from "@mui/material";
|
||||
import { carrierStore, mediaStore } from "@shared";
|
||||
import { MediaViewer } from "@widgets";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { ArrowLeft } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
|
||||
export const CarrierPreviewPage = observer(() => {
|
||||
const { id } = useParams();
|
||||
const { getCarrier, carrier, setEditCarrierData } = carrierStore;
|
||||
const { oneMedia, getOneMedia } = mediaStore;
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const carrierResponse = await getCarrier(Number(id));
|
||||
setEditCarrierData(
|
||||
carrierResponse?.full_name as string,
|
||||
carrierResponse?.short_name as string,
|
||||
carrierResponse?.city as string,
|
||||
carrierResponse?.city_id as number,
|
||||
// carrierResponse?.main_color as string,
|
||||
// carrierResponse?.left_color as string,
|
||||
// carrierResponse?.right_color as string,
|
||||
carrierResponse?.slogan as string,
|
||||
carrierResponse?.logo as string
|
||||
);
|
||||
console.log(carrierResponse);
|
||||
await getOneMedia(carrierResponse?.logo as string);
|
||||
})();
|
||||
}, [id]);
|
||||
|
||||
return (
|
||||
<Paper className="w-full h-full p-3 flex flex-col gap-10">
|
||||
{carrier && (
|
||||
<>
|
||||
<div className="flex justify-between items-center">
|
||||
<button
|
||||
className="flex items-center gap-2"
|
||||
onClick={() => navigate(-1)}
|
||||
>
|
||||
<ArrowLeft size={20} />
|
||||
Назад
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex flex-col gap-10 w-full">
|
||||
<div className="flex flex-col gap-2">
|
||||
<h1 className="text-lg font-bold">Полное имя</h1>
|
||||
<p>{carrier[Number(id)]?.full_name}</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<h1 className="text-lg font-bold">Полное имя</h1>
|
||||
<p>{carrier[Number(id)]?.full_name}</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<h1 className="text-lg font-bold">Город</h1>
|
||||
<p>{carrier[Number(id)]?.city}</p>
|
||||
</div>
|
||||
{/* <div className="flex flex-col gap-2 ">
|
||||
<h1 className="text-lg font-bold">Основной цвет</h1>
|
||||
<div
|
||||
className="w-min"
|
||||
style={{
|
||||
backgroundColor: `${carrier[Number(id)]?.main_color}90`,
|
||||
}}
|
||||
>
|
||||
{carrier[Number(id)]?.main_color}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<h1 className="text-lg font-bold">Цвет левого виджета</h1>
|
||||
<div
|
||||
className="w-min"
|
||||
style={{
|
||||
backgroundColor: `${carrier[Number(id)]?.left_color}90`,
|
||||
}}
|
||||
>
|
||||
{carrier[Number(id)]?.left_color}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<h1 className="text-lg font-bold">Цвет правого виджета</h1>
|
||||
<div
|
||||
className="w-min"
|
||||
style={{
|
||||
backgroundColor: `${carrier[Number(id)]?.right_color}90`,
|
||||
}}
|
||||
>
|
||||
{carrier[Number(id)]?.right_color}
|
||||
</div>
|
||||
</div> */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<h1 className="text-lg font-bold">Краткое имя</h1>
|
||||
<p>{carrier[Number(id)]?.short_name}</p>
|
||||
</div>
|
||||
{oneMedia && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<h1 className="text-lg font-bold">Логотип</h1>
|
||||
|
||||
<MediaViewer
|
||||
media={{
|
||||
id: oneMedia?.id as string,
|
||||
media_type: oneMedia?.media_type as number,
|
||||
filename: oneMedia?.filename,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Paper>
|
||||
);
|
||||
});
|
@ -1,4 +1,4 @@
|
||||
export * from "./CarrierListPage";
|
||||
export * from "./CarrierPreviewPage";
|
||||
|
||||
export * from "./CarrierCreatePage";
|
||||
export * from "./CarrierEditPage";
|
||||
|
@ -6,16 +6,15 @@ import {
|
||||
MenuItem,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
Box,
|
||||
} from "@mui/material";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { ArrowLeft, Save, ImagePlus, Minus } from "lucide-react";
|
||||
import { ArrowLeft, Save, Minus } from "lucide-react";
|
||||
import { Loader2 } from "lucide-react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { toast } from "react-toastify";
|
||||
import { cityStore, countryStore, languageStore, mediaStore } from "@shared";
|
||||
import { useState, useEffect } from "react";
|
||||
import { LanguageSwitcher, MediaViewer, ImageUploadCard } from "@widgets";
|
||||
import { LanguageSwitcher, ImageUploadCard } from "@widgets";
|
||||
import {
|
||||
SelectMediaDialog,
|
||||
UploadMediaDialog,
|
||||
@ -91,8 +90,8 @@ export const CityCreatePage = observer(() => {
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-10 w-full items-end">
|
||||
<div className="flex gap-10 items-center mb-5 max-w-[80%]">
|
||||
<h1 className="text-3xl break-words">Создание города</h1>
|
||||
<div className="flex gap-10 items-center mb-5 max-w-[80%] self-start">
|
||||
<h1 className="text-3xl break-words">{createCityData.ru.name}</h1>
|
||||
</div>
|
||||
<TextField
|
||||
fullWidth
|
||||
|
@ -6,10 +6,9 @@ import {
|
||||
MenuItem,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
Box,
|
||||
} from "@mui/material";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { ArrowLeft, Save, ImagePlus } from "lucide-react";
|
||||
import { ArrowLeft, Save } from "lucide-react";
|
||||
import { Loader2 } from "lucide-react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { toast } from "react-toastify";
|
||||
@ -21,7 +20,7 @@ import {
|
||||
CashedCities,
|
||||
} from "@shared";
|
||||
import { useEffect, useState } from "react";
|
||||
import { LanguageSwitcher, MediaViewer, ImageUploadCard } from "@widgets";
|
||||
import { LanguageSwitcher, ImageUploadCard } from "@widgets";
|
||||
import {
|
||||
SelectMediaDialog,
|
||||
UploadMediaDialog,
|
||||
@ -70,7 +69,7 @@ export const CityEditPage = observer(() => {
|
||||
setEditCityData(zhData.name, zhData.country_code, zhData.arms, "zh");
|
||||
|
||||
await getOneMedia(ruData.arms as string);
|
||||
await getCountries(language);
|
||||
await getCountries("ru");
|
||||
await getMedia();
|
||||
}
|
||||
})();
|
||||
@ -108,7 +107,7 @@ export const CityEditPage = observer(() => {
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-10 w-full items-end">
|
||||
<div className="flex gap-10 items-center mb-5 max-w-[80%]">
|
||||
<div className="flex gap-10 items-center mb-5 max-w-[80%] self-start ">
|
||||
<h1 className="text-3xl break-words">{editCityData.ru.name}</h1>
|
||||
</div>
|
||||
<TextField
|
||||
@ -141,7 +140,7 @@ export const CityEditPage = observer(() => {
|
||||
);
|
||||
}}
|
||||
>
|
||||
{countryStore.countries[language].data.map((country) => (
|
||||
{countryStore.countries.ru.data.map((country) => (
|
||||
<MenuItem key={country.code} value={country.code}>
|
||||
{country.name}
|
||||
</MenuItem>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { DataGrid, GridColDef, GridRenderCellParams } from "@mui/x-data-grid";
|
||||
import { languageStore, cityStore, CashedCities } from "@shared";
|
||||
import { languageStore, cityStore } from "@shared";
|
||||
import { useEffect, useState } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Eye, Pencil, Trash2, Minus } from "lucide-react";
|
||||
|
@ -41,8 +41,8 @@ export const CountryCreatePage = observer(() => {
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-10 w-full items-end">
|
||||
<div className="flex gap-10 items-center mb-5 max-w-[80%]">
|
||||
<h1 className="text-3xl break-words">Создание страны</h1>
|
||||
<div className="flex gap-10 items-center mb-5 max-w-[80%] self-start">
|
||||
<h1 className="text-3xl break-words">{createCountryData.ru.name}</h1>
|
||||
</div>
|
||||
<TextField
|
||||
fullWidth
|
||||
|
@ -58,8 +58,8 @@ export const CountryEditPage = observer(() => {
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-10 w-full items-end">
|
||||
<div className="flex gap-10 items-center mb-5 max-w-[80%]">
|
||||
<h1 className="text-3xl break-words">{editCountryData.ru.name}</h1>
|
||||
<div className="flex gap-10 items-center mb-5 max-w-[80%] self-start">
|
||||
<h1 className="text-3xl break-words t">{editCountryData.ru.name}</h1>
|
||||
</div>
|
||||
<TextField
|
||||
fullWidth
|
||||
|
@ -3,12 +3,7 @@ import { InformationTab, LeaveAgree, RightWidgetTab } from "@widgets";
|
||||
import { LeftWidgetTab } from "@widgets";
|
||||
import { useEffect, useState } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import {
|
||||
articlesStore,
|
||||
cityStore,
|
||||
editSightStore,
|
||||
languageStore,
|
||||
} from "@shared";
|
||||
import { articlesStore, cityStore, editSightStore } from "@shared";
|
||||
import { useBlocker, useParams } from "react-router-dom";
|
||||
|
||||
function a11yProps(index: number) {
|
||||
@ -22,7 +17,7 @@ export const EditSightPage = observer(() => {
|
||||
const [value, setValue] = useState(0);
|
||||
const { sight, getSightInfo, needLeaveAgree } = editSightStore;
|
||||
const { getArticles } = articlesStore;
|
||||
const { language } = languageStore;
|
||||
|
||||
const { id } = useParams();
|
||||
const { getRuCities } = cityStore;
|
||||
|
||||
|
@ -3,7 +3,6 @@ import React, {
|
||||
useRef,
|
||||
useState,
|
||||
useCallback,
|
||||
ReactNode,
|
||||
useMemo,
|
||||
} from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
@ -33,7 +32,6 @@ import {
|
||||
ArrowRightLeft,
|
||||
Landmark,
|
||||
Pencil,
|
||||
Lasso,
|
||||
InfoIcon,
|
||||
X,
|
||||
Loader2,
|
||||
@ -86,9 +84,7 @@ class MapStore {
|
||||
for (const id of routesIds) {
|
||||
const route = await languageInstance("ru").get(`/route/${id}`);
|
||||
this.routes.push({
|
||||
id: route.data.id,
|
||||
route_number: route.data.route_number,
|
||||
path: route.data.path,
|
||||
...route.data,
|
||||
});
|
||||
}
|
||||
|
||||
@ -100,21 +96,14 @@ class MapStore {
|
||||
getStations = async () => {
|
||||
const stations = await languageInstance("ru").get("/station");
|
||||
this.stations = stations.data.map((station: any) => ({
|
||||
id: station.id,
|
||||
name: station.name,
|
||||
latitude: station.latitude,
|
||||
longitude: station.longitude,
|
||||
...station,
|
||||
}));
|
||||
};
|
||||
|
||||
getSights = async () => {
|
||||
const sights = await languageInstance("ru").get("/sight");
|
||||
this.sights = sights.data.map((sight: any) => ({
|
||||
id: sight.id,
|
||||
name: sight.name,
|
||||
description: sight.description,
|
||||
latitude: sight.latitude,
|
||||
longitude: sight.longitude,
|
||||
...sight,
|
||||
}));
|
||||
};
|
||||
|
||||
@ -194,9 +183,25 @@ class MapStore {
|
||||
throw new Error(`Unknown feature type for update: ${featureType}`);
|
||||
}
|
||||
|
||||
let oldData;
|
||||
if (featureType === "route") {
|
||||
oldData = this.routes.find((f) => f.id === numericId);
|
||||
} else if (featureType === "station") {
|
||||
oldData = this.stations.find((f) => f.id === numericId);
|
||||
} else if (featureType === "sight") {
|
||||
oldData = this.sights.find((f) => f.id === numericId);
|
||||
}
|
||||
|
||||
console.log(oldData);
|
||||
console.log(data);
|
||||
|
||||
const response = await languageInstance("ru").patch(
|
||||
`/${featureType}/${numericId}`,
|
||||
data
|
||||
{
|
||||
...oldData,
|
||||
latitude: data.latitude,
|
||||
longitude: data.longitude,
|
||||
}
|
||||
);
|
||||
|
||||
if (featureType === "route") {
|
||||
@ -277,6 +282,7 @@ class MapService {
|
||||
private tooltipElement: HTMLElement;
|
||||
private tooltipOverlay: Overlay | null;
|
||||
private mode: string | null;
|
||||
// @ts-ignore
|
||||
private currentDrawingType: "Point" | "LineString" | null;
|
||||
private currentDrawingFeatureType: FeatureType | null;
|
||||
private currentInteraction: Draw | null;
|
||||
@ -620,7 +626,7 @@ class MapService {
|
||||
filter: (_: FeatureLike, l: Layer<Source, any> | null) =>
|
||||
l === this.vectorLayer,
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
this.modifyInteraction.on("modifystart", (event) => {
|
||||
const geoJSONFormat = new GeoJSON();
|
||||
if (!this.map) return;
|
||||
@ -959,7 +965,8 @@ class MapService {
|
||||
feature.set("name", `${baseName} ${maxNumber + 1}`);
|
||||
|
||||
await this.saveNewFeature(feature);
|
||||
this.stopDrawing();
|
||||
// Убираем вызов stopDrawing, чтобы режим рисования оставался активным
|
||||
// this.stopDrawing();
|
||||
});
|
||||
|
||||
this.map.addInteraction(this.currentInteraction);
|
||||
@ -988,7 +995,8 @@ class MapService {
|
||||
this.currentInteraction = null;
|
||||
this.currentDrawingType = null;
|
||||
this.currentDrawingFeatureType = null;
|
||||
this.activateEditMode();
|
||||
// Убираем автоматическое переключение в режим редактирования
|
||||
// this.activateEditMode();
|
||||
}
|
||||
|
||||
public finishDrawing(): void {
|
||||
@ -1007,6 +1015,10 @@ class MapService {
|
||||
this.currentInteraction instanceof Draw
|
||||
) {
|
||||
this.finishDrawing();
|
||||
// После завершения рисования маршрута, останавливаем режим рисования
|
||||
if (this.currentDrawingType === "LineString") {
|
||||
this.stopDrawing();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1098,6 +1110,7 @@ class MapService {
|
||||
if (this.mode === "edit") {
|
||||
this.selectInteraction.getFeatures().clear();
|
||||
this.selectInteraction.getFeatures().push(feature);
|
||||
// @ts-ignore
|
||||
const selectEvent = new SelectEvent("select", [feature], []);
|
||||
this.selectInteraction.dispatchEvent(selectEvent);
|
||||
}
|
||||
@ -1332,7 +1345,8 @@ class MapService {
|
||||
feature.set("name", newName);
|
||||
|
||||
this.updateFeaturesInReact();
|
||||
this.selectFeature(newFeatureId);
|
||||
// Убираем автоматический выбор созданного объекта
|
||||
// this.selectFeature(newFeatureId);
|
||||
} catch (error) {
|
||||
console.error("Failed to save new feature:", error);
|
||||
toast.error("Не удалось сохранить объект.");
|
||||
@ -1366,6 +1380,7 @@ interface ControlItem {
|
||||
const MapControls: React.FC<MapControlsProps> = ({
|
||||
mapService,
|
||||
activeMode,
|
||||
// @ts-ignore
|
||||
isLassoActive,
|
||||
isUnselectDisabled,
|
||||
}) => {
|
||||
@ -1473,11 +1488,13 @@ const MapSightbar: React.FC<MapSightbarProps> = ({
|
||||
}, [mapFeatures, searchQuery]);
|
||||
|
||||
const handleFeatureClick = useCallback(
|
||||
// @ts-ignore
|
||||
(id) => mapService?.selectFeature(id),
|
||||
[mapService]
|
||||
);
|
||||
|
||||
const handleDeleteFeature = useCallback(
|
||||
// @ts-ignore
|
||||
(id, recourse) => {
|
||||
if (
|
||||
mapService &&
|
||||
@ -1490,6 +1507,7 @@ const MapSightbar: React.FC<MapSightbarProps> = ({
|
||||
);
|
||||
|
||||
const handleCheckboxChange = useCallback(
|
||||
// @ts-ignore
|
||||
(id) => {
|
||||
if (id === undefined) return;
|
||||
const newSet = new Set(selectedIds);
|
||||
@ -1512,7 +1530,10 @@ const MapSightbar: React.FC<MapSightbarProps> = ({
|
||||
}
|
||||
}, [mapService, selectedIds, setSelectedIds]);
|
||||
|
||||
// @ts-ignore
|
||||
|
||||
const handleEditFeature = useCallback(
|
||||
// @ts-ignore
|
||||
(featureType, fullId) => {
|
||||
if (!featureType || !fullId) return;
|
||||
const numericId = String(fullId).split("-")[1];
|
||||
@ -1522,8 +1543,11 @@ const MapSightbar: React.FC<MapSightbarProps> = ({
|
||||
);
|
||||
|
||||
const sortFeatures = (
|
||||
// @ts-ignore
|
||||
features,
|
||||
// @ts-ignore
|
||||
currentSelectedIds,
|
||||
// @ts-ignore
|
||||
currentSelectedFeature
|
||||
) => {
|
||||
const selectedId = currentSelectedFeature?.getId();
|
||||
|
@ -6,7 +6,7 @@ import {
|
||||
MenuItem,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
Typography,
|
||||
// Typography,
|
||||
Box,
|
||||
} from "@mui/material";
|
||||
import { LanguageSwitcher } from "@widgets";
|
||||
@ -18,6 +18,7 @@ import { toast } from "react-toastify";
|
||||
import { carrierStore } from "../../../shared/store/CarrierStore";
|
||||
import { articlesStore } from "../../../shared/store/ArticlesStore";
|
||||
import { Route, routeStore } from "../../../shared/store/RouteStore";
|
||||
import { languageStore } from "@shared";
|
||||
|
||||
export const RouteCreatePage = observer(() => {
|
||||
const navigate = useNavigate();
|
||||
@ -33,11 +34,11 @@ export const RouteCreatePage = observer(() => {
|
||||
const [centerLat, setCenterLat] = useState("");
|
||||
const [centerLng, setCenterLng] = useState("");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const { language } = languageStore;
|
||||
useEffect(() => {
|
||||
carrierStore.getCarriers();
|
||||
carrierStore.getCarriers(language);
|
||||
articlesStore.getArticleList();
|
||||
}, []);
|
||||
}, [language]);
|
||||
|
||||
const handleCreateRoute = async () => {
|
||||
try {
|
||||
@ -65,8 +66,9 @@ export const RouteCreatePage = observer(() => {
|
||||
// Собираем объект маршрута
|
||||
const newRoute: Partial<Route> = {
|
||||
carrier:
|
||||
carrierStore.carriers.data.find((c: any) => c.id === carrier_id)
|
||||
?.full_name || "",
|
||||
carrierStore.carriers[
|
||||
language as keyof typeof carrierStore.carriers
|
||||
].data?.find((c: any) => c.id === carrier_id)?.full_name || "",
|
||||
carrier_id,
|
||||
route_number: routeNumber,
|
||||
route_sys_number: govRouteNumber,
|
||||
@ -112,16 +114,20 @@ export const RouteCreatePage = observer(() => {
|
||||
value={carrier}
|
||||
label="Выберите перевозчика"
|
||||
onChange={(e) => setCarrier(e.target.value as string)}
|
||||
disabled={carrierStore.carriers.data.length === 0}
|
||||
disabled={
|
||||
carrierStore.carriers[
|
||||
language as keyof typeof carrierStore.carriers
|
||||
].data?.length === 0
|
||||
}
|
||||
>
|
||||
<MenuItem value="">Не выбрано</MenuItem>
|
||||
{carrierStore.carriers.data.map(
|
||||
(c: (typeof carrierStore.carriers.data)[number]) => (
|
||||
<MenuItem key={c.id} value={c.id}>
|
||||
{c.full_name}
|
||||
</MenuItem>
|
||||
)
|
||||
)}
|
||||
{carrierStore.carriers[
|
||||
language as keyof typeof carrierStore.carriers
|
||||
].data?.map((carrier) => (
|
||||
<MenuItem key={carrier.id} value={carrier.id}>
|
||||
{carrier.full_name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<TextField
|
||||
|
@ -6,7 +6,7 @@ import {
|
||||
MenuItem,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
Typography,
|
||||
// Typography,
|
||||
Box,
|
||||
} from "@mui/material";
|
||||
import { LanguageSwitcher } from "@widgets";
|
||||
@ -19,22 +19,23 @@ import { carrierStore } from "../../../shared/store/CarrierStore";
|
||||
import { articlesStore } from "../../../shared/store/ArticlesStore";
|
||||
import { routeStore } from "../../../shared/store/RouteStore";
|
||||
import { toast } from "react-toastify";
|
||||
import { languageStore } from "@shared";
|
||||
|
||||
export const RouteEditPage = observer(() => {
|
||||
const navigate = useNavigate();
|
||||
const { id } = useParams();
|
||||
const { editRouteData } = routeStore;
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const { language } = languageStore;
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
const response = await routeStore.getRoute(Number(id));
|
||||
routeStore.setEditRouteData(response);
|
||||
carrierStore.getCarriers();
|
||||
carrierStore.getCarriers(language);
|
||||
articlesStore.getArticleList();
|
||||
};
|
||||
fetchData();
|
||||
}, [id]);
|
||||
}, [id, language]);
|
||||
|
||||
const handleSave = async () => {
|
||||
setIsLoading(true);
|
||||
@ -67,21 +68,26 @@ export const RouteEditPage = observer(() => {
|
||||
routeStore.setEditRouteData({
|
||||
carrier_id: Number(e.target.value),
|
||||
carrier:
|
||||
carrierStore.carriers.data.find(
|
||||
(c) => c.id === Number(e.target.value)
|
||||
)?.full_name || "",
|
||||
carrierStore.carriers[
|
||||
language as keyof typeof carrierStore.carriers
|
||||
].data?.find((c) => c.id === Number(e.target.value))
|
||||
?.full_name || "",
|
||||
})
|
||||
}
|
||||
disabled={carrierStore.carriers.data.length === 0}
|
||||
disabled={
|
||||
carrierStore.carriers[
|
||||
language as keyof typeof carrierStore.carriers
|
||||
].data?.length === 0
|
||||
}
|
||||
>
|
||||
<MenuItem value="">Не выбрано</MenuItem>
|
||||
{carrierStore.carriers.data.map(
|
||||
(c: (typeof carrierStore.carriers.data)[number]) => (
|
||||
<MenuItem key={c.id} value={c.id}>
|
||||
{c.full_name}
|
||||
</MenuItem>
|
||||
)
|
||||
)}
|
||||
{carrierStore.carriers[
|
||||
language as keyof typeof carrierStore.carriers
|
||||
].data?.map((carrier) => (
|
||||
<MenuItem key={carrier.id} value={carrier.id}>
|
||||
{carrier.full_name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<TextField
|
||||
|
@ -48,8 +48,8 @@ export const StationCreatePage = observer(() => {
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex flex-col gap-10 w-full items-end">
|
||||
<div className="flex gap-10 items-center mb-5 max-w-[80%]">
|
||||
<h1 className="text-3xl break-words">Создание станции</h1>
|
||||
<div className="flex gap-10 items-center mb-5 max-w-[80%] self-start">
|
||||
<h1 className="text-3xl break-words">{name}</h1>
|
||||
</div>
|
||||
<TextField
|
||||
className="w-full"
|
||||
|
@ -7,7 +7,12 @@ import {
|
||||
FormControl,
|
||||
InputLabel,
|
||||
} from "@mui/material";
|
||||
import { vehicleStore, VEHICLE_TYPES, carrierStore } from "@shared";
|
||||
import {
|
||||
vehicleStore,
|
||||
VEHICLE_TYPES,
|
||||
carrierStore,
|
||||
languageStore,
|
||||
} from "@shared";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { ArrowLeft, Save } from "lucide-react";
|
||||
import { Loader2 } from "lucide-react";
|
||||
@ -21,10 +26,11 @@ export const VehicleCreatePage = observer(() => {
|
||||
const [type, setType] = useState("");
|
||||
const [carrierId, setCarrierId] = useState<number | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { language } = languageStore;
|
||||
|
||||
useEffect(() => {
|
||||
carrierStore.getCarriers();
|
||||
}, []);
|
||||
carrierStore.getCarriers(language);
|
||||
}, [language]);
|
||||
|
||||
const handleCreate = async () => {
|
||||
try {
|
||||
@ -32,7 +38,8 @@ export const VehicleCreatePage = observer(() => {
|
||||
await vehicleStore.createVehicle(
|
||||
Number(tailNumber),
|
||||
Number(type),
|
||||
carrierStore.carriers.data.find((c) => c.id === carrierId)?.full_name!,
|
||||
carrierStore.carriers[language].data?.find((c) => c.id === carrierId)
|
||||
?.full_name as string,
|
||||
carrierId!
|
||||
);
|
||||
toast.success("Транспорт успешно создан");
|
||||
@ -88,7 +95,7 @@ export const VehicleCreatePage = observer(() => {
|
||||
required
|
||||
onChange={(e) => setCarrierId(e.target.value as number)}
|
||||
>
|
||||
{carrierStore.carriers.data.map((carrier) => (
|
||||
{carrierStore.carriers[language].data?.map((carrier) => (
|
||||
<MenuItem key={carrier.id} value={carrier.id}>
|
||||
{carrier.full_name}
|
||||
</MenuItem>
|
||||
|
@ -11,7 +11,12 @@ import { observer } from "mobx-react-lite";
|
||||
import { ArrowLeft, Loader2, Save } from "lucide-react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { useEffect, useState } from "react";
|
||||
import { carrierStore, VEHICLE_TYPES, vehicleStore } from "@shared";
|
||||
import {
|
||||
carrierStore,
|
||||
languageStore,
|
||||
VEHICLE_TYPES,
|
||||
vehicleStore,
|
||||
} from "@shared";
|
||||
import { toast } from "react-toastify";
|
||||
|
||||
export const VehicleEditPage = observer(() => {
|
||||
@ -25,11 +30,12 @@ export const VehicleEditPage = observer(() => {
|
||||
editVehicle,
|
||||
} = vehicleStore;
|
||||
const { getCarriers } = carrierStore;
|
||||
|
||||
const { language } = languageStore;
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
await getVehicle(Number(id));
|
||||
await getCarriers();
|
||||
await getCarriers(language);
|
||||
|
||||
setEditVehicleData({
|
||||
tail_number: vehicle[Number(id)]?.vehicle.tail_number,
|
||||
type: vehicle[Number(id)]?.vehicle.type,
|
||||
@ -37,7 +43,7 @@ export const VehicleEditPage = observer(() => {
|
||||
carrier_id: vehicle[Number(id)]?.vehicle.carrier_id,
|
||||
});
|
||||
})();
|
||||
}, [id]);
|
||||
}, [id, language]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const handleEdit = async () => {
|
||||
try {
|
||||
@ -108,7 +114,7 @@ export const VehicleEditPage = observer(() => {
|
||||
})
|
||||
}
|
||||
>
|
||||
{carrierStore.carriers.data.map((carrier) => (
|
||||
{carrierStore.carriers[language].data?.map((carrier) => (
|
||||
<MenuItem key={carrier.id} value={carrier.id}>
|
||||
{carrier.full_name}
|
||||
</MenuItem>
|
||||
|
@ -19,7 +19,7 @@ export const VehicleListPage = observer(() => {
|
||||
|
||||
useEffect(() => {
|
||||
getVehicles();
|
||||
getCarriers();
|
||||
getCarriers(language);
|
||||
}, [language]);
|
||||
|
||||
const columns: GridColDef[] = [
|
||||
@ -123,7 +123,7 @@ export const VehicleListPage = observer(() => {
|
||||
tail_number: vehicle.vehicle.tail_number,
|
||||
type: vehicle.vehicle.type,
|
||||
carrier: vehicle.vehicle.carrier,
|
||||
city: carriers.data?.find(
|
||||
city: carriers[language].data?.find(
|
||||
(carrier) => carrier.id === vehicle.vehicle.carrier_id
|
||||
)?.city,
|
||||
}));
|
||||
|
@ -8,7 +8,7 @@ import {
|
||||
Earth,
|
||||
Landmark,
|
||||
GitBranch,
|
||||
Car,
|
||||
// Car,
|
||||
Table,
|
||||
Notebook,
|
||||
Split,
|
||||
|
@ -1,4 +1,10 @@
|
||||
import { authInstance, cityStore, languageStore } from "@shared";
|
||||
import {
|
||||
authInstance,
|
||||
cityStore,
|
||||
languageStore,
|
||||
languageInstance,
|
||||
Language,
|
||||
} from "@shared";
|
||||
import { makeAutoObservable, runInAction } from "mobx";
|
||||
|
||||
export type Carrier = {
|
||||
@ -14,17 +20,40 @@ export type Carrier = {
|
||||
// right_color: string;
|
||||
};
|
||||
|
||||
type Carriers = {
|
||||
type CarrierData = {
|
||||
data: Carrier[];
|
||||
loaded: boolean;
|
||||
};
|
||||
|
||||
type CashedCarrier = Record<number, Carrier>;
|
||||
type Carriers = {
|
||||
ru: CarrierData;
|
||||
en: CarrierData;
|
||||
zh: CarrierData;
|
||||
};
|
||||
|
||||
type CashedCarrier = Record<
|
||||
number,
|
||||
{
|
||||
ru: Carrier | null;
|
||||
en: Carrier | null;
|
||||
zh: Carrier | null;
|
||||
}
|
||||
>;
|
||||
|
||||
class CarrierStore {
|
||||
carriers: Carriers = {
|
||||
data: [],
|
||||
loaded: false,
|
||||
ru: {
|
||||
data: [],
|
||||
loaded: false,
|
||||
},
|
||||
en: {
|
||||
data: [],
|
||||
loaded: false,
|
||||
},
|
||||
zh: {
|
||||
data: [],
|
||||
loaded: false,
|
||||
},
|
||||
};
|
||||
carrier: CashedCarrier = {};
|
||||
|
||||
@ -32,14 +61,14 @@ class CarrierStore {
|
||||
makeAutoObservable(this);
|
||||
}
|
||||
|
||||
getCarriers = async () => {
|
||||
if (this.carriers.loaded) return;
|
||||
getCarriers = async (language: Language) => {
|
||||
if (this.carriers[language as keyof Carriers].loaded) return;
|
||||
|
||||
const response = await authInstance.get("/carrier");
|
||||
|
||||
runInAction(() => {
|
||||
this.carriers.data = response.data;
|
||||
this.carriers.loaded = true;
|
||||
this.carriers[language as keyof Carriers].data = response.data;
|
||||
this.carriers[language as keyof Carriers].loaded = true;
|
||||
});
|
||||
};
|
||||
|
||||
@ -47,116 +76,163 @@ class CarrierStore {
|
||||
await authInstance.delete(`/carrier/${id}`);
|
||||
|
||||
runInAction(() => {
|
||||
this.carriers.data = this.carriers.data.filter(
|
||||
(carrier) => carrier.id !== id
|
||||
);
|
||||
for (const language of ["ru", "en", "zh"] as const) {
|
||||
this.carriers[language].data = this.carriers[language].data.filter(
|
||||
(carrier: Carrier) => carrier.id !== id
|
||||
);
|
||||
}
|
||||
delete this.carrier[id];
|
||||
});
|
||||
};
|
||||
|
||||
getCarrier = async (id: number) => {
|
||||
if (this.carrier[id]) return;
|
||||
const response = await authInstance.get(`/carrier/${id}`);
|
||||
if (this.carrier[id]?.ru && this.carrier[id]?.en && this.carrier[id]?.zh)
|
||||
return;
|
||||
|
||||
const ruResponse = await languageInstance("ru").get(`/carrier/${id}`);
|
||||
const enResponse = await languageInstance("en").get(`/carrier/${id}`);
|
||||
const zhResponse = await languageInstance("zh").get(`/carrier/${id}`);
|
||||
|
||||
runInAction(() => {
|
||||
if (!this.carrier[id]) {
|
||||
this.carrier[id] = {
|
||||
id: 0,
|
||||
short_name: "",
|
||||
full_name: "",
|
||||
slogan: "",
|
||||
city: "",
|
||||
city_id: 0,
|
||||
logo: "",
|
||||
// main_color: "",
|
||||
// left_color: "",
|
||||
// right_color: "",
|
||||
ru: null,
|
||||
en: null,
|
||||
zh: null,
|
||||
};
|
||||
}
|
||||
this.carrier[id] = response.data;
|
||||
this.carrier[id].ru = ruResponse.data;
|
||||
this.carrier[id].en = enResponse.data;
|
||||
this.carrier[id].zh = zhResponse.data;
|
||||
});
|
||||
return response.data;
|
||||
return this.carrier[id];
|
||||
};
|
||||
|
||||
createCarrier = async (
|
||||
fullName: string,
|
||||
shortName: string,
|
||||
|
||||
cityId: number,
|
||||
// main_color: string,
|
||||
// left_color: string,
|
||||
// right_color: string,
|
||||
slogan: string,
|
||||
logoId: string
|
||||
) => {
|
||||
const response = await authInstance.post("/carrier", {
|
||||
const { language } = languageStore;
|
||||
const cityName =
|
||||
cityStore.cities[language].data.find((city) => city.id === cityId)
|
||||
?.name || "";
|
||||
|
||||
const response = await languageInstance(language).post("/carrier", {
|
||||
full_name: fullName,
|
||||
short_name: shortName,
|
||||
city: "",
|
||||
city: cityName,
|
||||
city_id: cityId,
|
||||
// main_color,
|
||||
// left_color,
|
||||
// right_color,
|
||||
slogan,
|
||||
logo: logoId,
|
||||
});
|
||||
|
||||
const carrierId = response.data.id;
|
||||
|
||||
// Create translations for other languages
|
||||
for (const lang of ["ru", "en", "zh"].filter((l) => l !== language)) {
|
||||
await languageInstance(lang as Language).patch(`/carrier/${carrierId}`, {
|
||||
full_name: fullName,
|
||||
short_name: shortName,
|
||||
city: cityName,
|
||||
city_id: cityId,
|
||||
slogan,
|
||||
logo: logoId,
|
||||
});
|
||||
}
|
||||
|
||||
runInAction(() => {
|
||||
this.carriers.data.push(response.data);
|
||||
for (const language of ["ru", "en", "zh"] as const) {
|
||||
this.carriers[language].data.push(response.data);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
editCarrierData = {
|
||||
full_name: "",
|
||||
short_name: "",
|
||||
ru: {
|
||||
full_name: "",
|
||||
short_name: "",
|
||||
|
||||
// main_color: "",
|
||||
// left_color: "",
|
||||
// right_color: "",
|
||||
slogan: "",
|
||||
},
|
||||
en: {
|
||||
full_name: "",
|
||||
short_name: "",
|
||||
|
||||
// main_color: "",
|
||||
// left_color: "",
|
||||
// right_color: "",
|
||||
slogan: "",
|
||||
},
|
||||
city_id: 0,
|
||||
// main_color: "",
|
||||
// left_color: "",
|
||||
// right_color: "",
|
||||
slogan: "",
|
||||
logo: "",
|
||||
zh: {
|
||||
full_name: "",
|
||||
short_name: "",
|
||||
|
||||
// main_color: "",
|
||||
// left_color: "",
|
||||
// right_color: "",
|
||||
slogan: "",
|
||||
},
|
||||
};
|
||||
|
||||
setEditCarrierData = (
|
||||
fullName: string,
|
||||
shortName: string,
|
||||
|
||||
cityId: number,
|
||||
// main_color: string,
|
||||
// left_color: string,
|
||||
// right_color: string,
|
||||
slogan: string,
|
||||
logoId: string
|
||||
logoId: string,
|
||||
language: Language
|
||||
) => {
|
||||
this.editCarrierData = {
|
||||
this.editCarrierData.city_id = cityId;
|
||||
this.editCarrierData.logo = logoId;
|
||||
this.editCarrierData[language] = {
|
||||
full_name: fullName,
|
||||
short_name: shortName,
|
||||
|
||||
city_id: cityId,
|
||||
// main_color: main_color,
|
||||
// left_color: left_color,
|
||||
// right_color: right_color,
|
||||
slogan: slogan,
|
||||
logo: logoId,
|
||||
};
|
||||
};
|
||||
|
||||
editCarrier = async (id: number) => {
|
||||
const { language } = languageStore;
|
||||
const response = await authInstance.patch(`/carrier/${id}`, {
|
||||
...this.editCarrierData,
|
||||
city: cityStore.cities[language].data.find(
|
||||
const cityName =
|
||||
cityStore.cities[languageStore.language].data.find(
|
||||
(city) => city.id === this.editCarrierData.city_id
|
||||
)?.name,
|
||||
});
|
||||
)?.name || "";
|
||||
|
||||
runInAction(() => {
|
||||
this.carriers.data = this.carriers.data.map((carrier) =>
|
||||
carrier.id === id ? { ...carrier, ...response.data } : carrier
|
||||
for (const language of ["ru", "en", "zh"] as const) {
|
||||
const response = await languageInstance(language).patch(
|
||||
`/carrier/${id}`,
|
||||
{
|
||||
...this.editCarrierData[language],
|
||||
city: cityName,
|
||||
logo: this.editCarrierData.logo,
|
||||
}
|
||||
);
|
||||
|
||||
this.carrier[id] = response.data;
|
||||
});
|
||||
runInAction(() => {
|
||||
if (this.carrier[id]) {
|
||||
this.carrier[id][language] = response.data;
|
||||
}
|
||||
for (const language of ["ru", "en", "zh"] as const) {
|
||||
this.carriers[language].data = this.carriers[language].data.map(
|
||||
(carrier: Carrier) =>
|
||||
carrier.id === id ? { ...carrier, ...response.data } : carrier
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -175,9 +175,10 @@ export const CreateInformationTab = observer(
|
||||
/>
|
||||
|
||||
<Autocomplete
|
||||
options={ruCities ?? []}
|
||||
options={ruCities.data ?? []}
|
||||
value={
|
||||
ruCities.find((city) => city.id === sight.city_id) ?? null
|
||||
ruCities.data.find((city) => city.id === sight.city_id) ??
|
||||
null
|
||||
}
|
||||
getOptionLabel={(option) => option.name}
|
||||
onChange={(_, value) => {
|
||||
|
@ -19,14 +19,7 @@ import {
|
||||
MediaViewer,
|
||||
DeleteModal,
|
||||
} from "@widgets";
|
||||
import {
|
||||
Trash2,
|
||||
ImagePlus,
|
||||
Unlink,
|
||||
MousePointer,
|
||||
Plus,
|
||||
Save,
|
||||
} from "lucide-react";
|
||||
import { Trash2, ImagePlus, Unlink, Plus, Save, Search } from "lucide-react";
|
||||
import { useState, useCallback } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { toast } from "react-toastify";
|
||||
@ -160,7 +153,7 @@ export const CreateLeftTab = observer(
|
||||
variant="contained"
|
||||
color="primary"
|
||||
size="small"
|
||||
startIcon={<MousePointer color="white" size={18} />}
|
||||
startIcon={<Search color="white" size={18} />}
|
||||
onClick={() => setIsSelectArticleDialogOpen(true)}
|
||||
>
|
||||
Выбрать статью
|
||||
|
@ -551,6 +551,7 @@ export const CreateRightTab = observer(
|
||||
media={
|
||||
sight[language].right[activeArticleIndex].media[0]
|
||||
}
|
||||
fullWidth
|
||||
/>
|
||||
</Box>
|
||||
) : (
|
||||
|
@ -18,14 +18,7 @@ import {
|
||||
MediaViewer,
|
||||
DeleteModal,
|
||||
} from "@widgets";
|
||||
import {
|
||||
Trash2,
|
||||
ImagePlus,
|
||||
Unlink,
|
||||
Plus,
|
||||
MousePointer,
|
||||
Save,
|
||||
} from "lucide-react";
|
||||
import { Trash2, ImagePlus, Unlink, Plus, Save, Search } from "lucide-react";
|
||||
import { useState, useCallback } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { toast } from "react-toastify";
|
||||
@ -175,7 +168,7 @@ export const LeftWidgetTab = observer(
|
||||
variant="contained"
|
||||
color="primary"
|
||||
size="small"
|
||||
startIcon={<MousePointer color="white" size={18} />}
|
||||
startIcon={<Search color="white" size={18} />}
|
||||
onClick={() => setIsSelectArticleDialogOpen(true)}
|
||||
>
|
||||
Выбрать статью
|
||||
|
@ -493,6 +493,7 @@ export const RightWidgetTab = observer(
|
||||
media={
|
||||
sight[language].right[activeArticleIndex].media[0]
|
||||
}
|
||||
fullWidth
|
||||
/>
|
||||
</Box>
|
||||
) : (
|
||||
|
@ -67,7 +67,7 @@ export const SightsTable = observer(() => {
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{rows(sights, cities[language])?.map((row) => (
|
||||
{rows(sights, cities[language]?.data ?? [])?.map((row) => (
|
||||
<TableRow
|
||||
key={row?.id}
|
||||
sx={{ "&:last-child td, &:last-child th": { border: 0 } }}
|
||||
|
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user