281 lines
8.2 KiB
TypeScript
281 lines
8.2 KiB
TypeScript
import {
|
||
Button,
|
||
Paper,
|
||
TextField,
|
||
Typography,
|
||
Box,
|
||
Divider,
|
||
} from "@mui/material";
|
||
import { observer } from "mobx-react-lite";
|
||
import { ArrowLeft, Loader2, Save } from "lucide-react";
|
||
import { useNavigate } from "react-router-dom";
|
||
import { toast } from "react-toastify";
|
||
import {
|
||
userStore,
|
||
mediaStore,
|
||
isMediaIdEmpty,
|
||
SelectMediaDialog,
|
||
UploadMediaDialog,
|
||
PreviewMediaDialog,
|
||
selectedCityStore,
|
||
} from "@shared";
|
||
import { useState, useEffect } from "react";
|
||
import { ImageUploadCard, PermissionsTable, RolesHintTable, ROLE_RESOURCES } 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);
|
||
|
||
const [localRoles, setLocalRoles] = useState<string[]>(
|
||
createUserData.roles ?? ["articles_ro", "articles_rw", "media_ro", "media_rw"]
|
||
);
|
||
|
||
useEffect(() => {
|
||
selectedCityStore.setIsLocked(true);
|
||
return () => selectedCityStore.setIsLocked(false);
|
||
}, []);
|
||
|
||
useEffect(() => {
|
||
mediaStore.getMedia();
|
||
}, []);
|
||
|
||
useEffect(() => {
|
||
const allRw = ROLE_RESOURCES.every(({ key }) => localRoles.includes(`${key}_rw`));
|
||
const isAdmin = allRw && !localRoles.includes("devices_maintenance_rw");
|
||
if (isAdmin !== createUserData.is_admin) {
|
||
setCreateUserData(
|
||
createUserData.name || "",
|
||
createUserData.email || "",
|
||
createUserData.password || "",
|
||
isAdmin,
|
||
createUserData.icon
|
||
);
|
||
}
|
||
}, [localRoles]);
|
||
|
||
const handleCreate = async () => {
|
||
try {
|
||
setIsLoading(true);
|
||
// Убеждаемся, что роли в сторе обновлены перед созданием
|
||
userStore.createUserData.roles = localRoles;
|
||
await createUser();
|
||
toast.success("Пользователь успешно создан");
|
||
navigate("/user");
|
||
} catch (error) {
|
||
toast.error("Ошибка при создании пользователя");
|
||
} finally {
|
||
setIsLoading(false);
|
||
}
|
||
};
|
||
|
||
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 p-6 flex flex-col gap-8">
|
||
<button className="flex items-center gap-2 self-start" onClick={() => navigate(-1)}>
|
||
<ArrowLeft size={20} />
|
||
Назад
|
||
</button>
|
||
|
||
<section className="flex flex-col gap-6">
|
||
<Typography variant="h6">Основные данные</Typography>
|
||
|
||
<TextField
|
||
fullWidth
|
||
label="Имя"
|
||
value={createUserData.name || ""}
|
||
required
|
||
onChange={(e) =>
|
||
setCreateUserData(
|
||
e.target.value,
|
||
createUserData.email || "",
|
||
createUserData.password || "",
|
||
createUserData.is_admin || false,
|
||
createUserData.icon
|
||
)
|
||
}
|
||
/>
|
||
|
||
<TextField
|
||
fullWidth
|
||
label="Email"
|
||
value={createUserData.email || ""}
|
||
required
|
||
onChange={(e) =>
|
||
setCreateUserData(
|
||
createUserData.name || "",
|
||
e.target.value,
|
||
createUserData.password || "",
|
||
createUserData.is_admin || false,
|
||
createUserData.icon
|
||
)
|
||
}
|
||
/>
|
||
|
||
<TextField
|
||
fullWidth
|
||
label="Пароль"
|
||
value={createUserData.password || ""}
|
||
required
|
||
type="password"
|
||
onChange={(e) =>
|
||
setCreateUserData(
|
||
createUserData.name || "",
|
||
createUserData.email || "",
|
||
e.target.value,
|
||
createUserData.is_admin || false,
|
||
createUserData.icon
|
||
)
|
||
}
|
||
/>
|
||
|
||
<div className="w-full flex flex-col gap-4 max-w-[300px]">
|
||
<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>
|
||
</section>
|
||
|
||
<Divider />
|
||
|
||
<section className="flex flex-col gap-4">
|
||
<Typography variant="h6">Права доступа</Typography>
|
||
|
||
<Box sx={{ display: "flex", gap: 1 }}>
|
||
<Button
|
||
variant="outlined"
|
||
size="small"
|
||
onClick={() => {
|
||
setCreateUserData(
|
||
createUserData.name || "",
|
||
createUserData.email || "",
|
||
createUserData.password || "",
|
||
true,
|
||
createUserData.icon
|
||
);
|
||
const next: string[] = [];
|
||
for (const { key } of ROLE_RESOURCES) {
|
||
next.push(`${key}_rw`);
|
||
}
|
||
next.push("snapshot_create");
|
||
setLocalRoles(next);
|
||
}}
|
||
>
|
||
Полный доступ (admin)
|
||
</Button>
|
||
<Button
|
||
variant="outlined"
|
||
size="small"
|
||
onClick={() => {
|
||
setCreateUserData(
|
||
createUserData.name || "",
|
||
createUserData.email || "",
|
||
createUserData.password || "",
|
||
false,
|
||
createUserData.icon
|
||
);
|
||
setLocalRoles(["devices_ro", "vehicles_ro", "devices_maintenance_rw"]);
|
||
}}
|
||
>
|
||
Администратор ТО
|
||
</Button>
|
||
</Box>
|
||
|
||
<PermissionsTable localRoles={localRoles} setLocalRoles={setLocalRoles} />
|
||
<RolesHintTable />
|
||
</section>
|
||
|
||
<Button
|
||
variant="contained"
|
||
className="self-end w-min flex gap-2 items-center"
|
||
startIcon={<Save size={20} />}
|
||
onClick={handleCreate}
|
||
disabled={
|
||
isLoading || !createUserData.name || !createUserData.password || !createUserData.email
|
||
}
|
||
>
|
||
{isLoading ? (
|
||
<Loader2 size={20} className="animate-spin" />
|
||
) : (
|
||
"Создать"
|
||
)}
|
||
</Button>
|
||
|
||
<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>
|
||
);
|
||
});
|