hotfix admin panel
This commit is contained in:
@@ -8,7 +8,7 @@ import { AppBar } from "./ui/AppBar";
|
||||
import { Drawer } from "./ui/Drawer";
|
||||
import { DrawerHeader } from "./ui/DrawerHeader";
|
||||
import { NavigationList } from "@features";
|
||||
import { authStore, userStore } from "@shared";
|
||||
import { authStore, userStore, menuStore } from "@shared";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useEffect } from "react";
|
||||
import { Typography } from "@mui/material";
|
||||
@@ -20,6 +20,14 @@ interface LayoutProps {
|
||||
export const Layout: React.FC<LayoutProps> = observer(({ children }) => {
|
||||
const theme = useTheme();
|
||||
const [open, setOpen] = React.useState(true);
|
||||
const { setIsMenuOpen } = menuStore;
|
||||
|
||||
React.useEffect(() => {
|
||||
setIsMenuOpen(open);
|
||||
}, [open]);
|
||||
|
||||
|
||||
|
||||
const { getUsers, users } = userStore;
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
23
src/widgets/SaveWithoutCityAgree/index.tsx
Normal file
23
src/widgets/SaveWithoutCityAgree/index.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { Button } from "@mui/material";
|
||||
|
||||
export const SaveWithoutCityAgree = ({ blocker }: { blocker: any }) => {
|
||||
return (
|
||||
<div className="fixed top-0 left-0 w-screen h-screen flex justify-center items-center z-10000 bg-black/30">
|
||||
<div className="bg-white p-4 w-140 rounded-lg flex flex-col gap-4 items-center">
|
||||
<p className="text-black w-140 text-center">
|
||||
Вы не указали город и/или не заполнили названия на всех языках.
|
||||
<br />
|
||||
Сохранить достопримечательность без этой информации?
|
||||
</p>
|
||||
<div className="flex gap-4 justify-center">
|
||||
<Button variant="contained" onClick={() => blocker.proceed()}>
|
||||
Да
|
||||
</Button>
|
||||
<Button variant="outlined" onClick={() => blocker.reset()}>
|
||||
Нет
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -35,8 +35,7 @@ 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 }) => {
|
||||
@@ -51,7 +50,6 @@ export const CreateInformationTab = observer(
|
||||
const [, setCity] = useState<number>(sight.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" | "video_preview" | null
|
||||
@@ -62,21 +60,15 @@ export const CreateInformationTab = observer(
|
||||
"thumbnail" | "watermark_lu" | "watermark_rd" | "video_preview" | null
|
||||
>(null);
|
||||
|
||||
// НОВОЕ СОСТОЯНИЕ ДЛЯ ПРЕДУПРЕЖДАЮЩЕГО ОКНА
|
||||
const [isSaveWarningOpen, setIsSaveWarningOpen] = useState(false);
|
||||
|
||||
useEffect(() => {}, [hardcodeType]);
|
||||
// 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 = () => {
|
||||
@@ -125,6 +117,29 @@ export const CreateInformationTab = observer(
|
||||
}
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
const isCityMissing = !sight.city_id;
|
||||
const isNameMissing = !sight[language].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}>
|
||||
@@ -134,7 +149,7 @@ export const CreateInformationTab = observer(
|
||||
flexDirection: "column",
|
||||
gap: 3,
|
||||
position: "relative",
|
||||
paddingBottom: "70px" /* Space for save button */,
|
||||
paddingBottom: "70px",
|
||||
}}
|
||||
>
|
||||
<div className="flex gap-10 items-center mb-5 max-w-[80%]">
|
||||
@@ -146,12 +161,11 @@ export const CreateInformationTab = observer(
|
||||
sx={{
|
||||
display: "flex",
|
||||
|
||||
gap: 4, // Added gap between the two main columns
|
||||
gap: 4,
|
||||
width: "100%",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
{/* Left column with main fields */}
|
||||
<Box
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
@@ -215,14 +229,13 @@ export const CreateInformationTab = observer(
|
||||
value={coordinates}
|
||||
onChange={(e) => {
|
||||
const input = e.target.value;
|
||||
setCoordinates(input); // показываем как есть
|
||||
setCoordinates(input);
|
||||
|
||||
const [latStr, lonStr] = input.split(/\s+/); // учитываем любые пробелы
|
||||
const [latStr, lonStr] = input.split(/\s+/);
|
||||
|
||||
const lat = parseFloat(latStr);
|
||||
const lon = parseFloat(lonStr);
|
||||
|
||||
// Проверка, что обе координаты валидные числа
|
||||
const isValidLat = !isNaN(lat);
|
||||
const isValidLon = !isNaN(lon);
|
||||
|
||||
@@ -260,7 +273,7 @@ export const CreateInformationTab = observer(
|
||||
justifyContent: "space-around",
|
||||
width: "80%",
|
||||
gap: 2,
|
||||
flexDirection: { xs: "column", sm: "row" }, // Stack on extra small, side-by-side on small and up
|
||||
flexDirection: { xs: "column", sm: "row" },
|
||||
}}
|
||||
>
|
||||
<ImageUploadCard
|
||||
@@ -348,7 +361,7 @@ export const CreateInformationTab = observer(
|
||||
/>
|
||||
|
||||
<VideoPreviewCard
|
||||
title="Видео превью"
|
||||
title="Видеозаставка"
|
||||
videoId={sight.video_preview}
|
||||
onVideoClick={handleVideoPreviewClick}
|
||||
onDeleteVideoClick={() => {
|
||||
@@ -358,12 +371,10 @@ export const CreateInformationTab = observer(
|
||||
}}
|
||||
onSelectVideoClick={(file) => {
|
||||
if (file) {
|
||||
// Если передан файл, открываем диалог загрузки медиа
|
||||
createSightStore.setFileToUpload(file);
|
||||
setActiveMenuType("video_preview");
|
||||
setIsUploadMediaOpen(true);
|
||||
} else {
|
||||
// Если файл не передан, открываем диалог выбора существующих медиа
|
||||
setActiveMenuType("video_preview");
|
||||
setIsAddMediaOpen(true);
|
||||
}
|
||||
@@ -373,31 +384,25 @@ export const CreateInformationTab = observer(
|
||||
</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
|
||||
backgroundColor: "background.paper",
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
justifyContent: "flex-end", // Align to the right
|
||||
justifyContent: "flex-end",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="success"
|
||||
startIcon={<Save color="white" size={18} />}
|
||||
onClick={async () => {
|
||||
await createSight(language);
|
||||
toast.success("Достопримечательность создана");
|
||||
}}
|
||||
onClick={handleSave}
|
||||
>
|
||||
Сохранить
|
||||
</Button>
|
||||
@@ -405,7 +410,6 @@ export const CreateInformationTab = observer(
|
||||
</Box>
|
||||
</TabPanel>
|
||||
|
||||
{/* Media Menu */}
|
||||
<MuiMenu
|
||||
anchorEl={menuAnchorEl}
|
||||
open={Boolean(menuAnchorEl)}
|
||||
@@ -471,7 +475,6 @@ export const CreateInformationTab = observer(
|
||||
initialFile={createSightStore.fileToUpload || undefined}
|
||||
/>
|
||||
|
||||
{/* Модальное окно предпросмотра видео */}
|
||||
{sight.video_preview && sight.video_preview !== "" && (
|
||||
<Dialog
|
||||
open={isVideoPreviewOpen}
|
||||
@@ -498,7 +501,17 @@ export const CreateInformationTab = observer(
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
)}
|
||||
|
||||
{/* ИНТЕГРИРОВАННОЕ ПРЕДУПРЕЖДАЮЩЕЕ ОКНО */}
|
||||
{isSaveWarningOpen && (
|
||||
<SaveWithoutCityAgree
|
||||
blocker={{
|
||||
proceed: handleConfirmSave,
|
||||
reset: handleCancelSave,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
);
|
||||
@@ -37,7 +37,8 @@ import { useEffect, useState } from "react";
|
||||
|
||||
import { toast } from "react-toastify";
|
||||
|
||||
// Мокап для всплывающей подсказки
|
||||
// Компонент предупреждающего окна (перенесен сюда)
|
||||
import { SaveWithoutCityAgree } from "@widgets";
|
||||
|
||||
export const InformationTab = observer(
|
||||
({ value, index }: { value: number; index: number }) => {
|
||||
@@ -51,7 +52,6 @@ export const InformationTab = observer(
|
||||
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" | "video_preview" | null
|
||||
@@ -62,15 +62,15 @@ export const InformationTab = observer(
|
||||
"thumbnail" | "watermark_lu" | "watermark_rd" | "video_preview" | null
|
||||
>(null);
|
||||
const { cities } = cityStore;
|
||||
// НОВОЕ СОСТОЯНИЕ ДЛЯ ПРЕДУПРЕЖДАЮЩЕГО ОКНА
|
||||
const [isSaveWarningOpen, setIsSaveWarningOpen] = useState(false);
|
||||
|
||||
useEffect(() => {}, [hardcodeType]);
|
||||
|
||||
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 = () => {
|
||||
@@ -119,6 +119,36 @@ export const InformationTab = observer(
|
||||
updateSightInfo(language, content, common);
|
||||
};
|
||||
|
||||
// НОВАЯ ФУНКЦИЯ: Фактическое сохранение (вызывается после подтверждения)
|
||||
const executeSave = async () => {
|
||||
await updateSight();
|
||||
toast.success("Достопримечательность сохранена");
|
||||
};
|
||||
|
||||
// ОБНОВЛЕННАЯ ФУНКЦИЯ: Проверка и вызов окна или сохранения
|
||||
const handleSave = async () => {
|
||||
const isCityMissing = !sight.common.city_id;
|
||||
const isNameMissing = !sight[language].name;
|
||||
|
||||
if (isCityMissing || isNameMissing) {
|
||||
setIsSaveWarningOpen(true);
|
||||
return;
|
||||
}
|
||||
|
||||
await executeSave();
|
||||
};
|
||||
|
||||
// Обработчик "Да" в предупреждающем окне
|
||||
const handleConfirmSave = async () => {
|
||||
setIsSaveWarningOpen(false);
|
||||
await executeSave();
|
||||
};
|
||||
|
||||
// Обработчик "Нет" в предупреждающем окне
|
||||
const handleCancelSave = () => {
|
||||
setIsSaveWarningOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<TabPanel value={value} index={index}>
|
||||
@@ -128,7 +158,7 @@ export const InformationTab = observer(
|
||||
flexDirection: "column",
|
||||
gap: 3,
|
||||
position: "relative",
|
||||
paddingBottom: "70px" /* Space for save button */,
|
||||
paddingBottom: "70px",
|
||||
}}
|
||||
>
|
||||
<div className="flex gap-10 items-center mb-5 max-w-[80%]">
|
||||
@@ -141,12 +171,11 @@ export const InformationTab = observer(
|
||||
sx={{
|
||||
display: "flex",
|
||||
|
||||
gap: 4, // Added gap between the two main columns
|
||||
gap: 4,
|
||||
width: "100%",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
{/* Left column with main fields */}
|
||||
<Box
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
@@ -208,16 +237,14 @@ export const InformationTab = observer(
|
||||
value={coordinates}
|
||||
onChange={(e) => {
|
||||
const newValue = e.target.value;
|
||||
setCoordinates(newValue); // сохраняем ввод пользователя как есть
|
||||
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);
|
||||
|
||||
@@ -260,7 +287,7 @@ export const InformationTab = observer(
|
||||
justifyContent: "space-around",
|
||||
width: "80%",
|
||||
gap: 2,
|
||||
flexDirection: { xs: "column", sm: "row" }, // Stack on extra small, side-by-side on small and up
|
||||
flexDirection: { xs: "column", sm: "row" },
|
||||
}}
|
||||
>
|
||||
<ImageUploadCard
|
||||
@@ -358,7 +385,7 @@ export const InformationTab = observer(
|
||||
/>
|
||||
|
||||
<VideoPreviewCard
|
||||
title="Видео превью"
|
||||
title="Видеозаставка"
|
||||
videoId={sight.common.video_preview}
|
||||
onVideoClick={handleVideoPreviewClick}
|
||||
onDeleteVideoClick={() => {
|
||||
@@ -372,12 +399,10 @@ export const InformationTab = observer(
|
||||
}}
|
||||
onSelectVideoClick={(file) => {
|
||||
if (file) {
|
||||
// Если передан файл, открываем диалог загрузки медиа
|
||||
editSightStore.setFileToUpload(file);
|
||||
setActiveMenuType("video_preview");
|
||||
setIsUploadMediaOpen(true);
|
||||
} else {
|
||||
// Если файл не передан, открываем диалог выбора существующих медиа
|
||||
setActiveMenuType("video_preview");
|
||||
setIsAddMediaOpen(true);
|
||||
}
|
||||
@@ -387,31 +412,25 @@ export const InformationTab = observer(
|
||||
</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
|
||||
backgroundColor: "background.paper",
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
justifyContent: "flex-end", // Align to the right
|
||||
justifyContent: "flex-end",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="success"
|
||||
startIcon={<Save color="white" size={18} />}
|
||||
onClick={async () => {
|
||||
await updateSight();
|
||||
toast.success("Достопримечательность сохранена");
|
||||
}}
|
||||
onClick={handleSave} // Используем новую функцию-обработчик
|
||||
>
|
||||
Сохранить
|
||||
</Button>
|
||||
@@ -419,7 +438,6 @@ export const InformationTab = observer(
|
||||
</Box>
|
||||
</TabPanel>
|
||||
|
||||
{/* Media Menu */}
|
||||
<MuiMenu
|
||||
anchorEl={menuAnchorEl}
|
||||
open={Boolean(menuAnchorEl)}
|
||||
@@ -492,7 +510,6 @@ export const InformationTab = observer(
|
||||
mediaId={mediaId}
|
||||
/>
|
||||
|
||||
{/* Модальное окно предпросмотра видео */}
|
||||
{sight.common.video_preview && sight.common.video_preview !== "" && (
|
||||
<Dialog
|
||||
open={isVideoPreviewOpen}
|
||||
@@ -519,7 +536,17 @@ export const InformationTab = observer(
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
)}
|
||||
|
||||
{/* ИНТЕГРИРОВАННОЕ ПРЕДУПРЕЖДАЮЩЕЕ ОКНО */}
|
||||
{isSaveWarningOpen && (
|
||||
<SaveWithoutCityAgree
|
||||
blocker={{
|
||||
proceed: handleConfirmSave,
|
||||
reset: handleCancelSave,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
);
|
||||
@@ -17,4 +17,5 @@ export * from "./LeaveAgree";
|
||||
export * from "./DeleteModal";
|
||||
export * from "./SnapshotRestore";
|
||||
export * from "./CreateButton";
|
||||
export * from "./SaveWithoutCityAgree"
|
||||
export * from "./modals";
|
||||
|
||||
Reference in New Issue
Block a user