Skip to content

Commit

Permalink
Merge pull request #1440 from danskernesdigitalebibliotek/DDFHER-76-f…
Browse files Browse the repository at this point in the history
…iltering-on-onshelf-location-and-sublocation-should-be-reflected-in-links

DDFHER-76 - Add "location" and "sublocation" URL parameters in advanced search (CQL)
  • Loading branch information
kasperbirch1 authored Oct 1, 2024
2 parents dd12bd0 + 32e9fc1 commit dd03394
Show file tree
Hide file tree
Showing 25 changed files with 226 additions and 62 deletions.
14 changes: 6 additions & 8 deletions .storybook/preview.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
import "../src/components/components.scss";
import "@danskernesdigitalebibliotek/dpl-design-system/build/css/base.css";
import {
setToken,
TOKEN_LIBRARY_KEY,
TOKEN_USER_KEY
} from "../src/core/token";
import { setToken, TOKEN_LIBRARY_KEY, TOKEN_USER_KEY } from "../src/core/token";
import "../src/core/mount";
import Store from "../src/components/store";

import React from "react";
import { withErrorBoundary } from "react-error-boundary";
import ErrorBoundaryAlert from "../src/components/error-boundary-alert/ErrorBoundaryAlert";


const getSessionStorage = (type) => window.sessionStorage.getItem(type);
const userToken =
process.env.STORYBOOK_USER_TOKEN ?? getSessionStorage(TOKEN_USER_KEY);
Expand Down Expand Up @@ -51,7 +45,11 @@ const App = ({ story }) => <Store>{WrappedStory(story)}</Store>;
// Consideration for the future - using addon-redux could bring value.
// It wasn't implemented to begin with because it wasn't compatible with Storybook 6.
export const decorators = [
Story => <><App story={Story} /></>
(Story) => (
<>
<App story={Story} />
</>
)
];

export const parameters = {
Expand Down
20 changes: 20 additions & 0 deletions src/apps/advanced-search/AdvancedSearch.dev.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,26 @@ export default {
name: "Advanced search filter - holding status",
defaultValue: "Holding Status On Shelf",
control: { type: "text" }
},
advancedSearchFilterLocationText: {
name: "Advanced search filter - location",
defaultValue: "Location",
control: { type: "text" }
},
advancedSearchFilterLocationDescriptionText: {
name: "Advanced search filter - location description",
defaultValue: "Add a comma separated list for multiple locations",
control: { type: "text" }
},
advancedSearchFilterSublocationText: {
name: "Advanced search filter - sublocation",
defaultValue: "Sublocation",
control: { type: "text" }
},
advancedSearchFilterSublocationDescriptionText: {
name: "Advanced search filter - sublocation description",
defaultValue: "Add a comma separated list for multiple sublocations",
control: { type: "text" }
}
}
} as ComponentMeta<typeof AdvancedSearchEntry>;
Expand Down
4 changes: 4 additions & 0 deletions src/apps/advanced-search/AdvancedSearch.entry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ interface AdvancedSearchEntryTextProps {
advancedSearchFilterHoldingStatusText: string;
advancedSearchInputLabelText: string;
advancedSearchRemoveRowText: string;
advancedSearchFilterLocationText: string;
advancedSearchFilterLocationDescriptionText: string;
advancedSearchFilterSublocationText: string;
advancedSearchFilterSublocationDescriptionText: string;
}

