Merge pull request #162 from EETagent/grades_frontend

(frontend) grades table in register
This commit is contained in:
Vojtěch Jungmann 2023-01-17 17:15:48 +01:00 committed by GitHub
commit b6f4a66e5f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 227 additions and 37 deletions

View file

@ -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]);

View file

@ -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;
}

View 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>

View 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>

View file

@ -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: []
});

View file

@ -19,7 +19,8 @@ export const load: PageServerLoad = async ({ fetch, params }) => {
sex: '',
personalIdNumber: '',
schoolName: '',
healthInsurance: ''
healthInsurance: '',
grades: []
},
parents: []
};

View file

@ -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) => {