Skip to content

Commit f5babb7

Browse files
committed
YKI(Frontend): Use NativeSelect instead of ComboBox for exam listing filters when on mobile [deploy]
1 parent 7e1f999 commit f5babb7

File tree

3 files changed

+212
-128
lines changed

3 files changed

+212
-128
lines changed

frontend/packages/yki/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
"yki:tslint": "yarn g:tsc --pretty --noEmit"
2727
},
2828
"dependencies": {
29-
"shared": "npm:@opetushallitus/kieli-ja-kaantajatutkinnot.shared@1.9.29"
29+
"shared": "npm:@opetushallitus/kieli-ja-kaantajatutkinnot.shared@1.10.0"
3030
},
3131
"devDependencies": {
3232
"multer": "^1.4.5-lts.1"

frontend/packages/yki/src/components/registration/examSession/PublicExamSessionListingFilters.tsx

Lines changed: 209 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,17 @@ import {
77
FormGroup,
88
Typography,
99
} from '@mui/material';
10-
import { useState } from 'react';
10+
import { useCallback, useState } from 'react';
1111
import {
1212
AutocompleteValue,
1313
ComboBox,
1414
CustomButton,
1515
LanguageSelect,
16+
NativeSelect,
1617
Text,
1718
} from 'shared/components';
1819
import { Color, Severity, TextFieldVariant, Variant } from 'shared/enums';
19-
import { useDialog } from 'shared/hooks';
20+
import { useDialog, useWindowProperties } from 'shared/hooks';
2021

2122
import { useCommonTranslation, usePublicTranslation } from 'configs/i18n';
2223
import { useAppDispatch, useAppSelector } from 'configs/redux';
@@ -28,6 +29,200 @@ import {
2829
selectFilteredPublicExamSessions,
2930
} from 'redux/selectors/examSessions';
3031

32+
const municipalityToComboBoxOption = (m: string) => ({
33+
value: m,
34+
label: m,
35+
});
36+
37+
const SelectMunicipality = () => {
38+
const municipalities = useAppSelector(examSessionsSelector).municipalities;
39+
const { municipality } = useAppSelector(examSessionsSelector).filters;
40+
const dispatch = useAppDispatch();
41+
const onMunicipalityChange = useCallback(
42+
(municipality?: string) => {
43+
dispatch(setPublicExamSessionFilters({ municipality }));
44+
},
45+
[dispatch]
46+
);
47+
48+
const { t } = usePublicTranslation({
49+
keyPrefix: 'yki.pages.registrationPage',
50+
});
51+
const { isPhone } = useWindowProperties();
52+
53+
return (
54+
<div className="public-exam-session-filters__filter public-exam-session-filters__municipality">
55+
<Typography
56+
variant="h3"
57+
component="label"
58+
htmlFor="public-exam-session-filters__municipality-filter"
59+
>
60+
{t('labels.selectMunicipality')}
61+
</Typography>
62+
{isPhone ? (
63+
<NativeSelect
64+
id="public-exam-session-filters__municipality-filter"
65+
onChange={(e) => {
66+
onMunicipalityChange(e.target.value as string);
67+
}}
68+
placeholder={t('labels.selectMunicipality')}
69+
value={
70+
municipality ? municipalityToComboBoxOption(municipality) : null
71+
}
72+
values={municipalities.map(municipalityToComboBoxOption)}
73+
/>
74+
) : (
75+
<ComboBox
76+
id="public-exam-session-filters__municipality-filter"
77+
variant={TextFieldVariant.Outlined}
78+
values={municipalities.map(municipalityToComboBoxOption)}
79+
value={
80+
municipality ? municipalityToComboBoxOption(municipality) : null
81+
}
82+
onChange={(_, v: AutocompleteValue) => {
83+
const municipality = v?.value;
84+
onMunicipalityChange(municipality);
85+
}}
86+
label={t('labels.selectMunicipality')}
87+
aria-label={t('labels.selectMunicipality')}
88+
/>
89+
)}
90+
</div>
91+
);
92+
};
93+
94+
const SelectExamLanguage = ({
95+
showError,
96+
onFilterChange,
97+
}: {
98+
showError: boolean;
99+
onFilterChange: (filter: Partial<ExamSessionFilters>) => void;
100+
}) => {
101+
const { language } = useAppSelector(examSessionsSelector).filters;
102+
103+
const { t } = usePublicTranslation({
104+
keyPrefix: 'yki.pages.registrationPage',
105+
});
106+
const translateCommon = useCommonTranslation();
107+
const translateLanguage = (language: string) =>
108+
translateCommon('languages.' + language);
109+
const languages = Object.values(ExamLanguage);
110+
111+
return (
112+
<FormControl
113+
className="public-exam-session-filters__filter"
114+
error={showError && !language}
115+
>
116+
<Typography
117+
variant="h3"
118+
component="label"
119+
htmlFor="public-exam-session-filters__language-filter"
120+
sx={showError && !language ? { color: 'error.main' } : {}}
121+
>
122+
{translateCommon('language')}{' '}
123+
<span className="public-exam-session-filters__hint">
124+
{t('filters.selectExamDetails.required')}
125+
</span>
126+
</Typography>
127+
<LanguageSelect
128+
id="public-exam-session-filters__language-filter"
129+
primaryLanguages={[
130+
ExamLanguage.ALL,
131+
ExamLanguage.FIN,
132+
ExamLanguage.SWE,
133+
ExamLanguage.ENG,
134+
]}
135+
languages={languages}
136+
translateLanguage={translateLanguage}
137+
variant={TextFieldVariant.Outlined}
138+
value={
139+
language
140+
? { value: language, label: translateLanguage(language) }
141+
: null
142+
}
143+
onLanguageChange={(v) => {
144+
const language = v as ExamLanguage | undefined;
145+
onFilterChange({ language });
146+
}}
147+
label={t('labels.selectLanguage')}
148+
aria-label={t('labels.selectLanguage')}
149+
showError={showError && !language}
150+
helperText={showError && !language ? t('filters.errors.required') : ''}
151+
/>
152+
</FormControl>
153+
);
154+
};
155+
156+
const SelectExamLevel = ({
157+
showError,
158+
onFilterChange,
159+
}: {
160+
showError: boolean;
161+
onFilterChange: (filter: Partial<ExamSessionFilters>) => void;
162+
}) => {
163+
const { level } = useAppSelector(examSessionsSelector).filters;
164+
165+
const translateCommon = useCommonTranslation();
166+
const { t } = usePublicTranslation({
167+
keyPrefix: 'yki.pages.registrationPage',
168+
});
169+
170+
const levelToComboBoxOption = (v: ExamLevel) => ({
171+
value: v.toString(),
172+
label: translateCommon('languageLevel.' + v.toString()),
173+
});
174+
const levelValues = Object.values(ExamLevel).map(levelToComboBoxOption);
175+
176+
const errorStyles = { color: 'error.main' };
177+
const { isPhone } = useWindowProperties();
178+
179+
return (
180+
<FormControl
181+
className="public-exam-session-filters__filter"
182+
error={showError && !level}
183+
>
184+
<Typography
185+
variant="h3"
186+
component="label"
187+
htmlFor="public-exam-session-filters__level-filter"
188+
sx={showError && !level ? errorStyles : {}}
189+
>
190+
{translateCommon('level')}{' '}
191+
<span className="public-exam-session-filters__hint">
192+
{t('filters.selectExamDetails.required')}
193+
</span>
194+
</Typography>
195+
{isPhone ? (
196+
<NativeSelect
197+
id="public-exam-session-filters__level-filter"
198+
onChange={(e) => {
199+
const level = e.target.value as ExamLevel | undefined;
200+
onFilterChange({ level });
201+
}}
202+
placeholder={t('labels.selectLevel')}
203+
value={level ? levelToComboBoxOption(level) : null}
204+
values={levelValues}
205+
/>
206+
) : (
207+
<ComboBox
208+
id="public-exam-session-filters__level-filter"
209+
variant={TextFieldVariant.Outlined}
210+
values={levelValues}
211+
value={level ? levelToComboBoxOption(level) : null}
212+
onChange={(_, v: AutocompleteValue) => {
213+
const level = v?.value as ExamLevel | undefined;
214+
onFilterChange({ level });
215+
}}
216+
label={t('labels.selectLevel')}
217+
aria-label={t('labels.selectLevel')}
218+
showError={showError && !level}
219+
helperText={showError && !level ? t('filters.errors.required') : ''}
220+
/>
221+
)}
222+
</FormControl>
223+
);
224+
};
225+
31226
export const PublicExamSessionFilters = ({
32227
onApplyFilters,
33228
}: {
@@ -41,15 +236,9 @@ export const PublicExamSessionFilters = ({
41236

42237
const { showDialog } = useDialog();
43238

44-
const { filters, municipalities } = useAppSelector(examSessionsSelector);
45239
const filteredExamSessions = useAppSelector(selectFilteredPublicExamSessions);
46-
const {
47-
language,
48-
level,
49-
municipality,
50-
excludeFullSessions,
51-
excludeNonOpenSessions,
52-
} = filters;
240+
const { language, level, excludeFullSessions, excludeNonOpenSessions } =
241+
useAppSelector(examSessionsSelector).filters;
53242

54243
const dispatch = useAppDispatch();
55244
const onFilterChange = (filter: Partial<ExamSessionFilters>) => {
@@ -59,7 +248,7 @@ export const PublicExamSessionFilters = ({
59248
const [showError, setShowError] = useState(false);
60249

61250
const handleSubmitBtnClick = () => {
62-
if (!filters.language || !filters.level) {
251+
if (!language || !level) {
63252
setShowError(true);
64253
showDialog({
65254
severity: Severity.Error,
@@ -78,22 +267,6 @@ export const PublicExamSessionFilters = ({
78267
}
79268
};
80269

81-
const languages = Object.values(ExamLanguage);
82-
const translateLanguage = (language: string) =>
83-
translateCommon('languages.' + language);
84-
85-
const levelToComboBoxOption = (v: ExamLevel) => ({
86-
value: v.toString(),
87-
label: translateCommon('languageLevel.' + v.toString()),
88-
});
89-
const levelValues = Object.values(ExamLevel).map(levelToComboBoxOption);
90-
const municipalityToComboBoxOption = (m: string) => ({
91-
value: m,
92-
label: m,
93-
});
94-
95-
const errorStyles = { color: 'error.main' };
96-
97270
return (
98271
<div className="public-exam-session-filters">
99272
<div className="public-exam-session-filters__dropdown-filters-container">
@@ -104,106 +277,17 @@ export const PublicExamSessionFilters = ({
104277
</Text>
105278
</legend>
106279
<div className="public-exam-session-filters__dropdown-filters-box">
107-
<FormControl
108-
className="public-exam-session-filters__filter"
109-
error={showError && !language}
110-
>
111-
<Typography
112-
variant="h3"
113-
component="label"
114-
htmlFor="public-exam-session-filters__language-filter"
115-
sx={showError && !language ? { color: 'error.main' } : {}}
116-
>
117-
{translateCommon('language')}{' '}
118-
<span className="public-exam-session-filters__hint">
119-
{t('filters.selectExamDetails.required')}
120-
</span>
121-
</Typography>
122-
<LanguageSelect
123-
id="public-exam-session-filters__language-filter"
124-
primaryLanguages={[
125-
ExamLanguage.ALL,
126-
ExamLanguage.FIN,
127-
ExamLanguage.SWE,
128-
ExamLanguage.ENG,
129-
]}
130-
languages={languages}
131-
translateLanguage={translateLanguage}
132-
variant={TextFieldVariant.Outlined}
133-
value={
134-
language
135-
? { value: language, label: translateLanguage(language) }
136-
: null
137-
}
138-
onChange={(_, v: AutocompleteValue) => {
139-
const language = v?.value as ExamLanguage | undefined;
140-
onFilterChange({ language });
141-
}}
142-
label={t('labels.selectLanguage')}
143-
aria-label={t('labels.selectLanguage')}
144-
showError={showError && !language}
145-
helperText={
146-
showError && !language ? t('filters.errors.required') : ''
147-
}
148-
/>
149-
</FormControl>
150-
<FormControl
151-
className="public-exam-session-filters__filter"
152-
error={showError && !level}
153-
>
154-
<Typography
155-
variant="h3"
156-
component="label"
157-
htmlFor="public-exam-session-filters__level-filter"
158-
sx={showError && !level ? errorStyles : {}}
159-
>
160-
{translateCommon('level')}{' '}
161-
<span className="public-exam-session-filters__hint">
162-
{t('filters.selectExamDetails.required')}
163-
</span>
164-
</Typography>
165-
<ComboBox
166-
id="public-exam-session-filters__level-filter"
167-
variant={TextFieldVariant.Outlined}
168-
values={levelValues}
169-
value={level ? levelToComboBoxOption(level) : null}
170-
onChange={(_, v: AutocompleteValue) => {
171-
const level = v?.value as ExamLevel | undefined;
172-
onFilterChange({ level });
173-
}}
174-
label={t('labels.selectLevel')}
175-
aria-label={t('labels.selectLevel')}
176-
showError={showError && !level}
177-
helperText={
178-
showError && !level ? t('filters.errors.required') : ''
179-
}
180-
/>
181-
</FormControl>
280+
<SelectExamLanguage
281+
showError={showError}
282+
onFilterChange={onFilterChange}
283+
/>
284+
<SelectExamLevel
285+
showError={showError}
286+
onFilterChange={onFilterChange}
287+
/>
182288
</div>
183289
</fieldset>
184-
<div className="public-exam-session-filters__filter public-exam-session-filters__municipality">
185-
<Typography
186-
variant="h3"
187-
component="label"
188-
htmlFor="public-exam-session-filters__municipality-filter"
189-
>
190-
{t('labels.selectMunicipality')}
191-
</Typography>
192-
<ComboBox
193-
id="public-exam-session-filters__municipality-filter"
194-
variant={TextFieldVariant.Outlined}
195-
values={municipalities.map(municipalityToComboBoxOption)}
196-
value={
197-
municipality ? municipalityToComboBoxOption(municipality) : null
198-
}
199-
onChange={(_, v: AutocompleteValue) => {
200-
const municipality = v?.value;
201-
onFilterChange({ municipality });
202-
}}
203-
label={t('labels.selectMunicipality')}
204-
aria-label={t('labels.selectMunicipality')}
205-
/>
206-
</div>
290+
<SelectMunicipality />
207291
</div>
208292
<Box className="public-exam-session-filters__toggle-box">
209293
<FormControl component="fieldset" variant={TextFieldVariant.Standard}>

0 commit comments

Comments
 (0)