feat: big major update

This commit is contained in:
2026-02-02 04:00:37 +03:00
parent bbab6fc46a
commit d557664b25
34 changed files with 1801 additions and 665 deletions

View File

@@ -6,17 +6,35 @@ import {
FormControlLabel,
} from "@mui/material";
import { observer } from "mobx-react-lite";
import { ArrowLeft, Save } from "lucide-react";
import { Loader2 } from "lucide-react";
import { ArrowLeft, Loader2, Save } from "lucide-react";
import { useNavigate } from "react-router-dom";
import { toast } from "react-toastify";
import { userStore } from "@shared";
import { useState } from "react";
import {
userStore,
mediaStore,
isMediaIdEmpty,
SelectMediaDialog,
UploadMediaDialog,
PreviewMediaDialog,
} from "@shared";
import { useState, useEffect } from "react";
import { ImageUploadCard } from "@widgets";
export const UserCreatePage = observer(() => {
const navigate = useNavigate();
const { createUserData, setCreateUserData, createUser } = userStore;
const [isLoading, setIsLoading] = useState(false);
const [isSelectMediaOpen, setIsSelectMediaOpen] = useState(false);
const [isUploadMediaOpen, setIsUploadMediaOpen] = useState(false);
const [isPreviewMediaOpen, setIsPreviewMediaOpen] = useState(false);
const [mediaId, setMediaId] = useState("");
const [activeMenuType, setActiveMenuType] = useState<
"thumbnail" | "watermark_lu" | "watermark_rd" | "image" | null
>(null);
useEffect(() => {
mediaStore.getMedia();
}, []);
const handleCreate = async () => {
try {
@@ -31,6 +49,29 @@ export const UserCreatePage = observer(() => {
}
};
const handleMediaSelect = (media: {
id: string;
filename: string;
media_name?: string;
media_type: number;
}) => {
setCreateUserData(
createUserData.name || "",
createUserData.email || "",
createUserData.password || "",
createUserData.is_admin || false,
media.id
);
};
const selectedMedia =
createUserData.icon && !isMediaIdEmpty(createUserData.icon)
? mediaStore.media.find((m) => m.id === createUserData.icon)
: null;
const effectiveIconUrl = isMediaIdEmpty(createUserData.icon)
? null
: selectedMedia?.id ?? createUserData.icon ?? null;
return (
<Paper className="w-full h-full p-3 flex flex-col gap-10">
<div className="flex items-center gap-4">
@@ -54,7 +95,8 @@ export const UserCreatePage = observer(() => {
e.target.value,
createUserData.email || "",
createUserData.password || "",
createUserData.is_admin || false
createUserData.is_admin || false,
createUserData.icon
)
}
/>
@@ -69,7 +111,8 @@ export const UserCreatePage = observer(() => {
createUserData.name || "",
e.target.value,
createUserData.password || "",
createUserData.is_admin || false
createUserData.is_admin || false,
createUserData.icon
)
}
/>
@@ -84,7 +127,8 @@ export const UserCreatePage = observer(() => {
createUserData.name || "",
createUserData.email || "",
e.target.value,
createUserData.is_admin || false
createUserData.is_admin || false,
createUserData.icon
)
}
/>
@@ -99,7 +143,8 @@ export const UserCreatePage = observer(() => {
createUserData.name || "",
createUserData.email || "",
createUserData.password || "",
e.target.checked
e.target.checked,
createUserData.icon
);
}}
/>
@@ -108,6 +153,36 @@ export const UserCreatePage = observer(() => {
/>
</div>
<div className="w-full flex flex-col gap-4 max-w-[300px] mx-auto">
<ImageUploadCard
title="Аватар"
imageKey="thumbnail"
imageUrl={effectiveIconUrl}
onImageClick={() => {
setIsPreviewMediaOpen(true);
setMediaId(effectiveIconUrl ?? "");
}}
onDeleteImageClick={() => {
setCreateUserData(
createUserData.name || "",
createUserData.email || "",
createUserData.password || "",
createUserData.is_admin || false,
""
);
setActiveMenuType(null);
}}
onSelectFileClick={() => {
setActiveMenuType("image");
setIsSelectMediaOpen(true);
}}
setUploadMediaOpen={() => {
setIsUploadMediaOpen(true);
setActiveMenuType("image");
}}
/>
</div>
<Button
variant="contained"
className="w-min flex gap-2 items-center"
@@ -124,6 +199,28 @@ export const UserCreatePage = observer(() => {
)}
</Button>
</div>
<SelectMediaDialog
open={isSelectMediaOpen}
onClose={() => setIsSelectMediaOpen(false)}
onSelectMedia={handleMediaSelect}
mediaType={1}
/>
<UploadMediaDialog
open={isUploadMediaOpen}
onClose={() => setIsUploadMediaOpen(false)}
contextObjectName={createUserData.name || "Пользователь"}
contextType="user"
afterUpload={handleMediaSelect}
hardcodeType={activeMenuType}
/>
<PreviewMediaDialog
open={isPreviewMediaOpen}
onClose={() => setIsPreviewMediaOpen(false)}
mediaId={mediaId}
/>
</Paper>
);
});

View File

@@ -7,12 +7,21 @@ import {
Box,
} from "@mui/material";
import { observer } from "mobx-react-lite";
import { ArrowLeft, Save } from "lucide-react";
import { Loader2 } from "lucide-react";
import { ArrowLeft, Loader2, Save } from "lucide-react";
import { useNavigate, useParams } from "react-router-dom";
import { toast } from "react-toastify";
import { userStore, languageStore, LoadingSpinner } from "@shared";
import {
userStore,
languageStore,
LoadingSpinner,
mediaStore,
isMediaIdEmpty,
SelectMediaDialog,
UploadMediaDialog,
PreviewMediaDialog,
} from "@shared";
import { useEffect, useState } from "react";
import { ImageUploadCard, DeleteModal } from "@widgets";
export const UserEditPage = observer(() => {
const navigate = useNavigate();
@@ -22,8 +31,16 @@ export const UserEditPage = observer(() => {
const { id } = useParams();
const { editUserData, editUser, getUser, setEditUserData } = userStore;
const [isSelectMediaOpen, setIsSelectMediaOpen] = useState(false);
const [isUploadMediaOpen, setIsUploadMediaOpen] = useState(false);
const [isPreviewMediaOpen, setIsPreviewMediaOpen] = useState(false);
const [mediaId, setMediaId] = useState("");
const [isDeleteIconModalOpen, setIsDeleteIconModalOpen] = useState(false);
const [activeMenuType, setActiveMenuType] = useState<
"thumbnail" | "watermark_lu" | "watermark_rd" | "image" | null
>(null);
useEffect(() => {
// Устанавливаем русский язык при загрузке страницы
languageStore.setLanguage("ru");
}, []);
@@ -40,19 +57,38 @@ export const UserEditPage = observer(() => {
}
};
const handleMediaSelect = (media: {
id: string;
filename: string;
media_name?: string;
media_type: number;
}) => {
setEditUserData(
editUserData.name || "",
editUserData.email || "",
editUserData.password || "",
editUserData.is_admin || false,
media.id
);
};
useEffect(() => {
(async () => {
if (id) {
setIsLoadingData(true);
try {
await mediaStore.getMedia();
const data = await getUser(Number(id));
setEditUserData(
data?.name || "",
data?.email || "",
data?.password || "",
data?.is_admin || false
);
if (data) {
setEditUserData(
data.name || "",
data.email || "",
data.password || "",
data.is_admin || false,
data.icon || ""
);
}
} finally {
setIsLoadingData(false);
}
@@ -62,6 +98,14 @@ export const UserEditPage = observer(() => {
})();
}, [id]);
const selectedMedia =
editUserData.icon && !isMediaIdEmpty(editUserData.icon)
? mediaStore.media.find((m) => m.id === editUserData.icon)
: null;
const effectiveIconUrl = isMediaIdEmpty(editUserData.icon)
? null
: selectedMedia?.id ?? editUserData.icon ?? null;
if (isLoadingData) {
return (
<Box
@@ -100,7 +144,8 @@ export const UserEditPage = observer(() => {
e.target.value,
editUserData.email || "",
editUserData.password || "",
editUserData.is_admin || false
editUserData.is_admin || false,
editUserData.icon
)
}
/>
@@ -114,7 +159,8 @@ export const UserEditPage = observer(() => {
editUserData.name || "",
e.target.value,
editUserData.password || "",
editUserData.is_admin || false
editUserData.is_admin || false,
editUserData.icon
)
}
/>
@@ -129,7 +175,8 @@ export const UserEditPage = observer(() => {
editUserData.name || "",
editUserData.email || "",
e.target.value,
editUserData.is_admin || false
editUserData.is_admin || false,
editUserData.icon
)
}
/>
@@ -142,7 +189,8 @@ export const UserEditPage = observer(() => {
editUserData.name || "",
editUserData.email || "",
editUserData.password || "",
e.target.checked
e.target.checked,
editUserData.icon
)
}
/>
@@ -150,6 +198,27 @@ export const UserEditPage = observer(() => {
label="Администратор"
/>
<div className="w-full flex flex-col gap-4 max-w-[300px]">
<ImageUploadCard
title="Аватар"
imageKey="thumbnail"
imageUrl={effectiveIconUrl}
onImageClick={() => {
setIsPreviewMediaOpen(true);
setMediaId(effectiveIconUrl ?? "");
}}
onDeleteImageClick={() => setIsDeleteIconModalOpen(true)}
onSelectFileClick={() => {
setActiveMenuType("image");
setIsSelectMediaOpen(true);
}}
setUploadMediaOpen={() => {
setIsUploadMediaOpen(true);
setActiveMenuType("image");
}}
/>
</div>
<Button
variant="contained"
className="w-min flex gap-2 items-center self-end"
@@ -164,6 +233,44 @@ export const UserEditPage = observer(() => {
)}
</Button>
</div>
<SelectMediaDialog
open={isSelectMediaOpen}
onClose={() => setIsSelectMediaOpen(false)}
onSelectMedia={handleMediaSelect}
mediaType={1}
/>
<UploadMediaDialog
open={isUploadMediaOpen}
onClose={() => setIsUploadMediaOpen(false)}
contextObjectName={editUserData.name || "Пользователь"}
contextType="user"
afterUpload={handleMediaSelect}
hardcodeType={activeMenuType}
/>
<PreviewMediaDialog
open={isPreviewMediaOpen}
onClose={() => setIsPreviewMediaOpen(false)}
mediaId={mediaId}
/>
<DeleteModal
open={isDeleteIconModalOpen}
onDelete={() => {
setEditUserData(
editUserData.name || "",
editUserData.email || "",
editUserData.password || "",
editUserData.is_admin || false,
""
);
setIsDeleteIconModalOpen(false);
}}
onCancel={() => setIsDeleteIconModalOpen(false)}
edit
/>
</Paper>
);
});