added comp_systems and modular instances frontend
This commit is contained in:
194
src/Pages/DevicesPage/DevicePage/DevicePage.tsx
Normal file
194
src/Pages/DevicesPage/DevicePage/DevicePage.tsx
Normal 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;
|
||||
@@ -0,0 +1,5 @@
|
||||
.DevicesPage {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
|
||||
@@ -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 />}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
70
src/Pages/TaskPage/TaskPage.tsx
Normal file
70
src/Pages/TaskPage/TaskPage.tsx
Normal 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;
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user