- added task rendering instead of molecule
- fied a lot of errors
- changes experiment page to show generic experiment info
This commit is contained in:
2026-05-25 12:55:46 +03:00
parent 26623dc2c1
commit f3372eb0ad
43 changed files with 5541 additions and 6908 deletions

View File

@@ -1,24 +1,45 @@
import { Card, Pill, SimpleGrid, Text, UnstyledButton } from "@mantine/core";
import "./ExperimentsListCard.css";
import { useNavigate } from "react-router";
import type { Experiment, TaskData } from "Types/Experiment/Experiment";
import type {
ExperimentData,
SimpleInstanceData,
} from "Types/Experiment/Experiment";
import { IconTrash } from "@tabler/icons-react";
import type { MouseEvent } from "react";
import { useExperimentStore } from "Stores/ExperimentStore";
import { deleteExperiment } from "Api/QuantumBackend/ExperimentsManagment";
function ExperimentsListCard(props: {
experiment: Experiment;
team: { team_id: number; team_name: string } | undefined;
}) {
function ExperimentsListCard(props: { experiment: ExperimentData }) {
const navigate = useNavigate();
const { removeExperiment, tasks } = useExperimentStore();
const { removeExperiment } = useExperimentStore();
const experiment_tasks = tasks.filter(
(a) => a.id in props.experiment.tasks_ids,
);
const getStatusColor = (status: string) => {
const statusLower = status;
switch (statusLower) {
case "draft":
return "var(--mantine-color-gray-5)";
case "in queue":
return "var(--mantine-color-blue-5)";
case "processing":
case "running":
return "var(--mantine-color-yellow-5)";
case "complete":
return "var(--mantine-color-green-5)";
case "error":
case "complete with error":
case "failed":
return "var(--mantine-color-red-5)";
default:
return "var(--mantine-color-gray-5)";
}
};
const handleDelete = () => {
removeExperiment(props.experiment.id);
deleteExperiment(props.experiment.id).then(() => {
removeExperiment(props.experiment.id);
});
};
return (
@@ -34,30 +55,38 @@ function ExperimentsListCard(props: {
Эксперимент: {props.experiment.name}
</Text>
<Text mb="sm" size="md">
Команда: {props.team?.team_name}
Команда: {props.experiment.team?.team_name}
</Text>
<div style={{ display: "flex", gap: "10px" }}>
<Text size="md">Статус: </Text>
<Pill style={{ backgroundColor: "var(--mantine-color-yellow-5)" }}>
<Text size="md">{props.experiment.experiment_status}</Text>
<Pill
style={{
backgroundColor: getStatusColor(
props.experiment.status != undefined
? props.experiment.status.toLowerCase()
: "DRAFT",
),
color: "white",
}}
>
<Text size="md">{props.experiment.status}</Text>
</Pill>
</div>
</div>
<div className="ExperimentSectionWithLine">
<SimpleGrid cols={2} verticalSpacing="0px">
<Text>Задачи:</Text>
<SimpleGrid cols={2} verticalSpacing="0px" style={{ rowGap: "4px" }}>
<Text size="sm">Задачи:</Text>
{experiment_tasks.map(
{props.experiment.instance_preview.slice(0, 6).map(
(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
task: TaskData<any>,
instance: SimpleInstanceData,
) => {
return (
<Pill
size="md"
size="sm"
className="ExperimentPill"
style={{
backgroundColor: "transparent",
border: "2px solid black",
alignContent: "center",
alignItems: "center",
justifyContent: "center",
@@ -65,40 +94,50 @@ function ExperimentsListCard(props: {
}}
>
<Text
size="md"
size="sm"
style={{
textWrap: "nowrap",
textOverflow: "ellipsis",
overflow: "hidden",
}}
>
{task.name}
{instance.name}
</Text>
</Pill>
);
},
)}
{experiment_tasks.length == 0 ? (
<Pill size="md" className="ExperimentPill2">
{props.experiment.instances_count == 0 ? (
<Pill size="sm" className="ExperimentPill2">
Нет Задач
</Pill>
) : (
<></>
)}
{props.experiment.instances_count >
props.experiment.instance_preview.length ? (
<Pill size="sm" className="ExperimentPill2">
и еще{" "}
{props.experiment.instances_count -
props.experiment.instance_preview.length}
</Pill>
) : (
<></>
)}
</SimpleGrid>
</div>
<div className="RightExperimentSection">
<div className="TopRightContainer">
<div className="dateContainer">
<Text size="sm">
{new Date(
props.experiment.date_created + "Z",
).toLocaleDateString("ru")}{" "}
{new Date(props.experiment.created_at + "Z").toLocaleDateString(
"ru",
)}{" "}
</Text>
<Text size="sm">
{new Date(
props.experiment.date_created + "Z",
).toLocaleTimeString("ru")}
{new Date(props.experiment.created_at + "Z").toLocaleTimeString(
"ru",
)}
</Text>
</div>
<UnstyledButton
@@ -122,7 +161,7 @@ function ExperimentsListCard(props: {
>
<Text> Тип эксперимента: </Text>
<Pill className="ExperimentPill">
{props.experiment.experiment_type}
{props.experiment.experiment_type.name}
</Pill>
</div>
<Text c="dimmed" size="sm">

View File

@@ -64,7 +64,8 @@ function MachinesListCard(props: SystemWithTeams) {
//className="MachinePill"
style={{
marginLeft: "10px",
backgroundColor: colors[props.system.status || "ONLINE"],
backgroundColor:
colors[props.system.status as "ONLINE" | "OFFLINE" | "BUSY"],
}}
>
<Text size="md">{props.system.status}</Text>
@@ -82,12 +83,12 @@ function MachinesListCard(props: SystemWithTeams) {
</Text>
<div style={{ display: "flex", gap: "10px", justifyContent: "right" }}>
<Text size="sm">
{new Date(props.system.created_at + "Z").toLocaleDateString(
{new Date(props.system.last_updated + "Z").toLocaleDateString(
"ru",
)}{" "}
</Text>
<Text size="sm">
{new Date(props.system.created_at + "Z").toLocaleTimeString("ru")}
{new Date(props.system.last_updated + "Z").toLocaleTimeString("ru")}
</Text>
</div>
</Card.Section>

View File

@@ -0,0 +1,28 @@
.InstancesListCard {
height: 120px;
}
.InstanceSectionWithLine {
width: 250px;
}
.InstanceIframeSection {
flex-grow: 1;
position: relative;
}
.DatesContainer {
display: flex;
flex-direction: column;
}
.RightInstanceSection {
display: flex;
flex-direction: row;
width: 275px;
justify-content: space-between;
}
.MachinePill {
background-color: var(--mantine-color-contrast-filled);
color: var(--mantine-color-primary-filled);
}

View File

@@ -0,0 +1,176 @@
import { Card, Pill, Text, UnstyledButton } from "@mantine/core";
import { useNavigate } from "react-router";
import type { InstanceData } from "Types/Experiment/Experiment";
import { IconCheck, IconTrash } from "@tabler/icons-react";
import type { MouseEvent } from "react";
import { IframePlugin } from "Api/PluginLoader/PluginLoader";
import "./TaskListCard.css";
import { deleteInstance } from "Api/QuantumBackend/ExperimentsManagment";
import { useExperimentStore } from "Stores/ExperimentStore";
import { notifications } from "@mantine/notifications";
function InstancesListCard(props: { instance: InstanceData; plugin: string }) {
const navigate = useNavigate();
const { removeInstance } = useExperimentStore();
const getStatusColor = (status: string) => {
const statusLower = status.toLowerCase();
switch (statusLower) {
case "draft":
return "var(--mantine-color-gray-5)";
case "in queue":
return "var(--mantine-color-blue-5)";
case "processing":
case "running":
return "var(--mantine-color-yellow-5)";
case "complete":
case "completed":
return "var(--mantine-color-green-5)";
case "error":
case "failed":
return "var(--mantine-color-red-5)";
default:
return "var(--mantine-color-gray-5)";
}
};
const handleDelete = () => {
deleteInstance(props.instance.instance_id).then(() => {
removeInstance(props.instance.instance_id);
notifications.show({
radius: "md",
title: "Задача удалена успешно",
message: "",
icon: <IconCheck />,
style: { paddingLeft: "5px" },
});
});
};
return (
<Card
className="InstancesListCard"
orientation="horizontal"
onClick={(ev) => {
console.log("A");
ev.stopPropagation();
navigate(props.instance.instance_id.toString());
}}
>
<Card.Section
withBorder
inheritPadding
px="md"
className="InstanceSectionWithLine"
>
<Text mb="sm" size="md" style={{ textDecorationLine: "underline" }}>
Задача: {props.instance.name}
</Text>
<div style={{ display: "flex", gap: "10px" }}>
<Text size="md">Статус: </Text>
<Pill
style={{
backgroundColor: getStatusColor(
props.instance.simulation_result?.status || "",
),
color: "white",
}}
>
<Text size="md">
{props.instance.simulation_result?.status || "DRAFT"}
</Text>
</Pill>
</div>
<div style={{ display: "flex", gap: "10px" }}>
<Text size="md">ВС: </Text>
<Pill className="MachinePill">
<Text size="md">
{props.instance.simulation_result?.comp_system?.system_name ||
"--"}
</Text>
</Pill>
</div>
</Card.Section>
<Card.Section withBorder className="InstanceIframeSection">
{JSON.stringify(props.instance.instance_data) && (
<IframePlugin
plugin={props.plugin}
mode="List"
taskData={JSON.stringify(props.instance.instance_data)}
index={props.instance.instance_id}
qubits_needed={0}
simProgress={JSON.stringify(
props.instance.simulation_result?.simulation_result,
)}
/>
)}
</Card.Section>
<Card.Section className="RightInstanceSection" inheritPadding px="sm">
<div className="DatesContainer">
<div className="dateContainer">
<Text size="sm">Время начала:</Text>
{props.instance.simulation_result ? (
<>
<Text size="sm">
{new Date(
props.instance.simulation_result?.started_at + "Z",
).toLocaleDateString("ru")}{" "}
</Text>
<Text size="sm">
{new Date(
props.instance.simulation_result?.started_at + "Z",
).toLocaleTimeString("ru")}
</Text>
</>
) : (
<Text size="sm">--</Text>
)}
</div>
<div className="dateContainer">
<Text size="sm">Время окончания:</Text>
{props.instance.simulation_result?.ended_at ? (
<>
<Text size="sm">
{new Date(
props.instance.simulation_result?.ended_at + "Z",
).toLocaleDateString("ru")}{" "}
</Text>
<Text size="sm">
{new Date(
props.instance.simulation_result?.ended_at + "Z",
).toLocaleTimeString("ru")}
</Text>
</>
) : (
<Text size="sm">--</Text>
)}
</div>
</div>
<div
style={{
display: "flex",
flexDirection: "column",
justifyContent: "space-between",
paddingLeft: "10px",
}}
>
<UnstyledButton
onClick={(e: MouseEvent) => {
e.stopPropagation();
handleDelete();
}}
style={{ cursor: "pointer" }}
>
<IconTrash size={20} />
</UnstyledButton>
<Text c="dimmed" size="sm">
#{props.instance.instance_id}
</Text>
</div>
</Card.Section>
</Card>
);
}
export default InstancesListCard;

View File

@@ -1,16 +1,26 @@
import { Card, Text, UnstyledButton } from "@mantine/core";
import { IconCancel, IconCheck, IconTrash } from "@tabler/icons-react";
import type { MouseEvent } from "react";
import { Card, NumberInput, Text, UnstyledButton } from "@mantine/core";
import {
IconCancel,
IconCheck,
IconPencil,
IconTrash,
} from "@tabler/icons-react";
import { useState, type MouseEvent } from "react";
import { notifications } from "@mantine/notifications";
import type { SystemTeamData, SystemWithTeams } from "Types/Machine/Machine";
import { removeSystemFromTeam } from "Api/QuantumBackend/MachineManagment";
import {
giveSystemToTeam,
removeSystemFromTeam,
} from "Api/QuantumBackend/MachineManagment";
import { useDeviceStore } from "Stores/DeviceStore";
function TeamInMachineListCard(props: {
team: SystemTeamData;
system: SystemWithTeams;
}) {
const [isEditing, setIsEditing] = useState(false);
const { updateDevice } = useDeviceStore();
const [count, setCount] = useState(props.team.num_qubits);
const handleRemovePerm = () => {
// TODO: fix delete
@@ -76,7 +86,69 @@ function TeamInMachineListCard(props: {
flexDirection: "row",
}}
>
<Text>Количество кубит: {props.team.num_qubits}</Text>
{isEditing && (
<>
<Text>Количество кубит: </Text>
<NumberInput
value={count}
onChange={(ev) => {
setCount(Number(ev.valueOf()));
}}
></NumberInput>
<UnstyledButton
onClick={() => {
giveSystemToTeam({
system_id: props.system.system.id,
team_id: props.team.team.team_id,
qubits_given: count,
}).then(() => {
const team = props.system.teams.find((t) => {
return t.team.team_id == props.team.team.team_id;
});
if (team) {
const updatedItem = { ...team, num_qubits: count };
// Create new array with the updated item in the same position
const updatedItems = props.system.teams.map(
(currentItem) =>
currentItem.team.team_id === team.team.team_id
? updatedItem
: currentItem,
);
updateDevice(props.system.system.id, {
teams: updatedItems,
});
}
setIsEditing(false);
});
}}
>
<IconCheck size={25} />
</UnstyledButton>
<UnstyledButton
onClick={() => {
setCount(props.team.num_qubits);
setIsEditing(false);
}}
>
<IconCancel size={25} />
</UnstyledButton>
</>
)}
{!isEditing && (
<>
<Text>Количество кубит: {count}</Text>
<UnstyledButton
onClick={() => {
setIsEditing(true);
}}
>
<IconPencil size={25} />
</UnstyledButton>
</>
)}
</Card.Section>
<Card.Section
inheritPadding

View File

@@ -8,6 +8,7 @@ import {
UnstyledButton,
} from "@mantine/core";
import {
IconCancel,
IconCheck,
IconCrown,
IconForbid,
@@ -196,23 +197,35 @@ function TeamMemberCard(props: { cur_team: Team; member: TeamMember }) {
deleteMember({
team_id: props.cur_team.id,
user_id: props.member.user.keycloak_id,
}).then(() => {
notifications.show({
radius: "md",
title: "Пользователь удален успешно",
message: "",
icon: <IconCheck />,
style: { paddingLeft: "5px" },
})
.then(() => {
notifications.show({
radius: "md",
title: "Пользователь удален успешно",
message: "",
icon: <IconCheck />,
style: { paddingLeft: "5px" },
});
updateTeam(props.cur_team.id, {
members: [
...props.cur_team.members.filter(
(member) =>
member.user.keycloak_id !=
props.member.user.keycloak_id,
),
],
});
})
.catch(() => {
notifications.show({
radius: "md",
title: "Пользователя не получилось удалить",
message: "",
color: "red",
icon: <IconCancel />,
style: { paddingLeft: "5px" },
});
});
updateTeam(props.cur_team.id, {
members: [
...props.cur_team.members.filter(
(member) =>
member.user.keycloak_id != props.member.user.keycloak_id,
),
],
});
});
}}
style={{
cursor: "pointer",