diff --git a/src/api/schema.d.ts b/src/api/schema.d.ts index c430f07c..2852a98a 100644 --- a/src/api/schema.d.ts +++ b/src/api/schema.d.ts @@ -590,6 +590,8 @@ export interface components { trendi?: 'NOUSEVA' | 'LASKEVA'; /** Format: int32 */ tyollisyysNakyma?: number; + /** Format: int32 */ + aakkosIndeksi: number; }; SivuDtoTyomahdollisuusDto: { sisalto: components['schemas']['TyomahdollisuusDto'][]; @@ -1612,8 +1614,12 @@ export interface operations { }; mahdollisuudetCreateEhdotus: { parameters: { - query?: never; - header?: never; + query?: { + sort?: 'ASC' | 'DESC'; + }; + header?: { + 'Content-Language'?: 'fi' | 'sv' | 'en'; + }; path?: never; cookie?: never; }; diff --git a/src/routes/Tool/Tool.tsx b/src/routes/Tool/Tool.tsx index 6b7f67db..4b596ef1 100644 --- a/src/routes/Tool/Tool.tsx +++ b/src/routes/Tool/Tool.tsx @@ -186,7 +186,7 @@ const YourOpportunitiesPagination = ({ } }; - return toolStore.tyomahdollisuudet.length > 0 ? ( + return toolStore.mixedMahdollisuudet.length > 0 ? (
void onPageChange(data)} />
diff --git a/src/stores/useToolStore/index.ts b/src/stores/useToolStore/index.ts index b853c27e..cd943e74 100644 --- a/src/stores/useToolStore/index.ts +++ b/src/stores/useToolStore/index.ts @@ -47,6 +47,8 @@ interface ToolState { ehdotuksetCount: Record; sorting: OpportunitySortingValue; filter: OpportunityFilterValue; + itemCount: number; + previousEhdotusUpdateLang: string; reset: () => void; setTavoitteet: (state: ToolState['tavoitteet']) => void; @@ -59,12 +61,13 @@ interface ToolState { setOsaamisKiinnostusPainotus: (state: number) => void; setRajoitePainotus: (state: number) => void; - updateEhdotukset: (signal?: AbortSignal) => Promise; + updateEhdotukset: (lang: string, signal?: AbortSignal) => Promise; fetchMahdollisuudetPage: (signal?: AbortSignal, page?: number) => Promise; updateEhdotuksetAndTyomahdollisuudet: () => Promise; setSorting: (state: string) => void; setFilter: (state: string) => void; + updateItemCount: () => void; } export const useToolStore = create()( @@ -89,6 +92,8 @@ export const useToolStore = create()( ehdotuksetCount: { TYOMAHDOLLISUUS: 0, KOULUTUSMAHDOLLISUUS: 0 }, sorting: DEFAULT_SORTING, filter: DEFAULT_FILTER, + itemCount: 0, + previousEhdotusUpdateLang: '', reset: () => { set({ tavoitteet: {}, @@ -98,6 +103,7 @@ export const useToolStore = create()( tyomahdollisuudet: [], koulutusmahdollisuudet: [], mixedMahdollisuudet: [], + itemCount: 0, }); }, @@ -115,7 +121,7 @@ export const useToolStore = create()( setRajoitePainotus: (state: number) => { set({ rajoitePainotus: state }); }, - updateEhdotukset: async (signal?: AbortSignal) => { + updateEhdotukset: async (lang: string, signal?: AbortSignal) => { const { osaamiset, kiinnostukset, osaamisKiinnostusPainotus, rajoitePainotus } = get(); set({ ehdotuksetLoading: true }); @@ -129,6 +135,7 @@ export const useToolStore = create()( rajoitePainotus: rajoitePainotus / 100, }, signal, + headers: { 'Content-Language': lang }, }); const mahdollisuusEhdotukset = ehdotusDataToRecord(mahdollisuusData ?? []); @@ -157,23 +164,23 @@ export const useToolStore = create()( } }, fetchMahdollisuudetPage: async (signal, newPage = 1) => { - const { filter, ehdotuksetPageSize } = get(); + const { filter, ehdotuksetPageSize, sorting } = get(); let ehdotukset = get().mahdollisuusEhdotukset; - if (Object.keys(ehdotukset).length === 0) { - await get().updateEhdotukset(signal); - ehdotukset = get().mahdollisuusEhdotukset; - } - - const allIds = Object.keys(ehdotukset ?? []); + // apply ID sorting and filter + const allSortedIds = await fetchSortedAndFilteredEhdotusIds(filter); set({ mahdollisuudetLoading: true }); try { const sortedMixedMahdollisuudet = []; + // paginate before fetch to fetch only the ids of selected newPage + const pagedIds = paginate(allSortedIds, newPage, ehdotuksetPageSize); + + // fetch tyomahdollisuudet of the page if ([filterValues.ALL, filterValues.TYOMAHDOLLISUUS].includes(filter)) { - const ids = allIds.filter((id) => ehdotukset[id].tyyppi === 'TYOMAHDOLLISUUS'); - const tyomahdollisuudet = await fetchTyomahdollisuudet(ids, newPage, ehdotuksetPageSize).then((data) => + const ids = pagedIds.filter((id) => ehdotukset[id].tyyppi === 'TYOMAHDOLLISUUS'); + const tyomahdollisuudet = await fetchTyomahdollisuudet(ids).then((data) => data.tyomahdollisuusResults.map( (tm) => ({ ...tm, mahdollisuusTyyppi: 'TYOMAHDOLLISUUS' }) as TypedMahdollisuus, ), @@ -182,13 +189,14 @@ export const useToolStore = create()( set({ tyomahdollisuudet }); } + // fetch koulutusmahdollisuudet of the page if ([filterValues.ALL, filterValues.KOULUTUSMAHDOLLISUUS].includes(filter)) { - const ids = allIds.filter((id) => ehdotukset[id].tyyppi === 'KOULUTUSMAHDOLLISUUS'); - const koulutusmahdollisuudet = await fetchKoulutusMahdollisuudet(ids, newPage, ehdotuksetPageSize).then( - (data) => - data.koulutusmahdollisuusResults.map( - (km) => ({ ...km, mahdollisuusTyyppi: 'KOULUTUSMAHDOLLISUUS' }) as TypedMahdollisuus, - ), + const ids = pagedIds.filter((id) => ehdotukset[id].tyyppi === 'KOULUTUSMAHDOLLISUUS'); + + const koulutusmahdollisuudet = await fetchKoulutusMahdollisuudet(ids).then((data) => + data.koulutusmahdollisuusResults.map( + (km) => ({ ...km, mahdollisuusTyyppi: 'KOULUTUSMAHDOLLISUUS' }) as TypedMahdollisuus, + ), ); sortedMixedMahdollisuudet.push(...koulutusmahdollisuudet); @@ -199,10 +207,13 @@ export const useToolStore = create()( }); } + // Apply final sorting of page items based on selected sorting sortedMixedMahdollisuudet.sort((a, b) => - get().sorting === sortingValues.RELEVANCE - ? (ehdotukset[b.id]?.pisteet ?? 0) - (ehdotukset[a.id]?.pisteet ?? 0) - : a.otsikko[i18n.language].localeCompare(b.otsikko[i18n.language]), + sorting === sortingValues.RELEVANCE + ? // sort by scores + (ehdotukset[b.id]?.pisteet ?? 0) - (ehdotukset[a.id]?.pisteet ?? 0) + : // sort by backend provided lexicalOrder + ehdotukset[a.id].aakkosIndeksi - ehdotukset[b.id].aakkosIndeksi, ); set({ @@ -218,18 +229,36 @@ export const useToolStore = create()( mahdollisuudetLoading: false, }); } + + async function fetchSortedAndFilteredEhdotusIds(filter: OpportunityFilterValue) { + if (Object.keys(ehdotukset).length === 0 || i18n.language !== get().previousEhdotusUpdateLang) { + await get().updateEhdotukset(i18n.language, signal); + ehdotukset = get().mahdollisuusEhdotukset; + set({ previousEhdotusUpdateLang: i18n.language }); + } + + return Object.entries(ehdotukset ?? []) + .filter(([, meta]) => (filter === 'ALL' ? true : filter === meta.tyyppi)) + .sort(([, metadataA], [, metadataB]) => + sorting === sortingValues.RELEVANCE + ? (metadataB?.pisteet ?? 0) - (metadataA?.pisteet ?? 0) + : (metadataA?.aakkosIndeksi ?? 0) - (metadataB?.aakkosIndeksi ?? 0), + ) + .map(([key]) => key); + } }, updateEhdotuksetAndTyomahdollisuudet: async () => { - const { updateEhdotukset, fetchMahdollisuudetPage, updateSuosikit } = get(); + const { updateEhdotukset, fetchMahdollisuudetPage, updateSuosikit, updateItemCount } = get(); abortController.abort(); abortController = new AbortController(); const signal = abortController.signal; - await updateEhdotukset(signal); + await updateEhdotukset(i18n.language, signal); await fetchMahdollisuudetPage(signal, 1); await updateSuosikit(); + updateItemCount(); }, toggleSuosikki: async (suosionKohdeId: string, tyyppi: MahdollisuusTyyppi) => { @@ -281,6 +310,21 @@ export const useToolStore = create()( setFilter: (state) => { set({ filter: state as OpportunityFilterValue, ehdotuksetPageNr: 1 }); void get().fetchMahdollisuudetPage(abortController.signal, 1); + get().updateItemCount(); + }, + updateItemCount: () => { + const { filter, ehdotuksetCount } = get(); + switch (filter) { + case 'TYOMAHDOLLISUUS': + set({ itemCount: ehdotuksetCount.TYOMAHDOLLISUUS }); + break; + case 'KOULUTUSMAHDOLLISUUS': + set({ itemCount: ehdotuksetCount.KOULUTUSMAHDOLLISUUS }); + break; + case 'ALL': + set({ itemCount: ehdotuksetCount.TYOMAHDOLLISUUS + ehdotuksetCount.KOULUTUSMAHDOLLISUUS }); + break; + } }, }), { @@ -290,26 +334,32 @@ export const useToolStore = create()( ), ); -async function fetchKoulutusMahdollisuudet(ids: string[], newPage: number, pageSize: number) { - const { data: koulutusmahdollisuudet } = await client.GET('/api/koulutusmahdollisuudet', { - params: { - query: { - id: paginate(ids, newPage, pageSize), - }, - }, - }); +async function fetchKoulutusMahdollisuudet(ids: string[]) { + const { data: koulutusmahdollisuudet } = + ids.length > 0 + ? await client.GET('/api/koulutusmahdollisuudet', { + params: { + query: { + id: ids, + }, + }, + }) + : { data: undefined }; return { koulutusmahdollisuusResults: koulutusmahdollisuudet?.sisalto ?? [] }; } -async function fetchTyomahdollisuudet(ids: string[], newPage: number, pageSize: number) { - const { data: tyomahdollisuudet } = await client.GET('/api/tyomahdollisuudet', { - params: { - query: { - id: paginate(ids, newPage, pageSize), - }, - }, - }); +async function fetchTyomahdollisuudet(ids: string[]) { + const { data: tyomahdollisuudet } = + ids.length > 0 + ? await client.GET('/api/tyomahdollisuudet', { + params: { + query: { + id: ids, + }, + }, + }) + : { data: undefined }; return { tyomahdollisuusResults: tyomahdollisuudet?.sisalto ?? [] }; }