feat: Delete user preview + snapshot preview + coordinates update

This commit is contained in:
2025-06-06 20:15:02 +03:00
parent d74789a0d8
commit 1104e94ba8
14 changed files with 456 additions and 233 deletions

View File

@ -19,9 +19,7 @@ import {
VehicleListPage,
ArticleListPage,
CityPreviewPage,
UserPreviewPage,
CountryPreviewPage,
SnapshotPreviewPage,
VehiclePreviewPage,
CarrierPreviewPage,
SnapshotCreatePage,
@ -132,12 +130,10 @@ const router = createBrowserRouter([
// User
{ path: "user", element: <UserListPage /> },
{ path: "user/:id", element: <UserPreviewPage /> },
// Snapshot
{ path: "snapshot", element: <SnapshotListPage /> },
{ path: "snapshot/create", element: <SnapshotCreatePage /> },
{ path: "snapshot/:id", element: <SnapshotPreviewPage /> },
// Carrier
{ path: "carrier", element: <CarrierListPage /> },

View File

@ -3,3 +3,7 @@
button {
cursor: pointer;
}
.mde-preview {
background-color: #f5f5f5;
}

View File

@ -1135,7 +1135,7 @@ const MapControls: React.FC<MapControlsProps> = ({
},
{
mode: "statistics",
title: "Инфо",
title: "Информация",
longTitle: "Информация",
icon: <StatsIcon />,
action: () => mapService.activateStatisticsMode(),

View File

@ -53,9 +53,7 @@ export const SnapshotListPage = observer(() => {
>
<DatabaseBackup size={20} className="text-blue-500" />
</button>
<button onClick={() => navigate(`/snapshot/${params.row.id}`)}>
<Eye size={20} className="text-green-500" />
</button>
<button
onClick={() => {
setIsDeleteModalOpen(true);

View File

@ -1,58 +0,0 @@
import { Paper } from "@mui/material";
import { snapshotStore } from "@shared";
import { observer } from "mobx-react-lite";
import { ArrowLeft } from "lucide-react";
import { useEffect } from "react";
import { useNavigate, useParams } from "react-router-dom";
export const SnapshotPreviewPage = observer(() => {
const { id } = useParams();
const { getSnapshot, snapshot } = snapshotStore;
const navigate = useNavigate();
useEffect(() => {
(async () => {
await getSnapshot(id as string);
})();
}, [id]);
return (
<Paper className="w-full h-full p-3 flex flex-col gap-10">
<div className="flex justify-between items-center">
<button
className="flex items-center gap-2"
onClick={() => navigate(-1)}
>
<ArrowLeft size={20} />
Назад
</button>
{/* <div className="flex gap-2">
<Button
variant="contained"
color="primary"
onClick={() => navigate(`/snapshot/${id}/edit`)}
startIcon={<Pencil size={20} />}
>
Редактировать
</Button>
<Button
variant="contained"
color="error"
onClick={() => navigate(`/snapshot/${id}/delete`)}
startIcon={<Trash2 size={20} />}
>
Удалить
</Button>
</div> */}
</div>
{snapshot && (
<div className="flex flex-col gap-10 w-full">
<div className="flex flex-col gap-2">
<h1 className="text-lg font-bold">Название</h1>
<p>{snapshot?.Name}</p>
</div>
</div>
)}
</Paper>
);
});

View File

@ -1,3 +1,3 @@
export * from "./SnapshotListPage";
export * from "./SnapshotPreviewPage";
export * from "./SnapshotCreatePage";

View File

@ -56,9 +56,6 @@ export const UserListPage = observer(() => {
renderCell: (params: GridRenderCellParams) => {
return (
<div className="flex h-full gap-7 justify-center items-center">
<button onClick={() => navigate(`/user/${params.row.id}`)}>
<Eye size={20} className="text-green-500" />
</button>
<button
onClick={() => {
setIsDeleteModalOpen(true);

View File

@ -1,74 +0,0 @@
import { Paper } from "@mui/material";
import { userStore } from "@shared";
import { observer } from "mobx-react-lite";
import { ArrowLeft } from "lucide-react";
import { useEffect } from "react";
import { useNavigate, useParams } from "react-router-dom";
export const UserPreviewPage = observer(() => {
const { id } = useParams();
const { getUser, user } = userStore;
const navigate = useNavigate();
useEffect(() => {
(async () => {
await getUser(Number(id));
})();
}, [id]);
return (
<Paper className="w-full h-full p-3 flex flex-col gap-10">
<div className="flex justify-between items-center">
<button
className="flex items-center gap-2"
onClick={() => navigate(-1)}
>
<ArrowLeft size={20} />
Назад
</button>
{/* <div className="flex gap-2">
<Button
variant="contained"
color="primary"
onClick={() => navigate(`/user/${id}/edit`)}
startIcon={<Pencil size={20} />}
>
Редактировать
</Button>
<Button
variant="contained"
color="error"
onClick={() => navigate(`/user/${id}/delete`)}
startIcon={<Trash2 size={20} />}
>
Удалить
</Button>
</div> */}
</div>
{user && (
<div className="flex flex-col gap-10 w-full">
<div className="flex flex-col gap-2">
<h1 className="text-lg font-bold">Название</h1>
<p>{user?.name}</p>
</div>
<div className="flex flex-col gap-2">
<h1 className="text-lg font-bold">Email</h1>
<p>{user?.email}</p>
</div>
<div className="flex flex-col gap-2">
<h1 className="text-lg font-bold">Роль</h1>
<p
className={
user?.is_admin === true ? "text-green-500" : "text-red-500"
}
>
{user?.is_admin ? "Администратор" : "Пользователь"}
</p>
</div>
</div>
)}
</Paper>
);
});

View File

@ -1,2 +1 @@
export * from "./UserListPage";
export * from "./UserPreviewPage";

View File

@ -1,5 +1,6 @@
import { useMemo } from "react";
import { styled } from "@mui/material/styles";
import SimpleMDE, { SimpleMDEReactProps } from "react-simplemde-editor";
import SimpleMDE from "react-simplemde-editor";
import "easymde/dist/easymde.min.css";
const StyledMarkdownEditor = styled("div")(({ theme }) => ({
@ -19,7 +20,6 @@ const StyledMarkdownEditor = styled("div")(({ theme }) => ({
"& .editor-statusbar": {
display: "none",
},
// Стили для самого редактора
"& .CodeMirror": {
backgroundColor: theme.palette.background.paper,
color: theme.palette.text.primary,
@ -33,14 +33,12 @@ const StyledMarkdownEditor = styled("div")(({ theme }) => ({
minHeight: "200px",
maxHeight: "500px",
},
// Стили для текста в редакторе
"& .CodeMirror-selected": {
backgroundColor: `${theme.palette.action.selected} !important`,
},
"& .CodeMirror-cursor": {
borderLeftColor: theme.palette.text.primary,
},
// Стили для markdown разметки
"& .cm-header": {
color: theme.palette.primary.main,
},
@ -57,13 +55,11 @@ const StyledMarkdownEditor = styled("div")(({ theme }) => ({
"& .cm-formatting": {
color: theme.palette.text.secondary,
},
"& .CodeMirror .editor-preview-full": {
backgroundColor: theme.palette.background.paper,
color: theme.palette.text.primary,
borderColor: theme.palette.divider,
},
"& .EasyMDEContainer": {
position: "relative",
zIndex: 1000,
@ -73,45 +69,53 @@ const StyledMarkdownEditor = styled("div")(({ theme }) => ({
},
}));
export const ReactMarkdownEditor = (props: SimpleMDEReactProps) => {
if (props.options)
props.options.toolbar = [
"bold",
"italic",
"strikethrough",
{
name: "Underline",
action: (editor: any) => {
const cm = editor.codemirror;
let output = "";
const selectedText = cm.getSelection();
const text = selectedText ?? "placeholder";
output = "<u>" + text + "</u>";
cm.replaceSelection(output);
export const ReactMarkdownEditor = ({
options: incomingOptions,
...restProps
}: any) => {
const mergedOptions = useMemo(() => {
return {
...incomingOptions,
forceSync: true,
spellChecker: false,
toolbar: [
"bold",
"italic",
"strikethrough",
{
name: "Underline",
action: (editor: any) => {
const cm = editor.codemirror;
const selectedText = cm.getSelection();
const text = selectedText || "placeholder";
const output = `<u>${text}</u>`;
cm.replaceSelection(output);
},
className: "fa fa-underline",
title: "Underline (Ctrl/Cmd-Alt-U)",
},
className: "fa fa-underline", // Look for a suitable icon
title: "Underline (Ctrl/Cmd-Alt-U)",
},
"heading",
"quote",
"unordered-list",
"ordered-list",
"link",
"image",
"code",
"table",
"horizontal-rule",
"preview",
"fullscreen",
"guide",
];
"heading",
"quote",
"unordered-list",
"ordered-list",
"link",
"image",
"code",
"table",
"horizontal-rule",
"preview",
"fullscreen",
"guide",
],
};
}, []); // создаётся один раз
return (
<StyledMarkdownEditor
className="my-markdown-editor"
sx={{ marginTop: 1.5, marginBottom: 3 }}
>
<SimpleMDE {...props} />
<SimpleMDE options={mergedOptions} {...restProps} />
</StyledMarkdownEditor>
);
};

View File

@ -40,7 +40,7 @@ export const CreateInformationTab = observer(
const data = sight[language];
const [, setCity] = useState<number>(sight.city_id ?? 0);
const [coordinates, setCoordinates] = useState<string>(`0 0`);
const [coordinates, setCoordinates] = useState<string>(`0, 0`);
// Menu state for each media button
const [menuAnchorEl, setMenuAnchorEl] = useState<null | HTMLElement>(null);
@ -60,7 +60,7 @@ export const CreateInformationTab = observer(
useEffect(() => {
// Показывать только при инициализации (не менять при ошибках пользователя)
if (sight.latitude !== 0 || sight.longitude !== 0) {
setCoordinates(`${sight.latitude} ${sight.longitude}`);
setCoordinates(`${sight.latitude}, ${sight.longitude}`);
}
// если координаты обнулились — оставить поле как есть
}, [sight.latitude, sight.longitude]);

View File

@ -418,7 +418,7 @@ export const CreateRightTab = observer(
<Box sx={{ minHeight: 200, flexGrow: 1 }}>
<ReactMarkdownEditor
value={currentRightArticle.body}
onChange={(mdValue) =>
onChange={(mdValue: any) =>
activeArticleIndex !== null &&
updateRightArticleInfo(
activeArticleIndex,

View File

@ -42,7 +42,7 @@ export const InformationTab = observer(
const { sight, updateSightInfo, updateSight } = editSightStore;
const [, setCity] = useState<number>(sight.common.city_id ?? 0);
const [coordinates, setCoordinates] = useState<string>(`0 0`);
const [coordinates, setCoordinates] = useState<string>(`0, 0`);
// Menu state for each media button
const [menuAnchorEl, setMenuAnchorEl] = useState<null | HTMLElement>(null);
@ -54,7 +54,7 @@ export const InformationTab = observer(
useEffect(() => {
// Показывать только при инициализации (не менять при ошибках пользователя)
if (sight.common.latitude !== 0 || sight.common.longitude !== 0) {
setCoordinates(`${sight.common.latitude} ${sight.common.longitude}`);
setCoordinates(`${sight.common.latitude}, ${sight.common.longitude}`);
}
// если координаты обнулились — оставить поле как есть
}, [sight.common.latitude, sight.common.longitude]);
@ -178,10 +178,12 @@ export const InformationTab = observer(
label="Координаты"
value={coordinates}
onChange={(e) => {
const input = e.target.value;
setCoordinates(input); // показываем как есть
const newValue = e.target.value;
setCoordinates(newValue); // сохраняем ввод пользователя как есть
const [latStr, lonStr] = input.split(/\s+/); // учитываем любые пробелы
// Обрабатываем значение для сохранения
const input = newValue.replace(/,/g, " ").trim();
const [latStr, lonStr] = input.split(/\s+/);
const lat = parseFloat(latStr);
const lon = parseFloat(lonStr);
@ -212,7 +214,7 @@ export const InformationTab = observer(
}}
fullWidth
variant="outlined"
placeholder="Введите координаты в формате: широта долгота"
placeholder="Введите координаты в формате: широта долгота (можно использовать запятые или пробелы)"
/>
</Box>