added comp_systems and modular instances frontend

This commit is contained in:
2026-05-06 14:46:16 +03:00
parent 45c157b910
commit 26623dc2c1
44 changed files with 7739 additions and 899 deletions

View File

@@ -0,0 +1,194 @@
import {
Alert,
Card,
Center,
LoadingOverlay,
Pill,
Space,
Text,
Title,
} from "@mantine/core";
import { useEffect, useState } from "react";
import { Helmet } from "react-helmet";
import { IconPlus } from "@tabler/icons-react";
import { useParams } from "react-router";
import CustomButton from "Components/CustomButton/CustomButton";
import { useAuthenticationStore } from "Stores/AuthenticationStore";
import { useDeviceStore } from "Stores/DeviceStore";
import { getComputationalSystemById } from "Api/QuantumBackend/MachineManagment";
import type { SystemTeamData, SystemWithTeams } from "Types/Machine/Machine";
import TeamInMachineListCard from "Components/ListCard/TeamListCard/TeamInMachineCard/TeamInMachine";
import { AddTeamToDevice } from "Modals/AddTeamToDevice/AddTeamToDevice";
import { getShortTeamsList } from "Api/QuantumBackend/TeamManagement";
function DevicePage() {
const [isOpen, setIsOpen] = useState(false);
const { device_id } = useParams();
const { setDevices, devices } = useDeviceStore();
const { profile } = useAuthenticationStore();
const [is_loading, set_is_loading] = useState(true);
const [cur_device, set_cur_device] = useState<SystemWithTeams | undefined>(
devices.find((e) => e.system.id === Number(device_id)),
);
const [teams, setTeams] =
useState<{ team_id: number; team_name: string }[]>();
useEffect(() => {
if (!cur_device && profile) {
getComputationalSystemById(Number(device_id))
.then((device) => {
if (device) {
console.log(device);
setDevices([device]);
set_cur_device(device);
set_is_loading(false);
}
})
.catch(() => {
set_is_loading(false);
});
} else {
if (profile) {
set_is_loading(false);
}
}
if (devices.find((e) => e.system.id === Number(device_id)) != cur_device) {
set_cur_device(devices.find((e) => e.system.id === Number(device_id)));
}
if (profile && !teams) {
getShortTeamsList({ permission: "manage_machines" })
.then((teams) => {
if (teams) {
setTeams(teams);
}
})
.catch(() => {
setTeams([]);
});
}
}, [cur_device, profile, devices]);
return (
<>
<Helmet>
<title>
{cur_device
? "Device " + cur_device.system.id + " | QMolSim"
: "Error | QmolSim"}
</title>
<meta
name="description"
content="See the information on the currently selected team"
/>
</Helmet>
{cur_device && (
<AddTeamToDevice
isOpened={isOpen}
setIsOpened={setIsOpen}
device={cur_device}
teams={teams}
/>
)}
<div className="TeamPage">
<LoadingOverlay
visible={is_loading}
zIndex={1000}
overlayProps={{ radius: "sm", blur: 2 }}
loaderProps={{ size: 50, type: "dots" }}
/>
<Card className="TeamData">
<div>
<Title order={3}>
Устройство: {cur_device ? cur_device.system.system_name : ""}
</Title>
<Space h="xl" />
<div style={{ display: "flex", flexDirection: "row", gap: "15px" }}>
<Text size="md">Статус: </Text>
<Pill className="ExperimentPill">
<Text size="md">{cur_device?.system.status}</Text>
</Pill>
<div className="dateContainer">
<Text size={"lg"}>Статус обновлен: </Text>
<Text size="md" style={{ alignContent: "center" }}>
{new Date(
cur_device?.system.last_updated
? cur_device?.system.last_updated + "Z"
: "",
).toLocaleDateString("ru")}{" "}
</Text>
<Text size="md" style={{ alignContent: "center" }}>
{new Date(
cur_device?.system.last_updated
? cur_device?.system.last_updated + "Z"
: "",
).toLocaleTimeString("ru")}
</Text>
</div>
</div>
<Space h="md" />
<Text size="lg">
Максимальное количество кубит:{" "}
<b>{cur_device?.system.max_qubits}</b>
</Text>
</div>
</Card>
<div
style={{
display: "flex",
flexWrap: "nowrap",
gap: "20px",
alignItems: "center",
}}
>
<Title order={3} style={{ textWrap: "nowrap" }}>
Команды с доступом:
</Title>
<div>
<CustomButton
color="contrast"
onClick={() => {
setIsOpen(true);
}}
icon={<IconPlus />}
text="Добавить команду"
/>
</div>
</div>
{cur_device && cur_device.teams.length > 0 && (
<div className="TeamMembers">
{cur_device.teams.map((team: SystemTeamData) => {
return <TeamInMachineListCard team={team} system={cur_device} />;
})}
</div>
)}
{cur_device && cur_device.teams.length == 0 && (
<div className="TeamMembers">
<Alert>
<Center>
{" "}
<Text c="contrast" size={"xl"}>
Команд с доступом нет
</Text>{" "}
</Center>
</Alert>
</div>
)}
{!cur_device && !is_loading && (
<Alert color="red">
<Center>
{" "}
<Text c="contrast" size={"xl"}>
Ошибка. Команда не найдена
</Text>{" "}
</Center>
</Alert>
)}
</div>
</>
);
}
export default DevicePage;

