@public-ui/sample-react 1.7.1 → 1.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintignore +2 -1
- package/dist/1474.js +1 -1
- package/dist/1531.js +2 -0
- package/dist/183.js +1 -1
- package/dist/1932.js +2 -0
- package/dist/2337.js +1 -1
- package/dist/2412.js +1 -1
- package/dist/3059.js +2 -0
- package/dist/3303.js +1 -1
- package/dist/3325.js +1 -1
- package/dist/3459.js +1 -1
- package/dist/3537.js +1 -1
- package/dist/4021.js +1 -1
- package/dist/4291.js +1 -1
- package/dist/4323.js +1 -1
- package/dist/4355.js +1 -1
- package/dist/4477.js +1 -1
- package/dist/4564.js +1 -1
- package/dist/4891.js +1 -1
- package/dist/5183.js +1 -1
- package/dist/5369.js +1 -1
- package/dist/5390.js +1 -1
- package/dist/540.js +1 -1
- package/dist/5866.js +1 -1
- package/dist/6012.js +1 -1
- package/dist/6068.js +1 -1
- package/dist/6210.js +1 -1
- package/dist/6320.js +1 -1
- package/dist/6558.js +1 -1
- package/dist/6655.js +1 -1
- package/dist/6908.js +1 -1
- package/dist/7029.js +1 -1
- package/dist/7255.js +1 -1
- package/dist/7447.js +1 -1
- package/dist/7715.js +1 -1
- package/dist/7722.js +1 -1
- package/dist/7801.js +1 -1
- package/dist/7955.js +1 -1
- package/dist/7995.js +1 -1
- package/dist/8065.js +1 -1
- package/dist/8099.js +1 -1
- package/dist/8111.js +1 -1
- package/dist/8255.js +1 -1
- package/dist/8291.js +1 -1
- package/dist/8709.js +1 -1
- package/dist/8761.js +1 -1
- package/dist/9106.js +1 -1
- package/dist/9118.js +2 -0
- package/dist/9734.js +1 -1
- package/dist/9747.js +1 -1
- package/dist/9792.js +1 -1
- package/dist/9963.js +1 -1
- package/dist/main.css +1 -1
- package/dist/main.js +8653 -39
- package/dist/main.js.LICENSE.txt +9 -0
- package/package.json +7 -5
- package/src/components/FormWrap.tsx +9 -0
- package/src/components/modal/basic.tsx +1 -1
- package/src/scenarios/appointment-form/AppointmentForm.tsx +127 -0
- package/src/scenarios/appointment-form/AvailableAppointmentsForm.tsx +118 -0
- package/src/scenarios/appointment-form/DistrictForm.tsx +80 -0
- package/src/scenarios/appointment-form/ErrorList.tsx +35 -0
- package/src/scenarios/appointment-form/PersonalInformationForm.tsx +157 -0
- package/src/scenarios/appointment-form/Summary.tsx +55 -0
- package/src/scenarios/appointment-form/appointmentService.ts +37 -0
- package/src/scenarios/complex-form/common/form/component.tsx +25 -0
- package/src/scenarios/complex-form/common/form/types.ts +13 -0
- package/src/scenarios/complex-form/component.tsx +163 -0
- package/src/scenarios/complex-form/kopfdaten/component.tsx +50 -0
- package/src/scenarios/complex-form/location/component.tsx +16 -0
- package/src/scenarios/complex-form/location/location.form.ts +22 -0
- package/src/scenarios/complex-form/schedule/component.tsx +16 -0
- package/src/scenarios/complex-form/schedule/schedule.form.ts +34 -0
- package/src/scenarios/routes.ts +10 -0
- package/src/shares/routes.ts +8 -6
- package/dist/3153.js +0 -2
- package/dist/4436.js +0 -2
- package/dist/4864.js +0 -2
- package/dist/5293.js +0 -2
- /package/dist/{3153.js.LICENSE.txt → 1531.js.LICENSE.txt} +0 -0
- /package/dist/{4436.js.LICENSE.txt → 1932.js.LICENSE.txt} +0 -0
- /package/dist/{4864.js.LICENSE.txt → 3059.js.LICENSE.txt} +0 -0
- /package/dist/{5293.js.LICENSE.txt → 9118.js.LICENSE.txt} +0 -0
package/dist/main.js.LICENSE.txt
CHANGED
|
@@ -55,3 +55,12 @@
|
|
|
55
55
|
*
|
|
56
56
|
* @license MIT
|
|
57
57
|
*/
|
|
58
|
+
|
|
59
|
+
/** @license React v16.13.1
|
|
60
|
+
* react-is.production.min.js
|
|
61
|
+
*
|
|
62
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
63
|
+
*
|
|
64
|
+
* This source code is licensed under the MIT license found in the
|
|
65
|
+
* LICENSE file in the root directory of this source tree.
|
|
66
|
+
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@public-ui/sample-react",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.2",
|
|
4
4
|
"description": "This app contains samples for the KoliBri/Public UI",
|
|
5
5
|
"license": "EUPL-1.2",
|
|
6
6
|
"dependencies": {
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
"@leanup/stack-react": "1.3.48",
|
|
9
9
|
"@leanup/stack-webpack": "1.3.48",
|
|
10
10
|
"@public-oss/kolibri-themes": "0.0.3",
|
|
11
|
-
"@public-ui/components": "1.7.
|
|
12
|
-
"@public-ui/react": "1.7.
|
|
13
|
-
"@public-ui/themes": "1.7.
|
|
11
|
+
"@public-ui/components": "1.7.2",
|
|
12
|
+
"@public-ui/react": "1.7.2",
|
|
13
|
+
"@public-ui/themes": "1.7.2",
|
|
14
14
|
"@types/node": "20.8.0",
|
|
15
15
|
"@types/react": "18.2.23",
|
|
16
16
|
"@types/react-dom": "18.2.8",
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"cpy-cli": "5.0.0",
|
|
22
22
|
"eslint-plugin-jsx-a11y": "6.7.1",
|
|
23
23
|
"eslint-plugin-react": "7.33.2",
|
|
24
|
+
"formik": "2.4.5",
|
|
24
25
|
"nightwatch-axe-verbose": "2.2.2",
|
|
25
26
|
"npm-run-all": "4.1.5",
|
|
26
27
|
"react": "18.2.0",
|
|
@@ -30,7 +31,8 @@
|
|
|
30
31
|
"rimraf": "3.0.2",
|
|
31
32
|
"ts-prune": "0.10.3",
|
|
32
33
|
"typescript": "5.2.2",
|
|
33
|
-
"world_countries_lists": "2.8.2"
|
|
34
|
+
"world_countries_lists": "2.8.2",
|
|
35
|
+
"yup": "1.3.1"
|
|
34
36
|
},
|
|
35
37
|
"files": [
|
|
36
38
|
".eslintignore",
|
|
@@ -13,6 +13,15 @@ export const FormWrap: FC<FormWrapProps> = (props) => (
|
|
|
13
13
|
<FocusElement {...props} />
|
|
14
14
|
<div className="flex gap-4">
|
|
15
15
|
<KolButton _label="Submit" _icons="codicon codicon-arrow-right" _type="submit" _variant="primary" />
|
|
16
|
+
<KolButton
|
|
17
|
+
_label="Bunte Icons"
|
|
18
|
+
_icons={{
|
|
19
|
+
left: { icon: 'codicon codicon-heart-filled', style: { color: '#cc006e' } },
|
|
20
|
+
right: { icon: 'codicon codicon-squirrel', style: { color: '#b41b1b' } },
|
|
21
|
+
}}
|
|
22
|
+
_type="submit"
|
|
23
|
+
_variant="secondary"
|
|
24
|
+
/>
|
|
16
25
|
<KolButton _label="Reset" _type="reset" _variant="tertiary" />
|
|
17
26
|
<KolButton _label="Help" _type="button" _variant="ghost" />
|
|
18
27
|
</div>
|
|
@@ -6,7 +6,7 @@ export const ModalBasic: FC = () => {
|
|
|
6
6
|
|
|
7
7
|
return (
|
|
8
8
|
<div>
|
|
9
|
-
<KolModal _ariaLabel="" _width="80%" ref={modalElement}>
|
|
9
|
+
<KolModal _ariaLabel="" _width="80%" ref={modalElement} _on={{ onClose: () => console.log('Modal closed') }}>
|
|
10
10
|
<KolCard _heading="Ich bin ein Modal" style={{ width: '100%' }}>
|
|
11
11
|
<div slot="content">
|
|
12
12
|
<KolButton
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { KolTabs } from '@public-ui/react';
|
|
3
|
+
import { DistrictForm } from './DistrictForm';
|
|
4
|
+
import { Summary } from './Summary';
|
|
5
|
+
import { PersonalInformationForm } from './PersonalInformationForm';
|
|
6
|
+
import { Formik, FormikHelpers } from 'formik';
|
|
7
|
+
import * as Yup from 'yup';
|
|
8
|
+
import { AvailableAppointmentsForm } from './AvailableAppointmentsForm';
|
|
9
|
+
import { Iso8601 } from '@public-ui/components';
|
|
10
|
+
import { checkAppointmentAvailability } from './appointmentService';
|
|
11
|
+
|
|
12
|
+
// export interface FormProps {}
|
|
13
|
+
export interface FormValues {
|
|
14
|
+
district: string;
|
|
15
|
+
date: Iso8601;
|
|
16
|
+
time: Iso8601;
|
|
17
|
+
salutation: string;
|
|
18
|
+
name: string;
|
|
19
|
+
company: string;
|
|
20
|
+
email: string;
|
|
21
|
+
phone: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
enum FormSection {
|
|
25
|
+
DISTRICT,
|
|
26
|
+
AVAILABLE_APPOINTMENTS,
|
|
27
|
+
PERSONAL_INFORMATION,
|
|
28
|
+
SUMMARY,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const formSectionSequence = [FormSection.DISTRICT, FormSection.AVAILABLE_APPOINTMENTS, FormSection.PERSONAL_INFORMATION, FormSection.SUMMARY] as const;
|
|
32
|
+
|
|
33
|
+
const initialValues: FormValues = {
|
|
34
|
+
district: '',
|
|
35
|
+
date: '' as Iso8601,
|
|
36
|
+
time: '' as Iso8601,
|
|
37
|
+
salutation: '',
|
|
38
|
+
name: '',
|
|
39
|
+
company: '',
|
|
40
|
+
email: '',
|
|
41
|
+
phone: '',
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const districtSchema = {
|
|
45
|
+
district: Yup.string().required('Bitte Stadtteil wählen.'),
|
|
46
|
+
};
|
|
47
|
+
const personalInformationSchema = {
|
|
48
|
+
salutation: Yup.string().required('Bitte Anrede auswählen.'),
|
|
49
|
+
name: Yup.string().required('Bitte Name eingeben.'),
|
|
50
|
+
company: Yup.string().when('salutation', {
|
|
51
|
+
is: (salutation: string) => salutation === 'Firma',
|
|
52
|
+
then: (schema) => schema.required('Bitte Firmenname angeben.'),
|
|
53
|
+
}),
|
|
54
|
+
email: Yup.string().required('Bitte E-Mail-Adresse eingeben.'),
|
|
55
|
+
};
|
|
56
|
+
const availableAppointmentsSchema = {
|
|
57
|
+
date: Yup.string().required('Bitte Datum eingeben.'),
|
|
58
|
+
time: Yup.string().when('date', {
|
|
59
|
+
is: (date: string) => Boolean(date), // only validate time when date is already set
|
|
60
|
+
then: (schema) => schema.test('checkTimeAvailability', 'Termin leider nicht mehr verfügbar.', checkAppointmentAvailability),
|
|
61
|
+
}),
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export function AppointmentForm() {
|
|
65
|
+
const [activeFormSection, setActiveFormSection] = useState(FormSection.DISTRICT);
|
|
66
|
+
const [selectedTab, setSelectedTab] = useState(activeFormSection);
|
|
67
|
+
|
|
68
|
+
const validationSchema = Yup.object().shape({
|
|
69
|
+
...(activeFormSection === FormSection.DISTRICT ? districtSchema : {}),
|
|
70
|
+
...(activeFormSection === FormSection.AVAILABLE_APPOINTMENTS ? availableAppointmentsSchema : {}),
|
|
71
|
+
...(activeFormSection === FormSection.PERSONAL_INFORMATION ? personalInformationSchema : {}),
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
setSelectedTab(activeFormSection);
|
|
76
|
+
}, [activeFormSection]);
|
|
77
|
+
|
|
78
|
+
const handleSubmit = async (_values: FormValues, formik: FormikHelpers<FormValues>) => {
|
|
79
|
+
console.log(_values, formik);
|
|
80
|
+
const currentSectionIndex = formSectionSequence.indexOf(activeFormSection);
|
|
81
|
+
const nextSection = formSectionSequence[currentSectionIndex + 1];
|
|
82
|
+
if (nextSection !== undefined) {
|
|
83
|
+
await formik.setTouched({});
|
|
84
|
+
setTimeout(() => setActiveFormSection(nextSection), 1000);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<Formik<FormValues> initialValues={initialValues} validationSchema={validationSchema} onSubmit={handleSubmit}>
|
|
90
|
+
<KolTabs
|
|
91
|
+
_tabs={[
|
|
92
|
+
{
|
|
93
|
+
_label: '1. Einwohnermeldeamt wählen',
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
_label: '2. Freie Termine',
|
|
97
|
+
_disabled: activeFormSection < FormSection.AVAILABLE_APPOINTMENTS,
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
_label: '3. Persönliche Daten',
|
|
101
|
+
_disabled: activeFormSection < FormSection.PERSONAL_INFORMATION,
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
_label: 'Zusammenfassung',
|
|
105
|
+
_disabled: activeFormSection < FormSection.SUMMARY,
|
|
106
|
+
},
|
|
107
|
+
]}
|
|
108
|
+
_label="Formular-Navigation"
|
|
109
|
+
_selected={selectedTab}
|
|
110
|
+
_on={{ onSelect: (_event, selectedTab) => setActiveFormSection(selectedTab) }}
|
|
111
|
+
>
|
|
112
|
+
<div>
|
|
113
|
+
<DistrictForm />
|
|
114
|
+
</div>
|
|
115
|
+
<div>
|
|
116
|
+
<AvailableAppointmentsForm />
|
|
117
|
+
</div>
|
|
118
|
+
<div>
|
|
119
|
+
<PersonalInformationForm />
|
|
120
|
+
</div>
|
|
121
|
+
<div>
|
|
122
|
+
<Summary />
|
|
123
|
+
</div>
|
|
124
|
+
</KolTabs>
|
|
125
|
+
</Formik>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { KolButton, KolForm, KolHeading, KolInputDate, KolInputRadio, KolSpin } from '@public-ui/react';
|
|
3
|
+
import { FormValues } from './AppointmentForm';
|
|
4
|
+
import { ErrorList } from './ErrorList';
|
|
5
|
+
import { Field, FieldProps, useFormikContext } from 'formik';
|
|
6
|
+
import { fetchAvailableTimes } from './appointmentService';
|
|
7
|
+
import { Option } from '@public-ui/components/src';
|
|
8
|
+
|
|
9
|
+
export function AvailableAppointmentsForm() {
|
|
10
|
+
const form = useFormikContext<FormValues>();
|
|
11
|
+
|
|
12
|
+
const [sectionSubmitted, setSectionSubmitted] = useState(false);
|
|
13
|
+
const [availableTimes, setAvailableTimes] = useState<Option<string>[] | null>(null);
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
let ignoreResponse = false;
|
|
17
|
+
setAvailableTimes(null);
|
|
18
|
+
|
|
19
|
+
if (form.values.date) {
|
|
20
|
+
fetchAvailableTimes().then(
|
|
21
|
+
(times) => {
|
|
22
|
+
if (!ignoreResponse) {
|
|
23
|
+
setAvailableTimes(times);
|
|
24
|
+
void form.setFieldValue('time', times[0].value);
|
|
25
|
+
void form.setFieldTouched('time');
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
() => {},
|
|
29
|
+
); // ignore errors
|
|
30
|
+
}
|
|
31
|
+
return () => {
|
|
32
|
+
ignoreResponse = true;
|
|
33
|
+
};
|
|
34
|
+
}, [form.values.date]);
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div className="p-2">
|
|
38
|
+
<KolHeading _level={2} _label="Wählen Sie einen Termin aus"></KolHeading>
|
|
39
|
+
|
|
40
|
+
{sectionSubmitted && Object.keys(form.errors).length ? (
|
|
41
|
+
<div className="mt-2">
|
|
42
|
+
<ErrorList errors={form.errors} />
|
|
43
|
+
</div>
|
|
44
|
+
) : null}
|
|
45
|
+
|
|
46
|
+
<KolForm
|
|
47
|
+
_on={{
|
|
48
|
+
onSubmit: () => {
|
|
49
|
+
void form.submitForm();
|
|
50
|
+
setSectionSubmitted(true);
|
|
51
|
+
},
|
|
52
|
+
}}
|
|
53
|
+
>
|
|
54
|
+
<Field name="date">
|
|
55
|
+
{({ field }: FieldProps<FormValues['date']>) => (
|
|
56
|
+
<KolInputDate
|
|
57
|
+
id="field-date"
|
|
58
|
+
_label="Datum"
|
|
59
|
+
_value={field.value}
|
|
60
|
+
_error={form.errors.date || ''}
|
|
61
|
+
_touched={form.touched.date}
|
|
62
|
+
_required
|
|
63
|
+
_on={{
|
|
64
|
+
onChange: (event: Event, value: unknown): void => {
|
|
65
|
+
if (event.target) {
|
|
66
|
+
void form.setFieldValue('date', value, true);
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
onBlur: () => {
|
|
70
|
+
void form.setFieldTouched('date', true);
|
|
71
|
+
},
|
|
72
|
+
}}
|
|
73
|
+
/>
|
|
74
|
+
)}
|
|
75
|
+
</Field>
|
|
76
|
+
|
|
77
|
+
{form.values.date && (
|
|
78
|
+
<div className="grid gap-4 mt-4">
|
|
79
|
+
{availableTimes ? (
|
|
80
|
+
<>
|
|
81
|
+
<Field name="time">
|
|
82
|
+
{({ field }: FieldProps<FormValues['time']>) => (
|
|
83
|
+
<KolInputRadio
|
|
84
|
+
id="field-date"
|
|
85
|
+
_label="Zeit"
|
|
86
|
+
_orientation="horizontal"
|
|
87
|
+
_options={availableTimes}
|
|
88
|
+
_value={field.value}
|
|
89
|
+
_error={form.errors.time || ''}
|
|
90
|
+
_touched={form.touched.time}
|
|
91
|
+
_required
|
|
92
|
+
_on={{
|
|
93
|
+
onChange: (event: Event, value: unknown): void => {
|
|
94
|
+
if (event.target) {
|
|
95
|
+
void form.setFieldTouched('time', true);
|
|
96
|
+
void form.setFieldValue('time', value, true);
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
}}
|
|
100
|
+
/>
|
|
101
|
+
)}
|
|
102
|
+
</Field>
|
|
103
|
+
<p>
|
|
104
|
+
<em>Aus Testzwecken sind nur die Termine zu jeder halben Stunde verfügbar.</em>
|
|
105
|
+
</p>
|
|
106
|
+
</>
|
|
107
|
+
) : (
|
|
108
|
+
<KolSpin _show className="block" aria-label="Termine werden geladen." _variant="cycle" />
|
|
109
|
+
)}
|
|
110
|
+
</div>
|
|
111
|
+
)}
|
|
112
|
+
|
|
113
|
+
<KolButton _label="Weiter" _type="submit" className="mt-2" _disabled={form.isValidating} />
|
|
114
|
+
{form.values.date && form.isValidating ? <KolSpin _show aria-label="Termin wird geprüft." /> : ''}
|
|
115
|
+
</KolForm>
|
|
116
|
+
</div>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { KolButton, KolForm, KolHeading, KolSelect } from '@public-ui/react';
|
|
3
|
+
import { Field, FieldProps, useFormikContext } from 'formik';
|
|
4
|
+
import { FormValues } from './AppointmentForm';
|
|
5
|
+
import { ErrorList } from './ErrorList';
|
|
6
|
+
|
|
7
|
+
const LOCATION_OPTIONS = [
|
|
8
|
+
{
|
|
9
|
+
value: 'Aplerbeck',
|
|
10
|
+
label: 'Aplerbeck',
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
value: 'Brackel',
|
|
14
|
+
label: 'Brackel',
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
value: 'Dorstfeld',
|
|
18
|
+
label: 'Dorstfeld',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
value: 'Innenstadt Ost',
|
|
22
|
+
label: 'Innenstadt Ost',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
value: 'Innenstadt West',
|
|
26
|
+
label: 'Innenstadt West',
|
|
27
|
+
},
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
export function DistrictForm() {
|
|
31
|
+
const form = useFormikContext<FormValues>();
|
|
32
|
+
const [sectionSubmitted, setSectionSubmitted] = useState(false);
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<div className="p-2">
|
|
36
|
+
<KolHeading _level={2} _label="Wählen Sie einen Stadtteil aus"></KolHeading>
|
|
37
|
+
|
|
38
|
+
{sectionSubmitted && Object.keys(form.errors).length ? (
|
|
39
|
+
<div className="mt-2">
|
|
40
|
+
<ErrorList errors={form.errors} />
|
|
41
|
+
</div>
|
|
42
|
+
) : null}
|
|
43
|
+
|
|
44
|
+
<KolForm
|
|
45
|
+
_on={{
|
|
46
|
+
onSubmit: () => {
|
|
47
|
+
void form.submitForm();
|
|
48
|
+
setSectionSubmitted(true);
|
|
49
|
+
},
|
|
50
|
+
}}
|
|
51
|
+
>
|
|
52
|
+
<Field name="district">
|
|
53
|
+
{({ field }: FieldProps<FormValues['district']>) => (
|
|
54
|
+
<KolSelect
|
|
55
|
+
id="field-district"
|
|
56
|
+
_label="Stadtteil"
|
|
57
|
+
_options={[{ label: 'Bitte wählen…', value: '' }, ...LOCATION_OPTIONS]}
|
|
58
|
+
_value={[field.value]}
|
|
59
|
+
_error={form.errors.district || ''}
|
|
60
|
+
_touched={form.touched.district}
|
|
61
|
+
_required
|
|
62
|
+
_on={{
|
|
63
|
+
onChange: (event, values: unknown) => {
|
|
64
|
+
// Select und Radio setzen den Wert immer initial.
|
|
65
|
+
if (event.target) {
|
|
66
|
+
const [value] = values as [FormValues['district']];
|
|
67
|
+
void form.setFieldTouched('district', true);
|
|
68
|
+
void form.setFieldValue('district', value, true);
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
}}
|
|
72
|
+
/>
|
|
73
|
+
)}
|
|
74
|
+
</Field>
|
|
75
|
+
|
|
76
|
+
<KolButton _label="Weiter" _type="submit" className="mt-2" />
|
|
77
|
+
</KolForm>
|
|
78
|
+
</div>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { KolAlert, KolLink } from '@public-ui/react';
|
|
3
|
+
|
|
4
|
+
type ErrorListPropType = {
|
|
5
|
+
errors: Record<string, string>;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export function ErrorList({ errors }: ErrorListPropType) {
|
|
9
|
+
const handleLinkClick = (event: Event) => {
|
|
10
|
+
const href = (event.target as HTMLAnchorElement | undefined)?.href;
|
|
11
|
+
if (href) {
|
|
12
|
+
const hrefUrl = new URL(href);
|
|
13
|
+
|
|
14
|
+
const targetElement = document.querySelector<HTMLElement>(hrefUrl.hash);
|
|
15
|
+
if (targetElement && typeof targetElement.focus === 'function') {
|
|
16
|
+
targetElement.focus();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<KolAlert _type="error" _variant="msg">
|
|
23
|
+
Bitte korrigieren Sie folgende Fehler:
|
|
24
|
+
<nav aria-label="Fehlerliste">
|
|
25
|
+
<ul>
|
|
26
|
+
{Object.entries(errors).map(([field, error]) => (
|
|
27
|
+
<li key={field}>
|
|
28
|
+
<KolLink _href={`#field-${field}`} _label={error} _on={{ onClick: handleLinkClick }} />
|
|
29
|
+
</li>
|
|
30
|
+
))}
|
|
31
|
+
</ul>
|
|
32
|
+
</nav>
|
|
33
|
+
</KolAlert>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { KolButton, KolForm, KolHeading, KolInputEmail, KolInputText, KolSelect } from '@public-ui/react';
|
|
3
|
+
import { Field, FieldProps, useFormikContext } from 'formik';
|
|
4
|
+
import { FormValues } from './AppointmentForm';
|
|
5
|
+
|
|
6
|
+
const SALUTATION_OPTIONS = [
|
|
7
|
+
{
|
|
8
|
+
value: 'Firma',
|
|
9
|
+
label: 'Firma',
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
value: 'Frau',
|
|
13
|
+
label: 'Frau',
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
value: 'Herr',
|
|
17
|
+
label: 'Herr',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
value: 'Hallo',
|
|
21
|
+
label: 'Hallo',
|
|
22
|
+
},
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
export function PersonalInformationForm() {
|
|
26
|
+
const form = useFormikContext<FormValues>();
|
|
27
|
+
const [sectionSubmitted, setSectionSubmitted] = useState(false);
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<div className="p-2">
|
|
31
|
+
<KolHeading _level={2} _label="Geben Sie Ihre Kontaktdaten ein"></KolHeading>
|
|
32
|
+
<ul>{sectionSubmitted && Object.entries(form.errors).map(([field, error]) => <li key={field}>{error}</li>)}</ul>
|
|
33
|
+
<KolForm
|
|
34
|
+
_on={{
|
|
35
|
+
onSubmit: () => {
|
|
36
|
+
void form.submitForm();
|
|
37
|
+
setSectionSubmitted(true);
|
|
38
|
+
},
|
|
39
|
+
}}
|
|
40
|
+
>
|
|
41
|
+
<Field name="salutation">
|
|
42
|
+
{({ field }: FieldProps<FormValues['salutation']>) => (
|
|
43
|
+
<KolSelect
|
|
44
|
+
_label="Anrede"
|
|
45
|
+
_value={[field.value]}
|
|
46
|
+
_error={form.errors.salutation || ''}
|
|
47
|
+
_touched={form.touched.salutation}
|
|
48
|
+
_options={[{ label: 'Bitte wählen…', value: '' }, ...SALUTATION_OPTIONS]}
|
|
49
|
+
_required
|
|
50
|
+
_on={{
|
|
51
|
+
onChange: (event, values: unknown) => {
|
|
52
|
+
if (event.target) {
|
|
53
|
+
const [value] = values as [FormValues['salutation']];
|
|
54
|
+
void form.setFieldTouched('salutation', true);
|
|
55
|
+
void form.setFieldValue('salutation', value, true);
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
}}
|
|
59
|
+
/>
|
|
60
|
+
)}
|
|
61
|
+
</Field>
|
|
62
|
+
|
|
63
|
+
{form.values.salutation === 'Firma' && (
|
|
64
|
+
<Field name="company">
|
|
65
|
+
{({ field }: FieldProps<FormValues['company']>) => (
|
|
66
|
+
<div className="block mt-2">
|
|
67
|
+
<KolInputText
|
|
68
|
+
_label="Firma"
|
|
69
|
+
_value={field.value}
|
|
70
|
+
_error={form.errors.company || ''}
|
|
71
|
+
_touched={form.touched.company}
|
|
72
|
+
_required
|
|
73
|
+
_on={{
|
|
74
|
+
onChange: (event, value: unknown) => {
|
|
75
|
+
if (event.target) {
|
|
76
|
+
void form.setFieldTouched('company', true);
|
|
77
|
+
void form.setFieldValue('company', value, true);
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
}}
|
|
81
|
+
/>
|
|
82
|
+
</div>
|
|
83
|
+
)}
|
|
84
|
+
</Field>
|
|
85
|
+
)}
|
|
86
|
+
|
|
87
|
+
<Field name="name">
|
|
88
|
+
{({ field }: FieldProps<FormValues['name']>) => (
|
|
89
|
+
<div className="block mt-2">
|
|
90
|
+
<KolInputText
|
|
91
|
+
_label="Vor- und Zuname"
|
|
92
|
+
_value={field.value}
|
|
93
|
+
_error={form.errors.name || ''}
|
|
94
|
+
_touched={form.touched.name}
|
|
95
|
+
_required
|
|
96
|
+
_on={{
|
|
97
|
+
onChange: (event, value: unknown) => {
|
|
98
|
+
if (event.target) {
|
|
99
|
+
void form.setFieldTouched('name', true);
|
|
100
|
+
void form.setFieldValue('name', value, true);
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
}}
|
|
104
|
+
/>
|
|
105
|
+
</div>
|
|
106
|
+
)}
|
|
107
|
+
</Field>
|
|
108
|
+
|
|
109
|
+
<Field name="email">
|
|
110
|
+
{({ field }: FieldProps<FormValues['email']>) => (
|
|
111
|
+
<div className="block mt-2">
|
|
112
|
+
<KolInputEmail
|
|
113
|
+
_label="E-Mail"
|
|
114
|
+
_value={field.value}
|
|
115
|
+
_error={form.errors.email || ''}
|
|
116
|
+
_touched={form.touched.email}
|
|
117
|
+
_required
|
|
118
|
+
_on={{
|
|
119
|
+
onChange: (event, value: unknown) => {
|
|
120
|
+
if (event.target) {
|
|
121
|
+
void form.setFieldTouched('email', true);
|
|
122
|
+
void form.setFieldValue('email', value, true);
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
}}
|
|
126
|
+
/>
|
|
127
|
+
</div>
|
|
128
|
+
)}
|
|
129
|
+
</Field>
|
|
130
|
+
|
|
131
|
+
<Field name="phone">
|
|
132
|
+
{({ field }: FieldProps<FormValues['phone']>) => (
|
|
133
|
+
<div className="block mt-2">
|
|
134
|
+
<KolInputText
|
|
135
|
+
_type="tel"
|
|
136
|
+
_label="Telefonnumer"
|
|
137
|
+
_value={field.value}
|
|
138
|
+
_error={form.errors.phone || ''}
|
|
139
|
+
_touched={form.touched.phone}
|
|
140
|
+
_on={{
|
|
141
|
+
onChange: (event, value: unknown) => {
|
|
142
|
+
if (event.target) {
|
|
143
|
+
void form.setFieldTouched('phone', true);
|
|
144
|
+
void form.setFieldValue('phone', value, true);
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
}}
|
|
148
|
+
/>
|
|
149
|
+
</div>
|
|
150
|
+
)}
|
|
151
|
+
</Field>
|
|
152
|
+
|
|
153
|
+
<KolButton _label="Weiter" _type="submit" className="mt-2" />
|
|
154
|
+
</KolForm>
|
|
155
|
+
</div>
|
|
156
|
+
);
|
|
157
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useFormikContext } from 'formik';
|
|
3
|
+
import { KolHeading } from '@public-ui/react';
|
|
4
|
+
import { FormValues } from './AppointmentForm';
|
|
5
|
+
|
|
6
|
+
const ValueFallback = () => <i>Nicht angegeben</i>;
|
|
7
|
+
const ValueWithFallback = ({ value }: { value: string }) => (value ? value : <ValueFallback />);
|
|
8
|
+
|
|
9
|
+
export function Summary() {
|
|
10
|
+
const { values } = useFormikContext<FormValues>();
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<>
|
|
14
|
+
<KolHeading _level={2} _label="Zusammenfassung"></KolHeading>
|
|
15
|
+
|
|
16
|
+
<dl>
|
|
17
|
+
<dt>Stadtteil</dt>
|
|
18
|
+
<dd>
|
|
19
|
+
<ValueWithFallback value={values.district} />
|
|
20
|
+
</dd>
|
|
21
|
+
<dt>Termin</dt>
|
|
22
|
+
<dd>{values.date && values.time ? `${values.date} ${values.time} Uhr` : <ValueFallback />}</dd>
|
|
23
|
+
|
|
24
|
+
{values.salutation === 'Firma' ? (
|
|
25
|
+
<>
|
|
26
|
+
<dt>Firma</dt>
|
|
27
|
+
<dd>
|
|
28
|
+
<ValueWithFallback value={values.company} />
|
|
29
|
+
</dd>
|
|
30
|
+
</>
|
|
31
|
+
) : (
|
|
32
|
+
<>
|
|
33
|
+
<dt>Anrede</dt>
|
|
34
|
+
<dd>
|
|
35
|
+
<ValueWithFallback value={values.salutation} />
|
|
36
|
+
</dd>
|
|
37
|
+
</>
|
|
38
|
+
)}
|
|
39
|
+
|
|
40
|
+
<dt>Name</dt>
|
|
41
|
+
<dd>
|
|
42
|
+
<ValueWithFallback value={values.name} />
|
|
43
|
+
</dd>
|
|
44
|
+
<dt>E-Mail</dt>
|
|
45
|
+
<dd>
|
|
46
|
+
<ValueWithFallback value={values.email} />
|
|
47
|
+
</dd>
|
|
48
|
+
<dt>Telefon</dt>
|
|
49
|
+
<dd>
|
|
50
|
+
<ValueWithFallback value={values.phone} />
|
|
51
|
+
</dd>
|
|
52
|
+
</dl>
|
|
53
|
+
</>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Option } from '@public-ui/components';
|
|
2
|
+
|
|
3
|
+
const getRandomIntInclusive = (min: number, max: number) => {
|
|
4
|
+
min = Math.ceil(min);
|
|
5
|
+
max = Math.floor(max);
|
|
6
|
+
return Math.floor(Math.random() * (max - min + 1) + min);
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const padHours = (hours: number): string => `${hours < 10 ? '0' : ''}${hours}`;
|
|
10
|
+
|
|
11
|
+
const getRandomTimes = () => {
|
|
12
|
+
const earliest = 8;
|
|
13
|
+
const latest = 17;
|
|
14
|
+
const amount = getRandomIntInclusive(2, 9);
|
|
15
|
+
|
|
16
|
+
const times = new Set<number>();
|
|
17
|
+
while (times.size !== amount) {
|
|
18
|
+
times.add(getRandomIntInclusive(earliest, latest));
|
|
19
|
+
}
|
|
20
|
+
return [...times].sort((timeA, timeB) => timeA - timeB).flatMap((hours) => [`${padHours(hours)}:00`, `${padHours(hours)}:30`]);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const sleep = (timeout: number) => {
|
|
24
|
+
return new Promise((resolve) => setTimeout(resolve, timeout));
|
|
25
|
+
};
|
|
26
|
+
export const fetchAvailableTimes = async (): Promise<Option<string>[]> => {
|
|
27
|
+
await sleep(1000);
|
|
28
|
+
return getRandomTimes().map((time) => ({
|
|
29
|
+
label: time,
|
|
30
|
+
value: time,
|
|
31
|
+
}));
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const checkAppointmentAvailability = async (time?: string): Promise<boolean> => {
|
|
35
|
+
await sleep(500);
|
|
36
|
+
return time?.endsWith(':30') ?? false;
|
|
37
|
+
};
|