feat(tester):
This commit is contained in:
parent
78e61899cd
commit
408e77d89c
41 changed files with 1262 additions and 521 deletions
|
@ -18,6 +18,7 @@
|
|||
"@tanstack/react-query": "^5.66.7",
|
||||
"axios": "^1.7.9",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"katex": "^0.16.21",
|
||||
"next": "^15.1.7",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
|
|
11
pnpm-lock.yaml
generated
11
pnpm-lock.yaml
generated
|
@ -29,6 +29,9 @@ dependencies:
|
|||
jsonwebtoken:
|
||||
specifier: ^9.0.2
|
||||
version: 9.0.2
|
||||
katex:
|
||||
specifier: ^0.16.21
|
||||
version: 0.16.21
|
||||
next:
|
||||
specifier: ^15.1.7
|
||||
version: 15.1.7(react-dom@19.0.0)(react@19.0.0)
|
||||
|
@ -867,7 +870,6 @@ packages:
|
|||
/commander@8.3.0:
|
||||
resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==}
|
||||
engines: {node: '>= 12'}
|
||||
dev: true
|
||||
|
||||
/compare-versions@4.1.4:
|
||||
resolution: {integrity: sha512-FemMreK9xNyL8gQevsdRMrvO4lFCkQP7qbuktn1q8ndcNk1+0mz7lgE7b/sNvbhVgY4w6tMN1FDp6aADjqw2rw==}
|
||||
|
@ -1413,6 +1415,13 @@ packages:
|
|||
safe-buffer: 5.2.1
|
||||
dev: false
|
||||
|
||||
/katex@0.16.21:
|
||||
resolution: {integrity: sha512-XvqR7FgOHtWupfMiigNzmh+MgUVmDGU2kXZm899ZkPfcuoPuFxyHmXsgATDpFZDAXCI8tvinaVcDo8PIIJSo4A==}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
commander: 8.3.0
|
||||
dev: false
|
||||
|
||||
/klona@2.0.6:
|
||||
resolution: {integrity: sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==}
|
||||
engines: {node: '>= 8'}
|
||||
|
|
2
proto
2
proto
|
@ -1 +1 @@
|
|||
Subproject commit ea2a76c1f4001bba8405a5a447f085831dc5cf18
|
||||
Subproject commit 16781a46412eea455f27372045c216126c39d628
|
|
@ -1,142 +1 @@
|
|||
"use client";
|
||||
|
||||
import React from 'react';
|
||||
import {Anchor, Group, SegmentedControl, Stack, Text, Title} from "@mantine/core";
|
||||
import {Header} from "@/components/header";
|
||||
import {Code} from "@/components/code";
|
||||
import Link from "next/link";
|
||||
|
||||
type PageProps = {
|
||||
params: {
|
||||
task_id: string
|
||||
}
|
||||
}
|
||||
|
||||
const problems = [
|
||||
"A. Сумма двух чисел",
|
||||
"B. Разность двух чисел",
|
||||
"C. Театральная площадь"
|
||||
]
|
||||
|
||||
const problem = {
|
||||
legend: `<p>Эта задача немного необычна — в ней вам предстоит реализовать
|
||||
интерактивное взаимодействие с тестирующей системой. Это означает, что
|
||||
вы можете делать запросы и получать ответы в online-режиме. Обратите
|
||||
внимание, что ввод/вывод в этой задаче — стандартный (то есть с экрана
|
||||
на экран). После вывода очередного запроса обязательно используйте
|
||||
функции очистки потока, чтобы часть вашего вывода не осталась в
|
||||
каком-нибудь буфере. Например, на С++ надо использовать функцию
|
||||
<code>fflush(stdout)</code>, на Java вызов
|
||||
<code>System.out.flush()</code>, на Pascal <code>flush(output)</code> и
|
||||
<code>stdout.flush()</code> для языка Python.</p>
|
||||
<p>В этой задаче вам предстоит в интерактивном режиме угадать число
|
||||
<span class="math inline"><em>x</em></span>, которое загадала
|
||||
тестирующая система. Про загаданное число <span
|
||||
class="math inline"><em>x</em></span> известно, что оно целое и лежит в
|
||||
границах от <span class="math inline">1</span> до <span
|
||||
class="math inline"><em>n</em></span> включительно (значение <span
|
||||
class="math inline"><em>n</em></span> известно заранее).</p>
|
||||
<p>Вы можете делать запросы к тестирующей системе, каждый запрос — это
|
||||
вывод одного целого числа от <span class="math inline">1</span> до <span
|
||||
class="math inline"><em>n</em></span>. Есть два варианта ответа
|
||||
тестирующей системы на запрос:</p>
|
||||
<ul>
|
||||
<li><p>строка <<<code><</code>>> (без кавычек), если
|
||||
загаданное число меньше числа из запроса;</p></li>
|
||||
<li><p>строка <<<code>>=</code>>> (без кавычек), если
|
||||
загаданное число больше либо равно числу из запроса.</p></li>
|
||||
</ul>
|
||||
<p>В случае, если ваша программа наверняка угадала нужное число <span
|
||||
class="math inline"><em>x</em></span>, выведите строку вида
|
||||
<<<code>! x</code>>>, где <span
|
||||
class="math inline"><em>x</em></span> — это ответ, и завершите работу
|
||||
своей программы.</p>
|
||||
<p>Вашей программе разрешается сделать не более <span
|
||||
class="math inline">25</span> запросов.</p>`,
|
||||
input: `<p>Для чтения ответов на запросы программа должна использовать
|
||||
стандартный ввод.</p>
|
||||
<p>В первой строке входных данных будет содержаться целое положительное
|
||||
число <span class="math inline"><em>n</em></span> (<span
|
||||
class="math inline">1 ≤ <em>n</em> ≤ 10<sup>6</sup></span>) —
|
||||
максимально возможное число, которое может быть загадано.</p>
|
||||
<p>В следующих строках на вход вашей программе будут подаваться строки
|
||||
вида <<<code><</code>>> и
|
||||
<<<code>>=</code>>>. <span
|
||||
class="math inline"><em>i</em></span>-я из этих строк является ответом
|
||||
системы на ваш <span class="math inline"><em>i</em></span>-й запрос.
|
||||
После того, как ваша программа угадала число, выведите
|
||||
<<<code>! x</code>>> (без кавычек), где <span
|
||||
class="math inline"><em>x</em></span> — это ответ, и завершите работу
|
||||
своей программы.</p>
|
||||
<p>Тестирующая система даст вашей программе прочитать ответ на запрос из
|
||||
входных данных только после того, как ваша программа вывела
|
||||
соответствующий запрос системе и выполнила операцию
|
||||
<code>flush</code>.</p>`,
|
||||
name: `<p>Отгадай число</p>`,
|
||||
output: `<p>Для осуществления запросов программа должна использовать стандартный
|
||||
вывод.</p>
|
||||
<p>Ваша программа должна выводить запросы — целые числа <span
|
||||
class="math inline"><em>x</em><sub><em>i</em></sub></span> (<span
|
||||
class="math inline">1 ≤ <em>x</em><sub><em>i</em></sub> ≤ <em>n</em></span>)
|
||||
по одному в строке (не забывайте выводить <<<em>перевод
|
||||
строки</em>>> после каждого значения <span
|
||||
class="math inline"><em>x</em><sub><em>i</em></sub></span>). После
|
||||
вывода каждой строки программа должна выполнить операцию
|
||||
<code>flush</code>.</p>
|
||||
<p>Каждое из значений <span
|
||||
class="math inline"><em>x</em><sub><em>i</em></sub></span> обозначает
|
||||
очередной запрос к системе. Ответ на запрос программа сможет прочесть из
|
||||
стандартного ввода. В случае, если ваша программа угадала число <span
|
||||
class="math inline"><em>x</em></span>, выведите строку вида
|
||||
<<<code>! x</code>>> (без кавычек), где <span
|
||||
class="math inline"><em>x</em></span> — ответ, и завершите работу
|
||||
программы.</p>`
|
||||
}
|
||||
|
||||
const Page = ({params}: PageProps) => {
|
||||
return (
|
||||
<>
|
||||
<header>
|
||||
<Header/>
|
||||
</header>
|
||||
<Group align="start" justify="center">
|
||||
<nav style={{padding: "var(--mantine-spacing-xs) var(--mantine-spacing-md)", width: "fit-content"}}>
|
||||
<Stack w={200}>
|
||||
<Anchor component={Link} href="/contests/1" c="var(--mantine-color-bright)">
|
||||
<Title c="black" order={4} ta="center">
|
||||
Простой контест с длинным названием из восьми слов
|
||||
</Title>
|
||||
</Anchor>
|
||||
<SegmentedControl
|
||||
orientation="vertical"
|
||||
withItemsBorders={false}
|
||||
data={problems}
|
||||
fullWidth
|
||||
defaultValue={problems[0]}/>
|
||||
</Stack>
|
||||
</nav>
|
||||
<main style={{flex: 5, maxWidth: "1080px"}}>
|
||||
<div dangerouslySetInnerHTML={{__html: problem.legend + problem.legend + problem.legend}}>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
<aside style={{padding: "var(--mantine-spacing-xs) var(--mantine-spacing-md)", width: "fit-content"}}>
|
||||
<Stack>
|
||||
<Code/>
|
||||
<Text fw={500}>Последние посылки
|
||||
<Anchor
|
||||
component={Link}
|
||||
href="/"
|
||||
fs="italic"
|
||||
c="var(--mantine-color-bright)" fw={500}>
|
||||
(посмотреть все)
|
||||
</Anchor>:
|
||||
</Text>
|
||||
</Stack>
|
||||
</aside>
|
||||
</Group>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Page;
|
||||
export {TaskPage as default, generateMetadata} from "@/plain-pages/task";
|
1
src/app/problems/[problem_id]/edit/page.tsx
Normal file
1
src/app/problems/[problem_id]/edit/page.tsx
Normal file
|
@ -0,0 +1 @@
|
|||
export {ProblemEditPage as default, generateMetadata} from "@/plain-pages/problem-edit";
|
|
@ -1,24 +1 @@
|
|||
import React from 'react';
|
||||
import {AppShell, AppShellHeader, AppShellMain} from "@mantine/core";
|
||||
import {Header} from "@/components/header";
|
||||
|
||||
type PageProps = {
|
||||
params: {
|
||||
problem_id: number
|
||||
}
|
||||
}
|
||||
|
||||
const Page = ({params}: PageProps) => {
|
||||
return (
|
||||
<AppShell header={{height: 70}}>
|
||||
<AppShellHeader>
|
||||
<Header/>
|
||||
</AppShellHeader>
|
||||
<AppShellMain>
|
||||
|
||||
</AppShellMain>
|
||||
</AppShell>
|
||||
);
|
||||
};
|
||||
|
||||
export default Page;
|
||||
export {ProblemPage as default, generateMetadata} from "@/plain-pages/problem";
|
|
@ -1,108 +1 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
ActionIcon,
|
||||
AppShell,
|
||||
AppShellAside,
|
||||
AppShellHeader,
|
||||
AppShellMain,
|
||||
Button,
|
||||
Group,
|
||||
Pagination,
|
||||
Stack,
|
||||
Table,
|
||||
TableTbody,
|
||||
TableTd,
|
||||
TableTh,
|
||||
TableThead,
|
||||
TableTr,
|
||||
Text,
|
||||
TextInput,
|
||||
Title
|
||||
} from "@mantine/core";
|
||||
import {Header} from "@/components/header";
|
||||
import Link from "next/link";
|
||||
import {IconCheck, IconPencil, IconUser} from "@tabler/icons-react";
|
||||
|
||||
export const metadata = {
|
||||
title: 'Мастерская',
|
||||
description: '',
|
||||
};
|
||||
|
||||
const problems = [
|
||||
{
|
||||
id: 1,
|
||||
title: "Линейка",
|
||||
accepted: 102,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "Секретное сообщение",
|
||||
accepted: 152,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: "Арзуб",
|
||||
accepted: 342,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: "Укладка доминошками",
|
||||
accepted: 89,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
const Page = () => {
|
||||
const rows = problems.map((problem) => (
|
||||
<TableTr key={problem.id}>
|
||||
<TableTd>
|
||||
<Text td="underline">{problem.title}</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Group align="end" gap="2" justify="center">
|
||||
<IconUser/>
|
||||
<Text td="underline">{problem.accepted}</Text>
|
||||
</Group>
|
||||
</TableTd>
|
||||
<TableTd>{<ActionIcon size="xs" component={Link} href={`/workshop/${problem.id}`}>
|
||||
<IconPencil/>
|
||||
</ActionIcon>}
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
));
|
||||
|
||||
return (
|
||||
<AppShell header={{height: 70}}>
|
||||
<AppShellHeader>
|
||||
<Header/>
|
||||
</AppShellHeader>
|
||||
<AppShellMain px="16">
|
||||
<Stack align="center" w="fit-content" m="auto" pt="16" gap="16">
|
||||
<Title>Архив задач</Title>
|
||||
<Table horizontalSpacing="xl">
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Название</TableTh>
|
||||
<TableTh><IconCheck/></TableTh>
|
||||
<TableTh><IconPencil/></TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>{rows}</TableTbody>
|
||||
</Table>
|
||||
<Pagination total={10}/>
|
||||
</Stack>
|
||||
</AppShellMain>
|
||||
<AppShellAside withBorder={false} px="16">
|
||||
<Stack pt="16">
|
||||
<Stack gap="5">
|
||||
<Button title="Создать контест">Создать контест</Button>
|
||||
<Button title="Создать задачу">Создать задачу</Button>
|
||||
</Stack>
|
||||
<TextInput placeholder="Поиск"/>
|
||||
</Stack>
|
||||
</AppShellAside>
|
||||
</AppShell>
|
||||
);
|
||||
};
|
||||
|
||||
export default Page;
|
||||
export {ProblemsPage as default, generateMetadata} from "@/plain-pages/problems";
|
1
src/app/solutions/[solution_id]/page.tsx
Normal file
1
src/app/solutions/[solution_id]/page.tsx
Normal file
|
@ -0,0 +1 @@
|
|||
export {ProblemPage as default, generateMetadata} from "@/plain-pages/problem";
|
1
src/app/solutions/page.tsx
Normal file
1
src/app/solutions/page.tsx
Normal file
|
@ -0,0 +1 @@
|
|||
export {SolutionsPage as default, generateMetadata} from "@/plain-pages/solutions";
|
|
@ -82,9 +82,9 @@ const Header = () => {
|
|||
const [drawerOpened, {toggle: toggleDrawer, close: closeDrawer}] = useDisclosure(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<header>
|
||||
<div className={classes.header}>
|
||||
<Group justify="space-between" h="100%">
|
||||
<Group justify="space-between" h="100%" maw="1920px" mx="auto">
|
||||
<Burger opened={drawerOpened} onClick={toggleDrawer} hiddenFrom="xs"/>
|
||||
<Group component={Link} href="/" className={classes.link} visibleFrom="xs">
|
||||
<Image component={NextImage} src="/gate_logo.svg" alt="Gate logo" width="40" height="40"/>
|
||||
|
@ -134,7 +134,7 @@ const Header = () => {
|
|||
<Divider my="sm"/>
|
||||
</ScrollArea>
|
||||
</Drawer>
|
||||
</>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
1
src/components/problem/index.ts
Normal file
1
src/components/problem/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from "./problem";
|
60
src/components/problem/problem.css
Normal file
60
src/components/problem/problem.css
Normal file
|
@ -0,0 +1,60 @@
|
|||
.container {
|
||||
max-width: 60em;
|
||||
padding: 0 10px 10px 10px;
|
||||
hyphens: auto;
|
||||
overflow-wrap: break-word;
|
||||
text-rendering: optimizeLegibility;
|
||||
font-kerning: normal;
|
||||
color: #1a1a1a;
|
||||
background-color: #fdfdfd;
|
||||
}
|
||||
|
||||
.content {
|
||||
& table {
|
||||
margin: 1em 0;
|
||||
overflow-x: auto;
|
||||
font-variant-numeric: lining-nums tabular-nums;
|
||||
}
|
||||
|
||||
& table & caption {
|
||||
margin-bottom: 0.75em;
|
||||
}
|
||||
|
||||
& table, & th, & td {
|
||||
border: 1px solid black;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
& th {
|
||||
padding: 0.25em 0.5em 0.25em 0.5em;
|
||||
}
|
||||
|
||||
& td {
|
||||
padding: 0.125em 0.5em 0.25em 0.5em;
|
||||
}
|
||||
|
||||
.center {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
& ul, & ol {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
& p {
|
||||
margin: 0.75em;
|
||||
}
|
||||
|
||||
& p {
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
& a {
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
& a:visited {
|
||||
color: #1a1a1a;
|
||||
}
|
||||
}
|
116
src/components/problem/problem.tsx
Normal file
116
src/components/problem/problem.tsx
Normal file
|
@ -0,0 +1,116 @@
|
|||
'use client';
|
||||
|
||||
import {Stack, Text, Title} from "@mantine/core";
|
||||
import {useEffect, useRef} from 'react';
|
||||
import katex from 'katex';
|
||||
import 'katex/dist/katex.min.css';
|
||||
import './problem.css';
|
||||
|
||||
type Props = {
|
||||
problem: {
|
||||
id: number,
|
||||
title: string,
|
||||
time_limit: number,
|
||||
memory_limit: number,
|
||||
legend_html: string,
|
||||
input_format_html: string,
|
||||
output_format_html: string,
|
||||
notes_html: string,
|
||||
scoring_html: string,
|
||||
created_at: string,
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
letter?: string
|
||||
}
|
||||
|
||||
const prettifyTimeLimit = (time_limit: number) => {
|
||||
if (time_limit % 1000 === 0) {
|
||||
return `${time_limit / 1000} сек`
|
||||
}
|
||||
|
||||
return `${time_limit} мс`
|
||||
}
|
||||
|
||||
const prettifyMemoryLimit = (memory_limit: number) => {
|
||||
if (memory_limit % 1000 === 0) {
|
||||
return `${memory_limit / 1000} ГБ`
|
||||
}
|
||||
|
||||
return `${memory_limit} МБ`
|
||||
}
|
||||
|
||||
const Problem = ({problem, letter}: Props) => {
|
||||
letter = letter || 'A';
|
||||
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (ref.current) {
|
||||
const mathElements = ref.current.querySelectorAll('.math');
|
||||
mathElements.forEach((element) => {
|
||||
if (!element.hasAttribute('data-rendered')) {
|
||||
katex.render(element.textContent || '', element, {
|
||||
throwOnError: false,
|
||||
displayMode: element.classList.contains('display'),
|
||||
});
|
||||
// Помечаем элемент как обработанный
|
||||
element.setAttribute('data-rendered', 'true');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (ref.current) {
|
||||
ref.current.querySelectorAll('.math').forEach((element) => {
|
||||
element.removeAttribute('data-rendered');
|
||||
});
|
||||
}
|
||||
};
|
||||
}, [problem, letter]);
|
||||
|
||||
return (
|
||||
<Stack className="container" ref={ref}>
|
||||
<Stack align="center" gap={0} w="fit-content" mx="auto" my="0">
|
||||
<Title order={2} mb="sm">{letter}. {problem.title}</Title>
|
||||
<Stack mx="auto" my="0" align="flex-start" gap={0}>
|
||||
<Text>
|
||||
ограничение по времени: {prettifyTimeLimit(problem.time_limit)}
|
||||
</Text>
|
||||
<Text>
|
||||
ограничение по памяти: {prettifyMemoryLimit(problem.memory_limit)}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
{problem.legend_html && (
|
||||
<div className="content" dangerouslySetInnerHTML={{__html: problem.legend_html}}/>
|
||||
)}
|
||||
{problem.input_format_html && (
|
||||
<>
|
||||
<Title order={3}>Входные данные</Title>
|
||||
<div className="content" dangerouslySetInnerHTML={{__html: problem.input_format_html}}/>
|
||||
</>
|
||||
)}
|
||||
{problem.output_format_html && (
|
||||
<>
|
||||
<Title order={3}>Выходные данные</Title>
|
||||
<div className="content" dangerouslySetInnerHTML={{__html: problem.output_format_html}}/>
|
||||
</>
|
||||
)}
|
||||
{problem.scoring_html && (
|
||||
<>
|
||||
<Title order={3}>Система оценки</Title>
|
||||
<div className="content" dangerouslySetInnerHTML={{__html: problem.scoring_html}}/>
|
||||
</>
|
||||
)}
|
||||
{problem.notes_html && (
|
||||
<>
|
||||
<Title order={3}>Примечание</Title>
|
||||
<div className="content" dangerouslySetInnerHTML={{__html: problem.notes_html}}/>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export {Problem};
|
|
@ -23,6 +23,7 @@ import {IconMail, IconTrash} from "@tabler/icons-react";
|
|||
import {DeleteTask} from "@/shared/api";
|
||||
import {useMutation} from "@tanstack/react-query";
|
||||
import {useRouter} from "next/navigation";
|
||||
import {numberToLetters} from "@/shared/lib";
|
||||
|
||||
type PageProps = {
|
||||
contest: Contest,
|
||||
|
@ -48,10 +49,10 @@ const Page = (props: PageProps) => {
|
|||
c="var(--mantine-link-color)"
|
||||
underline="always"
|
||||
>
|
||||
{`${task.task.position + 1}. ${task.task.title}`}
|
||||
{`${numberToLetters(task.task.position)}. ${task.task.title}`}
|
||||
</Anchor>
|
||||
</TableTd>
|
||||
<TableTd>{task.best_solution.total_score}</TableTd>
|
||||
<TableTd>{task.solution.score}</TableTd>
|
||||
<TableTd>
|
||||
<Button component={Link} href={`/`} size="xs">
|
||||
Мои посылки
|
||||
|
@ -66,12 +67,10 @@ const Page = (props: PageProps) => {
|
|||
))
|
||||
|
||||
return (
|
||||
<Stack gap="xl">
|
||||
<header>
|
||||
<Header/>
|
||||
</header>
|
||||
<>
|
||||
<Header/>
|
||||
<main>
|
||||
<Stack gap="xl" align="center">
|
||||
<Stack gap="xl" align="center" mt="md">
|
||||
<Title order={1}>{props.contest.title}</Title>
|
||||
<Table horizontalSpacing="xl" align="center" w="fit-content">
|
||||
<TableThead>
|
||||
|
@ -95,20 +94,20 @@ const Page = (props: PageProps) => {
|
|||
<Stack>
|
||||
<Center>
|
||||
<Group>
|
||||
<Button size="md">
|
||||
<Button>
|
||||
Все посылки
|
||||
</Button>
|
||||
<Button size="md">
|
||||
<Button>
|
||||
Редактировать контест
|
||||
</Button>
|
||||
</Group>
|
||||
</Center>
|
||||
<Center>
|
||||
<Group>
|
||||
<Button size="md">
|
||||
<Button>
|
||||
Добавить пользователя
|
||||
</Button>
|
||||
<Button size="md">
|
||||
<Button>
|
||||
Добавить задачу
|
||||
</Button>
|
||||
</Group>
|
||||
|
@ -116,7 +115,7 @@ const Page = (props: PageProps) => {
|
|||
</Stack>
|
||||
</Stack>
|
||||
</main>
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -18,10 +18,10 @@ type Props = {
|
|||
const Page = async (props: Props) => {
|
||||
const page = (await props.searchParams).page || 1;
|
||||
|
||||
const response = await ListContests(page, 10);
|
||||
const contestsList = await ListContests(page, 10);
|
||||
|
||||
return (
|
||||
<ClientPage contests={response.contests} max_page={response.max_page} page={response.page}/>
|
||||
<ClientPage contests={contestsList.contests} pagination={contestsList.pagination}/>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -2,12 +2,8 @@
|
|||
|
||||
import React from 'react';
|
||||
import {
|
||||
AppShell,
|
||||
AppShellAside,
|
||||
AppShellHeader,
|
||||
AppShellMain,
|
||||
Button,
|
||||
Checkbox,
|
||||
Group,
|
||||
Pagination,
|
||||
Stack,
|
||||
Table,
|
||||
|
@ -16,52 +12,44 @@ import {
|
|||
TableTh,
|
||||
TableThead,
|
||||
TableTr,
|
||||
TextInput,
|
||||
Title
|
||||
} from "@mantine/core";
|
||||
import {Header} from "@/components/header";
|
||||
import Link from "next/link";
|
||||
import {ContestsListItem} from "../../../proto/tester/v1/api";
|
||||
import * as testerv1 from "../../../proto/tester/v1/api";
|
||||
|
||||
type Props = {
|
||||
contests: ContestsListItem[],
|
||||
page: number,
|
||||
max_page: number
|
||||
contests: testerv1.ContestsListItem[],
|
||||
pagination: testerv1.Pagination,
|
||||
}
|
||||
|
||||
// const contests = [
|
||||
// {
|
||||
// id: 1,
|
||||
// title: "Соревнование 1",
|
||||
// startsAt: (new Date()).toISOString(),
|
||||
// duration: "02:00",
|
||||
// started: true
|
||||
// },
|
||||
// {
|
||||
// id: 2,
|
||||
// title: "Соревнование 2",
|
||||
// startsAt: (new Date()).toISOString(),
|
||||
// duration: "02:00",
|
||||
// started: false
|
||||
// },
|
||||
// {
|
||||
// id: 3,
|
||||
// title: "Соревнование 3",
|
||||
// startsAt: (new Date()).toISOString(),
|
||||
// duration: "02:00",
|
||||
// started: false
|
||||
// },
|
||||
// {
|
||||
// id: 4,
|
||||
// title: "Соревнование 4",
|
||||
// startsAt: (new Date()).toISOString(),
|
||||
// duration: "02:00",
|
||||
// started: false
|
||||
// }
|
||||
// ]
|
||||
const Page = ({contests, pagination}: Props) => {
|
||||
const getItemProps = (page) => ({
|
||||
component: Link,
|
||||
href: `/contests?page=${page}`,
|
||||
});
|
||||
|
||||
const Page = (props: Props) => {
|
||||
const rows = props.contests.map((contest) => (
|
||||
const getControlProps = (control) => {
|
||||
if (control === 'next') {
|
||||
if (pagination.page === pagination.total) {
|
||||
return {component: Link, href: `/contests?page=${pagination.page}`};
|
||||
}
|
||||
|
||||
return {component: Link, href: `/contests?page=${+pagination.page + 1}`};
|
||||
}
|
||||
|
||||
if (control === 'previous') {
|
||||
if (pagination.page === 1) {
|
||||
return {component: Link, href: `/contests?page=${pagination.page}`};
|
||||
}
|
||||
return {component: Link, href: `/contests?page=${+pagination.page - 1}`};
|
||||
}
|
||||
|
||||
return {};
|
||||
};
|
||||
|
||||
|
||||
const rows = contests.map((contest) => (
|
||||
<TableTr key={contest.id}>
|
||||
<TableTd>{contest.title}</TableTd>
|
||||
<TableTd>
|
||||
|
@ -86,58 +74,38 @@ const Page = (props: Props) => {
|
|||
));
|
||||
|
||||
return (
|
||||
<AppShell header={{height: 70}}>
|
||||
<AppShellHeader>
|
||||
<Header/>
|
||||
</AppShellHeader>
|
||||
<AppShellMain px="16">
|
||||
<Stack align="center" w="fit-content" m="auto" pt="16" gap="16">
|
||||
<Title>Контесты</Title>
|
||||
<Table horizontalSpacing="xl">
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Название</TableTh>
|
||||
{/*<TableTh>Начало</TableTh>*/}
|
||||
{/*<TableTh>Длительность</TableTh>*/}
|
||||
<TableTh></TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>{rows}</TableTbody>
|
||||
</Table>
|
||||
<Pagination total={props.max_page}
|
||||
value={props.page}
|
||||
getItemProps={(page) => ({
|
||||
component: Link,
|
||||
href: `/contests?page=${page}`,
|
||||
})}
|
||||
getControlProps={(control) => {
|
||||
if (control === 'next') {
|
||||
if (props.page === props.max_page) {
|
||||
return {component: Link, href: `/contests?page=${props.page}`};
|
||||
}
|
||||
|
||||
return {component: Link, href: `/contests?page=${+props.page + 1}`};
|
||||
}
|
||||
|
||||
if (control === 'previous') {
|
||||
if (props.page === 1) {
|
||||
return {component: Link, href: `/contests?page=${props.page}`};
|
||||
}
|
||||
return {component: Link, href: `/contests?page=${+props.page - 1}`};
|
||||
}
|
||||
|
||||
return {};
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
</AppShellMain>
|
||||
<AppShellAside withBorder={false} px="16">
|
||||
<Stack pt="16">
|
||||
<TextInput placeholder="Поиск"/>
|
||||
<Checkbox label="Завершенные"/>
|
||||
</Stack>
|
||||
</AppShellAside>
|
||||
</AppShell>
|
||||
<>
|
||||
<Header/>
|
||||
<main>
|
||||
<Group px="16">
|
||||
<Stack align="center" w="fit-content" m="auto" pt="16" gap="16">
|
||||
<Title>Контесты</Title>
|
||||
<Table horizontalSpacing="xl">
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Название</TableTh>
|
||||
{/*<TableTh>Начало</TableTh>*/}
|
||||
{/*<TableTh>Длительность</TableTh>*/}
|
||||
<TableTh></TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>{rows}</TableTbody>
|
||||
</Table>
|
||||
<Pagination total={pagination.total}
|
||||
value={pagination.page}
|
||||
getItemProps={getItemProps}
|
||||
getControlProps={getControlProps}
|
||||
/>
|
||||
</Stack>
|
||||
</Group>
|
||||
</main>
|
||||
{/*<AppShellAside withBorder={false} px="16">*/}
|
||||
{/* <Stack pt="16">*/}
|
||||
{/* <TextInput placeholder="Поиск"/>*/}
|
||||
{/* <Checkbox label="Завершенные"/>*/}
|
||||
{/* </Stack>*/}
|
||||
{/*</AppShellAside>*/}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,16 +1,6 @@
|
|||
"use client";
|
||||
|
||||
import {
|
||||
AppShell,
|
||||
AppShellHeader,
|
||||
AppShellMain,
|
||||
Button,
|
||||
Image,
|
||||
PasswordInput,
|
||||
Stack,
|
||||
TextInput,
|
||||
Title
|
||||
} from "@mantine/core";
|
||||
import {Button, Image, PasswordInput, Stack, TextInput, Title} from "@mantine/core";
|
||||
import {Header} from "@/components/header";
|
||||
import React from "react";
|
||||
import {useForm} from "@mantine/form";
|
||||
|
@ -56,11 +46,9 @@ const Page = () => {
|
|||
}
|
||||
|
||||
return (
|
||||
<AppShell header={{height: 70}}>
|
||||
<AppShellHeader>
|
||||
<Header/>
|
||||
</AppShellHeader>
|
||||
<AppShellMain>
|
||||
<>
|
||||
<Header/>
|
||||
<main>
|
||||
<form
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
|
@ -102,8 +90,8 @@ const Page = () => {
|
|||
<Button type="submit" loading={mutation.isPending}>Войти</Button>
|
||||
</Stack>
|
||||
</form>
|
||||
</AppShellMain>
|
||||
</AppShell>
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
"use client";
|
||||
|
||||
import {AppShell, AppShellHeader, AppShellMain, Button, PasswordInput, Stack, TextInput, Title} from "@mantine/core";
|
||||
import {Button, PasswordInput, Stack, TextInput, Title} from "@mantine/core";
|
||||
import {Header} from "@/components/header";
|
||||
import React from "react";
|
||||
import {useForm} from "@mantine/form";
|
||||
|
@ -40,14 +40,10 @@ const Page = () => {
|
|||
}
|
||||
|
||||
return (
|
||||
<AppShell header={{height: 70}}>
|
||||
<AppShellHeader>
|
||||
<Header/>
|
||||
</AppShellHeader>
|
||||
<AppShellMain>
|
||||
<form
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<>
|
||||
<Header/>
|
||||
<main>
|
||||
<form onSubmit={onSubmit}>
|
||||
<Stack
|
||||
align="center"
|
||||
justify="center"
|
||||
|
@ -77,8 +73,8 @@ const Page = () => {
|
|||
<Button type="submit" loading={mutation.isPending}>Добавить</Button>
|
||||
</Stack>
|
||||
</form>
|
||||
</AppShellMain>
|
||||
</AppShell>
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
1
src/plain-pages/problem-edit/index.ts
Normal file
1
src/plain-pages/problem-edit/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from "./page"
|
33
src/plain-pages/problem-edit/page.tsx
Normal file
33
src/plain-pages/problem-edit/page.tsx
Normal file
|
@ -0,0 +1,33 @@
|
|||
'use server';
|
||||
|
||||
import React from 'react';
|
||||
import {Metadata} from "next";
|
||||
import {ClientPage} from "./ui";
|
||||
import {GetProblem} from "@/shared/api";
|
||||
|
||||
type Props = {
|
||||
params: Promise<{ problem_id: number }>
|
||||
}
|
||||
|
||||
const generateMetadata = async (props: Props): Promise<Metadata> => {
|
||||
const problem_id = (await props.params).problem_id;
|
||||
|
||||
const problem = await GetProblem(problem_id);
|
||||
|
||||
return {
|
||||
title: problem.problem.title,
|
||||
description: '',
|
||||
};
|
||||
}
|
||||
|
||||
const Page = async (props: Props) => {
|
||||
const problem_id = (await props.params).problem_id;
|
||||
|
||||
const problem = await GetProblem(problem_id);
|
||||
|
||||
return (
|
||||
<ClientPage problem={problem.problem}/>
|
||||
)
|
||||
};
|
||||
|
||||
export {Page as ProblemEditPage, generateMetadata};
|
136
src/plain-pages/problem-edit/ui.tsx
Normal file
136
src/plain-pages/problem-edit/ui.tsx
Normal file
|
@ -0,0 +1,136 @@
|
|||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import {Button, Group, NumberInput, Stack, Textarea, TextInput, Title} from "@mantine/core";
|
||||
import {Header} from "@/components/header";
|
||||
import * as testerv1 from "../../../proto/tester/v1/api";
|
||||
import {useForm} from "@mantine/form";
|
||||
import {useMutation} from "@tanstack/react-query";
|
||||
import {UpdateProblem} from "@/shared/api";
|
||||
import {useRouter} from "next/navigation";
|
||||
|
||||
type Props = {
|
||||
problem: testerv1.Problem
|
||||
}
|
||||
|
||||
const ClientPage = ({problem}: Props) => {
|
||||
const router = useRouter();
|
||||
|
||||
const form = useForm({
|
||||
initialValues: {
|
||||
title: problem.title,
|
||||
time_limit: problem.time_limit,
|
||||
memory_limit: problem.memory_limit,
|
||||
legend: problem.legend,
|
||||
input_format: problem.input_format,
|
||||
output_format: problem.output_format,
|
||||
notes: problem.notes,
|
||||
scoring: problem.scoring
|
||||
},
|
||||
});
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: async (data: any) => {
|
||||
return await UpdateProblem(problem.id, data);
|
||||
},
|
||||
onSuccess: async () => {
|
||||
form.resetDirty();
|
||||
await router.refresh();
|
||||
},
|
||||
onError: (error) => {
|
||||
form.clearErrors();
|
||||
},
|
||||
retry: false
|
||||
});
|
||||
|
||||
const onSubmit = (event) => {
|
||||
event.preventDefault();
|
||||
mutation.mutate(form.getValues())
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header/>
|
||||
<main>
|
||||
<form onSubmit={onSubmit}>
|
||||
<Stack px="16" pb="16" maw="1920px" align="center" m="0 auto">
|
||||
<Group justify="flex-end" position="apart" w="100%" mt="16">
|
||||
<Button type="submit" disabled={!form.isDirty()}>
|
||||
Сохранить
|
||||
</Button>
|
||||
</Group>
|
||||
<Stack align="center" w="65em" gap="16">
|
||||
<Title order={2}>{problem.title}</Title>
|
||||
<TextInput
|
||||
label="Название задачи"
|
||||
w="100%"
|
||||
key={form.key('title')}
|
||||
{...form.getInputProps('title')}
|
||||
/>
|
||||
<Title order={2}>Ограничения</Title>
|
||||
<Group w="100%" justify="space-evenly">
|
||||
<NumberInput
|
||||
label="Ограничение по времени в мс"
|
||||
placeholder="1000"
|
||||
flex={1}
|
||||
key={form.key('time_limit')}
|
||||
{...form.getInputProps('time_limit')}
|
||||
/>
|
||||
<NumberInput
|
||||
label="Ограничение по памяти в мб"
|
||||
placeholder="64"
|
||||
flex={1}
|
||||
key={form.key('memory_limit')}
|
||||
{...form.getInputProps('memory_limit')}
|
||||
/>
|
||||
</Group>
|
||||
<Title order={2}>Условие</Title>
|
||||
<Textarea
|
||||
label="Легенда"
|
||||
autosize
|
||||
placeholder="Условие"
|
||||
w="100%"
|
||||
key={form.key('legend')}
|
||||
{...form.getInputProps('legend')}
|
||||
/>
|
||||
<Textarea
|
||||
label="Формат входных данных"
|
||||
autosize
|
||||
placeholder="Условие"
|
||||
w="100%"
|
||||
key={form.key('input_format')}
|
||||
{...form.getInputProps('input_format')}
|
||||
/>
|
||||
<Textarea
|
||||
label="Формат выходных данных"
|
||||
autosize
|
||||
placeholder="Условие"
|
||||
w="100%"
|
||||
key={form.key('output_format')}
|
||||
{...form.getInputProps('output_format')}
|
||||
/>
|
||||
<Textarea
|
||||
label="Система оценки"
|
||||
autosize
|
||||
placeholder="Условие"
|
||||
w="100%"
|
||||
key={form.key('scoring')}
|
||||
{...form.getInputProps('scoring')}
|
||||
/>
|
||||
<Textarea
|
||||
label="Примечание"
|
||||
autosize
|
||||
placeholder="Условие"
|
||||
w="100%"
|
||||
key={form.key('notes')}
|
||||
{...form.getInputProps('notes')}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</form>
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export {ClientPage};
|
1
src/plain-pages/problem/index.ts
Normal file
1
src/plain-pages/problem/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from "./page"
|
33
src/plain-pages/problem/page.tsx
Normal file
33
src/plain-pages/problem/page.tsx
Normal file
|
@ -0,0 +1,33 @@
|
|||
'use server';
|
||||
|
||||
import React from 'react';
|
||||
import {Metadata} from "next";
|
||||
import {ClientPage} from "./ui";
|
||||
import {GetProblem} from "@/shared/api";
|
||||
|
||||
type Props = {
|
||||
params: Promise<{ problem_id: number }>
|
||||
}
|
||||
|
||||
const generateMetadata = async (props: Props): Promise<Metadata> => {
|
||||
const problem_id = (await props.params).problem_id;
|
||||
|
||||
const problem = await GetProblem(problem_id);
|
||||
|
||||
return {
|
||||
title: `A. ${problem.problem.title}`,
|
||||
description: '',
|
||||
};
|
||||
}
|
||||
|
||||
const Page = async (props: Props) => {
|
||||
const problem_id = (await props.params).problem_id;
|
||||
|
||||
const problem = await GetProblem(problem_id);
|
||||
|
||||
return (
|
||||
<ClientPage problem={problem.problem}/>
|
||||
)
|
||||
};
|
||||
|
||||
export {Page as ProblemPage, generateMetadata};
|
34
src/plain-pages/problem/ui.tsx
Normal file
34
src/plain-pages/problem/ui.tsx
Normal file
|
@ -0,0 +1,34 @@
|
|||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import {Button, Group, Stack} from "@mantine/core";
|
||||
import {Header} from "@/components/header";
|
||||
import * as testerv1 from "../../../proto/tester/v1/api";
|
||||
import {Problem} from "@/components/problem";
|
||||
import Link from "next/link";
|
||||
|
||||
type Props = {
|
||||
problem: testerv1.Problem
|
||||
}
|
||||
|
||||
const ClientPage = ({problem}: Props) => {
|
||||
return (
|
||||
<>
|
||||
<Header/>
|
||||
<main>
|
||||
<Stack px="16" pb="16" maw="1920px" m="0 auto">
|
||||
<Group justify="flex-end" w="100%" mt="16">
|
||||
<Button component={Link} href={`/problems/${problem.id}/edit`}>
|
||||
Редактировать
|
||||
</Button>
|
||||
</Group>
|
||||
<Stack align="center" w="fit-content" gap="16" m="0 auto">
|
||||
<Problem problem={problem} letter="A"/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export {ClientPage};
|
1
src/plain-pages/problems/index.ts
Normal file
1
src/plain-pages/problems/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from "./page";
|
30
src/plain-pages/problems/page.tsx
Normal file
30
src/plain-pages/problems/page.tsx
Normal file
|
@ -0,0 +1,30 @@
|
|||
'use server';
|
||||
|
||||
import React from 'react';
|
||||
import {Metadata} from "next";
|
||||
import {ClientPage} from "./ui";
|
||||
import {ListProblems} from "@/shared/api";
|
||||
|
||||
const generateMetadata = async (): Promise<Metadata> => {
|
||||
return {
|
||||
title: 'Мастерская',
|
||||
description: '',
|
||||
};
|
||||
}
|
||||
|
||||
type Props = {
|
||||
searchParams: Promise<{ page: number }>
|
||||
}
|
||||
|
||||
|
||||
const Page = async (props: Props) => {
|
||||
const page = (await props.searchParams).page || 1;
|
||||
|
||||
const problemsList = await ListProblems(page, 10);
|
||||
|
||||
return (
|
||||
<ClientPage problems={problemsList.problems} pagination={problemsList.pagination}/>
|
||||
)
|
||||
};
|
||||
|
||||
export {Page as ProblemsPage, generateMetadata};
|
119
src/plain-pages/problems/ui.tsx
Normal file
119
src/plain-pages/problems/ui.tsx
Normal file
|
@ -0,0 +1,119 @@
|
|||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
ActionIcon,
|
||||
AppShellAside,
|
||||
Button,
|
||||
Group,
|
||||
Pagination,
|
||||
Stack,
|
||||
Table,
|
||||
TableTbody,
|
||||
TableTd,
|
||||
TableTh,
|
||||
TableThead,
|
||||
TableTr,
|
||||
Text,
|
||||
TextInput,
|
||||
Title
|
||||
} from "@mantine/core";
|
||||
import {IconCheck, IconPencil, IconUser} from "@tabler/icons-react";
|
||||
import Link from "next/link";
|
||||
import {Header} from "@/components/header";
|
||||
import * as testerv1 from "../../../proto/tester/v1/api";
|
||||
|
||||
type Props = {
|
||||
problems: testerv1.ProblemsListItem[],
|
||||
pagination: testerv1.Pagination,
|
||||
}
|
||||
|
||||
const ClientPage = ({problems, pagination}: Props) => {
|
||||
const getItemProps = (page) => ({
|
||||
component: Link,
|
||||
href: `/problems?page=${page}`,
|
||||
});
|
||||
|
||||
const getControlProps = (control) => {
|
||||
if (control === 'next') {
|
||||
if (pagination.page === pagination.total) {
|
||||
return {component: Link, href: `/problems?page=${pagination.page}`};
|
||||
}
|
||||
|
||||
return {component: Link, href: `/problems?page=${+pagination.page + 1}`};
|
||||
}
|
||||
|
||||
if (control === 'previous') {
|
||||
if (pagination.page === 1) {
|
||||
return {component: Link, href: `/problems?page=${pagination.page}`};
|
||||
}
|
||||
return {component: Link, href: `/problems?page=${+pagination.page - 1}`};
|
||||
}
|
||||
|
||||
return {};
|
||||
};
|
||||
|
||||
const rows = problems.map((problem) => (
|
||||
<TableTr key={problem.id}>
|
||||
<TableTd>
|
||||
<Text td="underline">{problem.title}</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Group align="end" gap="2" justify="center">
|
||||
<IconUser/>
|
||||
<Text td="underline">{123}</Text>
|
||||
</Group>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<ActionIcon
|
||||
size="xs"
|
||||
component={Link}
|
||||
href={`/problems/${problem.id}`}
|
||||
>
|
||||
<IconPencil/>
|
||||
</ActionIcon>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
));
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header/>
|
||||
<main>
|
||||
<Group px="16">
|
||||
<Stack align="center" w="fit-content" m="auto" pt="16" gap="16">
|
||||
<Title>Архив задач</Title>
|
||||
<Table horizontalSpacing="xl">
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Название</TableTh>
|
||||
<TableTh><IconCheck/></TableTh>
|
||||
<TableTh><IconPencil/></TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>{rows}</TableTbody>
|
||||
</Table>
|
||||
<Pagination total={pagination.total}
|
||||
value={pagination.page}
|
||||
getItemProps={getItemProps}
|
||||
getControlProps={getControlProps}
|
||||
/>
|
||||
</Stack>
|
||||
</Group>
|
||||
</main>
|
||||
{/*<aside>*/}
|
||||
{/* <Stack px="16">*/}
|
||||
{/* <Stack pt="16">*/}
|
||||
{/* <Stack gap="5">*/}
|
||||
{/* <Button title="Создать контест">Создать контест</Button>*/}
|
||||
{/* <Button title="Создать задачу">Создать задачу</Button>*/}
|
||||
{/* </Stack>*/}
|
||||
{/* <TextInput placeholder="Поиск"/>*/}
|
||||
{/* </Stack>*/}
|
||||
{/* </Stack>*/}
|
||||
{/*</aside>*/}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export {ClientPage};
|
1
src/plain-pages/solution/index.ts
Normal file
1
src/plain-pages/solution/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from "./page"
|
35
src/plain-pages/solution/page.tsx
Normal file
35
src/plain-pages/solution/page.tsx
Normal file
|
@ -0,0 +1,35 @@
|
|||
'use server';
|
||||
|
||||
import React from 'react';
|
||||
import {Metadata} from "next";
|
||||
import {ClientPage} from "./ui";
|
||||
import {GetSolution} from "@/shared/api";
|
||||
|
||||
type Props = {
|
||||
params: Promise<{ solution_id: number }>
|
||||
}
|
||||
|
||||
const generateMetadata = async (props: Props): Promise<Metadata> => {
|
||||
const solutionId = (await props.params).solution_id;
|
||||
|
||||
const solution = await GetSolution(solutionId);
|
||||
|
||||
return {
|
||||
title: `Посылка #${solution.solution.id}`,
|
||||
description: '',
|
||||
};
|
||||
}
|
||||
|
||||
const Page = async (props: Props) => {
|
||||
const solutionId = (await props.params).solution_id;
|
||||
|
||||
console.log(solutionId);
|
||||
|
||||
const solution = await GetSolution(solutionId);
|
||||
|
||||
return (
|
||||
<ClientPage solution={solution.solution}/>
|
||||
)
|
||||
};
|
||||
|
||||
export {Page as SolutionPage, generateMetadata};
|
72
src/plain-pages/solution/ui.tsx
Normal file
72
src/plain-pages/solution/ui.tsx
Normal file
|
@ -0,0 +1,72 @@
|
|||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import {Code, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title} from "@mantine/core";
|
||||
import {Header} from "@/components/header";
|
||||
import * as testerv1 from "../../../proto/tester/v1/api";
|
||||
|
||||
type Props = {
|
||||
solution: testerv1.Solution
|
||||
}
|
||||
|
||||
const ClientPage = ({solution}: Props) => {
|
||||
const rows = [solution].map((solution) => (
|
||||
<TableTr key={solution.id}>
|
||||
<TableTd>
|
||||
<Text>{solution.created_at}</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Text td="underline">user123</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Text td="underline">C. Арбуз</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Text>PyPy 3.12</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Text c="green">AC</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Text>91 мс</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Text>4600 КБ</Text>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
));
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header/>
|
||||
<main>
|
||||
<Stack px="16">
|
||||
<Stack align="center" w="fit-content" m="auto" pt="16" gap="16">
|
||||
<Table horizontalSpacing="xl">
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Когда</TableTh>
|
||||
<TableTh>Кто</TableTh>
|
||||
<TableTh>Задача</TableTh>
|
||||
<TableTh>Язык</TableTh>
|
||||
<TableTh>Вердикт</TableTh>
|
||||
<TableTh>Время</TableTh>
|
||||
<TableTh>Память</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>{rows}</TableTbody>
|
||||
</Table>
|
||||
<Stack alig="flex-start" w="100%">
|
||||
<Title order={2}>Код решения</Title>
|
||||
<Code block w="100%" style={{minHeight: 100}}>
|
||||
{solution.solution}
|
||||
</Code>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export {ClientPage};
|
1
src/plain-pages/solutions/index.ts
Normal file
1
src/plain-pages/solutions/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from "./page"
|
30
src/plain-pages/solutions/page.tsx
Normal file
30
src/plain-pages/solutions/page.tsx
Normal file
|
@ -0,0 +1,30 @@
|
|||
'use server';
|
||||
|
||||
import React from 'react';
|
||||
import {Metadata} from "next";
|
||||
import {ClientPage} from "./ui";
|
||||
import {ListSolutions} from "@/shared/api";
|
||||
|
||||
const generateMetadata = async (): Promise<Metadata> => {
|
||||
return {
|
||||
title: 'Посылки',
|
||||
description: '',
|
||||
};
|
||||
}
|
||||
|
||||
type Props = {
|
||||
searchParams: Promise<{ page: number }>
|
||||
}
|
||||
|
||||
|
||||
const Page = async (props: Props) => {
|
||||
const page = (await props.searchParams).page || 1;
|
||||
|
||||
const problemsList = await ListSolutions(page, 10);
|
||||
|
||||
return (
|
||||
<ClientPage solutions={problemsList.solutions} pagination={problemsList.pagination}/>
|
||||
)
|
||||
};
|
||||
|
||||
export {Page as SolutionsPage, generateMetadata};
|
99
src/plain-pages/solutions/ui.tsx
Normal file
99
src/plain-pages/solutions/ui.tsx
Normal file
|
@ -0,0 +1,99 @@
|
|||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import {Pagination, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title} from "@mantine/core";
|
||||
import Link from "next/link";
|
||||
import {Header} from "@/components/header";
|
||||
import * as testerv1 from "../../../proto/tester/v1/api";
|
||||
|
||||
type Props = {
|
||||
solutions: testerv1.SolutionsListItem[],
|
||||
pagination: testerv1.Pagination,
|
||||
}
|
||||
|
||||
const ClientPage = ({solutions, pagination}: Props) => {
|
||||
const getItemProps = (page) => ({
|
||||
component: Link,
|
||||
href: `/solutions?page=${page}`,
|
||||
});
|
||||
|
||||
const getControlProps = (control) => {
|
||||
if (control === 'next') {
|
||||
if (pagination.page === pagination.total) {
|
||||
return {component: Link, href: `/solutions?page=${pagination.page}`};
|
||||
}
|
||||
|
||||
return {component: Link, href: `/solutions?page=${+pagination.page + 1}`};
|
||||
}
|
||||
|
||||
if (control === 'previous') {
|
||||
if (pagination.page === 1) {
|
||||
return {component: Link, href: `/solutions?page=${pagination.page}`};
|
||||
}
|
||||
return {component: Link, href: `/solutions?page=${+pagination.page - 1}`};
|
||||
}
|
||||
|
||||
return {};
|
||||
};
|
||||
|
||||
const rows = solutions.map((solution) => (
|
||||
<TableTr key={solution.id}>
|
||||
<TableTd>
|
||||
<Text>{solution.created_at}</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Text td="underline">user123</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Text td="underline">C. Арбуз</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Text>PyPy 3.12</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Text c="green">AC</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Text>91 мс</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Text>4600 КБ</Text>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
));
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header/>
|
||||
<main>
|
||||
<Stack px="16">
|
||||
<Stack align="center" w="fit-content" m="auto" pt="16" gap="16">
|
||||
<Title>Посылки</Title>
|
||||
<Table horizontalSpacing="xl">
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Когда</TableTh>
|
||||
<TableTh>Кто</TableTh>
|
||||
<TableTh>Задача</TableTh>
|
||||
<TableTh>Язык</TableTh>
|
||||
<TableTh>Вердикт</TableTh>
|
||||
<TableTh>Время</TableTh>
|
||||
<TableTh>Память</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>{rows}</TableTbody>
|
||||
</Table>
|
||||
<Pagination
|
||||
total={pagination.total}
|
||||
value={pagination.page}
|
||||
getItemProps={getItemProps}
|
||||
getControlProps={getControlProps}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export {ClientPage};
|
1
src/plain-pages/task/index.ts
Normal file
1
src/plain-pages/task/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from "./page"
|
34
src/plain-pages/task/page.tsx
Normal file
34
src/plain-pages/task/page.tsx
Normal file
|
@ -0,0 +1,34 @@
|
|||
'use server';
|
||||
|
||||
import React from 'react';
|
||||
import {Metadata} from "next";
|
||||
import {ClientPage} from "./ui";
|
||||
import {GetTask} from "@/shared/api";
|
||||
import {numberToLetters} from "@/shared/lib";
|
||||
|
||||
type Props = {
|
||||
params: Promise<{ task_id: number }>
|
||||
}
|
||||
|
||||
const generateMetadata = async (props: Props): Promise<Metadata> => {
|
||||
const task_id = (await props.params).task_id;
|
||||
|
||||
const task = await GetTask(task_id);
|
||||
|
||||
return {
|
||||
title: `${numberToLetters(task.task.position)}. ${task.task.title}`,
|
||||
description: '',
|
||||
};
|
||||
}
|
||||
|
||||
const Page = async (props: Props) => {
|
||||
const task_id = (await props.params).task_id;
|
||||
|
||||
const task = await GetTask(task_id);
|
||||
|
||||
return (
|
||||
<ClientPage tasks={task.tasks} task={task.task} contest={task.contest}/>
|
||||
)
|
||||
};
|
||||
|
||||
export {Page as TaskPage, generateMetadata};
|
65
src/plain-pages/task/ui.tsx
Normal file
65
src/plain-pages/task/ui.tsx
Normal file
|
@ -0,0 +1,65 @@
|
|||
"use client";
|
||||
|
||||
import React from 'react';
|
||||
import {Anchor, Group, SegmentedControl, Stack, Text, Title} from "@mantine/core";
|
||||
import {Header} from "@/components/header";
|
||||
import {Code} from "@/components/code";
|
||||
import Link from "next/link";
|
||||
import * as testerv1 from "../../../proto/tester/v1/api";
|
||||
import {Problem} from "@/components/problem";
|
||||
import {numberToLetters} from "@/shared/lib";
|
||||
|
||||
type PageProps = {
|
||||
tasks: testerv1.TasksListItem[]
|
||||
contest: testerv1.Contest,
|
||||
task: testerv1.Task
|
||||
}
|
||||
|
||||
|
||||
const Page = ({tasks, contest, task}: PageProps) => {
|
||||
const getTaskData = item => ({
|
||||
label: `${numberToLetters(item.position)}. ${item.title}`,
|
||||
value: item
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header/>
|
||||
<Group align="start" justify="center" mt="md">
|
||||
<nav style={{padding: "var(--mantine-spacing-xs) var(--mantine-spacing-md)", width: "fit-content"}}>
|
||||
<Stack w={200}>
|
||||
<Anchor component={Link} href={`/contests/${contest.id}`} c="var(--mantine-color-bright)">
|
||||
<Title c="black" order={4} ta="center">
|
||||
{contest.title}
|
||||
</Title>
|
||||
</Anchor>
|
||||
<SegmentedControl
|
||||
orientation="vertical"
|
||||
withItemsBorders={false}
|
||||
data={tasks.map(getTaskData)}
|
||||
fullWidth
|
||||
defaultValue={getTaskData(tasks.find((item) => item.id === task.id))}
|
||||
/>
|
||||
</Stack>
|
||||
</nav>
|
||||
<Problem problem={task} letter={numberToLetters(task.position)}/>
|
||||
<aside style={{padding: "var(--mantine-spacing-xs) var(--mantine-spacing-md)", width: "fit-content"}}>
|
||||
<Stack>
|
||||
<Code/>
|
||||
<Text fw={500}>Последние посылки
|
||||
<Anchor
|
||||
component={Link}
|
||||
href="/"
|
||||
fs="italic"
|
||||
c="var(--mantine-color-bright)" fw={500}>
|
||||
(посмотреть все)
|
||||
</Anchor>:
|
||||
</Text>
|
||||
</Stack>
|
||||
</aside>
|
||||
</Group>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export {Page as ClientPage};
|
|
@ -1,7 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import React, {useState} from 'react';
|
||||
import {AppShell, AppShellHeader, AppShellMain, Button, Group, Select, Stack, TextInput, Title} from "@mantine/core";
|
||||
import {Button, Group, Select, Stack, TextInput, Title} from "@mantine/core";
|
||||
import {Header} from "@/components/header";
|
||||
import {User} from "../../../proto/user/v1/api";
|
||||
import {UpdateUser} from "@/shared/api";
|
||||
|
@ -24,42 +24,42 @@ const ClientPage = (props: Props) => {
|
|||
const [user, setUser] = useState(props.user);
|
||||
|
||||
return (
|
||||
<AppShell header={{height: 70}}>
|
||||
<AppShellHeader>
|
||||
<Header/>
|
||||
</AppShellHeader>
|
||||
<AppShellMain px="sm">
|
||||
<Stack align="center" mt="sm">
|
||||
<Title order={2}>
|
||||
Профиль пользователя {props.user.username}
|
||||
</Title>
|
||||
<Group align="end" w="fit-content" m="auto" pt="sm" gap="sm">
|
||||
<TextInput
|
||||
label="Никнейм"
|
||||
placeholder="Никнейм"
|
||||
defaultValue={user.username}
|
||||
onChange={(event) => setUser({...user, username: event.target.value})}
|
||||
disabled={!props.canEdit}
|
||||
/>
|
||||
<Select
|
||||
data={roles}
|
||||
label="Роль"
|
||||
defaultValue={roles[user.role]}
|
||||
allowDeselect={false}
|
||||
onChange={(event) => setUser({...user, role: roles.indexOf(event)})}
|
||||
disabled={!props.canEdit}
|
||||
/>
|
||||
</Group>
|
||||
<Button
|
||||
disabled={JSON.stringify(user) === JSON.stringify(props.user)}
|
||||
w="fit-content"
|
||||
onClick={() => UpdateUser(user.id!, user.role, user.username)}
|
||||
>
|
||||
Сохранить
|
||||
</Button>
|
||||
</Stack>
|
||||
</AppShellMain>
|
||||
</AppShell>
|
||||
<>
|
||||
<Header/>
|
||||
<main>
|
||||
<Group px="sm">
|
||||
<Stack align="center" mt="sm">
|
||||
<Title order={2}>
|
||||
Профиль пользователя {props.user.username}
|
||||
</Title>
|
||||
<Group align="end" w="fit-content" m="auto" pt="sm" gap="sm">
|
||||
<TextInput
|
||||
label="Никнейм"
|
||||
placeholder="Никнейм"
|
||||
defaultValue={user.username}
|
||||
onChange={(event) => setUser({...user, username: event.target.value})}
|
||||
disabled={!props.canEdit}
|
||||
/>
|
||||
<Select
|
||||
data={roles}
|
||||
label="Роль"
|
||||
defaultValue={roles[user.role]}
|
||||
allowDeselect={false}
|
||||
onChange={(event) => setUser({...user, role: roles.indexOf(event)})}
|
||||
disabled={!props.canEdit}
|
||||
/>
|
||||
</Group>
|
||||
<Button
|
||||
disabled={JSON.stringify(user) === JSON.stringify(props.user)}
|
||||
w="fit-content"
|
||||
onClick={() => UpdateUser(user.id!, user.role, user.username)}
|
||||
>
|
||||
Сохранить
|
||||
</Button>
|
||||
</Stack>
|
||||
</Group>
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -19,10 +19,10 @@ type Props = {
|
|||
const Page = async (props: Props) => {
|
||||
const page = (await props.searchParams).page || 1;
|
||||
|
||||
const users = await GetUsers(page, 10);
|
||||
const usersList = await GetUsers(page, 10);
|
||||
|
||||
return (
|
||||
<ClientPage users={users.users} max_page={users.max_page} page={users.page}/>
|
||||
<ClientPage users={usersList.users} pagination={usersList.pagination}/>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -2,11 +2,6 @@
|
|||
|
||||
import {
|
||||
ActionIcon,
|
||||
AppShell,
|
||||
AppShellAside,
|
||||
AppShellHeader,
|
||||
AppShellMain,
|
||||
Button,
|
||||
Pagination,
|
||||
Stack,
|
||||
Table,
|
||||
|
@ -15,20 +10,18 @@ import {
|
|||
TableTh,
|
||||
TableThead,
|
||||
TableTr,
|
||||
TextInput,
|
||||
Title
|
||||
} from "@mantine/core";
|
||||
import Link from "next/link";
|
||||
import {IconPencil} from "@tabler/icons-react";
|
||||
import {Header} from "@/components/header";
|
||||
import React from "react";
|
||||
import {User} from "../../../proto/user/v1/api";
|
||||
import * as userv1 from "../../../proto/user/v1/api";
|
||||
|
||||
|
||||
type Props = {
|
||||
users: User[],
|
||||
page: number
|
||||
max_page: number
|
||||
users: userv1.User[],
|
||||
pagination: userv1.Pagination
|
||||
};
|
||||
|
||||
const roles = [
|
||||
|
@ -38,8 +31,32 @@ const roles = [
|
|||
];
|
||||
|
||||
|
||||
const ClientPage = (props: Props) => {
|
||||
const rows = props.users.map((user) => (
|
||||
const ClientPage = ({users, pagination}: Props) => {
|
||||
const getControlProps = (control) => {
|
||||
if (control === 'next') {
|
||||
if (pagination.page === pagination.total) {
|
||||
return {component: Link, href: `/users?page=${pagination.page}`};
|
||||
}
|
||||
|
||||
return {component: Link, href: `/users?page=${+pagination.page + 1}`};
|
||||
}
|
||||
|
||||
if (control === 'previous') {
|
||||
if (pagination.page === 1) {
|
||||
return {component: Link, href: `/users?page=${pagination.page}`};
|
||||
}
|
||||
return {component: Link, href: `/users?page=${+pagination.page - 1}`};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
const getItemProps = (page) => ({
|
||||
component: Link,
|
||||
href: `/users?page=${page}`,
|
||||
});
|
||||
|
||||
const rows = users.map((user) => (
|
||||
<TableTr key={user.id}>
|
||||
<TableTd>{user.username}</TableTd>
|
||||
{/*<TableTd>{user.email}</TableTd>*/}
|
||||
|
@ -52,60 +69,41 @@ const ClientPage = (props: Props) => {
|
|||
));
|
||||
|
||||
return (
|
||||
<AppShell header={{height: 70}}>
|
||||
<AppShellHeader>
|
||||
<Header/>
|
||||
</AppShellHeader>
|
||||
<AppShellMain px="16">
|
||||
<Stack align="center" w="fit-content" m="auto" pt="16" gap="16">
|
||||
<Title>Пользователи</Title>
|
||||
<Table horizontalSpacing="xl">
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Никнейм</TableTh>
|
||||
{/*<TableTh>Почта</TableTh>*/}
|
||||
<TableTh>Роль</TableTh>
|
||||
<TableTh></TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>{rows}</TableTbody>
|
||||
</Table>
|
||||
<Pagination total={props.max_page}
|
||||
value={props.page}
|
||||
getItemProps={(page) => ({
|
||||
component: Link,
|
||||
href: `/users?page=${page}`,
|
||||
})}
|
||||
getControlProps={(control) => {
|
||||
if (control === 'next') {
|
||||
if (props.page === props.max_page) {
|
||||
return {component: Link, href: `/users?page=${props.page}`};
|
||||
}
|
||||
|
||||
return {component: Link, href: `/users?page=${+props.page + 1}`};
|
||||
}
|
||||
|
||||
if (control === 'previous') {
|
||||
if (props.page === 1) {
|
||||
return {component: Link, href: `/users?page=${props.page}`};
|
||||
}
|
||||
return {component: Link, href: `/users?page=${+props.page - 1}`};
|
||||
}
|
||||
|
||||
return {};
|
||||
}}
|
||||
/>
|
||||
<>
|
||||
<Header/>
|
||||
<main>
|
||||
<Stack px="16">
|
||||
<Stack align="center" w="fit-content" m="auto" pt="16" gap="16">
|
||||
<Title>Пользователи</Title>
|
||||
<Table horizontalSpacing="xl">
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Никнейм</TableTh>
|
||||
{/*<TableTh>Почта</TableTh>*/}
|
||||
<TableTh>Роль</TableTh>
|
||||
<TableTh></TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>{rows}</TableTbody>
|
||||
</Table>
|
||||
<Pagination total={pagination.total}
|
||||
value={pagination.page}
|
||||
getItemProps={getItemProps}
|
||||
getControlProps={getControlProps}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</AppShellMain>
|
||||
<AppShellAside withBorder={false} px="16">
|
||||
<Stack pt="16">
|
||||
<TextInput placeholder="Поиск"/>
|
||||
<Button component={Link} href="/users/new">
|
||||
Добавить пользователя
|
||||
</Button>
|
||||
</Stack>
|
||||
</AppShellAside>
|
||||
</AppShell>
|
||||
</main>
|
||||
|
||||
{/*<AppShellAside withBorder={false} px="16">*/}
|
||||
{/* <Stack pt="16">*/}
|
||||
{/* <TextInput placeholder="Поиск"/>*/}
|
||||
{/* <Button component={Link} href="/users/new">*/}
|
||||
{/* Добавить пользователя*/}
|
||||
{/* </Button>*/}
|
||||
{/* </Stack>*/}
|
||||
{/*</AppShellAside>*/}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
"use server";
|
||||
|
||||
import {Configuration, DefaultApi} from "../../../proto/tester/v1/api";
|
||||
import {Configuration, DefaultApi, UpdateProblemRequest} from "../../../proto/tester/v1/api";
|
||||
import {cookies} from "next/headers";
|
||||
import {AxiosRequestConfig} from "axios";
|
||||
|
||||
|
@ -68,5 +68,135 @@ export const DeleteTask = async (taskId: number) => {
|
|||
};
|
||||
|
||||
const response = await testerApi.deleteTask(taskId, options);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export const ListProblems = async (page: number, pageSize: number) => {
|
||||
const cookieStore = await cookies();
|
||||
|
||||
const session = cookieStore.get(CookieName);
|
||||
|
||||
if (session === undefined) {
|
||||
throw new Error("Session id not found");
|
||||
}
|
||||
|
||||
const options: AxiosRequestConfig = {
|
||||
headers: {
|
||||
'Authorization': "Bearer " + session.value
|
||||
}
|
||||
};
|
||||
|
||||
const response = await testerApi.listProblems(page, pageSize, options);
|
||||
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export const ListSolutions = async (page: number, pageSize: number) => {
|
||||
const cookieStore = await cookies();
|
||||
|
||||
const session = cookieStore.get(CookieName);
|
||||
|
||||
if (session === undefined) {
|
||||
throw new Error("Session id not found");
|
||||
}
|
||||
|
||||
const options: AxiosRequestConfig = {
|
||||
headers: {
|
||||
'Authorization': "Bearer " + session.value
|
||||
}
|
||||
};
|
||||
|
||||
const response = await testerApi.listSolutions(
|
||||
page,
|
||||
pageSize,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
options,
|
||||
);
|
||||
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export const GetProblem = async (id: number) => {
|
||||
const cookieStore = await cookies();
|
||||
|
||||
const session = cookieStore.get(CookieName);
|
||||
|
||||
if (session === undefined) {
|
||||
throw new Error("Session id not found");
|
||||
}
|
||||
|
||||
const options: AxiosRequestConfig = {
|
||||
headers: {
|
||||
'Authorization': "Bearer " + session.value
|
||||
}
|
||||
};
|
||||
|
||||
const response = await testerApi.getProblem(id, options);
|
||||
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export const GetSolution = async (id: number) => {
|
||||
const cookieStore = await cookies();
|
||||
|
||||
const session = cookieStore.get(CookieName);
|
||||
|
||||
if (session === undefined) {
|
||||
throw new Error("Session id not found");
|
||||
}
|
||||
|
||||
const options: AxiosRequestConfig = {
|
||||
headers: {
|
||||
'Authorization': "Bearer " + session.value
|
||||
}
|
||||
};
|
||||
|
||||
const response = await testerApi.getSolution(id, options);
|
||||
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export const GetTask = async (id: number) => {
|
||||
const cookieStore = await cookies();
|
||||
|
||||
const session = cookieStore.get(CookieName);
|
||||
|
||||
if (session === undefined) {
|
||||
throw new Error("Session id not found");
|
||||
}
|
||||
|
||||
const options: AxiosRequestConfig = {
|
||||
headers: {
|
||||
'Authorization': "Bearer " + session.value
|
||||
}
|
||||
};
|
||||
|
||||
const response = await testerApi.getTask(id, options);
|
||||
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export const UpdateProblem = async (id: number, data: UpdateProblemRequest) => {
|
||||
const cookieStore = await cookies();
|
||||
|
||||
const session = cookieStore.get(CookieName);
|
||||
|
||||
if (session === undefined) {
|
||||
throw new Error("Session id not found");
|
||||
}
|
||||
|
||||
const options: AxiosRequestConfig = {
|
||||
headers: {
|
||||
'Authorization': "Bearer " + session.value
|
||||
}
|
||||
};
|
||||
|
||||
const response = await testerApi.updateProblem(id, data, options);
|
||||
|
||||
return response.data;
|
||||
}
|
17
src/shared/lib/index.ts
Normal file
17
src/shared/lib/index.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
function numberToLetters(num: number): string {
|
||||
if (num <= 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let result = '';
|
||||
while (num > 0) {
|
||||
const remainder = (num - 1) % 26;
|
||||
const charCode = remainder + 65;
|
||||
result = String.fromCharCode(charCode) + result;
|
||||
num = Math.floor((num - 1) / 26);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export {numberToLetters};
|
Loading…
Add table
Reference in a new issue