feat: Improving page loading
This commit is contained in:
@@ -1,9 +1,9 @@
|
|||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import { useEffect } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Box } from "@mui/material";
|
import { Box } from "@mui/material";
|
||||||
import { PreviewLeftWidget } from "./PreviewLeftWidget";
|
import { PreviewLeftWidget } from "./PreviewLeftWidget";
|
||||||
import { PreviewRightWidget } from "./PreviewRightWidget";
|
import { PreviewRightWidget } from "./PreviewRightWidget";
|
||||||
import { articlesStore, languageStore } from "@shared";
|
import { articlesStore, languageStore, LoadingSpinner } from "@shared";
|
||||||
import { ArrowLeft } from "lucide-react";
|
import { ArrowLeft } from "lucide-react";
|
||||||
|
|
||||||
export const ArticlePreviewPage = () => {
|
export const ArticlePreviewPage = () => {
|
||||||
@@ -11,18 +11,41 @@ export const ArticlePreviewPage = () => {
|
|||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
const { getArticle, getArticleMedia, getArticlePreview } = articlesStore;
|
const { getArticle, getArticleMedia, getArticlePreview } = articlesStore;
|
||||||
const { language } = languageStore;
|
const { language } = languageStore;
|
||||||
|
const [isLoadingData, setIsLoadingData] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
if (id) {
|
if (id) {
|
||||||
await getArticle(Number(id), language);
|
setIsLoadingData(true);
|
||||||
await getArticleMedia(Number(id));
|
try {
|
||||||
await getArticlePreview(Number(id));
|
await getArticle(Number(id), language);
|
||||||
|
await getArticleMedia(Number(id));
|
||||||
|
await getArticlePreview(Number(id));
|
||||||
|
} finally {
|
||||||
|
setIsLoadingData(false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setIsLoadingData(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
fetchData();
|
fetchData();
|
||||||
}, [id, language]);
|
}, [id, language]);
|
||||||
|
|
||||||
|
if (isLoadingData) {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
minHeight: "60vh",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<LoadingSpinner message="Загрузка данных статьи..." />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex items-center gap-4 mb-10">
|
<div className="flex items-center gap-4 mb-10">
|
||||||
|
|||||||
@@ -6,13 +6,20 @@ import {
|
|||||||
MenuItem,
|
MenuItem,
|
||||||
FormControl,
|
FormControl,
|
||||||
InputLabel,
|
InputLabel,
|
||||||
|
Box,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { ArrowLeft, Save } from "lucide-react";
|
import { ArrowLeft, Save } from "lucide-react";
|
||||||
import { Loader2 } from "lucide-react";
|
import { Loader2 } from "lucide-react";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import { carrierStore, cityStore, mediaStore, languageStore } from "@shared";
|
import {
|
||||||
|
carrierStore,
|
||||||
|
cityStore,
|
||||||
|
mediaStore,
|
||||||
|
languageStore,
|
||||||
|
LoadingSpinner,
|
||||||
|
} from "@shared";
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { ImageUploadCard, LanguageSwitcher, DeleteModal } from "@widgets";
|
import { ImageUploadCard, LanguageSwitcher, DeleteModal } from "@widgets";
|
||||||
import {
|
import {
|
||||||
@@ -28,6 +35,7 @@ export const CarrierEditPage = observer(() => {
|
|||||||
const { language } = languageStore;
|
const { language } = languageStore;
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [isLoadingData, setIsLoadingData] = useState(true);
|
||||||
const [isSelectMediaOpen, setIsSelectMediaOpen] = useState(false);
|
const [isSelectMediaOpen, setIsSelectMediaOpen] = useState(false);
|
||||||
const [isUploadMediaOpen, setIsUploadMediaOpen] = useState(false);
|
const [isUploadMediaOpen, setIsUploadMediaOpen] = useState(false);
|
||||||
const [isPreviewMediaOpen, setIsPreviewMediaOpen] = useState(false);
|
const [isPreviewMediaOpen, setIsPreviewMediaOpen] = useState(false);
|
||||||
@@ -39,39 +47,48 @@ export const CarrierEditPage = observer(() => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
await cityStore.getCities("ru");
|
if (!id) {
|
||||||
await cityStore.getCities("en");
|
setIsLoadingData(false);
|
||||||
await cityStore.getCities("zh");
|
return;
|
||||||
const carrierData = await getCarrier(Number(id));
|
|
||||||
|
|
||||||
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"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
setIsLoadingData(true);
|
||||||
|
try {
|
||||||
|
await cityStore.getCities("ru");
|
||||||
|
await cityStore.getCities("en");
|
||||||
|
await cityStore.getCities("zh");
|
||||||
|
const carrierData = await getCarrier(Number(id));
|
||||||
|
|
||||||
mediaStore.getMedia();
|
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"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await mediaStore.getMedia();
|
||||||
|
} finally {
|
||||||
|
setIsLoadingData(false);
|
||||||
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
languageStore.setLanguage("ru");
|
languageStore.setLanguage("ru");
|
||||||
@@ -110,6 +127,21 @@ export const CarrierEditPage = observer(() => {
|
|||||||
? mediaStore.media.find((m) => m.id === editCarrierData.logo)
|
? mediaStore.media.find((m) => m.id === editCarrierData.logo)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
if (isLoadingData) {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
minHeight: "60vh",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<LoadingSpinner message="Загрузка данных перевозчика..." />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper className="w-full h-full p-3 flex flex-col gap-10">
|
<Paper className="w-full h-full p-3 flex flex-col gap-10">
|
||||||
<LanguageSwitcher />
|
<LanguageSwitcher />
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
MenuItem,
|
MenuItem,
|
||||||
FormControl,
|
FormControl,
|
||||||
InputLabel,
|
InputLabel,
|
||||||
|
Box,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { ArrowLeft, Save } from "lucide-react";
|
import { ArrowLeft, Save } from "lucide-react";
|
||||||
@@ -18,6 +19,7 @@ import {
|
|||||||
languageStore,
|
languageStore,
|
||||||
mediaStore,
|
mediaStore,
|
||||||
CashedCities,
|
CashedCities,
|
||||||
|
LoadingSpinner,
|
||||||
} from "@shared";
|
} from "@shared";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { LanguageSwitcher, ImageUploadCard } from "@widgets";
|
import { LanguageSwitcher, ImageUploadCard } from "@widgets";
|
||||||
@@ -30,6 +32,7 @@ import {
|
|||||||
export const CityEditPage = observer(() => {
|
export const CityEditPage = observer(() => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [isLoadingData, setIsLoadingData] = useState(true);
|
||||||
const [isSelectMediaOpen, setIsSelectMediaOpen] = useState(false);
|
const [isSelectMediaOpen, setIsSelectMediaOpen] = useState(false);
|
||||||
const [isUploadMediaOpen, setIsUploadMediaOpen] = useState(false);
|
const [isUploadMediaOpen, setIsUploadMediaOpen] = useState(false);
|
||||||
const [isPreviewMediaOpen, setIsPreviewMediaOpen] = useState(false);
|
const [isPreviewMediaOpen, setIsPreviewMediaOpen] = useState(false);
|
||||||
@@ -62,19 +65,26 @@ export const CityEditPage = observer(() => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
if (id) {
|
if (id) {
|
||||||
await getCountries("ru");
|
setIsLoadingData(true);
|
||||||
|
try {
|
||||||
|
await getCountries("ru");
|
||||||
|
|
||||||
const ruData = await getCity(id as string, "ru");
|
const ruData = await getCity(id as string, "ru");
|
||||||
const enData = await getCity(id as string, "en");
|
const enData = await getCity(id as string, "en");
|
||||||
const zhData = await getCity(id as string, "zh");
|
const zhData = await getCity(id as string, "zh");
|
||||||
|
|
||||||
setEditCityData(ruData.name, ruData.country_code, ruData.arms, "ru");
|
setEditCityData(ruData.name, ruData.country_code, ruData.arms, "ru");
|
||||||
setEditCityData(enData.name, enData.country_code, enData.arms, "en");
|
setEditCityData(enData.name, enData.country_code, enData.arms, "en");
|
||||||
setEditCityData(zhData.name, zhData.country_code, zhData.arms, "zh");
|
setEditCityData(zhData.name, zhData.country_code, zhData.arms, "zh");
|
||||||
|
|
||||||
await getOneMedia(ruData.arms as string);
|
await getOneMedia(ruData.arms as string);
|
||||||
|
|
||||||
await getMedia();
|
await getMedia();
|
||||||
|
} finally {
|
||||||
|
setIsLoadingData(false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setIsLoadingData(false);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}, [id]);
|
}, [id]);
|
||||||
@@ -97,6 +107,21 @@ export const CityEditPage = observer(() => {
|
|||||||
? mediaStore.media.find((m) => m.id === editCityData.arms)
|
? mediaStore.media.find((m) => m.id === editCityData.arms)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
if (isLoadingData) {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
minHeight: "60vh",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<LoadingSpinner message="Загрузка данных города..." />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper className="w-full h-full p-3 flex flex-col gap-10">
|
<Paper className="w-full h-full p-3 flex flex-col gap-10">
|
||||||
<LanguageSwitcher />
|
<LanguageSwitcher />
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
import { Button, Paper, TextField } from "@mui/material";
|
import { Button, Paper, TextField, Box } from "@mui/material";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { ArrowLeft, Save } from "lucide-react";
|
import { ArrowLeft, Save } from "lucide-react";
|
||||||
import { Loader2 } from "lucide-react";
|
import { Loader2 } from "lucide-react";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import { countryStore, languageStore } from "@shared";
|
import { countryStore, languageStore, LoadingSpinner } from "@shared";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { LanguageSwitcher } from "@widgets";
|
import { LanguageSwitcher } from "@widgets";
|
||||||
|
|
||||||
export const CountryEditPage = observer(() => {
|
export const CountryEditPage = observer(() => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [isLoadingData, setIsLoadingData] = useState(true);
|
||||||
const { language } = languageStore;
|
const { language } = languageStore;
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
const { editCountryData, editCountry, getCountry, setEditCountryData } =
|
const { editCountryData, editCountry, getCountry, setEditCountryData } =
|
||||||
@@ -35,17 +36,39 @@ export const CountryEditPage = observer(() => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
if (id) {
|
if (id) {
|
||||||
const ruData = await getCountry(id as string, "ru");
|
setIsLoadingData(true);
|
||||||
const enData = await getCountry(id as string, "en");
|
try {
|
||||||
const zhData = await getCountry(id as string, "zh");
|
const ruData = await getCountry(id as string, "ru");
|
||||||
|
const enData = await getCountry(id as string, "en");
|
||||||
|
const zhData = await getCountry(id as string, "zh");
|
||||||
|
|
||||||
setEditCountryData(ruData.name, "ru");
|
setEditCountryData(ruData.name, "ru");
|
||||||
setEditCountryData(enData.name, "en");
|
setEditCountryData(enData.name, "en");
|
||||||
setEditCountryData(zhData.name, "zh");
|
setEditCountryData(zhData.name, "zh");
|
||||||
|
} finally {
|
||||||
|
setIsLoadingData(false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setIsLoadingData(false);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}, [id]);
|
}, [id]);
|
||||||
|
|
||||||
|
if (isLoadingData) {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
minHeight: "60vh",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<LoadingSpinner message="Загрузка данных страны..." />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper className="w-full h-full p-3 flex flex-col gap-10">
|
<Paper className="w-full h-full p-3 flex flex-col gap-10">
|
||||||
<LanguageSwitcher />
|
<LanguageSwitcher />
|
||||||
|
|||||||
@@ -3,7 +3,12 @@ import { InformationTab, LeaveAgree, RightWidgetTab } from "@widgets";
|
|||||||
import { LeftWidgetTab } from "@widgets";
|
import { LeftWidgetTab } from "@widgets";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { articlesStore, cityStore, editSightStore } from "@shared";
|
import {
|
||||||
|
articlesStore,
|
||||||
|
cityStore,
|
||||||
|
editSightStore,
|
||||||
|
LoadingSpinner,
|
||||||
|
} from "@shared";
|
||||||
import { useBlocker, useParams } from "react-router-dom";
|
import { useBlocker, useParams } from "react-router-dom";
|
||||||
|
|
||||||
function a11yProps(index: number) {
|
function a11yProps(index: number) {
|
||||||
@@ -15,6 +20,7 @@ function a11yProps(index: number) {
|
|||||||
|
|
||||||
export const EditSightPage = observer(() => {
|
export const EditSightPage = observer(() => {
|
||||||
const [value, setValue] = useState(0);
|
const [value, setValue] = useState(0);
|
||||||
|
const [isLoadingData, setIsLoadingData] = useState(true);
|
||||||
const { sight, getSightInfo, needLeaveAgree } = editSightStore;
|
const { sight, getSightInfo, needLeaveAgree } = editSightStore;
|
||||||
const { getArticles } = articlesStore;
|
const { getArticles } = articlesStore;
|
||||||
|
|
||||||
@@ -33,13 +39,20 @@ export const EditSightPage = observer(() => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
if (id) {
|
if (id) {
|
||||||
await getCities("ru");
|
setIsLoadingData(true);
|
||||||
await getSightInfo(+id, "ru");
|
try {
|
||||||
await getSightInfo(+id, "en");
|
await getCities("ru");
|
||||||
await getSightInfo(+id, "zh");
|
await getSightInfo(+id, "ru");
|
||||||
await getArticles("ru");
|
await getSightInfo(+id, "en");
|
||||||
await getArticles("en");
|
await getSightInfo(+id, "zh");
|
||||||
await getArticles("zh");
|
await getArticles("ru");
|
||||||
|
await getArticles("en");
|
||||||
|
await getArticles("zh");
|
||||||
|
} finally {
|
||||||
|
setIsLoadingData(false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setIsLoadingData(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
fetchData();
|
fetchData();
|
||||||
@@ -79,12 +92,25 @@ export const EditSightPage = observer(() => {
|
|||||||
</Tabs>
|
</Tabs>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{sight.common.id !== 0 && (
|
{isLoadingData ? (
|
||||||
<div className="flex-1">
|
<Box
|
||||||
<InformationTab value={value} index={0} />
|
sx={{
|
||||||
<LeftWidgetTab value={value} index={1} />
|
display: "flex",
|
||||||
<RightWidgetTab value={value} index={2} />
|
justifyContent: "center",
|
||||||
</div>
|
alignItems: "center",
|
||||||
|
minHeight: "60vh",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<LoadingSpinner message="Загрузка данных достопримечательности..." />
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
sight.common.id !== 0 && (
|
||||||
|
<div className="flex-1">
|
||||||
|
<InformationTab value={value} index={0} />
|
||||||
|
<LeftWidgetTab value={value} index={1} />
|
||||||
|
<RightWidgetTab value={value} index={2} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{blocker.state === "blocked" ? <LeaveAgree blocker={blocker} /> : null}
|
{blocker.state === "blocked" ? <LeaveAgree blocker={blocker} /> : null}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
mediaStore,
|
mediaStore,
|
||||||
MEDIA_TYPE_LABELS,
|
MEDIA_TYPE_LABELS,
|
||||||
languageStore,
|
languageStore,
|
||||||
|
LoadingSpinner,
|
||||||
} from "@shared";
|
} from "@shared";
|
||||||
import { MediaViewer } from "@widgets";
|
import { MediaViewer } from "@widgets";
|
||||||
|
|
||||||
@@ -138,8 +139,15 @@ export const MediaEditPage = observer(() => {
|
|||||||
|
|
||||||
if (!media && id) {
|
if (!media && id) {
|
||||||
return (
|
return (
|
||||||
<Box className="flex justify-center items-center h-screen">
|
<Box
|
||||||
<CircularProgress />
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
minHeight: "60vh",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<LoadingSpinner message="Загрузка данных медиа..." />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import {
|
|||||||
ArticleSelectOrCreateDialog,
|
ArticleSelectOrCreateDialog,
|
||||||
SelectMediaDialog,
|
SelectMediaDialog,
|
||||||
UploadMediaDialog,
|
UploadMediaDialog,
|
||||||
|
LoadingSpinner,
|
||||||
} from "@shared";
|
} from "@shared";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import { stationsStore } from "@shared";
|
import { stationsStore } from "@shared";
|
||||||
@@ -37,6 +38,7 @@ export const RouteEditPage = observer(() => {
|
|||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
const { editRouteData, copyRouteAction } = routeStore;
|
const { editRouteData, copyRouteAction } = routeStore;
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [isLoadingData, setIsLoadingData] = useState(true);
|
||||||
const [isSelectArticleDialogOpen, setIsSelectArticleDialogOpen] =
|
const [isSelectArticleDialogOpen, setIsSelectArticleDialogOpen] =
|
||||||
useState(false);
|
useState(false);
|
||||||
const [isSelectVideoDialogOpen, setIsSelectVideoDialogOpen] = useState(false);
|
const [isSelectVideoDialogOpen, setIsSelectVideoDialogOpen] = useState(false);
|
||||||
@@ -48,18 +50,27 @@ export const RouteEditPage = observer(() => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
const response = await routeStore.getRoute(Number(id));
|
if (!id) {
|
||||||
routeStore.setEditRouteData(response);
|
setIsLoadingData(false);
|
||||||
languageStore.setLanguage("ru");
|
return;
|
||||||
|
}
|
||||||
|
setIsLoadingData(true);
|
||||||
|
try {
|
||||||
|
const response = await routeStore.getRoute(Number(id));
|
||||||
|
routeStore.setEditRouteData(response);
|
||||||
|
languageStore.setLanguage("ru");
|
||||||
|
} finally {
|
||||||
|
setIsLoadingData(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
fetchData();
|
fetchData();
|
||||||
}, []);
|
}, [id]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
carrierStore.getCarriers(language);
|
await carrierStore.getCarriers(language);
|
||||||
stationsStore.getStations();
|
await stationsStore.getStations();
|
||||||
articlesStore.getArticleList();
|
await articlesStore.getArticleList();
|
||||||
};
|
};
|
||||||
fetchData();
|
fetchData();
|
||||||
}, [id, language]);
|
}, [id, language]);
|
||||||
@@ -233,6 +244,21 @@ export const RouteEditPage = observer(() => {
|
|||||||
(article) => article.id === editRouteData.governor_appeal
|
(article) => article.id === editRouteData.governor_appeal
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (isLoadingData) {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
minHeight: "60vh",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<LoadingSpinner message="Загрузка данных маршрута..." />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper className="w-full h-full p-3 flex flex-col gap-10">
|
<Paper className="w-full h-full p-3 flex flex-col gap-10">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
|
|||||||
@@ -6,13 +6,19 @@ import {
|
|||||||
MenuItem,
|
MenuItem,
|
||||||
FormControl,
|
FormControl,
|
||||||
InputLabel,
|
InputLabel,
|
||||||
|
Box,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { ArrowLeft, Save } from "lucide-react";
|
import { ArrowLeft, Save } from "lucide-react";
|
||||||
import { Loader2 } from "lucide-react";
|
import { Loader2 } from "lucide-react";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import { stationsStore, languageStore, cityStore } from "@shared";
|
import {
|
||||||
|
stationsStore,
|
||||||
|
languageStore,
|
||||||
|
cityStore,
|
||||||
|
LoadingSpinner,
|
||||||
|
} from "@shared";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { LanguageSwitcher } from "@widgets";
|
import { LanguageSwitcher } from "@widgets";
|
||||||
import { LinkedSights } from "../LinkedSights";
|
import { LinkedSights } from "../LinkedSights";
|
||||||
@@ -21,6 +27,7 @@ import { SaveWithoutCityAgree } from "@widgets";
|
|||||||
export const StationEditPage = observer(() => {
|
export const StationEditPage = observer(() => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [isLoadingData, setIsLoadingData] = useState(true);
|
||||||
const { language } = languageStore;
|
const { language } = languageStore;
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
const {
|
const {
|
||||||
@@ -90,18 +97,41 @@ export const StationEditPage = observer(() => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchAndSetStationData = async () => {
|
const fetchAndSetStationData = async () => {
|
||||||
if (!id) return;
|
if (!id) {
|
||||||
|
setIsLoadingData(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const stationId = Number(id);
|
setIsLoadingData(true);
|
||||||
await getEditStation(stationId);
|
try {
|
||||||
await getCities("ru");
|
const stationId = Number(id);
|
||||||
await getCities("en");
|
await getEditStation(stationId);
|
||||||
await getCities("zh");
|
await getCities("ru");
|
||||||
|
await getCities("en");
|
||||||
|
await getCities("zh");
|
||||||
|
} finally {
|
||||||
|
setIsLoadingData(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchAndSetStationData();
|
fetchAndSetStationData();
|
||||||
}, [id]);
|
}, [id]);
|
||||||
|
|
||||||
|
if (isLoadingData) {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
minHeight: "60vh",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<LoadingSpinner message="Загрузка данных станции..." />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper className="w-full h-full p-3 flex flex-col gap-10">
|
<Paper className="w-full h-full p-3 flex flex-col gap-10">
|
||||||
<LanguageSwitcher />
|
<LanguageSwitcher />
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { Paper } from "@mui/material";
|
import { Paper, Box } from "@mui/material";
|
||||||
import { languageStore, stationsStore } from "@shared";
|
import { languageStore, stationsStore, LoadingSpinner } from "@shared";
|
||||||
import { LanguageSwitcher } from "@widgets";
|
import { LanguageSwitcher } from "@widgets";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { ArrowLeft } from "lucide-react";
|
import { ArrowLeft } from "lucide-react";
|
||||||
import { useEffect } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import { LinkedSights } from "../LinkedSights";
|
import { LinkedSights } from "../LinkedSights";
|
||||||
|
|
||||||
@@ -12,15 +12,38 @@ export const StationPreviewPage = observer(() => {
|
|||||||
const { stationPreview, getStationPreview } = stationsStore;
|
const { stationPreview, getStationPreview } = stationsStore;
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { language } = languageStore;
|
const { language } = languageStore;
|
||||||
|
const [isLoadingData, setIsLoadingData] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
if (id) {
|
if (id) {
|
||||||
await getStationPreview(Number(id));
|
setIsLoadingData(true);
|
||||||
|
try {
|
||||||
|
await getStationPreview(Number(id));
|
||||||
|
} finally {
|
||||||
|
setIsLoadingData(false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setIsLoadingData(false);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}, [id, language]);
|
}, [id, language]);
|
||||||
|
|
||||||
|
if (isLoadingData) {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
minHeight: "60vh",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<LoadingSpinner message="Загрузка данных станции..." />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper className="w-full p-3 py-5 flex flex-col gap-10">
|
<Paper className="w-full p-3 py-5 flex flex-col gap-10">
|
||||||
<LanguageSwitcher />
|
<LanguageSwitcher />
|
||||||
|
|||||||
@@ -4,18 +4,20 @@ import {
|
|||||||
Checkbox,
|
Checkbox,
|
||||||
Paper,
|
Paper,
|
||||||
TextField,
|
TextField,
|
||||||
|
Box,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { ArrowLeft, Save } from "lucide-react";
|
import { ArrowLeft, Save } from "lucide-react";
|
||||||
import { Loader2 } from "lucide-react";
|
import { Loader2 } from "lucide-react";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import { userStore, languageStore } from "@shared";
|
import { userStore, languageStore, LoadingSpinner } from "@shared";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
export const UserEditPage = observer(() => {
|
export const UserEditPage = observer(() => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [isLoadingData, setIsLoadingData] = useState(true);
|
||||||
|
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
const { editUserData, editUser, getUser, setEditUserData } = userStore;
|
const { editUserData, editUser, getUser, setEditUserData } = userStore;
|
||||||
@@ -41,18 +43,40 @@ export const UserEditPage = observer(() => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
if (id) {
|
if (id) {
|
||||||
const data = await getUser(Number(id));
|
setIsLoadingData(true);
|
||||||
|
try {
|
||||||
|
const data = await getUser(Number(id));
|
||||||
|
|
||||||
setEditUserData(
|
setEditUserData(
|
||||||
data?.name || "",
|
data?.name || "",
|
||||||
data?.email || "",
|
data?.email || "",
|
||||||
data?.password || "",
|
data?.password || "",
|
||||||
data?.is_admin || false
|
data?.is_admin || false
|
||||||
);
|
);
|
||||||
|
} finally {
|
||||||
|
setIsLoadingData(false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setIsLoadingData(false);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}, [id]);
|
}, [id]);
|
||||||
|
|
||||||
|
if (isLoadingData) {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
minHeight: "60vh",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<LoadingSpinner message="Загрузка данных пользователя..." />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper className="w-full h-full p-3 flex flex-col gap-10">
|
<Paper className="w-full h-full p-3 flex flex-col gap-10">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
|
|||||||
@@ -86,28 +86,35 @@ class EditSightStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hasLoadedCommon = false;
|
hasLoadedCommon = false;
|
||||||
|
isLoading = false;
|
||||||
|
|
||||||
getSightInfo = async (id: number, language: Language) => {
|
getSightInfo = async (id: number, language: Language) => {
|
||||||
const response = await languageInstance(language).get(`/sight/${id}`);
|
this.isLoading = true;
|
||||||
const data = response.data;
|
try {
|
||||||
|
const response = await languageInstance(language).get(`/sight/${id}`);
|
||||||
|
const data = response.data;
|
||||||
|
|
||||||
if (data.left_article != 0 && data.left_article != null) {
|
if (data.left_article != 0 && data.left_article != null) {
|
||||||
await this.getLeftArticle(data.left_article);
|
await this.getLeftArticle(data.left_article);
|
||||||
}
|
}
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.sight[language] = {
|
this.sight[language] = {
|
||||||
...this.sight[language],
|
...this.sight[language],
|
||||||
...data,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!this.hasLoadedCommon) {
|
|
||||||
this.sight.common = {
|
|
||||||
...this.sight.common,
|
|
||||||
...data,
|
...data,
|
||||||
};
|
};
|
||||||
this.hasLoadedCommon = true;
|
|
||||||
}
|
if (!this.hasLoadedCommon) {
|
||||||
});
|
this.sight.common = {
|
||||||
|
...this.sight.common,
|
||||||
|
...data,
|
||||||
|
};
|
||||||
|
this.hasLoadedCommon = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
this.isLoading = false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
updateLeftInfo = (language: Language, heading: string, body: string) => {
|
updateLeftInfo = (language: Language, heading: string, body: string) => {
|
||||||
@@ -168,6 +175,8 @@ class EditSightStore {
|
|||||||
|
|
||||||
clearSightInfo = () => {
|
clearSightInfo = () => {
|
||||||
this.needLeaveAgree = false;
|
this.needLeaveAgree = false;
|
||||||
|
this.hasLoadedCommon = false;
|
||||||
|
this.isLoading = false;
|
||||||
this.sight = {
|
this.sight = {
|
||||||
common: {
|
common: {
|
||||||
id: 0,
|
id: 0,
|
||||||
|
|||||||
@@ -3,3 +3,4 @@ export * from "./BackButton";
|
|||||||
export * from "./Modal";
|
export * from "./Modal";
|
||||||
export * from "./CoordinatesInput";
|
export * from "./CoordinatesInput";
|
||||||
export * from "./AnimatedCircleButton";
|
export * from "./AnimatedCircleButton";
|
||||||
|
export * from "./LoadingSpinner";
|
||||||
|
|||||||
@@ -10,23 +10,25 @@ import { observer } from "mobx-react-lite";
|
|||||||
import { useState, DragEvent, useRef } from "react";
|
import { useState, DragEvent, useRef } from "react";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
|
|
||||||
|
interface MediaAreaProps {
|
||||||
|
articleId: number;
|
||||||
|
mediaIds: { id: string; media_type: number; filename: string }[];
|
||||||
|
deleteMedia: (id: number, media_id: string) => void;
|
||||||
|
onFilesDrop?: (files: File[]) => void;
|
||||||
|
setSelectMediaDialogOpen: (open: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
export const MediaArea = observer(
|
export const MediaArea = observer(
|
||||||
({
|
({
|
||||||
articleId,
|
articleId,
|
||||||
mediaIds,
|
mediaIds,
|
||||||
deleteMedia,
|
deleteMedia,
|
||||||
onFilesDrop, // 👈 Проп для обработки загруженных файлов
|
onFilesDrop,
|
||||||
setSelectMediaDialogOpen,
|
setSelectMediaDialogOpen,
|
||||||
}: {
|
}: MediaAreaProps) => {
|
||||||
articleId: number;
|
|
||||||
mediaIds: { id: string; media_type: number; filename: string }[];
|
|
||||||
deleteMedia: (id: number, media_id: string) => void;
|
|
||||||
onFilesDrop?: (files: File[]) => void;
|
|
||||||
setSelectMediaDialogOpen: (open: boolean) => void;
|
|
||||||
}) => {
|
|
||||||
const [mediaModal, setMediaModal] = useState<boolean>(false);
|
const [mediaModal, setMediaModal] = useState<boolean>(false);
|
||||||
const [mediaId, setMediaId] = useState<string>("");
|
const [mediaId, setMediaId] = useState<string>("");
|
||||||
const [isDragging, setIsDragging] = useState(false);
|
const [isDragging, setIsDragging] = useState<boolean>(false);
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
const handleMediaModal = (mediaId: string) => {
|
const handleMediaModal = (mediaId: string) => {
|
||||||
@@ -34,23 +36,29 @@ export const MediaArea = observer(
|
|||||||
setMediaId(mediaId);
|
setMediaId(mediaId);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const processFiles = (files: File[]) => {
|
||||||
|
if (!files.length || !onFilesDrop) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { validFiles, errors } = filterValidFiles(files);
|
||||||
|
|
||||||
|
if (errors.length > 0) {
|
||||||
|
errors.forEach((error) => toast.error(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validFiles.length > 0) {
|
||||||
|
onFilesDrop(validFiles);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleDrop = (e: DragEvent<HTMLDivElement>) => {
|
const handleDrop = (e: DragEvent<HTMLDivElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setIsDragging(false);
|
setIsDragging(false);
|
||||||
|
|
||||||
const files = Array.from(e.dataTransfer.files);
|
const files = Array.from(e.dataTransfer.files);
|
||||||
if (files.length && onFilesDrop) {
|
processFiles(files);
|
||||||
const { validFiles, errors } = filterValidFiles(files);
|
|
||||||
|
|
||||||
if (errors.length > 0) {
|
|
||||||
errors.forEach((error) => toast.error(error));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (validFiles.length > 0) {
|
|
||||||
onFilesDrop(validFiles);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDragOver = (e: DragEvent<HTMLDivElement>) => {
|
const handleDragOver = (e: DragEvent<HTMLDivElement>) => {
|
||||||
@@ -68,19 +76,11 @@ export const MediaArea = observer(
|
|||||||
|
|
||||||
const handleFileSelect = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleFileSelect = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const files = Array.from(event.target.files || []);
|
const files = Array.from(event.target.files || []);
|
||||||
if (files.length && onFilesDrop) {
|
processFiles(files);
|
||||||
const { validFiles, errors } = filterValidFiles(files);
|
|
||||||
|
|
||||||
if (errors.length > 0) {
|
if (event.target) {
|
||||||
errors.forEach((error) => toast.error(error));
|
event.target.value = "";
|
||||||
}
|
|
||||||
|
|
||||||
if (validFiles.length > 0) {
|
|
||||||
onFilesDrop(validFiles);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Сбрасываем значение input, чтобы можно было выбрать тот же файл снова
|
|
||||||
event.target.value = "";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -96,7 +96,7 @@ export const MediaArea = observer(
|
|||||||
<Box className="w-full flex flex-col items-center justify-center border rounded-md p-4">
|
<Box className="w-full flex flex-col items-center justify-center border rounded-md p-4">
|
||||||
<div className="w-full flex flex-col items-center justify-center">
|
<div className="w-full flex flex-col items-center justify-center">
|
||||||
<div
|
<div
|
||||||
className={`w-full h-40 flex flex-col justify-center items-center text-gray-400 border-dashed border-2 rounded-md border-gray-400 p-4 cursor-pointer hover:bg-gray-50 ${
|
className={`w-full h-40 flex flex-col justify-center items-center text-gray-400 border-dashed border-2 rounded-md border-gray-400 p-4 cursor-pointer hover:bg-gray-50 transition-colors ${
|
||||||
isDragging ? "bg-blue-100 border-blue-400" : ""
|
isDragging ? "bg-blue-100 border-blue-400" : ""
|
||||||
}`}
|
}`}
|
||||||
onDrop={handleDrop}
|
onDrop={handleDrop}
|
||||||
@@ -105,9 +105,11 @@ export const MediaArea = observer(
|
|||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
>
|
>
|
||||||
<Upload size={32} className="mb-2" />
|
<Upload size={32} className="mb-2" />
|
||||||
Перетащите медиа файлы сюда или нажмите для выбора
|
<span className="text-center">
|
||||||
|
Перетащите медиа файлы сюда или нажмите для выбора
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div>или</div>
|
<div className="my-2">или</div>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary"
|
color="primary"
|
||||||
@@ -117,33 +119,38 @@ export const MediaArea = observer(
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="w-full flex flex-start flex-wrap gap-2 mt-4 py-10">
|
{mediaIds.length > 0 && (
|
||||||
{mediaIds.map((m) => (
|
<div className="w-full flex flex-start flex-wrap gap-2 mt-4 py-10">
|
||||||
<button
|
{mediaIds.map((m) => (
|
||||||
className="relative w-20 h-20"
|
|
||||||
key={m.id}
|
|
||||||
onClick={() => handleMediaModal(m.id)}
|
|
||||||
>
|
|
||||||
<MediaViewer
|
|
||||||
media={{
|
|
||||||
id: m.id,
|
|
||||||
media_type: m.media_type,
|
|
||||||
filename: m.filename,
|
|
||||||
}}
|
|
||||||
height="40px"
|
|
||||||
/>
|
|
||||||
<button
|
<button
|
||||||
className="absolute top-2 right-2"
|
className="relative w-20 h-20"
|
||||||
onClick={(e) => {
|
key={m.id}
|
||||||
e.stopPropagation();
|
onClick={() => handleMediaModal(m.id)}
|
||||||
deleteMedia(articleId, m.id);
|
type="button"
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<X size={16} color="red" />
|
<MediaViewer
|
||||||
|
media={{
|
||||||
|
id: m.id,
|
||||||
|
media_type: m.media_type,
|
||||||
|
filename: m.filename,
|
||||||
|
}}
|
||||||
|
height="40px"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
className="absolute top-2 right-2 bg-white rounded-full p-1 shadow-md hover:shadow-lg transition-shadow"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
deleteMedia(articleId, m.id);
|
||||||
|
}}
|
||||||
|
type="button"
|
||||||
|
aria-label="Удалить медиа"
|
||||||
|
>
|
||||||
|
<X size={16} color="red" />
|
||||||
|
</button>
|
||||||
</button>
|
</button>
|
||||||
</button>
|
))}
|
||||||
))}
|
</div>
|
||||||
</div>
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<PreviewMediaDialog
|
<PreviewMediaDialog
|
||||||
|
|||||||
@@ -11,52 +11,72 @@ import { observer } from "mobx-react-lite";
|
|||||||
import { useState, DragEvent, useRef } from "react";
|
import { useState, DragEvent, useRef } from "react";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
|
|
||||||
|
type ContextType =
|
||||||
|
| "sight"
|
||||||
|
| "city"
|
||||||
|
| "carrier"
|
||||||
|
| "country"
|
||||||
|
| "vehicle"
|
||||||
|
| "station";
|
||||||
|
|
||||||
|
interface MediaAreaForSightProps {
|
||||||
|
onFilesDrop?: (files: File[]) => void;
|
||||||
|
onFinishUpload?: (mediaId: string) => void;
|
||||||
|
contextObjectName?: string;
|
||||||
|
contextType?: ContextType;
|
||||||
|
isArticle?: boolean;
|
||||||
|
articleName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export const MediaAreaForSight = observer(
|
export const MediaAreaForSight = observer(
|
||||||
({
|
({
|
||||||
onFilesDrop, // 👈 Проп для обработки загруженных файлов
|
onFilesDrop,
|
||||||
onFinishUpload,
|
onFinishUpload,
|
||||||
contextObjectName,
|
contextObjectName,
|
||||||
contextType,
|
contextType,
|
||||||
isArticle,
|
isArticle,
|
||||||
articleName,
|
articleName,
|
||||||
}: {
|
}: MediaAreaForSightProps) => {
|
||||||
onFilesDrop?: (files: File[]) => void;
|
const [selectMediaDialogOpen, setSelectMediaDialogOpen] =
|
||||||
onFinishUpload?: (mediaId: string) => void;
|
useState<boolean>(false);
|
||||||
contextObjectName?: string;
|
const [uploadMediaDialogOpen, setUploadMediaDialogOpen] =
|
||||||
contextType?:
|
useState<boolean>(false);
|
||||||
| "sight"
|
const [isDragging, setIsDragging] = useState<boolean>(false);
|
||||||
| "city"
|
|
||||||
| "carrier"
|
|
||||||
| "country"
|
|
||||||
| "vehicle"
|
|
||||||
| "station";
|
|
||||||
isArticle?: boolean;
|
|
||||||
articleName?: string;
|
|
||||||
}) => {
|
|
||||||
const [selectMediaDialogOpen, setSelectMediaDialogOpen] = useState(false);
|
|
||||||
const [uploadMediaDialogOpen, setUploadMediaDialogOpen] = useState(false);
|
|
||||||
const [isDragging, setIsDragging] = useState(false);
|
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
const { setFileToUpload } = editSightStore;
|
const { setFileToUpload } = editSightStore;
|
||||||
|
|
||||||
|
const processFiles = (files: File[]) => {
|
||||||
|
if (!files.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { validFiles, errors } = filterValidFiles(files);
|
||||||
|
|
||||||
|
if (errors.length > 0) {
|
||||||
|
errors.forEach((error: string) => toast.error(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validFiles.length > 0) {
|
||||||
|
// Сохраняем первый файл для загрузки
|
||||||
|
setFileToUpload(validFiles[0]);
|
||||||
|
|
||||||
|
// Вызываем колбэк, если он передан
|
||||||
|
if (onFilesDrop) {
|
||||||
|
onFilesDrop(validFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Открываем диалог загрузки
|
||||||
|
setUploadMediaDialogOpen(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleDrop = (e: DragEvent<HTMLDivElement>) => {
|
const handleDrop = (e: DragEvent<HTMLDivElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setIsDragging(false);
|
setIsDragging(false);
|
||||||
|
|
||||||
const files = Array.from(e.dataTransfer.files);
|
const files = Array.from(e.dataTransfer.files);
|
||||||
if (files.length) {
|
processFiles(files);
|
||||||
const { validFiles, errors } = filterValidFiles(files);
|
|
||||||
|
|
||||||
if (errors.length > 0) {
|
|
||||||
errors.forEach((error: string) => toast.error(error));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (validFiles.length > 0 && onFilesDrop) {
|
|
||||||
setFileToUpload(validFiles[0]);
|
|
||||||
setUploadMediaDialogOpen(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDragOver = (e: DragEvent<HTMLDivElement>) => {
|
const handleDragOver = (e: DragEvent<HTMLDivElement>) => {
|
||||||
@@ -74,22 +94,12 @@ export const MediaAreaForSight = observer(
|
|||||||
|
|
||||||
const handleFileSelect = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleFileSelect = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const files = Array.from(event.target.files || []);
|
const files = Array.from(event.target.files || []);
|
||||||
if (files.length) {
|
processFiles(files);
|
||||||
const { validFiles, errors } = filterValidFiles(files);
|
|
||||||
|
|
||||||
if (errors.length > 0) {
|
|
||||||
errors.forEach((error: string) => toast.error(error));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (validFiles.length > 0 && onFilesDrop) {
|
|
||||||
setFileToUpload(validFiles[0]);
|
|
||||||
onFilesDrop(validFiles);
|
|
||||||
setUploadMediaDialogOpen(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Сбрасываем значение input, чтобы можно было выбрать тот же файл снова
|
// Сбрасываем значение input, чтобы можно было выбрать тот же файл снова
|
||||||
event.target.value = "";
|
if (event.target) {
|
||||||
|
event.target.value = "";
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -105,7 +115,7 @@ export const MediaAreaForSight = observer(
|
|||||||
<Box className="w-full flex flex-col items-center justify-center border rounded-md p-4">
|
<Box className="w-full flex flex-col items-center justify-center border rounded-md p-4">
|
||||||
<div className="w-full flex flex-col items-center justify-center">
|
<div className="w-full flex flex-col items-center justify-center">
|
||||||
<div
|
<div
|
||||||
className={`w-full h-40 flex text-center flex-col justify-center items-center text-gray-400 border-dashed border-2 rounded-md border-gray-400 p-4 cursor-pointer hover:bg-gray-50 ${
|
className={`w-full h-40 flex flex-col justify-center items-center text-gray-400 border-dashed border-2 rounded-md border-gray-400 p-4 cursor-pointer hover:bg-gray-50 transition-colors ${
|
||||||
isDragging ? "bg-blue-100 border-blue-400" : ""
|
isDragging ? "bg-blue-100 border-blue-400" : ""
|
||||||
}`}
|
}`}
|
||||||
onDrop={handleDrop}
|
onDrop={handleDrop}
|
||||||
@@ -114,9 +124,11 @@ export const MediaAreaForSight = observer(
|
|||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
>
|
>
|
||||||
<Upload size={32} className="mb-2" />
|
<Upload size={32} className="mb-2" />
|
||||||
Перетащите медиа файлы сюда или нажмите для выбора
|
<span className="text-center">
|
||||||
|
Перетащите медиа файлы сюда или нажмите для выбора
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div>или</div>
|
<div className="my-2">или</div>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary"
|
color="primary"
|
||||||
|
|||||||
@@ -127,8 +127,8 @@ export function MediaViewer({
|
|||||||
src={`${import.meta.env.VITE_KRBL_MEDIA}${
|
src={`${import.meta.env.VITE_KRBL_MEDIA}${
|
||||||
media?.id
|
media?.id
|
||||||
}/download?token=${token}`}
|
}/download?token=${token}`}
|
||||||
width={width ? width : "500px"}
|
width={fullWidth ? "100%" : width ? width : "500px"}
|
||||||
height={height ? height : "300px"}
|
height={fullHeight ? "100%" : height ? height : "300px"}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -434,49 +434,61 @@ export const CreateRightTab = observer(
|
|||||||
</Box>
|
</Box>
|
||||||
) : type === "media" ? (
|
) : type === "media" ? (
|
||||||
<Box className="w-[80%] border border-gray-300 rounded-2xl relative flex items-center justify-center">
|
<Box className="w-[80%] border border-gray-300 rounded-2xl relative flex items-center justify-center">
|
||||||
{sight.preview_media && (
|
<>
|
||||||
<>
|
{type === "media" && (
|
||||||
{type === "media" && (
|
<Box className="w-[80%] h-full rounded-2xl relative flex items-center justify-center">
|
||||||
<Box className="w-[80%] h-full rounded-2xl relative flex items-center justify-center">
|
{previewMedia && (
|
||||||
{previewMedia && (
|
<>
|
||||||
<>
|
<Box className="absolute top-4 right-4 z-10">
|
||||||
<Box className="absolute top-4 right-4 z-10">
|
<button
|
||||||
<button
|
className="w-10 h-10 flex items-center justify-center z-10"
|
||||||
className="w-10 h-10 flex items-center justify-center z-10"
|
onClick={handleUnlinkPreviewMedia}
|
||||||
onClick={handleUnlinkPreviewMedia}
|
>
|
||||||
>
|
<X size={20} color="red" />
|
||||||
<X size={20} color="red" />
|
</button>
|
||||||
</button>
|
</Box>
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box className="w-1/2 h-1/2">
|
<Box className="w-1/2 h-1/2">
|
||||||
<MediaViewer
|
<MediaViewer
|
||||||
media={{
|
media={{
|
||||||
id: previewMedia.id || "",
|
id: previewMedia.id || "",
|
||||||
media_type: previewMedia.media_type,
|
media_type: previewMedia.media_type,
|
||||||
filename: previewMedia.filename || "",
|
filename: previewMedia.filename || "",
|
||||||
}}
|
}}
|
||||||
fullWidth
|
fullWidth
|
||||||
fullHeight
|
fullHeight
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Box>
|
|
||||||
)}
|
{!previewMedia && (
|
||||||
</>
|
<Box className="w-full h-full flex justify-center items-center">
|
||||||
)}
|
<Box
|
||||||
{!previewMedia && (
|
sx={{
|
||||||
<MediaAreaForSight
|
maxWidth: "500px",
|
||||||
onFinishUpload={(mediaId) => {
|
maxHeight: "100%",
|
||||||
linkPreviewMedia(mediaId);
|
display: "flex",
|
||||||
}}
|
flexGrow: 1,
|
||||||
onFilesDrop={() => {}}
|
margin: "0 auto",
|
||||||
contextObjectName={sight[language].name}
|
justifyContent: "center",
|
||||||
contextType="sight"
|
}}
|
||||||
isArticle={false}
|
>
|
||||||
/>
|
<MediaAreaForSight
|
||||||
)}
|
onFinishUpload={(mediaId) => {
|
||||||
|
linkPreviewMedia(mediaId);
|
||||||
|
}}
|
||||||
|
onFilesDrop={() => {}}
|
||||||
|
contextObjectName={sight[language].name}
|
||||||
|
contextType="sight"
|
||||||
|
isArticle={false}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
</Box>
|
</Box>
|
||||||
) : (
|
) : (
|
||||||
<Box className="w-[80%] h-[70vh] border border-gray-300 rounded-2xl p-3 flex justify-center items-center">
|
<Box className="w-[80%] h-[70vh] border border-gray-300 rounded-2xl p-3 flex justify-center items-center">
|
||||||
|
|||||||
@@ -415,21 +415,12 @@ export const RightWidgetTab = observer(
|
|||||||
media_type: previewMedia.media_type,
|
media_type: previewMedia.media_type,
|
||||||
filename: previewMedia.filename || "",
|
filename: previewMedia.filename || "",
|
||||||
}}
|
}}
|
||||||
|
fullWidth
|
||||||
|
fullHeight
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{!previewMedia && (
|
|
||||||
<MediaAreaForSight
|
|
||||||
onFinishUpload={(mediaId) => {
|
|
||||||
linkPreviewMedia(mediaId);
|
|
||||||
}}
|
|
||||||
onFilesDrop={() => {}}
|
|
||||||
contextObjectName={sight[language].name}
|
|
||||||
contextType="sight"
|
|
||||||
isArticle={false}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
Reference in New Issue
Block a user