Files
WhiteNightsAdminPanel/src/widgets/SightTabs/InformationTab/index.tsx
2025-06-12 22:50:43 +03:00

439 lines
14 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,
} from "@mui/material";
import {
BackButton,
TabPanel,
languageStore,
Language,
cityStore,
editSightStore,
SelectMediaDialog,
PreviewMediaDialog,
SightLanguageInfo,
SightCommonInfo,
UploadMediaDialog,
MEDIA_TYPE_VALUES,
} from "@shared";
import { ImageUploadCard, LanguageSwitcher } from "@widgets";
import { Save } from "lucide-react";
import { observer } from "mobx-react-lite";
import { useEffect, useState } from "react";
import { toast } from "react-toastify";
// Мокап для всплывающей подсказки
export const InformationTab = observer(
({ value, index }: { value: number; index: number }) => {
const { ruCities } = cityStore;
const [mediaId, setMediaId] = useState<string>("");
const [isPreviewMediaOpen, setIsPreviewMediaOpen] = useState(false);
const [isUploadMediaOpen, setIsUploadMediaOpen] = useState(false);
const { language } = languageStore;
const { sight, updateSightInfo, updateSight } = editSightStore;
const [, setCity] = useState<number>(sight.common.city_id ?? 0);
const [coordinates, setCoordinates] = useState<string>(`0, 0`);
// Menu state for each media button
const [menuAnchorEl, setMenuAnchorEl] = useState<null | HTMLElement>(null);
const [activeMenuType, setActiveMenuType] = useState<
"thumbnail" | "watermark_lu" | "watermark_rd" | null
>(null);
const [isAddMediaOpen, setIsAddMediaOpen] = useState(false);
const [hardcodeType, setHardcodeType] = useState<
"thumbnail" | "watermark_lu" | "watermark_rd" | null
>(null);
useEffect(() => {
// Показывать только при инициализации (не менять при ошибках пользователя)
if (sight.common.latitude !== 0 || sight.common.longitude !== 0) {
setCoordinates(`${sight.common.latitude}, ${sight.common.longitude}`);
}
// если координаты обнулились — оставить поле как есть
}, [sight.common.latitude, sight.common.longitude]);
const handleMenuClose = () => {
setMenuAnchorEl(null);
setActiveMenuType(null);
};
const handleCreateNew = () => {
handleMenuClose();
};
const handleAddMedia = () => {
setIsAddMediaOpen(true);
handleMenuClose();
};
const handleMediaSelect = (media: {
id: string;
filename: string;
media_name?: string;
media_type: number;
}) => {
if (!activeMenuType) return;
handleChange(
language as Language,
{
[activeMenuType ?? "thumbnail"]: media.id,
},
true
);
setActiveMenuType(null);
setIsUploadMediaOpen(false);
};
const handleChange = (
language: Language,
content: Partial<SightLanguageInfo | SightCommonInfo>,
common: boolean = false
) => {
updateSightInfo(language, content, common);
};
return (
<>
<TabPanel value={value} index={index}>
<Box
sx={{
display: "flex",
flexDirection: "column",
gap: 3,
position: "relative",
paddingBottom: "70px" /* Space for save button */,
}}
>
<div className="flex gap-10 items-center mb-5 max-w-[80%]">
<BackButton />
<h1 className="text-3xl break-words">{sight[language].name}</h1>
</div>
<LanguageSwitcher />
<Box
sx={{
display: "flex",
gap: 4, // Added gap between the two main columns
width: "100%",
flexDirection: "column",
}}
>
{/* Left column with main fields */}
<Box
sx={{
flexGrow: 1,
display: "flex",
width: "80%",
flexDirection: "column",
gap: 2.5,
}}
>
<TextField
label={`Название (${language.toUpperCase()})`}
value={sight[language].name}
onChange={(e) => {
handleChange(language as Language, {
name: e.target.value,
});
}}
fullWidth
variant="outlined"
/>
<TextField
label="Адрес"
value={sight[language].address}
onChange={(e) => {
handleChange(language as Language, {
address: e.target.value,
});
}}
fullWidth
variant="outlined"
/>
<Autocomplete
options={ruCities?.data ?? []}
value={
ruCities?.data?.find(
(city) => city.id === sight.common.city_id
) ?? null
}
getOptionLabel={(option) => option.name}
onChange={(_, value) => {
setCity(value?.id ?? 0);
handleChange(
language as Language,
{
city_id: value?.id ?? 0,
},
true
);
}}
renderInput={(params) => (
<TextField {...params} label="Город" />
)}
/>
<TextField
label="Координаты"
value={coordinates}
onChange={(e) => {
const newValue = e.target.value;
setCoordinates(newValue); // сохраняем ввод пользователя как есть
// Обрабатываем значение для сохранения
const input = newValue.replace(/,/g, " ").trim();
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(
language as Language,
{
latitude: lat,
longitude: lon,
},
true
);
} else {
handleChange(
language as Language,
{
latitude: 0,
longitude: 0,
},
true
);
}
}}
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" }, // Stack on extra small, side-by-side on small and up
}}
>
<ImageUploadCard
title="Логотип"
imageKey="thumbnail"
imageUrl={sight.common.thumbnail}
onImageClick={() => {
setIsPreviewMediaOpen(true);
setMediaId(sight.common.thumbnail ?? "");
}}
onDeleteImageClick={() => {
handleChange(
language as Language,
{
thumbnail: null,
},
true
);
setActiveMenuType(null);
}}
onSelectFileClick={() => {
setActiveMenuType("thumbnail");
setIsAddMediaOpen(true);
}}
setUploadMediaOpen={() => {
setIsUploadMediaOpen(true);
setActiveMenuType("thumbnail");
}}
setHardcodeType={(type) => {
setHardcodeType(
type as "thumbnail" | "watermark_lu" | "watermark_rd"
);
}}
/>
<ImageUploadCard
title="Водяной знак (левый верхний)"
imageKey="watermark_lu"
imageUrl={sight.common.watermark_lu}
onImageClick={() => {
setIsPreviewMediaOpen(true);
setMediaId(sight.common.watermark_lu ?? "");
}}
onDeleteImageClick={() => {
handleChange(
language as Language,
{
watermark_lu: null,
},
true
);
setActiveMenuType(null);
}}
onSelectFileClick={() => {
setActiveMenuType("watermark_lu");
setIsAddMediaOpen(true);
}}
setUploadMediaOpen={() => {
setIsUploadMediaOpen(true);
setActiveMenuType("watermark_lu");
}}
setHardcodeType={(type) => {
setHardcodeType(
type as "thumbnail" | "watermark_lu" | "watermark_rd"
);
}}
/>
<ImageUploadCard
title="Водяной знак (правый нижний)"
imageKey="watermark_rd"
imageUrl={sight.common.watermark_rd}
onImageClick={() => {
setIsPreviewMediaOpen(true);
setMediaId(sight.common.watermark_rd ?? "");
}}
onDeleteImageClick={() => {
handleChange(
language as Language,
{
watermark_rd: null,
},
true
);
setActiveMenuType(null);
}}
onSelectFileClick={() => {
setActiveMenuType("watermark_rd");
setIsAddMediaOpen(true);
}}
setUploadMediaOpen={() => {
setIsUploadMediaOpen(true);
setActiveMenuType("watermark_rd");
}}
setHardcodeType={(type) => {
setHardcodeType(
type as "thumbnail" | "watermark_lu" | "watermark_rd"
);
}}
/>
</Box>
</Box>
</Box>
{/* LanguageSwitcher positioned at the top right */}
<LanguageSwitcher />
{/* Save Button fixed at the bottom right */}
<Box
sx={{
position: "absolute",
bottom: 0,
right: 0,
padding: 2,
backgroundColor: "background.paper", // To ensure it stands out over content
width: "100%", // Take full width to cover content below it
display: "flex",
justifyContent: "flex-end", // Align to the right
}}
>
<Button
variant="contained"
color="success"
startIcon={<Save color="white" size={18} />}
onClick={async () => {
await updateSight();
toast.success("Достопримечательность сохранена");
}}
>
Сохранить
</Button>
</Box>
</Box>
</TabPanel>
{/* Media Menu */}
<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={handleMediaSelect}
mediaType={
activeMenuType
? MEDIA_TYPE_VALUES[
activeMenuType as keyof typeof MEDIA_TYPE_VALUES
]
: undefined
}
/>
<UploadMediaDialog
open={isUploadMediaOpen}
onClose={() => setIsUploadMediaOpen(false)}
afterUpload={(media) => {
handleChange(
language as Language,
{
[activeMenuType ?? "thumbnail"]: media.id,
},
true
);
setActiveMenuType(null);
setIsUploadMediaOpen(false);
}}
hardcodeType={hardcodeType}
/>
<PreviewMediaDialog
open={isPreviewMediaOpen}
onClose={() => setIsPreviewMediaOpen(false)}
mediaId={mediaId}
/>
</>
);
}
);