diff --git a/frontend/src/routes/admin/tests/groups/questions/+page.svelte b/frontend/src/routes/admin/tests/groups/questions/+page.svelte new file mode 100644 index 0000000000000000000000000000000000000000..eaf8322fb72635ada4cb2c3b54b031bde116e6ce --- /dev/null +++ b/frontend/src/routes/admin/tests/groups/questions/+page.svelte @@ -0,0 +1,44 @@ +<script lang="ts"> + import { t } from '$lib/services/i18n'; + import type { PageData } from './$types'; + import type TestTaskGroup from '$lib/types/testTaskGroups'; + + const { data }: { data: PageData } = $props(); + + let groups: TestTaskGroup[] = $state(data.groups); +</script> + +<h1 class="text-xl font-bold m-5 text-center">{$t('header.admin.groups')}</h1> + +<table class="table max-w-5xl mx-auto text-left"> + <thead> + <tr> + <th>#</th> + <th>{$t('utils.words.title')}</th> + <th>{$t('utils.words.demo')}</th> + <th>{$t('utils.words.randomize')}</th> + <th class="capitalize"># {$t('utils.words.questions')}</th> + </tr> + </thead> + <tbody> + {#each groups as group (group.id)} + <tr + class="hover:bg-gray-100 hover:cursor-pointer" + onclick={() => (window.location.href = `/admin/tests/groups/${group.id}`)} + > + <td>{group.id}</td> + <td>{group.title}</td> + <td>{$t(`utils.bool.${group.demo}`)}</td> + <td>{$t(`utils.bool.${group.randomize}`)}</td> + <td>{group.questions.length}</td> + </tr> + {/each} + </tbody> +</table> +<div class="mt-8 mx-auto w-[64rem] flex justify-between pb-8"> + <a class="button" href="/admin/tests/groups/new">{$t('tests.groups.create')}</a> + <span> + <a class="btn" href="/admin/tests">⎠{$t('tests.groups.backtotests')}</a> + <a class="btn" href="/admin/tests/groups/questions">{$t('tests.questions.manage')}</a> + </span> +</div> diff --git a/frontend/src/routes/admin/tests/groups/questions/+page.ts b/frontend/src/routes/admin/tests/groups/questions/+page.ts new file mode 100644 index 0000000000000000000000000000000000000000..d1d208c4d67b4ed2f4ff4ef5b0825eb6aabb983b --- /dev/null +++ b/frontend/src/routes/admin/tests/groups/questions/+page.ts @@ -0,0 +1,12 @@ +import { getTestQuestionsAPI } from '$lib/api/tests'; +import { type Load } from '@sveltejs/kit'; +import { TestTaskQuestion } from '$lib/types/testTaskQuestions'; + +export const load: Load = async ({ fetch }) => { + const questionsRaw = await getTestQuestionsAPI(fetch); + const questions = TestTaskQuestion.parseAll(questionsRaw); + + return { + questions + }; +}; diff --git a/frontend/src/routes/admin/tests/groups/questions/[id]/+page.server.ts b/frontend/src/routes/admin/tests/groups/questions/[id]/+page.server.ts new file mode 100644 index 0000000000000000000000000000000000000000..ec22d98a967220e474347ccd7ad6e0bd71bfb4bb --- /dev/null +++ b/frontend/src/routes/admin/tests/groups/questions/[id]/+page.server.ts @@ -0,0 +1,42 @@ +import { redirect, type Actions } from '@sveltejs/kit'; +import { updateTestTaskGroupAPI } from '$lib/api/tests'; + +export const actions: Actions = { + default: async ({ request, fetch }) => { + const formData = await request.formData(); + + const idStr = formData.get('id')?.toString(); + const title = formData.get('title')?.toString(); + const demo = formData.get('demo')?.toString() == 'on'; + const randomize = formData.get('randomize')?.toString() == 'on'; + + if (!idStr || !title) { + return { + message: 'Invalid request: Missing required fields' + }; + } + + const id = parseInt(idStr, 10); + + if (isNaN(id)) { + return { + message: 'Invalid request: Invalid ID' + }; + } + + const questions = formData + .getAll('questions[]') + .map((question) => parseInt(question.toString(), 10)) + .filter((question) => !isNaN(question)); + + const ok = await updateTestTaskGroupAPI(fetch, id, title, demo, randomize, questions); + + if (!ok) { + return { + message: 'Error updating test task group' + }; + } + + return redirect(303, `/admin/tests/groups`); + } +}; diff --git a/frontend/src/routes/admin/tests/groups/questions/[id]/+page.svelte b/frontend/src/routes/admin/tests/groups/questions/[id]/+page.svelte new file mode 100644 index 0000000000000000000000000000000000000000..f2e0a43c4e4ef8841005d5cb25f11ebe7aec5d37 --- /dev/null +++ b/frontend/src/routes/admin/tests/groups/questions/[id]/+page.svelte @@ -0,0 +1,9 @@ +<script lang="ts"> + import GroupForm from '$lib/components/tests/GroupForm.svelte'; + import type { PageData } from './$types'; + + const { data, form }: { data: PageData; form: FormData } = $props(); + const { group, possibleQuestions } = data; +</script> + +<GroupForm {group} {possibleQuestions} message={form?.message} /> diff --git a/frontend/src/routes/admin/tests/groups/questions/[id]/+page.ts b/frontend/src/routes/admin/tests/groups/questions/[id]/+page.ts new file mode 100644 index 0000000000000000000000000000000000000000..e33e6f88c12a8126f04455b8f3e0b815ee0b0ac1 --- /dev/null +++ b/frontend/src/routes/admin/tests/groups/questions/[id]/+page.ts @@ -0,0 +1,31 @@ +import { getTestQuestionsAPI, getTestTaskGroupAPI } from '$lib/api/tests'; +import TestTaskGroup from '$lib/types/testTaskGroups'; +import { TestTaskQuestion } from '$lib/types/testTaskQuestions'; +import { error, type Load } from '@sveltejs/kit'; + +export const load: Load = async ({ fetch, params }) => { + const idStr = params?.id; + + if (!idStr) { + return error(400, 'Invalid ID'); + } + + const id = parseInt(idStr, 10); + + if (isNaN(id)) { + return error(400, 'Invalid ID'); + } + + const groupRaw = await getTestTaskGroupAPI(fetch, id); + + if (!groupRaw) { + return error(404, 'Group not found'); + } + + const group = TestTaskGroup.parse(groupRaw); + + const questionsRaw = await getTestQuestionsAPI(fetch); + const questions = TestTaskQuestion.parseAll(questionsRaw); + + return { group, possibleQuestions: questions }; +}; diff --git a/frontend/src/routes/admin/tests/groups/questions/new/+page.server.ts b/frontend/src/routes/admin/tests/groups/questions/new/+page.server.ts new file mode 100644 index 0000000000000000000000000000000000000000..fd54c87ab26d19ce43b697a5ffefc8c4321ffc90 --- /dev/null +++ b/frontend/src/routes/admin/tests/groups/questions/new/+page.server.ts @@ -0,0 +1,33 @@ +import { redirect, type Actions } from '@sveltejs/kit'; +import { createTestTaskGroupAPI } from '$lib/api/tests'; + +export const actions: Actions = { + default: async ({ request, fetch }) => { + const formData = await request.formData(); + + const title = formData.get('title')?.toString(); + const demo = formData.get('demo')?.toString() == 'on'; + const randomize = formData.get('randomize')?.toString() == 'on'; + + if (!title) { + return { + message: 'Invalid request: Missing required fields' + }; + } + + const questions = formData + .getAll('questions[]') + .map((question) => parseInt(question.toString(), 10)) + .filter((question) => !isNaN(question)); + + const id = await createTestTaskGroupAPI(fetch, title, demo, randomize, questions); + + if (id === null) { + return { + message: 'Error creating test task group' + }; + } + + return redirect(303, `/admin/tests/groups`); + } +}; diff --git a/frontend/src/routes/admin/tests/groups/questions/new/+page.svelte b/frontend/src/routes/admin/tests/groups/questions/new/+page.svelte new file mode 100644 index 0000000000000000000000000000000000000000..4c3987e3c76401719759a7f0c44789e9f314512c --- /dev/null +++ b/frontend/src/routes/admin/tests/groups/questions/new/+page.svelte @@ -0,0 +1,7 @@ +<script lang="ts"> + import GroupForm from '$lib/components/tests/GroupForm.svelte'; + const { data, form }: { data: PageData; form: FormData } = $props(); + const { possibleQuestions } = data; +</script> + +<GroupForm group={null} {possibleQuestions} message={form?.message} /> diff --git a/frontend/src/routes/admin/tests/groups/questions/new/+page.ts b/frontend/src/routes/admin/tests/groups/questions/new/+page.ts new file mode 100644 index 0000000000000000000000000000000000000000..fcf5f18b9cacebac5116425d6d3c1619cc698065 --- /dev/null +++ b/frontend/src/routes/admin/tests/groups/questions/new/+page.ts @@ -0,0 +1,10 @@ +import { getTestQuestionsAPI } from '$lib/api/tests'; +import { TestTaskQuestion } from '$lib/types/testTaskQuestions'; +import { type Load } from '@sveltejs/kit'; + +export const load: Load = async ({ fetch }) => { + const questionsRaw = await getTestQuestionsAPI(fetch); + const questions = TestTaskQuestion.parseAll(questionsRaw); + + return { possibleQuestions: questions }; +};