519 lines
16 KiB
TypeScript
519 lines
16 KiB
TypeScript
import {
|
||
Button,
|
||
TextField,
|
||
Box,
|
||
Autocomplete,
|
||
MenuItem,
|
||
Menu as MuiMenu,
|
||
Dialog,
|
||
DialogTitle,
|
||
DialogContent,
|
||
DialogActions,
|
||
} from "@mui/material";
|
||
import {
|
||
BackButton,
|
||
TabPanel,
|
||
languageStore,
|
||
Language,
|
||
cityStore,
|
||
SelectMediaDialog,
|
||
PreviewMediaDialog,
|
||
SightLanguageInfo,
|
||
SightCommonInfo,
|
||
createSightStore,
|
||
UploadMediaDialog,
|
||
MEDIA_TYPE_VALUES,
|
||
} from "@shared";
|
||
import {
|
||
ImageUploadCard,
|
||
LanguageSwitcher,
|
||
VideoPreviewCard,
|
||
MediaViewer,
|
||
} from "@widgets";
|
||
import { Save } from "lucide-react";
|
||
|
||
import { observer } from "mobx-react-lite";
|
||
import { useEffect, useState } from "react";
|
||
import { toast } from "react-toastify";
|
||
import { SaveWithoutCityAgree } from "@widgets";
|
||
|
||
export const CreateInformationTab = observer(
|
||
({ value, index }: { value: number; index: number }) => {
|
||
const { cities } = cityStore;
|
||
const [mediaId, setMediaId] = useState<string>("");
|
||
const [isPreviewMediaOpen, setIsPreviewMediaOpen] = useState(false);
|
||
const [isUploadMediaOpen, setIsUploadMediaOpen] = useState(false);
|
||
const { language } = languageStore;
|
||
const { sight, updateSightInfo, createSight } = createSightStore;
|
||
const data = sight[language];
|
||
|
||
const [, setCity] = useState<number>(sight.city_id ?? 0);
|
||
const [coordinates, setCoordinates] = useState<string>(`0, 0`);
|
||
|
||
const [menuAnchorEl, setMenuAnchorEl] = useState<null | HTMLElement>(null);
|
||
const [activeMenuType, setActiveMenuType] = useState<
|
||
"thumbnail" | "watermark_lu" | "watermark_rd" | "video_preview" | null
|
||
>(null);
|
||
const [isAddMediaOpen, setIsAddMediaOpen] = useState(false);
|
||
const [isVideoPreviewOpen, setIsVideoPreviewOpen] = useState(false);
|
||
const [hardcodeType, setHardcodeType] = useState<
|
||
"thumbnail" | "watermark_lu" | "watermark_rd" | "video_preview" | null
|
||
>(null);
|
||
|
||
// НОВОЕ СОСТОЯНИЕ ДЛЯ ПРЕДУПРЕЖДАЮЩЕГО ОКНА
|
||
const [isSaveWarningOpen, setIsSaveWarningOpen] = useState(false);
|
||
|
||
useEffect(() => {}, [hardcodeType]);
|
||
|
||
useEffect(() => {
|
||
if (sight.latitude !== 0 || sight.longitude !== 0) {
|
||
setCoordinates(`${sight.latitude}, ${sight.longitude}`);
|
||
}
|
||
}, [sight.latitude, sight.longitude]);
|
||
|
||
const handleMenuClose = () => {
|
||
setMenuAnchorEl(null);
|
||
setActiveMenuType(null);
|
||
};
|
||
|
||
const handleCreateNew = () => {
|
||
handleMenuClose();
|
||
};
|
||
|
||
const handleAddMedia = () => {
|
||
setIsAddMediaOpen(true);
|
||
handleMenuClose();
|
||
};
|
||
|
||
const handleChange = (
|
||
content: Partial<SightLanguageInfo | SightCommonInfo>,
|
||
language?: Language
|
||
) => {
|
||
if (language) {
|
||
updateSightInfo(content, language);
|
||
} else {
|
||
updateSightInfo(content);
|
||
}
|
||
};
|
||
|
||
const handleMediaSelect = (
|
||
media: {
|
||
id: string;
|
||
filename: string;
|
||
media_name?: string;
|
||
media_type: number;
|
||
},
|
||
type: "thumbnail" | "watermark_lu" | "watermark_rd" | "video_preview"
|
||
) => {
|
||
handleChange({
|
||
[type]: media.id,
|
||
});
|
||
setActiveMenuType(null);
|
||
};
|
||
|
||
const handleVideoPreviewClick = () => {
|
||
if (sight.video_preview && sight.video_preview !== "") {
|
||
setIsVideoPreviewOpen(true);
|
||
}
|
||
};
|
||
|
||
const handleSave = async () => {
|
||
const isCityMissing = !sight.city_id;
|
||
// Проверяем названия на всех языках
|
||
const isNameMissing = !sight.ru.name || !sight.en.name || !sight.zh.name;
|
||
|
||
if (isCityMissing || isNameMissing) {
|
||
setIsSaveWarningOpen(true);
|
||
return;
|
||
}
|
||
|
||
await createSight(language);
|
||
toast.success("Достопримечательность создана");
|
||
};
|
||
|
||
const handleConfirmSave = async () => {
|
||
setIsSaveWarningOpen(false);
|
||
await createSight(language);
|
||
toast.success("Достопримечательность создана");
|
||
};
|
||
|
||
const handleCancelSave = () => {
|
||
setIsSaveWarningOpen(false);
|
||
};
|
||
|
||
return (
|
||
<>
|
||
<TabPanel value={value} index={index}>
|
||
<Box
|
||
sx={{
|
||
display: "flex",
|
||
flexDirection: "column",
|
||
gap: 3,
|
||
position: "relative",
|
||
paddingBottom: "70px",
|
||
}}
|
||
>
|
||
<div className="flex gap-10 items-center mb-5 max-w-[80%]">
|
||
<BackButton />
|
||
<h1 className="text-3xl break-words">{sight[language].name}</h1>
|
||
</div>
|
||
|
||
<Box
|
||
sx={{
|
||
display: "flex",
|
||
|
||
gap: 4,
|
||
width: "100%",
|
||
flexDirection: "column",
|
||
}}
|
||
>
|
||
<Box
|
||
sx={{
|
||
flexGrow: 1,
|
||
display: "flex",
|
||
width: "80%",
|
||
flexDirection: "column",
|
||
gap: 2.5,
|
||
}}
|
||
>
|
||
<TextField
|
||
label={`Название (${language.toUpperCase()})`}
|
||
value={data.name}
|
||
onChange={(e) => {
|
||
handleChange(
|
||
{
|
||
name: e.target.value,
|
||
},
|
||
language
|
||
);
|
||
}}
|
||
fullWidth
|
||
variant="outlined"
|
||
/>
|
||
|
||
<TextField
|
||
label="Адрес"
|
||
value={data.address}
|
||
onChange={(e) => {
|
||
handleChange(
|
||
{
|
||
address: e.target.value,
|
||
},
|
||
language
|
||
);
|
||
}}
|
||
fullWidth
|
||
variant="outlined"
|
||
/>
|
||
|
||
<Autocomplete
|
||
options={cities["ru"]?.data ?? []}
|
||
value={
|
||
cities["ru"]?.data?.find(
|
||
(city) => city.id === sight.city_id
|
||
) ?? null
|
||
}
|
||
getOptionLabel={(option) => option.name}
|
||
onChange={(_, value) => {
|
||
setCity(value?.id ?? 0);
|
||
handleChange({
|
||
city_id: value?.id ?? 0,
|
||
});
|
||
}}
|
||
renderInput={(params) => (
|
||
<TextField {...params} label="Город" />
|
||
)}
|
||
/>
|
||
|
||
<TextField
|
||
label="Координаты"
|
||
value={coordinates}
|
||
onChange={(e) => {
|
||
const input = e.target.value;
|
||
setCoordinates(input);
|
||
|
||
const [latStr, lonStr] = input.split(/\s+/);
|
||
|
||
const lat = parseFloat(latStr);
|
||
const lon = parseFloat(lonStr);
|
||
|
||
const isValidLat = !isNaN(lat);
|
||
const isValidLon = !isNaN(lon);
|
||
|
||
if (isValidLat && isValidLon) {
|
||
handleChange({
|
||
latitude: lat,
|
||
longitude: lon,
|
||
});
|
||
} else {
|
||
handleChange(
|
||
{
|
||
latitude: 0,
|
||
longitude: 0,
|
||
},
|
||
language
|
||
);
|
||
}
|
||
}}
|
||
fullWidth
|
||
variant="outlined"
|
||
placeholder="Введите координаты в формате: широта долгота"
|
||
/>
|
||
</Box>
|
||
|
||
<Box
|
||
sx={{
|
||
display: "flex",
|
||
|
||
gap: 4,
|
||
}}
|
||
>
|
||
<Box
|
||
sx={{
|
||
display: "flex",
|
||
justifyContent: "space-around",
|
||
width: "80%",
|
||
gap: 2,
|
||
flexDirection: { xs: "column", sm: "row" },
|
||
}}
|
||
>
|
||
<ImageUploadCard
|
||
title="Иконка"
|
||
imageKey="thumbnail"
|
||
imageUrl={sight.thumbnail}
|
||
onImageClick={() => {
|
||
setIsPreviewMediaOpen(true);
|
||
setMediaId(sight.thumbnail ?? "");
|
||
}}
|
||
onDeleteImageClick={() => {
|
||
handleChange({
|
||
thumbnail: null,
|
||
});
|
||
setActiveMenuType(null);
|
||
}}
|
||
onSelectFileClick={() => {
|
||
setActiveMenuType("thumbnail");
|
||
setIsAddMediaOpen(true);
|
||
}}
|
||
setUploadMediaOpen={() => {
|
||
setIsUploadMediaOpen(true);
|
||
setActiveMenuType("thumbnail");
|
||
setHardcodeType("thumbnail");
|
||
}}
|
||
setHardcodeType={() => {
|
||
setHardcodeType("thumbnail");
|
||
}}
|
||
/>
|
||
|
||
<ImageUploadCard
|
||
title="Водяной знак (левый верхний)"
|
||
imageKey="watermark_lu"
|
||
imageUrl={sight.watermark_lu}
|
||
onImageClick={() => {
|
||
setIsPreviewMediaOpen(true);
|
||
setMediaId(sight.watermark_lu ?? "");
|
||
}}
|
||
onDeleteImageClick={() => {
|
||
handleChange({
|
||
watermark_lu: null,
|
||
});
|
||
setActiveMenuType(null);
|
||
}}
|
||
onSelectFileClick={() => {
|
||
setActiveMenuType("watermark_lu");
|
||
setIsAddMediaOpen(true);
|
||
}}
|
||
setUploadMediaOpen={() => {
|
||
setIsUploadMediaOpen(true);
|
||
setActiveMenuType("watermark_lu");
|
||
setHardcodeType("watermark_lu");
|
||
}}
|
||
setHardcodeType={() => {
|
||
setHardcodeType("watermark_lu");
|
||
}}
|
||
/>
|
||
|
||
<ImageUploadCard
|
||
title="Водяной знак (правый верхний)"
|
||
imageKey="watermark_rd"
|
||
imageUrl={sight.watermark_rd}
|
||
onImageClick={() => {
|
||
setIsPreviewMediaOpen(true);
|
||
setMediaId(sight.watermark_rd ?? "");
|
||
}}
|
||
onDeleteImageClick={() => {
|
||
handleChange({
|
||
watermark_rd: null,
|
||
});
|
||
setActiveMenuType(null);
|
||
}}
|
||
onSelectFileClick={() => {
|
||
setActiveMenuType("watermark_rd");
|
||
setIsAddMediaOpen(true);
|
||
}}
|
||
setUploadMediaOpen={() => {
|
||
setIsUploadMediaOpen(true);
|
||
setActiveMenuType("watermark_rd");
|
||
setHardcodeType("watermark_rd");
|
||
}}
|
||
setHardcodeType={() => {
|
||
setHardcodeType("watermark_rd");
|
||
}}
|
||
/>
|
||
|
||
<VideoPreviewCard
|
||
title="Видеозаставка"
|
||
videoId={sight.video_preview}
|
||
onVideoClick={handleVideoPreviewClick}
|
||
onDeleteVideoClick={() => {
|
||
handleChange({
|
||
video_preview: null,
|
||
});
|
||
}}
|
||
onSelectVideoClick={(file) => {
|
||
if (file) {
|
||
createSightStore.setFileToUpload(file);
|
||
setActiveMenuType("video_preview");
|
||
setIsUploadMediaOpen(true);
|
||
} else {
|
||
setActiveMenuType("video_preview");
|
||
setIsAddMediaOpen(true);
|
||
}
|
||
}}
|
||
/>
|
||
</Box>
|
||
</Box>
|
||
</Box>
|
||
|
||
<LanguageSwitcher />
|
||
|
||
<Box
|
||
sx={{
|
||
position: "absolute",
|
||
bottom: 0,
|
||
right: 0,
|
||
padding: 2,
|
||
backgroundColor: "background.paper",
|
||
width: "100%",
|
||
display: "flex",
|
||
justifyContent: "flex-end",
|
||
}}
|
||
>
|
||
<Button
|
||
variant="contained"
|
||
color="success"
|
||
startIcon={<Save color="white" size={18} />}
|
||
onClick={handleSave}
|
||
>
|
||
Сохранить
|
||
</Button>
|
||
</Box>
|
||
</Box>
|
||
</TabPanel>
|
||
|
||
<MuiMenu
|
||
anchorEl={menuAnchorEl}
|
||
open={Boolean(menuAnchorEl)}
|
||
onClose={handleMenuClose}
|
||
anchorOrigin={{
|
||
vertical: "top",
|
||
horizontal: "right",
|
||
}}
|
||
transformOrigin={{
|
||
vertical: "bottom",
|
||
horizontal: "right",
|
||
}}
|
||
>
|
||
<MenuItem onClick={handleCreateNew}>Создать новую</MenuItem>
|
||
<MenuItem onClick={handleAddMedia}>Выбрать существующую</MenuItem>
|
||
</MuiMenu>
|
||
|
||
<SelectMediaDialog
|
||
open={isAddMediaOpen}
|
||
onClose={() => {
|
||
setIsAddMediaOpen(false);
|
||
setActiveMenuType(null);
|
||
}}
|
||
onSelectMedia={(media) => {
|
||
if (activeMenuType) {
|
||
handleMediaSelect(media, activeMenuType);
|
||
}
|
||
}}
|
||
mediaType={
|
||
activeMenuType
|
||
? MEDIA_TYPE_VALUES[
|
||
activeMenuType as keyof typeof MEDIA_TYPE_VALUES
|
||
]
|
||
: undefined
|
||
}
|
||
/>
|
||
|
||
<PreviewMediaDialog
|
||
open={isPreviewMediaOpen}
|
||
onClose={() => setIsPreviewMediaOpen(false)}
|
||
mediaId={mediaId}
|
||
/>
|
||
|
||
<UploadMediaDialog
|
||
open={isUploadMediaOpen}
|
||
onClose={() => setIsUploadMediaOpen(false)}
|
||
contextObjectName={sight[language].name}
|
||
contextType="sight"
|
||
afterUpload={(media) => {
|
||
if (activeMenuType === "video_preview") {
|
||
handleChange({
|
||
video_preview: media.id,
|
||
});
|
||
} else {
|
||
handleChange({
|
||
[activeMenuType ?? "thumbnail"]: media.id,
|
||
});
|
||
}
|
||
setActiveMenuType(null);
|
||
setIsUploadMediaOpen(false);
|
||
}}
|
||
hardcodeType={activeMenuType}
|
||
initialFile={createSightStore.fileToUpload || undefined}
|
||
/>
|
||
|
||
{sight.video_preview && sight.video_preview !== "" && (
|
||
<Dialog
|
||
open={isVideoPreviewOpen}
|
||
onClose={() => setIsVideoPreviewOpen(false)}
|
||
maxWidth="md"
|
||
fullWidth
|
||
>
|
||
<DialogTitle>Предпросмотр видео</DialogTitle>
|
||
<DialogContent>
|
||
<Box className="flex justify-center items-center p-4">
|
||
<MediaViewer
|
||
media={{
|
||
id: sight.video_preview,
|
||
media_type: 2,
|
||
filename: "video_preview",
|
||
}}
|
||
/>
|
||
</Box>
|
||
</DialogContent>
|
||
<DialogActions>
|
||
<Button onClick={() => setIsVideoPreviewOpen(false)}>
|
||
Закрыть
|
||
</Button>
|
||
</DialogActions>
|
||
</Dialog>
|
||
)}
|
||
|
||
{/* ИНТЕГРИРОВАННОЕ ПРЕДУПРЕЖДАЮЩЕЕ ОКНО */}
|
||
{isSaveWarningOpen && (
|
||
<SaveWithoutCityAgree
|
||
blocker={{
|
||
proceed: handleConfirmSave,
|
||
reset: handleCancelSave,
|
||
}}
|
||
/>
|
||
)}
|
||
</>
|
||
);
|
||
}
|
||
);
|