View File

@@ -0,0 +1,5 @@
.DevicesPage {
display: flex;
flex-direction: column;
flex-grow: 1;
}

View File

@@ -3,8 +3,78 @@ import "./DevicesPage.css";
import { PaginationContainer } from "Components/PaginationContainer/PaginationContainer";
import { Alert } from "@mantine/core";
import { Link } from "react-router";
import { useDeviceStore } from "Stores/DeviceStore";
import { useEffect, useState } from "react";
import { useAuthenticationStore } from "Stores/AuthenticationStore";
import { getMySystems } from "Api/QuantumBackend/MachineManagment";
import MachinesListCard from "Components/ListCard/MachineListCard/MachinesListCard";
function DevicesPage() {
const { devices, setDevices, setTotalDevices, totalDevices, removeDevice } =
useDeviceStore();
const [isLoading, setIsLoading] = useState(true);
const { profile } = useAuthenticationStore();
const [cur_page, set_cur_page] = useState<number>(1);
const [page_size, set_page_size] = useState<number>(256);
useEffect(() => {
setIsLoading(true);
if (profile) {
getMySystems({ page_num: cur_page })
.then((devices) => {
setDevices(devices.systems);
set_cur_page(devices.cur_page);
setTotalDevices(devices.total_systems);
set_page_size(devices.page_size);
setIsLoading(false);
})
.catch(() => setIsLoading(false));
}
}, [profile]);
useEffect(() => {
if (profile) {
if (devices && devices.length > 0 && devices.length > page_size) {
removeDevice(devices[devices.length - 1].system.id);
}
if (
devices &&
devices.length > 0 &&
cur_page > Math.ceil(totalDevices / page_size)
) {
setIsLoading(true);
getMySystems({ page_num: Math.max(1, cur_page - 1) })
.then((devices) => {
setDevices(devices.systems);
set_cur_page(devices.cur_page);
setTotalDevices(devices.total_systems);
set_page_size(devices.page_size);
setIsLoading(false);
scroll({ top: 0 });
})
.catch(() => setIsLoading(false));
set_cur_page(Math.max(1, cur_page - 1));
}
if (
devices &&
devices.length > 0 &&
devices.length < Math.min(page_size, totalDevices) &&
cur_page != Math.ceil(totalDevices / page_size)
) {
getMySystems({ page_num: cur_page })
.then((devices) => {
setDevices(devices.systems);
set_cur_page(devices.cur_page);
setTotalDevices(devices.total_systems);
set_page_size(devices.page_size);
setIsLoading(false);
scroll({ top: 0 });
})
.catch(() => setIsLoading(false));
}
}
}, [profile, devices]);
return (
<>
<Helmet>
@@ -16,17 +86,33 @@ function DevicesPage() {
</Helmet>
<div className="DevicesPage">
<PaginationContainer
numberOfPages={1}
isLoading={false}
activePage={1}
setPage={() => {}}
numberOfPages={Math.ceil(totalDevices / page_size)}
isLoading={isLoading}
activePage={cur_page}
setPage={(page) => {
getMySystems({ page_num: page })
.then((devices) => {
setDevices(devices.systems);
set_cur_page(devices.cur_page);
setTotalDevices(devices.total_systems);
set_page_size(devices.page_size);
setIsLoading(false);
scroll({ top: 0 });
})
.catch(() => setIsLoading(false));
}}
>
<Alert title="Подключенные устройства не найдены" color="blue">
Для того, чтобы добавить устройство, простмотрите{" "}
<Link to={"documentaion"} className="invisible_link">
документацию
</Link>
</Alert>
{devices.length == 0 && !isLoading && (
<Alert title="Подключенные устройства не найдены" color="blue">
Для того, чтобы добавить устройство, простмотрите{" "}
<Link to={"documentaion"} className="invisible_link">
документацию
</Link>
</Alert>
)}
{devices.map((machine) => {
return <MachinesListCard {...machine} />;
})}
</PaginationContainer>
</div>
</>

View File

@@ -1,7 +1,7 @@
import { Alert, Center, Text } from "@mantine/core";
import "./ExperimentPage.css";
import { PaginationContainer } from "Components/PaginationContainer/PaginationContainer";
import { NewMoleculeModal } from "Modals/NewMolecule/NewMolecule";
//import { NewMoleculeModal } from "Modals/NewMolecule/NewMolecule";
import { useState } from "react";
import { Helmet } from "react-helmet";
import { IconPlus, IconSettings } from "@tabler/icons-react";
@@ -12,7 +12,7 @@ import { useExperimentStore } from "Stores/ExperimentStore";
function ExperimentPage() {
const [isOpen, setIsOpen] = useState(false);
const { experiment_id } = useParams();
const { experiments } = useExperimentStore();
const { experiments, addTask } = useExperimentStore();
const experiment = experiments.find((exp) => {
return exp.id == Number(experiment_id);
@@ -33,10 +33,6 @@ function ExperimentPage() {
</Helmet>
{experiment && (
<div className="ExperimentPage">
<NewMoleculeModal
isOpened={isOpen}
setIsOpened={(toOpen: boolean) => setIsOpen(toOpen)}
/>
<div
style={{
display: "flex",
@@ -49,6 +45,12 @@ function ExperimentPage() {
<CustomButton
color="contrast"
onClick={() => {
addTask(Number(experiment_id) || 0, {
id: 1,
name: "Задача 1",
description: "",
data: {},
});
setIsOpen(true);
}}
icon={<IconPlus />}

View File

@@ -1,65 +0,0 @@
.MoleculeEdit {
height: 100%;
display: flex;
}
.MoleculeEditWrapper {
display: flex !important;
flex: 1 !important;
min-width: 0;
min-height: 0;
background-color: var(--mantine-color-secondary-filled);
}
.MoleculeEditLineNumber {
height: 100%;
padding-top: calc(1px + var(--input-padding-y, 0rem));
padding-bottom: calc(1px + var(--input-padding-y, 0rem));
font-family:
"Fira Code", "Courier New", Courier, monospace; /* Monospace fonts */
font-size: 14px; /* Comfortable size */
line-height: 1.5; /* Spacing like an editor */
letter-spacing: 0; /* Keeps punctuation aligned */
white-space: pre-line;
overflow: scroll;
scrollbar-width: none;
overscroll-behavior: none;
cursor: default;
}
.MoleculeEditInput {
font-family:
"Fira Code", "Courier New", Courier, monospace; /* Monospace fonts */
font-size: 14px; /* Comfortable size */
line-height: 1.5; /* Spacing like an editor */
letter-spacing: 0; /* Keeps punctuation aligned */
white-space: pre; /* preserves spaces & tabs */
overflow-x: auto; /* horizontal scroll when needed */
overflow-y: auto; /* vertical scroll when needed */
word-wrap: normal; /* prevent wrapping */
overscroll-behavior: none;
background-color: var(--mantine-color-secondary-filled);
}
.Separator {
width: 10px;
}
.moleculeInput {
height: 250px;
}
.moleculeInputWrapper {
height: 100%;
}
.moleculeInputWrapper > * {
height: 100%;
}
.tabPanel {
flex: 1;
padding: 10px;
border-left: 1px solid var(--tab-border-color);
border-right: 1px solid var(--tab-border-color);
border-bottom: 1px solid var(--tab-border-color);
}

View File

@@ -1,113 +0,0 @@
import MoleculeViewer from "Components/MoleculeViewer/MoleculeViewer";
import {
Button,
Center,
Divider,
Tabs,
Text,
useMantineTheme,
} from "@mantine/core";
import { Group, Panel, Separator } from "react-resizable-panels";
import "./MoleculePage.css";
import {
IconCheck,
IconCode,
IconGripVertical,
IconList,
IconX,
} from "@tabler/icons-react";
import { useMoleculeEditStore } from "Stores/MoleculeEditStore";
import { CodeTextArea } from "Components/CodeTextArea/CodeTextArea";
import { Helmet } from "react-helmet";
function MoleculeEditorPage() {
const {
currentMoleculeString,
setCurrentMoleculeString,
selectedAtom,
setSelectedAtom,
} = useMoleculeEditStore();
const theme = useMantineTheme();
return (
<>
<Helmet>
<title>Molecule Page | QMolSim</title>
<meta
name="description"
content="Edit the atomic structure of a molecule"
/>
</Helmet>
<div className="experimentButtons">
<Button color="secondary" onClick={() => {}}>
<IconCheck style={{ marginRight: "10px" }} />
<Text c="contrast">Сохранить</Text>
</Button>
<Button color="contrast" onClick={() => {}}>
<IconX style={{ marginRight: "10px" }} color="black" />
<Text c="secondary">Отменить</Text>
</Button>
</div>
<div style={{ display: "flex", flexDirection: "column", height: "85vh" }}>
<Divider style={{ margin: "10px" }} />
<Group style={{ flex: 1, gap: "5px" }}>
<Panel minSize={200} defaultSize={500}>
<Tabs
variant="outline"
style={{
height: "100%",
display: "flex",
flexDirection: "column",
}}
radius="lg"
defaultValue="code"
classNames={{ panel: "tabPanel" }}
>
<Tabs.List>
<Tabs.Tab value="code" leftSection={<IconCode size={12} />}>
Код
</Tabs.Tab>
<Tabs.Tab value="list" leftSection={<IconList size={12} />}>
Список
</Tabs.Tab>
</Tabs.List>
<Tabs.Panel value="code">
<CodeTextArea
text={currentMoleculeString}
onTextChange={(text: string) => {
setCurrentMoleculeString(text);
}}
/>
</Tabs.Panel>
<Tabs.Panel value="list">
<></>
</Tabs.Panel>
</Tabs>
</Panel>
<Separator>
<Center
style={{
height: "100%",
background: theme.colors.dark[4],
borderRadius: "5px",
}}
>
<IconGripVertical size={12} />
</Center>
</Separator>
<Panel style={{ flexShrink: 0 }} minSize={200}>
<MoleculeViewer
moleculeData={currentMoleculeString}
selectedAtom={selectedAtom}
setSelectedAtom={setSelectedAtom}
/>
</Panel>
</Group>
</div>
</>
);
}
export default MoleculeEditorPage;

View File

@@ -0,0 +1,70 @@
import { Helmet } from "react-helmet";
import { IconCancel, IconPlus, IconSettings } from "@tabler/icons-react";
import { useParams } from "react-router";
import CustomButton from "Components/CustomButton/CustomButton";
import { IframePlugin } from "Api/PluginLoader/PluginLoader";
import { useState } from "react";
function TaskPage() {
const { task_id } = useParams();
const [data, setData] = useState<{ text: string }>({ text: "" });
return (
<>
<Helmet>
<title>
{task_id ? "Task " + task_id + " | QMolSim" : "Error |QmolSim"}
</title>
<meta
name="description"
content="See the documentation on how to setup and use the qunatum computational system"
/>
</Helmet>
<div className="ExperimentPage">
<div
style={{
display: "flex",
flexDirection: "column",
gap: "10px",
marginBottom: "15px",
}}
>
<div className="experimentButtons">
<CustomButton
color="accent"
onClick={() => {}}
icon={<IconPlus />}
text="Сохранить"
/>
<CustomButton
color="red"
style="outline"
onClick={() => {}}
icon={<IconCancel />}
text="Отменить"
/>
</div>
</div>
<IframePlugin
pluginUrl={
window.location.origin +
"/" +
import.meta.env.VITE_BASE_PATH +
"/" +
"index.html"
}
mode="editor"
taskData={{
id: Number(task_id),
name: "Молекула 1",
description: "",
data: data,
}}
onUpdate={setData}
/>
</div>
</>
);
}
export default TaskPage;

View File

@@ -1,5 +1,5 @@
.TeamPage {
flex-grow: 1;
height: calc(100vh - 140px);
display: flex;
flex-direction: column;
position: relative;
@@ -9,3 +9,24 @@
.TeamData {
margin-bottom: 15px;
}
.TeamMembers {
overflow: scroll;
display: flex;
flex-direction: column;
flex: 1;
gap: 15px;
margin-top: 15px;
border: 1px solid var(--tab-border-color);
padding: 15px;
}
.tabPanel {
height: 100%;
display: flex;
flex-direction: column;
padding: 10px;
border-left: 1px solid var(--tab-border-color);
border-right: 1px solid var(--tab-border-color);
border-bottom: 1px solid var(--tab-border-color);
}

View File

@@ -5,6 +5,7 @@ import {
Center,
Grid,
LoadingOverlay,
Tabs,
Text,
Textarea,
TextInput,
@@ -23,35 +24,59 @@ import { useAuthenticationStore } from "Stores/AuthenticationStore";
import TeamMemberCard from "Components/ListCard/TeamListCard/TeamMemberCard/TeamMemberCard";
import { AddMember } from "Modals/AddMember/AddMember";
import { notifications } from "@mantine/notifications";
import { PaginationContainer } from "Components/PaginationContainer/PaginationContainer";
import { getTeamSystems } from "Api/QuantumBackend/MachineManagment";
import type { ComputationalSystemListResponse } from "Types/Machine/Machine";
import TeamMachinesListCard from "Components/ListCard/MachineListCard/TeamMachineListCard/TeamMachineListCard";
function TeamPage() {
const [isOpen, setIsOpen] = useState(false);
const { team_id } = useParams();
const { addTeam, teams, updateTeam } = useTeamStore();
const { profile } = useAuthenticationStore();
const [is_loading, set_is_loading] = useState(true);
const { profile, is_loading } = useAuthenticationStore();
const [is_loading_this, set_is_loading] = useState(true);
const [cur_team, set_cur_team] = useState<Team>();
const [team_name, set_team_name] = useState(cur_team?.name);
const [team_descr, set_team_descr] = useState(cur_team?.description);
const [team_name, set_team_name] = useState(cur_team?.name || "");
const [team_descr, set_team_descr] = useState(cur_team?.description || "");
const [machines, set_machines] = useState<ComputationalSystemListResponse>({
systems: [],
cur_page: 0,
total_systems: 0,
page_size: 0,
});
const [cur_page, set_page] = useState<number>(1);
useEffect(() => {
if (!cur_team && profile) {
getTeam(Number(team_id))
.then((team) => {
if (team) {
addTeam(team);
set_cur_team(team);
set_team_name(team.name);
set_team_descr(team.description);
set_is_loading(false);
if (!cur_team && profile && !is_loading) {
getTeam(Number(team_id)).then((team) => {
if (team) {
addTeam(team);
set_cur_team(team);
set_team_name(team.name);
set_team_descr(team.description);
set_is_loading(false);
}
});
}
if (
(!machines || cur_page != machines?.cur_page) &&
profile &&
!is_loading
) {
getTeamSystems(Number(team_id), cur_page, 9 )
.then((systems) => {
if (systems) {
set_machines(systems);
}
})
.catch(() => {
set_is_loading(false);
});
} else {
}
if (machines && cur_team && profile) {
if (profile) {
set_is_loading(false);
}
@@ -59,7 +84,7 @@ function TeamPage() {
if (teams.find((e) => e.id === Number(team_id)) != cur_team) {
set_cur_team(teams.find((e) => e.id === Number(team_id)));
}
}, [cur_team, profile, teams]);
}, [cur_team, profile, teams, machines, cur_page]);
return (
<>
@@ -79,147 +104,210 @@ function TeamPage() {
team={cur_team}
/>
<div className="TeamPage">
<LoadingOverlay
visible={is_loading}
zIndex={1000}
overlayProps={{ radius: "sm", blur: 2 }}
loaderProps={{ size: 50, type: "dots" }}
/>
<Tabs
defaultValue="team"
classNames={{ panel: "tabPanel" }}
style={{ display: "flex", flexDirection: "column", height: "100%" }}
>
<Tabs.List>
<Tabs.Tab value="team">Команда</Tabs.Tab>
<Tabs.Tab value="machines">Системы</Tabs.Tab>
</Tabs.List>
<Card className="TeamData">
<Grid>
<Grid.Col span={3}>
<Title size={"xl"}>Имя команды:</Title>
</Grid.Col>
<Grid.Col span={9}>
<TextInput
size="md"
value={team_name}
onChange={(val) => {
set_team_name(val.target.value);
}}
/>
</Grid.Col>
<Grid.Col span={3}>
<Title size={"xl"}>Описание:</Title>
</Grid.Col>
<Grid.Col span={9}>
<Textarea
size="md"
value={team_descr ? team_descr : ""}
onChange={(val) => {
set_team_descr(val.target.value);
}}
minRows={2}
maxRows={4}
/>
</Grid.Col>
<Grid.Col span={3}>
<Title size={"xl"}>Создатель:</Title>
</Grid.Col>
<Grid.Col
span={9}
<Tabs.Panel value="team">
<LoadingOverlay
visible={is_loading_this}
zIndex={1000}
overlayProps={{ radius: "sm", blur: 2 }}
loaderProps={{ size: 50, type: "dots" }}
/>
<Card className="TeamData">
<Grid>
<Grid.Col span={3}>
<Title size={"xl"}>Имя команды:</Title>
</Grid.Col>
<Grid.Col span={9}>
<TextInput
size="md"
value={team_name}
onChange={(val) => {
set_team_name(val.target.value);
}}
/>
</Grid.Col>
<Grid.Col span={3}>
<Title size={"xl"}>Описание:</Title>
</Grid.Col>
<Grid.Col span={9}>
<Textarea
size="md"
value={team_descr ? team_descr : ""}
onChange={(val) => {
set_team_descr(val.target.value);
}}
minRows={2}
maxRows={4}
/>
</Grid.Col>
<Grid.Col span={3}>
<Title size={"xl"}>Создатель:</Title>
</Grid.Col>
<Grid.Col
span={9}
style={{
display: "flex",
flexDirection: "row",
alignItems: "center",
gap: "10px",
}}
>
<Avatar
radius="xl"
src={cur_team?.creator.user.profile_picture_path}
/>
<Text size="md" style={{ textDecoration: "underline" }}>
{cur_team?.creator.user.username} (
{cur_team?.creator.user.email})
</Text>
</Grid.Col>
<Grid.Col span={6}>
<CustomButton
color="accent"
text="Сохранить"
disabled={
cur_team?.name == team_name &&
cur_team?.description == team_descr
}
onClick={() => {
if (cur_team && team_name)
updateTeamRequest({
team_id: cur_team.id,
name: team_name,
description: team_descr,
}).then((team) => {
updateTeam(team.team_id, {
name: team.name,
description: team.description,
});
notifications.show({
radius: "md",
title: "Информация команды обновлена успешно",
message: "",
icon: <IconCheck />,
style: { paddingLeft: "5px" },
});
});
}}
/>
</Grid.Col>
<Grid.Col span={6}>
<CustomButton
color="error"
text="Отменить"
disabled={
cur_team?.name == team_name &&
cur_team?.description == team_descr
}
onClick={() => {
set_team_name(cur_team?.name);
set_team_descr(cur_team?.description);
}}
/>
</Grid.Col>
</Grid>
</Card>
<div
style={{
display: "flex",
flexDirection: "row",
flexWrap: "nowrap",
gap: "20px",
alignItems: "center",
gap: "10px",
}}
>
<Avatar
radius="xl"
src={cur_team?.creator.user.profile_picture_path}
/>
<Text size="md" style={{ textDecoration: "underline" }}>
{cur_team?.creator.user.username} (
{cur_team?.creator.user.email})
</Text>
</Grid.Col>
<Grid.Col span={6}>
<CustomButton
color="accent"
text="Сохранить"
disabled={
cur_team?.name == team_name &&
cur_team?.description == team_descr
}
onClick={() => {
if (cur_team && team_name)
updateTeamRequest({
team_id: cur_team.id,
name: team_name,
description: team_descr,
}).then((team) => {
updateTeam(team.team_id, {
name: team.name,
description: team.description,
});
notifications.show({
radius: "md",
title: "Информация команды обновлена успешно",
message: "",
icon: <IconCheck />,
style: { paddingLeft: "5px" },
});
});
}}
/>
</Grid.Col>
<Grid.Col span={6}>
<CustomButton
color="error"
text="Отменить"
disabled={
cur_team?.name == team_name &&
cur_team?.description == team_descr
}
onClick={() => {
set_team_name(cur_team?.name);
set_team_descr(cur_team?.description);
}}
/>
</Grid.Col>
</Grid>
</Card>
<div
style={{
display: "flex",
flexWrap: "nowrap",
gap: "20px",
alignItems: "center",
}}
>
<Title order={2} style={{ textWrap: "nowrap" }}>
Члены команды:{" "}
</Title>
<div>
<CustomButton
color="contrast"
onClick={() => {
setIsOpen(true);
<Title order={2} style={{ textWrap: "nowrap" }}>
Члены команды:{" "}
</Title>
<div>
<CustomButton
color="contrast"
onClick={() => {
setIsOpen(true);
}}
icon={<IconPlus />}
text="Добавить члена"
/>
</div>
</div>
{cur_team && cur_team.members.length > 0 && (
<div className="TeamMembers">
{cur_team.members.map((member: TeamMember) => {
return (
<TeamMemberCard
cur_team={cur_team}
member={member}
key={member.user.keycloak_id}
/>
);
})}
</div>
)}
{!cur_team && !is_loading && (
<Alert color="red">
<Center>
{" "}
<Text c="contrast" size={"xl"}>
Ошибка. Команда не найдена
</Text>{" "}
</Center>
</Alert>
)}
</Tabs.Panel>
<Tabs.Panel value="machines">
<div
style={{
display: "flex",
flex: "1",
flexDirection: "column",
overflow: "scroll",
paddingRight: "10px",
margin: "0px",
}}
icon={<IconPlus />}
text="Добавить члена"
/>
</div>
</div>
{cur_team && cur_team.members.length > 0 && (
<div className="TeamMembers">
{cur_team.members.map((member: TeamMember) => {
return <TeamMemberCard cur_team={cur_team} member={member} />;
})}
</div>
)}
{!cur_team && !is_loading && (
<Alert color="red">
<Center>
{" "}
<Text c="contrast" size={"xl"}>
Ошибка. Команда не найдена
</Text>{" "}
</Center>
</Alert>
)}
>
{!is_loading && (
<PaginationContainer
numberOfPages={Math.ceil(
machines.total_systems / machines?.page_size,
)}
activePage={cur_page}
isLoading={is_loading}
setPage={set_page}
>
{machines?.systems.length == 0 && (
<Alert color="accent">
<Center>
{" "}
<Text c="contrast" size={"xl"}>
Нет вычислительных систем
</Text>{" "}
</Center>
</Alert>
)}
{machines?.systems.length > 0 &&
machines.systems.map((machine) => {
return (
<TeamMachinesListCard
{...machine}
key={machine.system.id}
/>
);
})}
</PaginationContainer>
)}
</div>
</Tabs.Panel>
</Tabs>
</div>
</>
);