Skip to content

Commit

Permalink
Merge pull request #24 from Opetushallitus/OK-498-hakukohteen-suodatt…
Browse files Browse the repository at this point in the history
…imet

Ok 498 hakukohteen suodattimet
  • Loading branch information
SalamaGofore authored Jun 11, 2024
2 parents 81a8443 + 7328804 commit 51a5c04
Show file tree
Hide file tree
Showing 10 changed files with 312 additions and 34 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# valintojen-toteuttaminen

[![Build](https://github.com/Opetushallitus/valintojen-toteuttaminen/actions/workflows/build.yml/badge.svg)](https://github.com/Opetushallitus/valintojen-toteuttaminen/actions/workflows/build.yml)

Valintojen toteuttamisen käyttöliittymä

## Lokaaliajo
Expand Down
1 change: 1 addition & 0 deletions pull_request_template.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
Oletko lisännyt tarvittavat yksikkö- tai ui-testit toiminnallisuudelle? Kyllä/En
Oletko tarkistanut ja päivittänyt riippuvuudet? Kyllä/En
Oletko kokeillut toimiiko käyttöliittymä mobiilissa landscape moodissa? Kyllä/En/Ei koske
Oletko testannut että lisäämäsi toiminto on saavutettava? Kyllä/En/Ei koske
66 changes: 39 additions & 27 deletions src/app/haku/[oid]/hakukohde-list.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
'use client';

import { useHakukohdeSearchResults } from '@/app/hooks/useHakukohdeSearch';
import { useTranslations } from '@/app/hooks/useTranslations';
import { useUserPermissions } from '@/app/hooks/useUserPermissions';
import { getHakukohteet } from '@/app/lib/kouta';
import { Hakukohde } from '@/app/lib/kouta-types';
import { CircularProgress, styled } from '@mui/material';
import { useQuery } from '@tanstack/react-query';
import { styled } from '@mui/material';
import { useRouter } from 'next/navigation';

const StyledList = styled('div')({
width: '20vw',
width: '100%',
textAlign: 'left',
overflowY: 'auto',
height: '80vh',
});

const StyledItem = styled('div')({
Expand All @@ -26,35 +26,47 @@ const StyledItem = styled('div')({
},
});

export const HakukohdeList = ({ oid }: { oid: string }) => {
export const HakukohdeList = ({ hakuOid }: { hakuOid: string }) => {
const router = useRouter();
const { translateEntity } = useTranslations();
const { t, translateEntity } = useTranslations();
const { results } = useHakukohdeSearchResults(hakuOid);

const selectHakukohde = (hakukohde: Hakukohde) => {
router.push(`/haku/${oid}/hakukohde/${hakukohde.oid}/perustiedot`);
router.push(`/haku/${hakuOid}/hakukohde/${hakukohde.oid}/perustiedot`);
};

const { data: userPermissions } = useUserPermissions();

const { isLoading, data: hakukohteet } = useQuery({
queryKey: ['getHakukohteet', oid],
queryFn: () => getHakukohteet(oid, userPermissions),
});
const handleHakukohdeKeyDown = (
event: React.KeyboardEvent<HTMLDivElement>,
hakukohde: Hakukohde,
) => {
if (event.key === 'Enter') {
event.preventDefault();
selectHakukohde(hakukohde);
}
};

return (
<StyledList>
{isLoading && <CircularProgress />}
{!isLoading &&
hakukohteet?.map((hk: Hakukohde) => (
<StyledItem key={hk.oid} onClick={() => selectHakukohde(hk)}>
<p title={hk.organisaatioOid} className="organizationLabel">
{hk.jarjestyspaikkaHierarkiaNimi
? translateEntity(hk.jarjestyspaikkaHierarkiaNimi)
: ''}
</p>
<p title={hk.oid}>{translateEntity(hk.nimi)}</p>
</StyledItem>
))}
<StyledList tabIndex={0}>
<p>
{results.length} {t('haku.hakukohdetta')}
</p>
{results?.map((hk: Hakukohde) => (
<StyledItem
key={hk.oid}
onClick={() => selectHakukohde(hk)}
onKeyDown={(event) => handleHakukohdeKeyDown(event, hk)}
tabIndex={0}
>
<p title={hk.organisaatioOid} className="organizationLabel">
{hk.jarjestyspaikkaHierarkiaNimi
? translateEntity(hk.jarjestyspaikkaHierarkiaNimi)
: ''}
</p>
<p title={hk.oid} className="hakukohdeLabel">
{translateEntity(hk.nimi)}
</p>
</StyledItem>
))}
</StyledList>
);
};
Expand Down
81 changes: 81 additions & 0 deletions src/app/haku/[oid]/hakukohde-panel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
'use client';

import { styled, IconButton, FormLabel, CircularProgress } from '@mui/material';
import CloseIcon from '@mui/icons-material/Close';
import KeyboardDoubleArrowRightIcon from '@mui/icons-material/KeyboardDoubleArrowRight';
import HakukohdeList from './hakukohde-list';
import HakukohdeSearch from './hakukohde-search';
import { useState } from 'react';
import { colors } from '@/app/theme';
import { useTranslations } from '@/app/hooks/useTranslations';
import { QuerySuspenseBoundary } from '@/app/components/query-suspense-boundary';

const StyledPanel = styled('div')({
width: '20vw',
textAlign: 'left',
minHeight: '85vh',
display: 'flex',
flexDirection: 'column',
rowGap: '7px',
paddingRight: '5px',
alignItems: 'start',
transition: 'width 300ms ease-in-out',
['label, button']: {
color: colors.blue2,
maxWidth: '50px',
alignSelf: 'end',
marginRight: '15px',
marginBottom: '5px',
},
'&.minimized': {
width: '50px',
rowGap: '3px',
['label, button']: {
margin: 0,
alignSelf: 'start',
},
},
});

export const HakukohdePanel = ({ oid }: { oid: string }) => {
const [minimized, setMinimized] = useState(false);
const { t } = useTranslations();

return (
<StyledPanel className={minimized ? 'minimized' : ''}>
{!minimized && (
<QuerySuspenseBoundary
suspenseFallback={
<CircularProgress aria-label={t('yleinen.ladataan')} />
}
>
<IconButton
sx={{ alignSelf: 'right', width: '1rem', height: '1rem' }}
onClick={() => setMinimized(true)}
aria-label={t('haku.pienenna')}
>
<CloseIcon />
</IconButton>
<HakukohdeSearch />

<HakukohdeList hakuOid={oid} />
</QuerySuspenseBoundary>
)}
{minimized && (
<>
<IconButton
id="expand-button"
name="expand-button"
onClick={() => setMinimized(false)}
aria-label={t('haku.suurenna')}
>
<KeyboardDoubleArrowRightIcon />
</IconButton>
<FormLabel htmlFor="expand-button">{t('yleinen.haku')}</FormLabel>
</>
)}
</StyledPanel>
);
};

export default HakukohdePanel;
40 changes: 40 additions & 0 deletions src/app/haku/[oid]/hakukohde-search.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { useHakukohdeSearchParams } from '@/app/hooks/useHakukohdeSearch';
import { useTranslations } from '@/app/hooks/useTranslations';
import { Search } from '@mui/icons-material';
import { FormControl, InputAdornment, OutlinedInput } from '@mui/material';
import { ChangeEvent } from 'react';

export const HakukohdeSearch = () => {
const { searchPhrase, setSearchPhrase } = useHakukohdeSearchParams();
const { t } = useTranslations();
const handleSearchChange = (e: ChangeEvent<HTMLInputElement>) => {
setSearchPhrase(e.target.value);
};

return (
<FormControl
sx={{
flexGrow: 4,
minWidth: '180px',
textAlign: 'left',
}}
>
<OutlinedInput
id="hakukohde-search"
name="hakukohde-search"
defaultValue={searchPhrase}
onChange={handleSearchChange}
autoFocus={true}
type="text"
placeholder={t('haku.haehakukohde')}
endAdornment={
<InputAdornment position="end">
<Search />
</InputAdornment>
}
/>
</FormControl>
);
};

export default HakukohdeSearch;
4 changes: 2 additions & 2 deletions src/app/haku/[oid]/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import HakukohdeList from './hakukohde-list';
import HakukohdePanel from './hakukohde-panel';

export default function HakuLayout({
children,
Expand All @@ -16,7 +16,7 @@ export default function HakuLayout({
className="mainContainer"
style={{ display: 'flex', flexDirection: 'row', textAlign: 'left' }}
>
<HakukohdeList oid={params.oid} />
<HakukohdePanel oid={params.oid} />
{children}
</main>
</>
Expand Down
20 changes: 18 additions & 2 deletions src/app/haku/[oid]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,29 @@
'use client';

import { useTranslations } from '@/app/hooks/useTranslations';
import { colors } from '@/app/theme';
import { ListAlt } from '@mui/icons-material';
import { styled } from '@mui/material';

export default function HakuPage() {
const { t } = useTranslations();

const Container = styled('div')({
textAlign: 'center',
padding: '5rem 2rem 1rem',
width: '70%',
});

return (
<div style={{ alignSelf: 'center', width: '70%', padding: '1rem 2rem' }}>
<Container>
<ListAlt
sx={{
borderRadius: '45px',
backgroundColor: colors.grey50,
padding: '15px',
}}
/>
<h2>{t('hakukohde.valitse')}</h2>
</div>
</Container>
);
}
62 changes: 62 additions & 0 deletions src/app/hooks/useHakukohdeSearch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
'use client';
import { useMemo } from 'react';
import { Hakukohde } from '../lib/kouta-types';
import { useDebounce } from '@/app/hooks/useDebounce';
import { useQueryState } from 'nuqs';
import { useSuspenseQuery } from '@tanstack/react-query';
import { HAKU_SEARCH_PHRASE_DEBOUNCE_DELAY } from '@/app/lib/constants';
import { useTranslations } from './useTranslations';
import { getHakukohteet } from '../lib/kouta';
import { useUserPermissions } from './useUserPermissions';

const DEFAULT_NUQS_OPTIONS = {
history: 'push',
clearOnDefault: true,
defaultValue: '',
} as const;

export const useHakukohdeSearchParams = () => {
const [searchPhrase, setSearchPhrase] = useQueryState(
'hksearch',
DEFAULT_NUQS_OPTIONS,
);

const setSearchDebounce = useDebounce(
setSearchPhrase,
HAKU_SEARCH_PHRASE_DEBOUNCE_DELAY,
);

return {
searchPhrase,
setSearchPhrase: setSearchDebounce,
};
};

export const useHakukohdeSearchResults = (hakuOid: string) => {
const { translateEntity } = useTranslations();
const { data: userPermissions } = useUserPermissions();

const { data: hakukohteet } = useSuspenseQuery({
queryKey: ['getHakukohteet' + hakuOid],
queryFn: () => getHakukohteet(hakuOid, userPermissions),
});

const { searchPhrase } = useHakukohdeSearchParams();

const results = useMemo(() => {
return hakukohteet.filter(
(hakukohde: Hakukohde) =>
translateEntity(hakukohde.nimi)
.toLowerCase()
.includes(searchPhrase?.toLowerCase() ?? '') ||
translateEntity(hakukohde.jarjestyspaikkaHierarkiaNimi)
.toLowerCase()
.includes(searchPhrase?.toLowerCase() || '') ||
hakukohde.oid.includes(searchPhrase?.toLowerCase() || ''),
);
}, [hakukohteet, searchPhrase]);

return {
results,
};
};
11 changes: 8 additions & 3 deletions src/app/lokalisaatio/fi.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"eiosumia": "Ei hakutuloksia",
"persivu": "Näytä per sivu:",
"ladataan": "ladataan...",
"lisatiedot": "Lisätiedot"
"lisatiedot": "Lisätiedot",
"haku": "Haku"
},
"virhe": {
"palvelin": "Palvelinpyyntö epäonnistui!",
Expand All @@ -23,12 +24,16 @@
"otsikko": "Haut",
"hakutapa": "Hakutapa",
"alkamiskausi": "Koulutuksen alkamiskausi",
"haehakukohde": "Hae hakukohteita",
"hakukohteet": "Hakukohteet",
"hakukohdetta": "hakukohdetta",
"hae": "Hae hakuja",
"maara": "Hakuja:"
"maara": "Hakuja:",
"pienenna": "Pienennä hakukohteitten listaus",
"suurenna": "Suurenna hakukohteitten listaus"
},
"hakukohde": {
"valitse": "Valitse hakukohde",
"valitse": "Valitse tai hae hakukohde",
"oid": "Hakukohde oid: "
},
"perustiedot": {
Expand Down
Loading

0 comments on commit 51a5c04

Please sign in to comment.