interface AdvancedSearchEntryConfigProps {
Expand Down
38 changes: 35 additions & 3 deletions src/apps/advanced-search/AdvancedSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import React, { useEffect, useState } from "react";
import { useEffectOnce } from "react-use";
import AdvancedSearchHeader from "./AdvancedSearchHeader";
import AdvancedSearchResult from "./AdvancedSearchResults";
import { translateSearchObjectToCql } from "./helpers";
import {
commaSeparatedStringToArray,
translateSearchObjectToCql
} from "./helpers";
import { AdvancedSearchQuery } from "./types";
import {
getUrlQueryParam,
Expand Down Expand Up @@ -30,17 +33,29 @@ const AdvancedSearch: React.FC<AdvancedSearchProps> = ({ pageSize }) => {
const [executedQuery, setExecutedQuery] = useState<string | null>(null);

const [locationFilter, setLocationFilter] = useState<LocationFilter>({});

const handleLocationChange = (location: string) => {
setLocationFilter((prevFilter) => ({
...prevFilter,
location: [location]
location: commaSeparatedStringToArray(location)
}));
if (location) {
setQueryParametersInUrl({ location });
} else {
removeQueryParametersFromUrl("location");
}
};

const handleSublocationChange = (sublocation: string) => {
setLocationFilter((prevFilter) => ({
...prevFilter,
sublocation: [sublocation]
sublocation: commaSeparatedStringToArray(sublocation)
}));
if (sublocation) {
setQueryParametersInUrl({ sublocation });
} else {
removeQueryParametersFromUrl("sublocation");
}
};

const [onShelf, setOnShelf] = useState(false);
Expand Down Expand Up @@ -83,6 +98,22 @@ const AdvancedSearch: React.FC<AdvancedSearchProps> = ({ pageSize }) => {
if (getUrlQueryParam("onshelf") === "true") {
setOnShelf(true);
}

const locationParam = getUrlQueryParam("location");
if (locationParam) {
setLocationFilter((prevFilter) => ({
...prevFilter,
location: commaSeparatedStringToArray(locationParam)
}));
}

const sublocationParam = getUrlQueryParam("sublocation");
if (sublocationParam) {
setLocationFilter((prevFilter) => ({
...prevFilter,
sublocation: commaSeparatedStringToArray(sublocationParam)
}));
}
});

useEffect(() => {
Expand Down Expand Up @@ -120,6 +151,7 @@ const AdvancedSearch: React.FC<AdvancedSearchProps> = ({ pageSize }) => {
setOnShelf={handleOnShelfChange}
onLocationChange={handleLocationChange}
onSublocationChange={handleSublocationChange}
locationFilter={locationFilter}
/>
)}
{executedQuery && (
Expand Down
6 changes: 5 additions & 1 deletion src/apps/advanced-search/AdvancedSearchHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
} from "./helpers";
import { Button } from "../../components/Buttons/Button";
import CheckBox from "../../components/checkbox/Checkbox";
import { LocationFilter } from "./LocationFilter";

export type AdvancedSearchHeaderProps = {
dataCy?: string;
Expand All @@ -33,6 +34,7 @@ export type AdvancedSearchHeaderProps = {
setOnShelf: (checked: boolean) => void;
onLocationChange: (location: string) => void;
onSublocationChange: (sublocation: string) => void;
locationFilter: LocationFilter;
};

const AdvancedSearchHeader: React.FC<AdvancedSearchHeaderProps> = ({
Expand All @@ -44,7 +46,8 @@ const AdvancedSearchHeader: React.FC<AdvancedSearchHeaderProps> = ({
onShelf,
setOnShelf,
onLocationChange,
onSublocationChange
onSublocationChange,
locationFilter
}) => {
const t = useText();
const [isFormMode, setIsFormMode] = useState<boolean>(true);
Expand Down Expand Up @@ -226,6 +229,7 @@ const AdvancedSearchHeader: React.FC<AdvancedSearchHeaderProps> = ({
handleOnShelfChange={handleOnShelfChange}
onLocationChange={onLocationChange}
onSublocationChange={onSublocationChange}
locationFilter={locationFilter}
/>
)}

Expand Down
6 changes: 4 additions & 2 deletions src/apps/advanced-search/AdvancedSearchResults.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,10 @@ const AdvancedSearchResult: React.FC<AdvancedSearchResultProps> = ({
filters: {
branchId: cleanBranches,
status: onShelf ? [HoldingsStatus.OnShelf] : [],
...(locationFilter?.location && { location: locationFilter.location }),
...(locationFilter?.sublocation && {
...(locationFilter?.location?.length && {
location: locationFilter.location
}),
...(locationFilter?.sublocation?.length && {
sublocation: locationFilter.sublocation
})
}
Expand Down
99 changes: 69 additions & 30 deletions src/apps/advanced-search/CqlSearchHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import React, { useEffect } from "react";
import React, { useEffect, useState } from "react";
import { useText } from "../../core/utils/text";
import CheckBox from "../../components/checkbox/Checkbox";
import TextInput from "../../components/atoms/input/TextInput";
import { LocationFilter } from "./LocationFilter";
import Textarea from "../../components/forms/textarea/Textarea";
import TextInput from "../../components/forms/input/TextInput";

export type CqlSearchHeaderProps = {
dataCy?: string;
Expand All @@ -11,6 +13,7 @@ export type CqlSearchHeaderProps = {
handleOnShelfChange: (newState: boolean) => void;
onLocationChange: (location: string) => void;
onSublocationChange: (sublocation: string) => void;
locationFilter: LocationFilter;
};

const CqlSearchHeader: React.FC<CqlSearchHeaderProps> = ({
Expand All @@ -20,7 +23,8 @@ const CqlSearchHeader: React.FC<CqlSearchHeaderProps> = ({
onShelf,
handleOnShelfChange,
onLocationChange,
onSublocationChange
onSublocationChange,
locationFilter
}) => {
const t = useText();

Expand All @@ -30,6 +34,31 @@ const CqlSearchHeader: React.FC<CqlSearchHeaderProps> = ({
}
}, [initialCql, setCql]);

// Local state is needed to track input values as plain strings,
// since onLocationChange expects a comma-separated string,
// while locationFilter location and sublocation are provided as arrays.
const [inputValues, setInputValues] = useState({
location: locationFilter?.location?.join(", ") ?? "",
sublocation: locationFilter?.sublocation?.join(", ") ?? ""
});

const handleInputChange = (
name: "location" | "sublocation",
value: string
) => {
setInputValues((prevValues) => ({
...prevValues,
[name]: value
}));

if (name === "location") {
onLocationChange(value);
}
if (name === "sublocation") {
onSublocationChange(value);
}
};

return (
<>
<h1
Expand All @@ -38,33 +67,43 @@ const CqlSearchHeader: React.FC<CqlSearchHeaderProps> = ({
>
{t("cqlSearchTitleText")}
</h1>
<textarea
className="advanced-search__cql-input focus-styling__input"
cols={100}
rows={5}
placeholder="e.g. title=snemand*"
data-cy={`${dataCy}-input`}
onChange={(e) => setCql(e.target.value)}
defaultValue={initialCql}
/>
<TextInput
id="location"
label="Location"
type="text"
onChange={(location) => onLocationChange(location)}
/>
<TextInput
id="location"
label="Sublocation"
type="text"
onChange={(sublocation) => onSublocationChange(sublocation)}
/>
<CheckBox
id="on-shelf"
selected={onShelf}
onChecked={handleOnShelfChange}
label={t("advancedSearchFilterHoldingStatusText")}
/>
<form className="advanced-search-cql-form">
<Textarea
id="cql"
label="CQL"
className="advanced-search-cql-form__input focus-styling__input"
cols={100}
rows={5}
placeholder="e.g. 'harry potter'"
dataCy={`${dataCy}-input`}
onChange={(e) => setCql(e.target.value)}
defaultValue={initialCql}
/>
<TextInput
id="location"
label={t("advancedSearchFilterLocationText")}
description={t("advancedSearchFilterLocationDescriptionText")}
type="text"
onChange={(location) => handleInputChange("location", location)}
value={inputValues.location}
/>
<TextInput
id="sublocation"
label={t("advancedSearchFilterSublocationText")}
description={t("advancedSearchFilterSublocationDescriptionText")}
type="text"
onChange={(sublocation) =>
handleInputChange("sublocation", sublocation)
}
value={inputValues.sublocation}
/>
<CheckBox
id="on-shelf"
selected={onShelf}
onChecked={handleOnShelfChange}
label={t("advancedSearchFilterHoldingStatusText")}
/>
</form>
</>
);
};
Expand Down
4 changes: 2 additions & 2 deletions src/apps/advanced-search/LocationFilter.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export interface LocationFilter {
location?: [string];
sublocation?: [string];
location?: string[];
sublocation?: string[];
}
7 changes: 7 additions & 0 deletions src/apps/advanced-search/helpers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,4 +130,11 @@ export const shouldAdvancedSearchButtonBeDisabled = (
}
};

export const commaSeparatedStringToArray = (input: string): string[] => {
return input
.split(",")
.map((s) => s.trim())
.filter((s) => s.length > 0);
};

export default {};
1 change: 0 additions & 1 deletion src/apps/dashboard/dashboard.mount.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import addMount from "../../core/addMount";

import DashBoard from "./dashboard.entry";

addMount({ appName: "DashBoard", app: DashBoard });
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { ComponentMeta, ComponentStory } from "@storybook/react";
import React from "react";

import globalTextArgs, {
GlobalEntryTextProps
} from "../../../core/storybook/globalTextArgs";
import serviceUrlArgs from "../../../core/storybook/serviceUrlArgs";

import MaterialGridAutomatic, {
MaterialGridAutomaticEntryProps
} from "./MaterialGridAutomatic.entry";
Expand Down
2 changes: 0 additions & 2 deletions src/apps/material-grid/manual/MaterialGridManual.dev.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { ComponentMeta, ComponentStory } from "@storybook/react";
import React from "react";

import globalTextArgs, {
GlobalEntryTextProps
} from "../../../core/storybook/globalTextArgs";
import serviceUrlArgs from "../../../core/storybook/serviceUrlArgs";

import MaterialGridManual, {
MaterialGridManualEntryProps
} from "./MaterialGridManual.entry";
Expand Down
1 change: 0 additions & 1 deletion src/apps/menu/menu-not-logged-in/menu-not-logged-in.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { useUrls } from "../../../core/utils/url";
import { useText } from "../../../core/utils/text";
import Link from "../../../components/atoms/links/Link";
import Modal from "../../../core/utils/modal";

import { getModalIds } from "../../../core/utils/helpers/modal-helpers";

const MenuNotLoggedInContent: FC = () => {
Expand Down
2 changes: 1 addition & 1 deletion src/apps/patron-page/sections/PincodeSection.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React, { useEffect, useState, FC } from "react";
import clsx from "clsx";
import TextInput from "../../../components/atoms/input/TextInput";
import { useConfig } from "../../../core/utils/config";
import { useText } from "../../../core/utils/text";
import TextInput from "../../../components/forms/input/TextInput";

interface PincodeSectionProps {
changePincode: (newPin: string | null) => void;
Expand Down
Loading

0 comments on commit dd03394

Please sign in to comment.