Skip to content

Commit 44af05f

Browse files
committed
feat: initial work on new search
1 parent b68ff22 commit 44af05f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+2081
-2380
lines changed

e2e/apiMocks/unauthenticated_search.spec.ts_LTI_contains_action_elements.har

+1-1
Large diffs are not rendered by default.

e2e/apiMocks/unauthenticated_search.spec.ts_contains_search_bar.har

+1-1
Large diffs are not rendered by default.

e2e/specs/unauthenticated/search.spec.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@ test("contains search bar", async ({ page }) => {
1313
await page.goto("/search/?disableSSR=true");
1414
await mockWaitResponse(page, "**/graphql-api/*");
1515

16-
const topicHeader = page.getByRole("heading").getByText("Emne");
17-
await expect(topicHeader).toBeVisible();
18-
1916
const input = page.getByRole("searchbox");
2017

2118
await expect(input).toBeVisible();
19+
20+
const subjectMaterialCheckbox = page.getByLabel("Læringssti");
21+
await expect(subjectMaterialCheckbox).toBeVisible();
2222
});
2323

2424
test("LTI contains action elements", async ({ page }) => {

src/App.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ import H5pPage from "./containers/ResourceEmbed/H5pPage";
4747
import ImagePage from "./containers/ResourceEmbed/ImagePage";
4848
import VideoPage from "./containers/ResourceEmbed/VideoPage";
4949
import ResourcePage from "./containers/ResourcePage/ResourcePage";
50-
import SearchPage from "./containers/SearchPage/SearchPage";
50+
import { SearchPage } from "./containers/SearchPage/SearchPage";
5151
import SharedFolderPage from "./containers/SharedFolderPage/SharedFolderPage";
5252
import SubjectPage from "./containers/SubjectPage/SubjectPage";
5353
import { TopicPage } from "./containers/TopicPage/TopicPage";

src/LtiContext.tsx

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* Copyright (c) 2025-present, NDLA.
3+
*
4+
* This source code is licensed under the GPLv3 license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
*/
8+
9+
import { createContext, ReactNode, useContext } from "react";
10+
11+
const LtiContext = createContext<boolean>(false);
12+
13+
interface Props {
14+
children?: ReactNode;
15+
}
16+
17+
export const LtiContextProvider = ({ children }: Props) => (
18+
<LtiContext.Provider value={true}>{children}</LtiContext.Provider>
19+
);
20+
21+
export const useLtiContext = () => {
22+
const context = useContext(LtiContext);
23+
return context;
24+
};

src/components/CompetenceGoalTab.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ const CoreElementWrapper = styled("div", {
146146
},
147147
});
148148

149-
export const CompetenceItem = ({ item, isOembed, showLinks = false }: CompetenceItemProps) => {
149+
const CompetenceItem = ({ item, isOembed, showLinks = false }: CompetenceItemProps) => {
150150
const { t } = useTranslation();
151151
return (
152152
<OuterList>

src/components/CompetenceGoals.tsx

+6-2
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,9 @@ export const groupCompetenceGoals = (
130130
goalType: CompetenceGoalsType,
131131
subjectId?: string,
132132
): ElementType["groupedCompetenceGoals"] => {
133-
const searchUrl = subjectId ? `/search?subjects=${subjectId}&grepCodes=` : "/search?grepCodes=";
133+
const searchUrl = subjectId
134+
? `/search?subjects=${subjectId.replace("urn:subject:", "")}&grepCodes=`
135+
: "/search?grepCodes=";
134136
const curriculumElements = getUniqueCurriculums(competenceGoals).map((curriculum) => ({
135137
title: `${curriculum?.title} (${curriculum?.id})`,
136138
elements: getUniqueCompetenceGoalSet(competenceGoals, curriculum!.id).map((competenceGoalSet) => ({
@@ -146,7 +148,9 @@ export const groupCoreElements = (
146148
coreElements: GQLCoreElement[],
147149
subjectId?: string,
148150
): ElementType["groupedCoreElementItems"] => {
149-
const searchUrl = subjectId ? `/search?subjects=${subjectId}&grepCodes=` : "/search?grepCodes=";
151+
const searchUrl = subjectId
152+
? `/search?subjects=${subjectId.replace("urn:subject:", "")}&grepCodes=`
153+
: "/search?grepCodes=";
150154
return getUniqueCurriculums(coreElements).map((curriculum) => ({
151155
title: `${curriculum?.title} (${curriculum!.id})`,
152156
elements: coreElements

src/components/__tests__/__snapshots__/competenceGoals-test.ts.snap

+3-3
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@ exports[`That addUrl and subjectId params adds extended url to element 1`] = `
1010
"id": "KM2648",
1111
"text": "carry out basic first aid (KM2648)",
1212
"type": "LK20",
13-
"url": "/search?subjects=urn:subject:20&grepCodes=KM2648",
13+
"url": "/search?subjects=20&grepCodes=KM2648",
1414
},
1515
{
1616
"id": "KM2647",
1717
"text": "discuss and give examples of what each person and society can do to improve their own health and public health (KM2647)",
1818
"type": "LK20",
19-
"url": "/search?subjects=urn:subject:20&grepCodes=KM2647",
19+
"url": "/search?subjects=20&grepCodes=KM2647",
2020
},
2121
],
2222
"id": "KV244",
@@ -33,7 +33,7 @@ exports[`That addUrl and subjectId params adds extended url to element 1`] = `
3333
"id": "KM1232",
3434
"text": "explain present-day changes in spoken Norwegian and reflect on relationships between language, culture and identity (KM1232)",
3535
"type": "LK20",
36-
"url": "/search?subjects=urn:subject:20&grepCodes=KM1232",
36+
"url": "/search?subjects=20&grepCodes=KM1232",
3737
},
3838
],
3939
"id": "KV115",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* Copyright (c) 2025-present, NDLA.
3+
*
4+
* This source code is licensed under the GPLv3 license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
*/
8+
9+
import { styled } from "@ndla/styled-system/jsx";
10+
11+
export const FilterContainer = styled("div", {
12+
base: {
13+
border: "1px solid",
14+
borderColor: "stroke.subtle",
15+
borderRadius: "xsmall",
16+
padding: "xsmall",
17+
display: "flex",
18+
flexDirection: "column",
19+
gap: "xsmall",
20+
},
21+
});
+167
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
/**
2+
* Copyright (c) 2025-present, NDLA.
3+
*
4+
* This source code is licensed under the GPLv3 license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
*/
8+
9+
import { useCallback, useEffect, useMemo } from "react";
10+
import { useTranslation } from "react-i18next";
11+
import { gql, useQuery } from "@apollo/client";
12+
import { CloseLine } from "@ndla/icons";
13+
import { Button, Heading, Spinner } from "@ndla/primitives";
14+
import { styled } from "@ndla/styled-system/jsx";
15+
import { FilterContainer } from "./FilterContainer";
16+
import { RESOURCE_NODE_TYPE } from "./searchUtils";
17+
import { useStableSearchPageParams } from "./useStableSearchPageParams";
18+
import { GQLGrepFilterQuery, GQLGrepFilterQueryVariables } from "../../graphqlTypes";
19+
20+
const FiltersWrapper = styled("div", {
21+
base: {
22+
display: "flex",
23+
gap: "small",
24+
flexWrap: "wrap",
25+
},
26+
});
27+
28+
// const CompetenceWrapper = styled("div", {
29+
// base: {
30+
// display: "flex",
31+
// flexDirection: "column",
32+
// gap: "small",
33+
// },
34+
// });
35+
// const CompetenceItemWrapper = styled("div", {
36+
// base: {
37+
// display: "flex",
38+
// flexDirection: "column",
39+
// gap: "xxsmall",
40+
// },
41+
// });
42+
43+
const grepFilterQuery = gql`
44+
query grepFilter($codes: [String!], $language: String!) {
45+
competenceGoals(codes: $codes, language: $language) {
46+
id
47+
title
48+
type
49+
curriculum {
50+
id
51+
title
52+
}
53+
competenceGoalSet {
54+
id
55+
title
56+
}
57+
}
58+
coreElements(codes: $codes, language: $language) {
59+
id
60+
title
61+
description
62+
}
63+
}
64+
`;
65+
66+
export const GrepFilter = () => {
67+
const [searchParams, setSearchParams] = useStableSearchPageParams();
68+
const { t, i18n } = useTranslation();
69+
const nodeType = searchParams.get("type");
70+
71+
const codes = useMemo(() => searchParams.get("grepCodes")?.split(",") ?? [], [searchParams]);
72+
73+
const grepQuery = useQuery<GQLGrepFilterQuery, GQLGrepFilterQueryVariables>(grepFilterQuery, {
74+
variables: { language: i18n.language, codes },
75+
skip: !codes.length || (!!nodeType && nodeType !== RESOURCE_NODE_TYPE),
76+
});
77+
78+
useEffect(() => {
79+
if (nodeType && nodeType !== RESOURCE_NODE_TYPE && codes.length) {
80+
setSearchParams({ grepCodes: null });
81+
}
82+
}, [codes.length, nodeType, setSearchParams]);
83+
84+
// const groupedCompetenceGoals = useMemo(() => {
85+
// return groupCompetenceGoals(grepQuery.data?.competenceGoals ?? [], true, "LK20");
86+
// }, [grepQuery.data?.competenceGoals]);
87+
//
88+
// const mappedCoreElements = useMemo(() => {
89+
// return (
90+
// grepQuery.data?.coreElements?.map((element) => ({
91+
// title: element.title,
92+
// text: element.description ?? "",
93+
// id: element.id,
94+
// url: "",
95+
// })) ?? []
96+
// );
97+
// }, [grepQuery.data?.coreElements]);
98+
99+
const data = grepQuery.data ?? grepQuery.previousData;
100+
101+
const grepElements = useMemo(
102+
() => [data?.competenceGoals, data?.coreElements].filter((arr) => !!arr).flat(),
103+
[data?.competenceGoals, data?.coreElements],
104+
);
105+
106+
const onRemoveCode = useCallback(
107+
(value: string) => {
108+
const newCodes = codes.filter((code) => code !== value);
109+
setSearchParams({ grepCodes: newCodes.join(",") });
110+
},
111+
[codes, setSearchParams],
112+
);
113+
114+
if (!grepQuery.loading && !grepQuery.data?.competenceGoals?.length && !grepQuery.data?.coreElements?.length) {
115+
return;
116+
}
117+
118+
return (
119+
<FilterContainer>
120+
<Heading asChild consumeCss textStyle="label.medium" fontWeight="bold">
121+
<h3>{t("searchPage.grepFilter.heading")}</h3>
122+
</Heading>
123+
{/* <CompetenceWrapper> */}
124+
{/* {!!groupedCompetenceGoals?.length && ( */}
125+
{/* <CompetenceItemWrapper> */}
126+
{/* <Heading textStyle="title.large" asChild consumeCss> */}
127+
{/* <h4>{t("competenceGoals.competenceGoalItem.title")}</h4> */}
128+
{/* </Heading> */}
129+
{/* {groupedCompetenceGoals.map((goal, index) => ( */}
130+
{/* <CompetenceItem item={goal} key={index} /> */}
131+
{/* ))} */}
132+
{/* </CompetenceItemWrapper> */}
133+
{/* )} */}
134+
{/* {!!grepQuery.data?.coreElements?.length && ( */}
135+
{/* <CompetenceItemWrapper> */}
136+
{/* <Heading textStyle="title.large" asChild consumeCss> */}
137+
{/* <h2>{t("competenceGoals.competenceTabCorelabel")}</h2> */}
138+
{/* </Heading> */}
139+
{/* <CompetenceItem item={{ elements: mappedCoreElements }} /> */}
140+
{/* </CompetenceItemWrapper> */}
141+
{/* )} */}
142+
{/* </CompetenceWrapper> */}
143+
{!!grepQuery.loading && !grepQuery.previousData && <Spinner />}
144+
<FiltersWrapper>
145+
{codes.map((grep) => {
146+
const item = grepElements.find((g) => g.id === grep);
147+
if (!item) return null;
148+
return (
149+
<Button
150+
key={item.id}
151+
size="small"
152+
variant="primary"
153+
onClick={() => onRemoveCode(item.id)}
154+
aria-label={t("searchPage.grepFilter.removeFilter", { code: item.id, title: item.title })}
155+
title={t("searchPage.grepFilter.removeFilter", { code: item.id, title: item.title })}
156+
>
157+
{item.id}
158+
{" - "}
159+
{item.title}
160+
<CloseLine />
161+
</Button>
162+
);
163+
})}
164+
</FiltersWrapper>
165+
</FilterContainer>
166+
);
167+
};

0 commit comments

Comments
 (0)