feat: Sight Page update

This commit is contained in:
2025-06-01 23:18:21 +03:00
parent 87386c6a73
commit a8777a974a
26 changed files with 3460 additions and 727 deletions

View File

@@ -0,0 +1,582 @@
import {
Button,
TextField,
Box,
Autocomplete,
Typography,
Paper,
Tooltip,
MenuItem,
Menu as MuiMenu,
} from "@mui/material";
import {
BackButton,
TabPanel,
languageStore,
Language,
cityStore,
SelectMediaDialog,
PreviewMediaDialog,
SightLanguageInfo,
SightCommonInfo,
createSightStore,
} from "@shared";
import { LanguageSwitcher } from "@widgets";
import { Info, X } from "lucide-react";
import { observer } from "mobx-react-lite";
import { useEffect, useState } from "react";
import { toast } from "react-toastify";
// Мокап для всплывающей подсказки
export const CreateInformationTab = observer(
({ value, index }: { value: number; index: number }) => {
const { cities } = cityStore;
const [, setIsMediaModalOpen] = useState(false);
const [mediaId, setMediaId] = useState<string>("");
const [isPreviewMediaOpen, setIsPreviewMediaOpen] = 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 token = localStorage.getItem("token");
// 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 handleMenuOpen = (
// event: React.MouseEvent<HTMLElement>,
// type: "thumbnail" | "watermark_lu" | "watermark_rd"
// ) => {
// setMenuAnchorEl(event.currentTarget);
// setActiveMenuType(type);
// };
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"
) => {
handleChange({
[type]: media.id,
});
setActiveMenuType(null);
};
return (
<>
<TabPanel value={value} index={index}>
<Box
sx={{
display: "flex",
flexDirection: "column",
gap: 3,
position: "relative",
paddingBottom: "70px" /* Space for save button */,
}}
>
<BackButton />
<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={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 ?? []}
value={
cities.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" }, // Stack on extra small, side-by-side on small and up
}}
>
<Paper
elevation={2}
sx={{
padding: 2,
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: 1,
flex: 1,
minWidth: 150, // Ensure a minimum width
}}
>
<Box sx={{ display: "flex", alignItems: "center" }}>
<Typography
variant="subtitle2"
gutterBottom
sx={{ mb: 0, mr: 0.5 }}
>
Логотип
</Typography>
</Box>
<Box
sx={{
position: "relative",
width: "200px",
height: "200px",
display: "flex",
alignItems: "center",
justifyContent: "center",
borderRadius: 1,
mb: 1,
cursor: sight.thumbnail ? "pointer" : "default",
}}
onClick={() => {
setIsMediaModalOpen(true);
}}
>
{sight.thumbnail && (
<button
className="absolute top-2 right-2"
onClick={() => {
handleChange({
thumbnail: null,
});
setActiveMenuType(null);
}}
>
<X color="red" />
</button>
)}
{sight.thumbnail ? (
<img
src={`${import.meta.env.VITE_KRBL_MEDIA}${
sight.thumbnail
}/download?token=${token}`}
alt="Логотип"
style={{ maxWidth: "100%", maxHeight: "100%" }}
onClick={() => {
setIsPreviewMediaOpen(true);
setMediaId(sight.thumbnail ?? "");
}}
/>
) : (
<div className="w-full flex flex-col items-center justify-center gap-3">
<div className="w-full h-20 border rounded-md flex flex-col items-center transition-all duration-300 justify-center border-dashed border-gray-300 cursor-pointer hover:bg-gray-100">
<p>Перетащите файл</p>
</div>
<p>или</p>
<Button
variant="contained"
color="primary"
onClick={() => {
setIsAddMediaOpen(true);
setActiveMenuType("thumbnail");
}}
>
Выбрать файл
</Button>
</div>
)}
</Box>
</Paper>
<Paper
elevation={2}
sx={{
padding: 2,
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: 1,
flex: 1,
minWidth: 150, // Ensure a minimum width
}}
>
<Box sx={{ display: "flex", alignItems: "center" }}>
<Typography
variant="subtitle2"
gutterBottom
sx={{ mb: 0, mr: 0.5 }}
>
Водяной знак (л.в)
</Typography>
<Tooltip title={"asf"}>
<Info
size={16}
color="gray"
style={{ cursor: "pointer" }}
/>
</Tooltip>
</Box>
<Box
sx={{
position: "relative",
width: "200px",
height: "200px",
display: "flex",
alignItems: "center",
justifyContent: "center",
borderRadius: 1,
mb: 1,
cursor: sight.watermark_lu ? "pointer" : "default",
}}
onClick={() => {
setIsMediaModalOpen(true);
}}
>
{sight.watermark_lu && (
<button
className="absolute top-2 right-2"
onClick={() => {
handleChange({
watermark_lu: null,
});
setActiveMenuType(null);
}}
>
<X color="red" />
</button>
)}
{sight.watermark_lu ? (
<img
src={`${import.meta.env.VITE_KRBL_MEDIA}${
sight.watermark_lu
}/download?token=${token}`}
alt="Логотип"
style={{ maxWidth: "100%", maxHeight: "100%" }}
onClick={() => {
setIsPreviewMediaOpen(true);
setMediaId(sight.watermark_lu ?? "");
}}
/>
) : (
<div className="w-full flex flex-col items-center justify-center gap-3">
<div className="w-full h-20 border rounded-md flex flex-col items-center transition-all duration-300 justify-center border-dashed border-gray-300 cursor-pointer hover:bg-gray-100">
<p>Перетащите файл</p>
</div>
<p>или</p>
<Button
variant="contained"
color="primary"
onClick={() => {
setActiveMenuType("watermark_lu");
setIsAddMediaOpen(true);
}}
>
Выбрать файл
</Button>
</div>
)}
</Box>
</Paper>
<Paper
elevation={2}
sx={{
padding: 2,
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: 1,
flex: 1,
minWidth: 150, // Ensure a minimum width
}}
>
<Box sx={{ display: "flex", alignItems: "center" }}>
<Typography
variant="subtitle2"
gutterBottom
sx={{ mb: 0, mr: 0.5 }}
>
Водяной знак (п.в)
</Typography>
<Tooltip title={"asfaf"}>
<Info
size={16}
color="gray"
style={{ cursor: "pointer" }}
/>
</Tooltip>
</Box>
<Box
sx={{
position: "relative",
width: "200px",
height: "200px",
display: "flex",
alignItems: "center",
justifyContent: "center",
borderRadius: 1,
mb: 1,
cursor: sight.watermark_rd ? "pointer" : "default",
}}
onClick={() => {
setIsMediaModalOpen(true);
}}
>
{sight.watermark_rd && (
<button
className="absolute top-2 right-2"
onClick={() => {
handleChange({
watermark_rd: null,
});
setActiveMenuType(null);
}}
>
<X color="red" />
</button>
)}
{sight.watermark_rd ? (
<img
src={`${import.meta.env.VITE_KRBL_MEDIA}${
sight.watermark_rd
}/download?token=${token}`}
alt="Логотип"
style={{ maxWidth: "100%", maxHeight: "100%" }}
onClick={() => {
setIsPreviewMediaOpen(true);
setMediaId(sight.watermark_rd ?? "");
}}
/>
) : (
<div className="w-full flex flex-col items-center justify-center gap-3">
<div className="w-full h-20 border rounded-md flex flex-col items-center transition-all duration-300 justify-center border-dashed border-gray-300 cursor-pointer hover:bg-gray-100">
<p>Перетащите файл</p>
</div>
<p>или</p>
<Button
variant="contained"
color="primary"
onClick={() => {
setActiveMenuType("watermark_rd");
setIsAddMediaOpen(true);
}}
>
Выбрать файл
</Button>
</div>
)}
</Box>
</Paper>
</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"
onClick={async () => {
await createSight(language);
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={(media) => {
handleMediaSelect(media, activeMenuType ?? "thumbnail");
}}
/>
<PreviewMediaDialog
open={isPreviewMediaOpen}
onClose={() => setIsPreviewMediaOpen(false)}
mediaId={mediaId}
/>
</>
);
}
);