Files
WhiteNightsAdminPanel/src/widgets/SightTabs/CreateInformationTab/index.tsx
2025-10-02 04:45:43 +03:00

519 lines
16 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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,
}}
/>
)}
</>
);
}
);