Merge pull request #109 from EETagent/feature_edit_details

(frontend) SSR Edit Details
This commit is contained in:
Vojtěch Jungmann 2022-12-26 17:41:58 +01:00 committed by GitHub
commit 5c10d79be4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 397 additions and 235 deletions

View file

@ -7,7 +7,8 @@
import StatusNotificationBig from './StatusNotificationBig.svelte'; import StatusNotificationBig from './StatusNotificationBig.svelte';
import InfoButton from './InfoButton.svelte'; import InfoButton from './InfoButton.svelte';
import { candidateData } from '$lib/stores/candidate'; import { candidateData } from '$lib/stores/candidate';
import tippy from 'tippy.js'; import tippy, {sticky} from 'tippy.js';
import { goto } from '$app/navigation';
export let title: string; export let title: string;
export let status: Status; export let status: Status;
@ -58,6 +59,10 @@
console.log(e); console.log(e);
} }
}; };
const editDetails = async () => {
goto('/register?edit=true')
}
</script> </script>
<div class="card flex flex-col"> <div class="card flex flex-col">
@ -86,52 +91,65 @@
</div> </div>
</div> </div>
{#if showDetails} {#if showDetails}
<div class="overflow-scroll"> <div class="overflow-scroll flex justify-between">
<div <div>
<div
use:tippy={{
content: '<span>Vámi vyplněné osobní údaje</span>',
allowHTML: true,
placement: 'top',
showOnCreate: false,
delay: 0
}}
class="mt-4 flex flex-col justify-between leading-10"
>
<span>Adresa: <span class="font-bold">{$candidateData.candidate.address}</span></span>
<span
>Datum narození: <span class="font-bold">{$candidateData.candidate.birthdate}</span
></span
>
<span
>Místo narození: <span class="font-bold">{$candidateData.candidate.birthplace}</span
></span
>
<span
>Rodné číslo: <span class="font-bold">{$candidateData.candidate.personalIdNumber}</span
></span
>
<span>Telefon: <span class="font-bold">{$candidateData.candidate.telephone}</span></span>
</div>
<div
use:tippy={{
content: '<span>Vámi vyplněné osobní údaje</span>',
allowHTML: true,
placement: 'top',
showOnCreate: false,
delay: 0
}}
class="mt-4 flex flex-col leading-10"
>
{#each $candidateData.parents as parent}
<div class="flex flex-col">
<span class="text-sspsBlue text-xl font-bold"
>{parent.name + ' ' + parent.surname}</span
>
<span>Email: <span class="font-bold">{parent.email}</span></span>
<span>Telefon: <span class="font-bold">{parent.telephone}</span></span>
</div>
{/each}
</div>
</div>
<span
use:tippy={{ use:tippy={{
content: '<span>Vámi vyplněné osobní údaje</span>', content: 'Upravit osobní údaje',
allowHTML: true,
placement: 'top', placement: 'top',
showOnCreate: false, showOnCreate: false,
delay: 0 sticky: true,
plugins: [sticky]
}} }}
class="mt-4 flex flex-col justify-between leading-10" on:click={(_) => editDetails()} on:keydown={(_) => editDetails()} class="mt-4 hover:cursor-pointer">
> <svg class="w-10 h-10 stroke-sspsBlue" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path></svg>
<span>Adresa: <span class="font-bold">{$candidateData.candidate.address}</span></span> </span>
<span
>Datum narození: <span class="font-bold">{$candidateData.candidate.birthdate}</span
></span
>
<span
>Místo narození: <span class="font-bold">{$candidateData.candidate.birthplace}</span
></span
>
<span
>Rodné číslo: <span class="font-bold">{$candidateData.candidate.personalIdNumber}</span
></span
>
<span>Telefon: <span class="font-bold">{$candidateData.candidate.telephone}</span></span>
</div>
<div
use:tippy={{
content: '<span>Vámi vyplněné osobní údaje</span>',
allowHTML: true,
placement: 'top',
showOnCreate: false,
delay: 0
}}
class="mt-4 flex flex-col leading-10"
>
{#each $candidateData.parents as parent}
<div class="flex flex-col">
<span class="text-sspsBlue text-xl font-bold"
>{parent.name + ' ' + parent.surname}</span
>
<span>Email: <span class="font-bold">{parent.email}</span></span>
<span>Telefon: <span class="font-bold">{parent.telephone}</span></span>
</div>
{/each}
</div>
</div> </div>
{/if} {/if}
</div> </div>

View file

@ -164,7 +164,7 @@
class="drag group" class="drag group"
on:mouseenter={dashAnimationStart} on:mouseenter={dashAnimationStart}
on:mouseleave={dashAnimationStop} on:mouseleave={dashAnimationStop}
style={`background-image: url("data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' rx='9' ry='9' stroke-opacity='50%' stroke='%23406280' stroke-width='4' stroke-dasharray='10' stroke-dashoffset='${dashAnimationProgress}' stroke-linecap='square'/%3e%3c/svg%3e");`} style={`background-image: url("data:image/svg+xml,%3csvg width='100%25' height='100%25' fill='none' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' rx='9' ry='9' stroke-opacity='50%' stroke='%23406280' stroke-width='4' stroke-dasharray='10' stroke-dashoffset='${dashAnimationProgress}' stroke-linecap='square'/%3e%3c/svg%3e");`}
> >
{#if error} {#if error}
<span class="font-semibold text-red-600">{error}</span> <span class="font-semibold text-red-600">{error}</span>

View file

@ -28,11 +28,11 @@
<style lang="postcss"> <style lang="postcss">
.bgImage { .bgImage {
@apply -z-20; @apply -z-20;
@apply min-w-screen absolute min-h-screen md:min-w-[50vw]; @apply min-w-screen absolute min-h-full md:min-w-[50vw];
@apply bg-cover bg-no-repeat; @apply bg-cover bg-no-repeat;
} }
.bgOverlay { .bgOverlay {
@apply min-w-screen absolute -z-10 min-h-screen md:min-w-[50vw]; @apply min-w-screen absolute -z-10 min-h-full md:min-w-[50vw];
background: linear-gradient(45deg, rgba(18, 48, 75, 1), rgba(119, 173, 224, 0.443)); background: linear-gradient(45deg, rgba(18, 48, 75, 1), rgba(119, 173, 224, 0.443));
@apply bg-cover; @apply bg-cover;
} }

View file

@ -2,23 +2,23 @@ import { writable } from 'svelte/store';
export interface CandidateData { export interface CandidateData {
candidate: { candidate: {
name?: string; name: string;
surname?: string; surname: string;
birthplace?: string; birthplace: string;
birthdate?: string; birthdate: string;
address?: string; address: string;
telephone?: string; telephone: string;
citizenship?: string; citizenship: string;
email?: string; email: string;
sex?: string; sex: string;
study?: string; study: string;
personalIdNumber?: string; personalIdNumber: string;
}; };
parents: Array<{ parents: Array<{
name?: string; name: string;
surname?: string; surname: string;
telephone?: string; telephone: string;
email?: string; email: string;
}>; }>;
} }
@ -44,6 +44,18 @@ export interface CreateCandidateLogin extends CreateCandidate {
} }
export const candidateData = writable<CandidateData>({ export const candidateData = writable<CandidateData>({
candidate: {}, candidate: {
name: '',
surname: '',
birthplace: '',
birthdate: '',
address: '',
telephone: '',
citizenship: '',
email: '',
sex: '',
study: '',
personalIdNumber: ''
},
parents: [] parents: []
}); });

View file

@ -7,7 +7,19 @@ export const load: PageServerLoad = async ({ fetch, params }) => {
const codeNumber = Number(code); const codeNumber = Number(code);
let candidateData: CandidateData = { let candidateData: CandidateData = {
candidate: {}, candidate: {
name: '',
surname: '',
birthplace: '',
birthdate: '',
address: '',
telephone: '',
citizenship: '',
email: '',
sex: '',
study: '',
personalIdNumber: ''
},
parents: [] parents: []
}; };
try { try {

View file

@ -81,7 +81,7 @@
{/each} {/each}
</div> </div>
<div class="dashboard dashboardMobile"> <div class="dashboard dashboardMobile">
<div class="name my-10 mx-auto w-[90%]"> <div class="name md:my-10 mx-auto w-[90%]">
<DashboardInfoCard <DashboardInfoCard
status={getUploadStatus($submissionProgress.status)} status={getUploadStatus($submissionProgress.status)}
title={$candidateData.candidate.name + ' ' + $candidateData.candidate.surname ?? ''} title={$candidateData.candidate.name + ' ' + $candidateData.candidate.surname ?? ''}

View file

@ -0,0 +1,14 @@
import { apiFetchDetails, apiFetchSubmissionProgress } from '$lib/@api/candidate';
import type { CandidateData } from '$lib/stores/candidate';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ fetch }) => {
const details: CandidateData | undefined = await apiFetchDetails(fetch).catch((e) => {
console.error(e);
return undefined;
});
return {
candidate: details,
};
};

View file

@ -13,15 +13,28 @@
import NameField from '$lib/components/textfield/NameField.svelte'; import NameField from '$lib/components/textfield/NameField.svelte';
import TelephoneField from '$lib/components/textfield/TelephoneField.svelte'; import TelephoneField from '$lib/components/textfield/TelephoneField.svelte';
import TextField from '$lib/components/textfield/TextField.svelte'; import TextField from '$lib/components/textfield/TextField.svelte';
import type { CandidateData } from '$lib/stores/candidate'; import type { PageData } from './$types';
import { createForm } from 'svelte-forms-lib'; import { createForm } from 'svelte-forms-lib';
import type { Writable } from 'svelte/store'; import type { Writable } from 'svelte/store';
import * as yup from 'yup'; import * as yup from 'yup';
import type { CandidateData } from '$lib/stores/candidate';
const pageCount = 5; const pageCount = 5;
let pageIndex = 0; let pageIndex = 0;
let pagesFilled = [false, false, false, false, false]; let pagesFilled = [false, false, false, false, false];
let pageTexts = [
'Zpracování osobních údajů',
'Registrace',
'Něco o Vás',
'Kontakt na zákonného zástupce',
'Kontakt na druhého zákonného zástupce',
'Poslední krok'
];
export let data: PageData;
let details = data.candidate;
const formInitialValues = { const formInitialValues = {
gdpr: false, gdpr: false,
@ -58,7 +71,7 @@
gdpr: yup.boolean().oneOf([true]), gdpr: yup.boolean().oneOf([true]),
candidate: yup.object().shape({ candidate: yup.object().shape({
name: yup.string().required(), name: yup.string().required(),
surname: yup.string(), surname: yup.string().required(),
email: yup.string().email().required(), email: yup.string().email().required(),
telephone: yup telephone: yup
.string() .string()
@ -68,7 +81,7 @@
birthdate: yup birthdate: yup
.string() .string()
.required() .required()
.matches(/^([0-3]?[0-9])\.([1-9]|1[0-2])\.[0-9]{4}$/), .matches(/^([0-3]?[0-9])\.(0?[1-9]|1[0-2])\.[0-9]{4}$/),
sex: yup.string(), sex: yup.string(),
address: yup.string().required(), address: yup.string().required(),
citizenship: yup.string().required(), citizenship: yup.string().required(),
@ -93,13 +106,13 @@
.string() .string()
.email() .email()
.test((_val, context) => { .test((_val, context) => {
if (context.path.includes('parents[1]')) { if (context.path.includes('parents[1]') && _val === '') {
return true; return true;
} }
return _val !== ''; return _val !== '';
}), }),
telephone: yup.string().test((_val, context) => { telephone: yup.string().test((_val, context) => {
if (context.path.includes('parents[1]')) { if (context.path.includes('parents[1]') && _val === '') {
return true; return true;
} }
return _val?.match(/^\+\d{1,3} \d{3} \d{3} \d{3}$/) !== null; return _val?.match(/^\+\d{1,3} \d{3} \d{3} \d{3}$/) !== null;
@ -108,45 +121,6 @@
) )
}); });
const { form, errors, handleSubmit, handleChange } = createForm({
initialValues: formInitialValues,
validationSchema: formValidationSchema,
onSubmit: async (values: CandidateData) => {
console.log('page count: ' + pageIndex);
console.log(values.candidate);
console.log(values.parents);
console.log(values);
if (pageIndex === pageCount) {
// clone values to oldValues
let oldValues = JSON.parse(JSON.stringify(values));
try {
console.log('submit');
// @ts-ignore // love javascript
delete values.undefined;
// convert birthdate from dd.mm.yyyy to yyyy-mm-dd
let birthdate_formttted = values.candidate
.birthdate!.split('.')
.map((x) => x.padStart(2, '0'))
.reverse()
.join('-');
values.candidate.birthdate = birthdate_formttted;
values.parents.filter(
(x) => x.name !== '' && x.surname !== '' && x.email !== '' && x.telephone !== ''
);
await apiFillDetails(values);
goto('/dashboard');
} catch (e) {
values = oldValues;
console.error('error while submitting data: ' + e);
}
}
}
});
type FormErrorType = { type FormErrorType = {
[K in keyof typeof formInitialValues]: typeof formInitialValues[K] extends Record< [K in keyof typeof formInitialValues]: typeof formInitialValues[K] extends Record<
string, string,
@ -163,8 +137,100 @@
// TODO: https://github.com/tjinauyeung/svelte-forms-lib/issues/171!! (Zatím tenhle mega typ) // TODO: https://github.com/tjinauyeung/svelte-forms-lib/issues/171!! (Zatím tenhle mega typ)
$: typedErrors = errors as unknown as Writable<FormErrorType>; $: typedErrors = errors as unknown as Writable<FormErrorType>;
const isPageInvalid = (): boolean => { // TODO: validate on admin dashboard, move somewhere
switch (pageIndex) { // TODO: nefunguje pro lidi nar. pred 1.1.1954 :D
const isPersonalIdNumberValid = (personalIdNumber: string): boolean => {
const idFmt = personalIdNumber
.split('/')
.join('');
const lastDigitCheck = Number(idFmt.slice(0, 9)) % 11 === Number(idFmt.at(-1)) ||
Number(idFmt.slice(0, 9)) % 11 === 10; // an edge case that could occur
const divisibleBy11 = Number(idFmt) % 11 === 0;
if (lastDigitCheck && divisibleBy11) {
return true;
} else {
return false;
}
}
const isPersonalIdNumberWithBirthdateValid = (personalIdNumber: string, birthdate: string): boolean => {
const dateFmt = birthdate
.split('.')
.map((x) => x.padStart(2, '0'))
.reverse()
.join('')
.slice(2);
const idFmt = personalIdNumber
.split('/')
.join('');
const divisionValid = isPersonalIdNumberValid(personalIdNumber);
const idMonth = Number(idFmt.slice(2, 4));
const dateMonth = Number(dateFmt.slice(2, 4));
const monthValid = idMonth === dateMonth || idMonth === dateMonth + 50 ||
idMonth === dateMonth + 20 || idMonth === dateMonth + 70;
if (
idFmt.slice(0, 2) === dateFmt.slice(0, 2) &&
monthValid &&
idFmt.slice(4, 6) === dateFmt.slice(4, 6) &&
divisionValid
) {
return true;
} else {
return false;
}
};
const onSubmit = async (values: CandidateData) => {
if (pageIndex === pageCount) {
// clone values to oldValues
let oldValues = JSON.parse(JSON.stringify(values));
try {
if (values.candidate.citizenship === 'Česká republika') {
if (!isPersonalIdNumberWithBirthdateValid(values.candidate.personalIdNumber, values.candidate.birthdate)) {
alert('Rodné číslo neodpovídá oficiální specifikaci či datumu narození'); // TODO: alerts
throw new Error('Rodné číslo neodpovídá datumu narození');
}
}
// @ts-ignore // love javascript
delete values.undefined;
// convert birthdate from dd.mm.yyyy to yyyy-mm-dd
let birthdate_formttted = values.candidate
.birthdate!.split('.')
.map((x) => x.padStart(2, '0'))
.reverse()
.join('-');
values.candidate.birthdate = birthdate_formttted;
values.parents = values.parents.filter(
(x) => x.name !== '' && x.surname !== '' && x.email !== '' && x.telephone !== ''
);
await apiFillDetails(values);
goto('/dashboard');
} catch (e) {
values = oldValues;
console.error('error while submitting data: ' + e);
}
}
}
const { form, errors, handleSubmit, handleChange } = createForm({
initialValues: formInitialValues,
validationSchema: formValidationSchema,
onSubmit: async (values: CandidateData) => onSubmit(values)
});
const isPageInvalid = (index: number): boolean => {
switch (index) {
case 0: case 0:
if ($typedErrors['gdpr']) { if ($typedErrors['gdpr']) {
return true; return true;
@ -182,9 +248,9 @@
case 2: case 2:
if ( if (
/* $typedErrors.birthdurname || */ $typedErrors['candidate']['birthplace'] || $typedErrors['candidate']['birthplace'] ||
$typedErrors['candidate']['birthdate'] || $typedErrors['candidate']['birthdate'] ||
$typedErrors['candidate']['address'] /* || $typedErrors.sex */ $typedErrors['candidate']['address']
) { ) {
return true; return true;
} }
@ -213,8 +279,7 @@
if ( if (
$typedErrors['candidate']['citizenship'] || $typedErrors['candidate']['citizenship'] ||
$typedErrors['candidate']['personalIdNumber'] || $typedErrors['candidate']['personalIdNumber'] ||
$typedErrors['candidate']['study'] //|| $typedErrors['candidate']['study']
// $typedErrors.applicationId
) { ) {
return true; return true;
} }
@ -224,21 +289,62 @@
} }
return false; return false;
}; };
const formatTelephone = (telephone: string) => {
return '+' + telephone
.match(/[0-9]{1,3}/g)!
.join(' ');
}
if (details !== undefined) {
details.candidate.birthdate = details.candidate.birthdate
.split('-')
.reverse()
.join('.');
details.candidate.telephone = formatTelephone(details.candidate.telephone);
details.parents.map((x) => x.telephone = x.telephone != '' ? formatTelephone(x.telephone) : '');
form.set({
gdpr: true,
candidate: {
...details.candidate
},
parents: [
{
...details.parents[0]
},
{
...details.parents[1] ?? {
name: '',
surname: '',
email: '',
telephone: ''
}
}
]
});
pageIndex = 1; // skip gdpr page
pageTexts[1] = 'Úprava osobních údajů'
}
</script> </script>
<SplitLayout> <SplitLayout>
<div class="form"> <div class="form relative">
<div class="h-24 w-24 md:h-auto md:w-auto"> <div class="overflow-scroll h-[65%] md:h-auto absolute bottom-3/12 flex flex-col w-full">
<SchoolBadge /> <div class="h-32 w-32 <md:hidden self-center mb-4">
</div> <SchoolBadge />
</div>
<form on:submit={handleSubmit} id="triggerForm" class="invisible hidden"></form>
{#if pageIndex === 0} {#if pageIndex === 0}
<form on:submit={handleSubmit}> <form on:submit={handleSubmit}>
<h1 class="text-sspsBlue mt-8 text-4xl font-semibold">Váš souhlas</h1> <h1 class="title mt-8">{pageTexts[0]}</h1>
<p class="text-sspsGray mt-8 block text-center font-light"> <p class="description mt-8 block text-center">
V rámci portálu pro přijímací řízení zpracováváme mnoho osobních údajů. Proto je nutný Váš V rámci portálu pro přijímací řízení zpracováváme mnoho osobních údajů. Proto je nutný Váš
souhlas s jejich zpracováním. souhlas s jejich zpracováním. O bezpečnosti zpracování Vašich osobních údajů si můžete přečíst
<a href="/bezpecnost" class="text-sspsBlue underline"> zde</a>.
</p> </p>
<div class="mt-8 w-full"> <div class="field">
<GdprCheckBox <GdprCheckBox
on:change={handleChange} on:change={handleChange}
bind:value={$form.gdpr} bind:value={$form.gdpr}
@ -248,13 +354,13 @@
</form> </form>
{:else if pageIndex === 1} {:else if pageIndex === 1}
<form on:submit={handleSubmit}> <form on:submit={handleSubmit}>
<h1 class="text-sspsBlue mt-8 text-4xl font-semibold">Registrace</h1> <h1 class="title mt-8">{pageTexts[1]}</h1>
<p class="text-sspsGray mt-8 block text-center font-light"> <p class="description mt-8 block text-center">
V rámci usnadnění přijímacího řízení jsme připravili online formulář, který vám pomůže s V rámci usnadnění přijímacího řízení jsme připravili online formulář, který Vám pomůže s
vyplněním potřebných údajů. vyplněním potřebných údajů.
</p> </p>
<div class="flex w-full items-center justify-center md:flex-col"> <div class="flex flex-col">
<span class="mt-8 w-full"> <span class="field">
<NameField <NameField
error={$typedErrors['candidate']['name']} error={$typedErrors['candidate']['name']}
on:change={handleChange} on:change={handleChange}
@ -263,7 +369,7 @@
placeholder="Jméno a příjmení" placeholder="Jméno a příjmení"
/> />
</span> </span>
<span class="mt-8 ml-2 w-full md:ml-0"> <span class="field">
<EmailField <EmailField
error={$typedErrors['candidate']['email']} error={$typedErrors['candidate']['email']}
on:change={handleChange} on:change={handleChange}
@ -271,24 +377,24 @@
placeholder="E-mail" placeholder="E-mail"
/> />
</span> </span>
</div> <span class="field">
<div class="mt-8 w-full"> <TelephoneField
<TelephoneField error={$typedErrors['candidate']['telephone']}
error={$typedErrors['candidate']['telephone']} on:change={handleChange}
on:change={handleChange} bind:value={$form.candidate.telephone}
bind:value={$form.candidate.telephone} placeholder="Telefon"
placeholder="Telefon" />
/> </span>
</div> </div>
</form> </form>
{:else if pageIndex === 2} {:else if pageIndex === 2}
<h1 class="text-sspsBlue mt-8 text-4xl font-semibold">Něco o tobě</h1> <h1 class="title mt-8">{pageTexts[2]}</h1>
<p class="text-sspsGray mt-8 block text-center font-light"> <p class="description mt-8 block text-center">
Pro registraci je potřeba vyplnit několik údajů o tobě. Tyto údaje budou použity pro Pro registraci je potřeba vyplnit několik údajů o Vás. Tyto údaje budou použity pro
přijímací řízení. Všechny údaje jsou důležité a bez nich se registrace nezdaří. přijímací řízení. Všechny údaje jsou důležité a bez nich se registrace nezdaří.
</p> </p>
<div class="flex w-full flex-row md:flex-col"> <div class="flex w-full flex-col">
<span class="mt-8 w-full"> <span class="field">
<TextField <TextField
error={$typedErrors['candidate']['address']} error={$typedErrors['candidate']['address']}
on:change={handleChange} on:change={handleChange}
@ -298,7 +404,7 @@
helperText="Uveďte ulici, č.p., město, PSČ" helperText="Uveďte ulici, č.p., město, PSČ"
/> />
</span> </span>
<span class="mt-8 ml-2 w-full md:ml-0"> <span class="field">
<TextField <TextField
error={$typedErrors['candidate']['birthplace']} error={$typedErrors['candidate']['birthplace']}
on:change={handleChange} on:change={handleChange}
@ -315,7 +421,7 @@
</span> </span>
</div> </div>
<div class="mt-8 flex w-full items-center"> <div class="field flex items-center">
<TextField <TextField
error={$typedErrors['candidate']['birthdate']} error={$typedErrors['candidate']['birthdate']}
on:change={handleChange} on:change={handleChange}
@ -335,80 +441,76 @@
</div> </div>
</div> </div>
{:else if pageIndex === 3} {:else if pageIndex === 3}
<h1 class="text-sspsBlue mt-8 text-4xl font-semibold">Už jen kousek!</h1> <h1 class="title mt-8">{pageTexts[3]}</h1>
<p class="text-sspsGray mt-8 block text-center font-light"> <p class="description mt-8 block text-center">
Sběr dat o zákonném zástupci je klíčový pro získání důležitých kontaktů a informací. Sběr dat o zákonném zástupci je klíčový pro získání důležitých kontaktů a informací.
</p> </p>
<div class="flex w-full flex-col"> <div class="flex w-full flex-col">
<span class="mt-8 w-full"> <span class="field">
<NameField <NameField
error={$typedErrors['parents'][0]['name'] || $typedErrors['parents'][0]['surname']} error={$typedErrors['parents'][0]['name'] || $typedErrors['parents'][0]['surname']}
on:change={handleChange} on:change={handleChange}
bind:valueName={$form.parents[0].name} bind:valueName={$form.parents[0].name}
bind:valueSurname={$form.parents[0].surname} bind:valueSurname={$form.parents[0].surname}
placeholder="Jméno a příjmení zákonného zástupce" placeholder="Jméno a příjmení zákonného zástupce"
/> />
</span> </span>
<div class="mt-8 flex flex-row items-center md:flex-col"> <span class="field">
<span class="w-full"> <EmailField
<EmailField error={$typedErrors['parents'][0]['email']}
error={$typedErrors['parents'][0]['email']} on:change={handleChange}
on:change={handleChange} bind:value={$form.parents[0].email}
bind:value={$form.parents[0].email} placeholder="E-mail zákonného zástupce"
placeholder="E-mail zákonného zástupce" />
/> </span>
</span> <span class="field">
<span class="ml-2 w-full md:ml-0 md:mt-8"> <TelephoneField
<TelephoneField error={$typedErrors['parents'][0]['telephone']}
error={$typedErrors['parents'][0]['telephone']} on:change={handleChange}
on:change={handleChange} bind:value={$form.parents[0].telephone}
bind:value={$form.parents[0].telephone} placeholder="Telefon zákonného zástupce"
placeholder="Telefon zákonného zástupce" />
/> </span>
</span> </div>
</div>
</div>
{:else if pageIndex === 4} {:else if pageIndex === 4}
<h1 class="text-sspsBlue mt-8 text-4xl font-semibold">Dobrovolné!</h1> <h1 class="title mt-8">{pageTexts[4]}</h1>
<p class="text-sspsGray mt-8 block text-center font-light"> <p class="description mt-8 block text-center">
V případě, že máte druhého zákonného zástupce (např. otec a matka), můžete jej zde zadat. Zde můžete zadat údaje o druhém zákonném zástupci. Škole tím umožníte lépe komunikovat.
</p> </p>
<div class="flex w-full flex-col"> <div class="flex w-full flex-col">
<span class="mt-8 w-full"> <span class="field">
<NameField <NameField
error={$typedErrors['parents'][1]['name'] || $typedErrors['parents'][1]['surname']} error={$typedErrors['parents'][1]['name'] || $typedErrors['parents'][1]['surname']}
on:change={handleChange} on:change={handleChange}
bind:valueName={$form.parents[1].name} bind:valueName={$form.parents[1].name}
bind:valueSurname={$form.parents[1].surname} bind:valueSurname={$form.parents[1].surname}
placeholder="Jméno a příjmení zákonného zástupce" placeholder="Jméno a příjmení zákonného zástupce (nepovinné)"
/> />
</span> </span>
<div class="mt-8 flex flex-row items-center md:flex-col"> <span class="field">
<span class="w-full"> <EmailField
<EmailField error={$typedErrors['parents'][1]['email']}
error={$typedErrors['parents'][1]['email']} on:change={handleChange}
on:change={handleChange} bind:value={$form.parents[1].email}
bind:value={$form.parents[1].email} placeholder="E-mail zákonného zástupce (nepovinné)"
placeholder="E-mail zákonného zástupce" />
/> </span>
<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é)"
/>
</span> </span>
<span class="ml-2 w-full md:ml-0 md:mt-8">
<TelephoneField
error={$typedErrors['parents'][1]['telephone']}
on:change={handleChange}
bind:value={$form.parents[1].telephone}
placeholder="Telefon zákonného zástupce"
/>
</span>
</div>
</div> </div>
{:else if pageIndex === 5} {:else if pageIndex === 5}
<h1 class="text-sspsBlue mt-8 text-4xl font-semibold">Poslední krok</h1> <h1 class="title mt-8">{pageTexts[5]}</h1>
<p class="text-sspsGray mt-8 block text-center font-light"> <p class="description mt-8 block text-center">
Zadejte prosím své občanství, rodné číslo a obor na který se hlásíte. Zadejte prosím své občanství, rodné číslo a obor na který se hlásíte.
</p> </p>
<div class="flex w-full flex-row md:flex-col"> <div class="flex w-full flex-row md:flex-col">
<span class="mt-8 w-full"> <span class="field">
<SelectField <SelectField
error={$typedErrors['candidate']['citizenship']} error={$typedErrors['candidate']['citizenship']}
on:change={handleChange} on:change={handleChange}
@ -417,11 +519,11 @@
options={['Česká republika', 'Slovenská republika', 'Ukrajina', 'Jiné']} options={['Česká republika', 'Slovenská republika', 'Ukrajina', 'Jiné']}
/> />
</span> </span>
<span class="mt-8 ml-2 w-full md:ml-0"> <span class="field ml-2 md:ml-0">
<TextField on:change={handleChange} type="text" placeholder="Evidenční číslo přihlášky" /> <TextField on:change={handleChange} type="text" placeholder="Evidenční číslo přihlášky" />
</span> </span>
</div> </div>
<div class="mt-8 flex w-full items-center justify-center"> <div class="field flex items-center justify-center">
{#if $form.candidate.citizenship === 'Česká republika' || !$form.candidate.citizenship} {#if $form.candidate.citizenship === 'Česká republika' || !$form.candidate.citizenship}
<IdField <IdField
error={$typedErrors['candidate']['personalIdNumber']} error={$typedErrors['candidate']['personalIdNumber']}
@ -448,53 +550,51 @@
</span> </span>
</div> </div>
{/if} {/if}
<div class="mt-8 w-full">
<Submit
on:click={async (e) => {
await handleSubmit(e);
console.log('clicked ' + isPageInvalid());
if (isPageInvalid()) return;
if (pageIndex === pageCount) {
} else {
pagesFilled[pageIndex] = true;
pageIndex++;
}
// @ts-ignore
errors.set(formInitialValues);
}}
value={pageIndex === pageCount ? 'Odeslat' : 'Pokračovat'}
/>
</div> </div>
<div class="controls w-full absolute bottom-1/12">
<div class="mt-8 flex flex-row justify-center"> <div class="field">
{#each Array(pageCount + 1) as _, i} <Submit
<button
class:dotActive={i === pageIndex}
on:click={async (e) => { on:click={async (e) => {
const progress = pagesFilled.slice(0, i).every((item) => item === true); await handleSubmit(e);
if (progress) { if (isPageInvalid(pageIndex)) return;
if (i > pageIndex) { if (pageIndex === pageCount) {
// if next page is clicked, validate current page } else {
await handleSubmit(e); pagesFilled[pageIndex] = true;
if (isPageInvalid()) return; pageIndex++;
pagesFilled[i] = true; }
pageIndex++; // @ts-ignore
} else { errors.set(formInitialValues);
}}
value={pageIndex === pageCount ? 'Odeslat' : 'Pokračovat'}
/>
</div>
<div class="mt-4 md:mt-8 flex flex-row justify-center">
{#each Array(pageCount + 1) as _, i}
<button
class:dotActive={i === pageIndex}
on:click={async (e) => {
pageIndex -= pageIndex === pageCount ? 1 : 0;
await handleSubmit(e);
pagesFilled = pagesFilled.map((_, i) => !isPageInvalid(i));
const progress = pagesFilled.slice(0, i).every((item) => item === true);
if (progress) {
pageIndex = i; pageIndex = i;
} }
// @ts-ignore }}
errors.set(formInitialValues); class="dot"
} />
}} {/each}
class="dot" </div>
/>
{/each}
</div> </div>
</div> </div>
</SplitLayout> </SplitLayout>
<style lang="postcss"> <style lang="postcss">
.field {
@apply mt-4 md:mt-8 w-full;
}
.form { .form {
@apply flex flex-col; @apply flex flex-col;
@apply mx-auto h-full w-[90%]; @apply mx-auto h-full w-[90%];
@ -513,4 +613,10 @@
.dotActive { .dotActive {
@apply bg-sspsBlue; @apply bg-sspsBlue;
} }
.description {
@apply text-gray-500;
}
.title {
@apply text-sspsBlue text-4xl font-semibold text-center;
}
</style> </style>

View file

@ -130,7 +130,7 @@
input { input {
@apply text-sspsBlue text-center font-semibold; @apply text-sspsBlue text-center font-semibold;
@apply transition-colors duration-300; @apply transition-colors duration-300;
@apply focus:border-sspsBlue hover:border-sspsBlue rounded-xl border border-2 bg-[#f8fafb] p-3 caret-transparent shadow-lg outline-none; @apply focus:border-sspsBlue hover:border-sspsBlue rounded-xl border border-2 bg-[#f8fafb] p-3 md:caret-transparent shadow-lg outline-none;
} }
.separater { .separater {
@apply bg-sspsBlue mr-2 hidden h-2 w-8 md:block; @apply bg-sspsBlue mr-2 hidden h-2 w-8 md:block;