mirror of
https://github.com/danbulant/Portfolio
synced 2026-05-24 12:35:31 +00:00
Merge pull request #162 from EETagent/grades_frontend
(frontend) grades table in register
This commit is contained in:
commit
b6f4a66e5f
7 changed files with 227 additions and 37 deletions
|
|
@ -85,6 +85,9 @@ export const apiLogin = async (data: CandidateLogin): Promise<number> => {
|
|||
export const apiFillDetails = async (data: CandidateData): Promise<CandidateData> => {
|
||||
// Sanitize candidate data
|
||||
Object.keys(data.candidate).forEach((key) => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
if (typeof data.candidate[key] !== 'string' && typeof data.candidate[key] !== 'number') return;
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
data.candidate[key] = DOMPurify.sanitize(data.candidate[key]);
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
input {
|
||||
@apply hover:cursor-pointer;
|
||||
@apply bg-sspsBlue hover:bg-sspsBlueDark
|
||||
@apply @apply rounded-lg p-3 text-xl font-semibold
|
||||
@apply rounded-lg p-3 text-xl font-semibold
|
||||
text-white transition-colors duration-300;
|
||||
@apply w-full;
|
||||
}
|
||||
|
|
|
|||
41
frontend/src/lib/components/grades/GradesRow.svelte
Normal file
41
frontend/src/lib/components/grades/GradesRow.svelte
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
<script context="module" lang="ts">
|
||||
export type Semester = '1/8' | '2/8' | '1/9' | '2/9';
|
||||
export type Grade = {
|
||||
subject?: string;
|
||||
semesters: {
|
||||
[semester in Semester]?: string;
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export let grade: Grade;
|
||||
const SEMESTERS: Semester[] = ['1/8', '2/8', '1/9', '2/9'];
|
||||
</script>
|
||||
|
||||
<div class="flex">
|
||||
<input class="w-1/2" on:keyup bind:value={grade.subject} type="text" />
|
||||
{#each SEMESTERS as semester}
|
||||
<select class="ml-0.5 w-1/6" on:change bind:value={grade.semesters[semester]} name="">
|
||||
<option value="" />
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
<option value="3">3</option>
|
||||
<option value="4">4</option>
|
||||
<option value="5">5</option>
|
||||
</select>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
input {
|
||||
@apply hover:border-sspsBlue rounded-lg border border-2 bg-[#f8fafb] outline-none;
|
||||
@apply w-1/2 w-full px-2;
|
||||
@applytransition-colors duration-300;
|
||||
}
|
||||
select {
|
||||
@apply ml-0.5 w-1/6;
|
||||
@apply hover:border-sspsBlue rounded-lg border border-2 bg-[#f8fafb] outline-none;
|
||||
@apply transition-colors duration-300;
|
||||
}
|
||||
</style>
|
||||
122
frontend/src/lib/components/grades/GradesTable.svelte
Normal file
122
frontend/src/lib/components/grades/GradesTable.svelte
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
<script context="module" lang="ts">
|
||||
export type GradeBackend = {
|
||||
subject: string;
|
||||
value: number;
|
||||
semester: Semester;
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import GradesRow, { type Grade, type Semester } from './GradesRow.svelte';
|
||||
|
||||
export let error: string | Array<unknown> = '';
|
||||
|
||||
export let grades: Array<GradeBackend>;
|
||||
|
||||
const convertGradeBackendToGrade = (gradesBackend: Array<GradeBackend>) => {
|
||||
const grades: Array<Grade> = [];
|
||||
for (let index = 0; index < gradesBackend.length; index++) {
|
||||
const gradeBackend = gradesBackend[index];
|
||||
let grade = grades.find((g) => g.subject === gradeBackend.subject);
|
||||
if (!grade) {
|
||||
grade = {
|
||||
subject: gradeBackend.subject,
|
||||
semesters: {}
|
||||
};
|
||||
grades.push(grade);
|
||||
}
|
||||
grade.semesters[gradeBackend.semester] = gradeBackend.value.toString();
|
||||
}
|
||||
return grades;
|
||||
};
|
||||
|
||||
let gradesLocal: Array<Grade> =
|
||||
grades.length > 0
|
||||
? convertGradeBackendToGrade(grades)
|
||||
: [
|
||||
{ subject: 'Chování', semesters: {} },
|
||||
{ subject: 'Český jazyk', semesters: {} },
|
||||
{ subject: 'Matematika', semesters: {} },
|
||||
{ subject: 'Anglický jazyk', semesters: {} },
|
||||
{ subject: 'Chemie', semesters: {} },
|
||||
{ subject: 'Fyzika', semesters: {} },
|
||||
{ subject: 'Dějepis', semesters: {} },
|
||||
{ subject: 'Tělesná výchova', semesters: {} }
|
||||
];
|
||||
/*let gradesLocal: Array<Grade> = Array.from({ length: 8 }, () => {
|
||||
return {
|
||||
subject: '',
|
||||
semesters: {
|
||||
'1/8': undefined,
|
||||
'2/8': undefined,
|
||||
'1/9': undefined,
|
||||
'2/9': undefined
|
||||
}
|
||||
};
|
||||
});*/
|
||||
// Convert local Grade type to expanded GradesBackend type
|
||||
const convertGradeToGradeBackend = () => {
|
||||
// Delay to wait for select to be updated
|
||||
setTimeout(() => {
|
||||
const gradesTemp: Array<GradeBackend> = [];
|
||||
for (let index = 0; index < gradesLocal.length; index++) {
|
||||
const grade = gradesLocal[index];
|
||||
for (const semester in grade.semesters) {
|
||||
const semesterTyped = semester as Semester;
|
||||
if (grade.semesters[semesterTyped] && grade.subject) {
|
||||
const gradeString = grade.semesters[semesterTyped]!;
|
||||
gradesTemp.push({
|
||||
subject: grade.subject,
|
||||
value: Number(gradeString),
|
||||
semester: semesterTyped
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
grades = [...gradesTemp];
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="mx-auto mt-8 flex text-gray-400 lg:w-4/5">
|
||||
<span class="w-1/2 text-center">Známky</span>
|
||||
<span class="ml-0.5 w-1/6 text-center">1/8</span>
|
||||
<span class="ml-0.5 w-1/6 text-center">2/8</span>
|
||||
<span class="ml-0.5 w-1/6 text-center">1/9</span>
|
||||
<span class="ml-0.5 w-1/6 text-center">2/9</span>
|
||||
</div>
|
||||
<div class="mx-auto flex max-h-[22rem] w-full flex-col overflow-scroll lg:w-4/5">
|
||||
{#each gradesLocal as _, i}
|
||||
<div class="mb-1">
|
||||
<GradesRow
|
||||
on:keyup={convertGradeToGradeBackend}
|
||||
on:change={convertGradeToGradeBackend}
|
||||
bind:grade={gradesLocal[i]}
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
<button
|
||||
class:isError={error.length > 0}
|
||||
class="ml-auto w-24 rounded-full bg-gray-400 p-1 text-xl text-white transition-colors duration-300 hover:bg-gray-500"
|
||||
on:click={() => {
|
||||
gradesLocal = [
|
||||
...gradesLocal,
|
||||
{
|
||||
subject: '',
|
||||
semesters: {
|
||||
'1/8': undefined,
|
||||
'2/8': undefined,
|
||||
'1/9': undefined,
|
||||
'2/9': undefined
|
||||
}
|
||||
}
|
||||
];
|
||||
}}>+</button
|
||||
>
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
.isError {
|
||||
@apply bg-red-500;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
import type { GradeBackend } from '$lib/components/grades/GradesTable.svelte';
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export interface CandidateData {
|
||||
|
|
@ -14,6 +15,7 @@ export interface CandidateData {
|
|||
personalIdNumber: string;
|
||||
schoolName: string;
|
||||
healthInsurance: string;
|
||||
grades: Array<GradeBackend>;
|
||||
};
|
||||
parents: Array<{
|
||||
name: string;
|
||||
|
|
@ -72,7 +74,8 @@ export const candidateData = writable<CandidateData>({
|
|||
sex: '',
|
||||
personalIdNumber: '',
|
||||
schoolName: '',
|
||||
healthInsurance: ''
|
||||
healthInsurance: '',
|
||||
grades: []
|
||||
},
|
||||
parents: []
|
||||
});
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@ export const load: PageServerLoad = async ({ fetch, params }) => {
|
|||
sex: '',
|
||||
personalIdNumber: '',
|
||||
schoolName: '',
|
||||
healthInsurance: ''
|
||||
healthInsurance: '',
|
||||
grades: []
|
||||
},
|
||||
parents: []
|
||||
};
|
||||
|
|
|
|||
|
|
@ -21,10 +21,11 @@
|
|||
import * as yup from 'yup';
|
||||
import type { CandidateData } from '$lib/stores/candidate';
|
||||
import AccountLinkCheckBox from '$lib/components/checkbox/AccountLinkCheckBox.svelte';
|
||||
import GradesTable from '$lib/components/grades/GradesTable.svelte';
|
||||
|
||||
const pageCount = 6;
|
||||
const pageCount = 7;
|
||||
let pageIndex = 0;
|
||||
let pagesFilled = [false, false, false, false, false, false];
|
||||
let pagesFilled = [false, false, false, false, false, false, false];
|
||||
let pageTexts = [
|
||||
'Zpracování osobních údajů',
|
||||
'Registrace',
|
||||
|
|
@ -58,7 +59,8 @@
|
|||
citizenship: '',
|
||||
personalIdNumber: '',
|
||||
schoolName: '',
|
||||
healthInsurance: ''
|
||||
healthInsurance: '',
|
||||
grades: []
|
||||
},
|
||||
parents: [
|
||||
{
|
||||
|
|
@ -105,7 +107,20 @@
|
|||
citizenship: yup.string().required(),
|
||||
personalIdNumber: yup.string().required(),
|
||||
schoolName: yup.string().required(),
|
||||
healthInsurance: yup.number().required()
|
||||
healthInsurance: yup.number().required(),
|
||||
grades: yup
|
||||
.array()
|
||||
.min(1)
|
||||
.of(
|
||||
yup
|
||||
.object()
|
||||
.shape({
|
||||
subject: yup.string().required(),
|
||||
value: yup.number().required(),
|
||||
semester: yup.string().required()
|
||||
})
|
||||
.required()
|
||||
).required()
|
||||
}),
|
||||
parents: yup.array().of(
|
||||
yup.object().shape({
|
||||
|
|
@ -244,22 +259,36 @@
|
|||
values.parents = values.parents.filter(
|
||||
(x) => x.name !== '' && x.surname !== '' && x.email !== '' && x.telephone !== ''
|
||||
);
|
||||
// @ts-ignore
|
||||
let addressArray: Array<string> = [values.candidate.street + ' ' + values.candidate.houseNumber, values.candidate.city, values.candidate.zip];
|
||||
let addressArray: Array<string> = [
|
||||
// @ts-ignore
|
||||
values.candidate.street + ' ' + values.candidate.houseNumber,
|
||||
// @ts-ignore
|
||||
values.candidate.city,
|
||||
// @ts-ignore
|
||||
values.candidate.zip
|
||||
];
|
||||
values.candidate.address = addressArray.map((x) => x.replaceAll(',', '').trim()).join(',');
|
||||
// @ts-ignore
|
||||
delete values.candidate.street; delete values.candidate.houseNumber; delete values.candidate.city; delete values.candidate.zip;
|
||||
delete values.candidate.street;
|
||||
// @ts-ignore
|
||||
delete values.candidate.houseNumber;
|
||||
// @ts-ignore
|
||||
delete values.candidate.city;
|
||||
// @ts-ignore
|
||||
delete values.candidate.zip;
|
||||
|
||||
await apiFillDetails(values);
|
||||
|
||||
goto('/dashboard');
|
||||
} catch (e) {
|
||||
values = oldValues;
|
||||
$form = oldValues;
|
||||
console.error('error while submitting data: ' + e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const { form, errors, handleSubmit, handleChange } = createForm({
|
||||
const { form, errors, handleSubmit } = createForm({
|
||||
initialValues: formInitialValues,
|
||||
validationSchema: formValidationSchema,
|
||||
|
||||
|
|
@ -332,6 +361,9 @@
|
|||
return true;
|
||||
}
|
||||
break;
|
||||
case 7:
|
||||
if ($typedErrors["candidate"]["grades"].length > 0) return true;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
|
@ -358,7 +390,9 @@
|
|||
street: details.candidate.address.split(',')[0].split(' ')[0],
|
||||
houseNumber: details.candidate.address.split(',')[0].split(' ')[1],
|
||||
city: details.candidate.address.split(',')[1],
|
||||
zip: details.candidate.address.split(',')[2]
|
||||
zip: details.candidate.address.split(',')[2],
|
||||
// @ts-ignore
|
||||
grades: details.candidate.grades
|
||||
},
|
||||
parents: [
|
||||
{
|
||||
|
|
@ -384,7 +418,7 @@
|
|||
<div class="form relative">
|
||||
<div class="bottom-3/12 absolute flex w-full flex-col md:h-auto">
|
||||
<!-- TODO: Find different way how to display SchoolBadge -->
|
||||
{#if pageIndex > 0}
|
||||
{#if pageIndex !== 0 && pageIndex !== 7}
|
||||
<div class="<md:h-24 <md:w-24 mb-4 h-32 w-32 self-center">
|
||||
<SchoolBadge />
|
||||
</div>
|
||||
|
|
@ -402,7 +436,6 @@
|
|||
applications={baseCandidateDetails.applications}
|
||||
bind:linkOk={$form.linkOk}
|
||||
bind:linkError={$form.linkError}
|
||||
on:change={handleChange}
|
||||
error={$typedErrors['linkOk']}
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -418,7 +451,6 @@
|
|||
</p>
|
||||
<div class="field">
|
||||
<GdprCheckBox
|
||||
on:change={handleChange}
|
||||
bind:value={$form.gdpr}
|
||||
error={$typedErrors['gdpr']}
|
||||
/>
|
||||
|
|
@ -435,7 +467,6 @@
|
|||
<span class="field">
|
||||
<NameField
|
||||
error={$typedErrors['candidate']['name'] || $typedErrors['candidate']['surname']}
|
||||
on:change={handleChange}
|
||||
bind:valueName={$form.candidate.name}
|
||||
bind:valueSurname={$form.candidate.surname}
|
||||
placeholder="Jméno a příjmení"
|
||||
|
|
@ -444,7 +475,6 @@
|
|||
<span class="field">
|
||||
<EmailField
|
||||
error={$typedErrors['candidate']['email']}
|
||||
on:change={handleChange}
|
||||
bind:value={$form.candidate.email}
|
||||
placeholder="E-mail"
|
||||
/>
|
||||
|
|
@ -452,7 +482,6 @@
|
|||
<span class="field">
|
||||
<TelephoneField
|
||||
error={$typedErrors['candidate']['telephone']}
|
||||
on:change={handleChange}
|
||||
bind:value={$form.candidate.telephone}
|
||||
placeholder="Telefon"
|
||||
/>
|
||||
|
|
@ -470,7 +499,6 @@
|
|||
<NameField
|
||||
error={$typedErrors['candidate']['street'] ||
|
||||
$typedErrors['candidate']['houseNumber']}
|
||||
on:change={handleChange}
|
||||
bind:valueName={$form.candidate.street}
|
||||
bind:valueSurname={$form.candidate.houseNumber}
|
||||
placeholder="Ulice a č. p."
|
||||
|
|
@ -480,7 +508,6 @@
|
|||
<span class="ml-2 w-[33%]">
|
||||
<TextField
|
||||
error={$typedErrors['candidate']['zip']}
|
||||
on:change={handleChange}
|
||||
bind:value={$form.candidate.zip}
|
||||
type="number"
|
||||
placeholder="PSČ"
|
||||
|
|
@ -492,7 +519,6 @@
|
|||
<span>
|
||||
<TextField
|
||||
error={$typedErrors['candidate']['city']}
|
||||
on:change={handleChange}
|
||||
bind:value={$form.candidate.city}
|
||||
type="text"
|
||||
placeholder="Město"
|
||||
|
|
@ -502,7 +528,6 @@
|
|||
<span class="ml-2">
|
||||
<TextField
|
||||
error={$typedErrors['candidate']['birthplace']}
|
||||
on:change={handleChange}
|
||||
bind:value={$form.candidate.birthplace}
|
||||
type="text"
|
||||
placeholder="Místo narození"
|
||||
|
|
@ -519,7 +544,6 @@
|
|||
<div class="field flex items-center">
|
||||
<TextField
|
||||
error={$typedErrors['candidate']['birthdate']}
|
||||
on:change={handleChange}
|
||||
bind:value={$form.candidate.birthdate}
|
||||
type="text"
|
||||
placeholder="Datum narození"
|
||||
|
|
@ -528,7 +552,6 @@
|
|||
<div class="ml-2">
|
||||
<SelectField
|
||||
error={$typedErrors['candidate']['sex']}
|
||||
on:change={handleChange}
|
||||
bind:value={$form.candidate.sex}
|
||||
options={['Žena', 'Muž']}
|
||||
placeholder="Pohlaví"
|
||||
|
|
@ -544,7 +567,6 @@
|
|||
<span class="field">
|
||||
<NameField
|
||||
error={$typedErrors['parents'][0]['name'] || $typedErrors['parents'][0]['surname']}
|
||||
on:change={handleChange}
|
||||
bind:valueName={$form.parents[0].name}
|
||||
bind:valueSurname={$form.parents[0].surname}
|
||||
placeholder="Jméno a příjmení zákonného zástupce"
|
||||
|
|
@ -553,7 +575,6 @@
|
|||
<span class="field">
|
||||
<EmailField
|
||||
error={$typedErrors['parents'][0]['email']}
|
||||
on:change={handleChange}
|
||||
bind:value={$form.parents[0].email}
|
||||
placeholder="E-mail zákonného zástupce"
|
||||
/>
|
||||
|
|
@ -561,7 +582,6 @@
|
|||
<span class="field">
|
||||
<TelephoneField
|
||||
error={$typedErrors['parents'][0]['telephone']}
|
||||
on:change={handleChange}
|
||||
bind:value={$form.parents[0].telephone}
|
||||
placeholder="Telefon zákonného zástupce"
|
||||
/>
|
||||
|
|
@ -576,7 +596,6 @@
|
|||
<span class="field">
|
||||
<NameField
|
||||
error={$typedErrors['parents'][1]['name'] || $typedErrors['parents'][1]['surname']}
|
||||
on:change={handleChange}
|
||||
bind:valueName={$form.parents[1].name}
|
||||
bind:valueSurname={$form.parents[1].surname}
|
||||
placeholder="Jméno a příjmení zákonného zástupce (nepovinné)"
|
||||
|
|
@ -585,7 +604,6 @@
|
|||
<span class="field">
|
||||
<EmailField
|
||||
error={$typedErrors['parents'][1]['email']}
|
||||
on:change={handleChange}
|
||||
bind:value={$form.parents[1].email}
|
||||
placeholder="E-mail zákonného zástupce (nepovinné)"
|
||||
/>
|
||||
|
|
@ -593,7 +611,6 @@
|
|||
<span class="field">
|
||||
<TelephoneField
|
||||
error={$typedErrors['parents'][1]['telephone']}
|
||||
on:change={handleChange}
|
||||
bind:value={$form.parents[1].telephone}
|
||||
placeholder="Telefon zákonného zástupce (nepovinné)"
|
||||
/>
|
||||
|
|
@ -609,7 +626,6 @@
|
|||
<span class="field">
|
||||
<SelectField
|
||||
error={$typedErrors['candidate']['citizenship']}
|
||||
on:change={handleChange}
|
||||
bind:value={$form.candidate.citizenship}
|
||||
placeholder="Občanství"
|
||||
options={['Česká republika', 'Slovenská republika', 'Ukrajina', 'Jiné']}
|
||||
|
|
@ -620,7 +636,6 @@
|
|||
{#if $form.candidate.citizenship === 'Česká republika' || !$form.candidate.citizenship}
|
||||
<TextField
|
||||
error={$typedErrors['candidate']['schoolName']}
|
||||
on:change={handleChange}
|
||||
type="number"
|
||||
bind:value={$form.candidate.schoolName}
|
||||
placeholder="IZO školy"
|
||||
|
|
@ -628,7 +643,6 @@
|
|||
{:else}
|
||||
<TextField
|
||||
error={$typedErrors['candidate']['schoolName']}
|
||||
on:change={handleChange}
|
||||
type="text"
|
||||
bind:value={$form.candidate.schoolName}
|
||||
placeholder="Název školy"
|
||||
|
|
@ -639,7 +653,6 @@
|
|||
<span class="ml-2">
|
||||
<TextField
|
||||
error={$typedErrors['candidate']['healthInsurance']}
|
||||
on:change={handleChange}
|
||||
type="text"
|
||||
bind:value={$form.candidate.healthInsurance}
|
||||
placeholder="Číslo zdravotní pojišťovny"
|
||||
|
|
@ -651,22 +664,29 @@
|
|||
{#if $form.candidate.citizenship === 'Česká republika' || !$form.candidate.citizenship}
|
||||
<IdField
|
||||
error={$typedErrors['candidate']['personalIdNumber']}
|
||||
on:change={handleChange}
|
||||
bind:value={$form.candidate.personalIdNumber}
|
||||
placeholder="Rodné číslo"
|
||||
/>
|
||||
{:else}
|
||||
<TextField
|
||||
error={$typedErrors['candidate']['personalIdNumber']}
|
||||
on:change={handleChange}
|
||||
bind:value={$form.candidate.personalIdNumber}
|
||||
placeholder="Rodné číslo"
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{:else if pageIndex === 7}
|
||||
<h1 class="title mt-8">{pageTexts[5]}</h1>
|
||||
<p class="description mt-8 block text-center">
|
||||
Přidejte prosím přepis Vaších známek z posledních dvou let studia
|
||||
</p>
|
||||
<GradesTable
|
||||
error={$typedErrors['candidate']['grades']}
|
||||
bind:grades={$form.candidate.grades}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="controls bottom-1/12 absolute w-full">
|
||||
<div class="bottom-1/12 absolute w-full">
|
||||
<div class="field">
|
||||
<Submit
|
||||
on:click={async (e) => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue