Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add accessType to search pages #1555

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 19 additions & 4 deletions apps/ui/components/search/ReservationUnitCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
IconSize,
ButtonSize,
ButtonVariant,
IconLock,
} from "hds-react";
import React from "react";
import { useTranslation } from "next-i18next";
Expand All @@ -32,7 +33,7 @@ export function ReservationUnitCard({
selectReservationUnit,
containsReservationUnit,
removeReservationUnit,
}: CardProps): JSX.Element {
}: Readonly<CardProps>): JSX.Element {
const { t } = useTranslation();

const name = getReservationUnitName(reservationUnit);
Expand Down Expand Up @@ -79,14 +80,28 @@ export function ReservationUnitCard({
}),
});
}
if (reservationUnit.currentAccessType) {
infos.push({
icon: (
<IconLock
aria-hidden="false"
aria-label={t("reservationUnit:accessType")}
size={IconSize.Small}
/>
),
value: t(
`reservationUnit:accessTypes.${reservationUnit.currentAccessType}`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this be null?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't be possible to my knowledge, as all reservation units have an accessType (which currentAccessType defaults to, if it's not specified separately)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

about to change

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shall be null until the information has been filled in on the reservation units (esp. production) since even disregarding keyless access some spaces are currently opened by staff or physical key needs to be procured beforehand, so the backend defaulting to direct access would be misleading

),
});
}

const buttons = [];
if (containsReservationUnit(reservationUnit)) {
buttons.push(
<Button
size={ButtonSize.Small}
variant={ButtonVariant.Primary}
iconEnd={<IconCheck aria-hidden="true" />}
iconEnd={<IconCheck />}
onClick={() => removeReservationUnit(reservationUnit)}
data-testid="reservation-unit-card__button--select"
key={t("common:removeReservationUnit")}
Expand All @@ -99,7 +114,7 @@ export function ReservationUnitCard({
<Button
size={ButtonSize.Small}
variant={ButtonVariant.Secondary}
iconEnd={<IconPlus aria-hidden="true" />}
iconEnd={<IconPlus />}
onClick={() => selectReservationUnit(reservationUnit)}
data-testid="reservation-unit-card__button--select"
key={t("common:selectReservationUnit")}
Expand All @@ -116,7 +131,7 @@ export function ReservationUnitCard({
data-testid="reservation-unit-card__button--link"
key="show"
>
<IconLinkExternal aria-hidden="true" />
<IconLinkExternal />
{t("common:show")}
</ButtonLikeLink>
);
Expand Down
51 changes: 34 additions & 17 deletions apps/ui/components/search/SeasonalSearchForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,11 @@ import { useSearchModify } from "@/hooks/useSearchValues";
import { FilterTagList } from "./FilterTagList";
import { ControlledSelect } from "common/src/components/form/ControlledSelect";
import { BottomContainer, Filters, StyledSubmitButton } from "./styled";
import {
mapQueryParamToNumber,
mapQueryParamToNumberArray,
mapSingleParamToFormValue,
} from "@/modules/search";
import { mapParamToNumber } from "@/modules/search";
import SingleLabelInputGroup from "@/components/common/SingleLabelInputGroup";
import { type URLSearchParams } from "node:url";
import { useSearchParams } from "next/navigation";
import { type ReadonlyURLSearchParams, useSearchParams } from "next/navigation";
import { AccessType } from "@gql/gql-types";
import { toNumber } from "common/src/helpers";

const filterOrder = [
"applicationRound",
Expand All @@ -29,6 +26,7 @@ const filterOrder = [
"reservationUnitTypes",
"unit",
"purposes",
"accessType",
] as const;

type FormValues = {
Expand All @@ -38,19 +36,22 @@ type FormValues = {
reservationUnitTypes: number[];
purposes: number[];
textSearch: string;
accessType: string[];
};

// TODO combine as much as possible with the one in single-search (move them to a common place)
function mapQueryToForm(query: URLSearchParams): FormValues {
function mapQueryToForm(params: ReadonlyURLSearchParams): FormValues {
return {
purposes: mapQueryParamToNumberArray(query.getAll("purposes")),
unit: mapQueryParamToNumberArray(query.getAll("unit")),
reservationUnitTypes: mapQueryParamToNumberArray(
query.getAll("reservationUnitTypes")
purposes: mapParamToNumber(params.getAll("purposes"), 1),
unit: mapParamToNumber(params.getAll("unit"), 1),
reservationUnitTypes: mapParamToNumber(
params.getAll("reservationUnitTypes"),
1
),
minPersons: mapQueryParamToNumber(query.getAll("minPersons")),
maxPersons: mapQueryParamToNumber(query.getAll("maxPersons")),
textSearch: mapSingleParamToFormValue(query.getAll("textSearch")) ?? "",
minPersons: toNumber(params.get("minPersons")),
maxPersons: toNumber(params.get("maxPersons")),
textSearch: params.get("textSearch") ?? "",
accessType: params.getAll("accessType"),
};
}

Expand All @@ -60,12 +61,12 @@ export function SeasonalSearchForm({
purposeOptions,
unitOptions,
isLoading,
}: {
}: Readonly<{
reservationUnitTypeOptions: OptionType[];
purposeOptions: OptionType[];
unitOptions: OptionType[];
isLoading: boolean;
}): JSX.Element | null {
}>): JSX.Element | null {
const { t } = useTranslation();

const { handleSearch } = useSearchModify();
Expand All @@ -79,6 +80,11 @@ export function SeasonalSearchForm({
handleSearch(criteria, true);
};

const accessTypeOptions = Object.values(AccessType).map((value) => ({
value,
label: t(`reservationUnit:accessTypes.${value}`),
}));

const translateTag = (key: string, value: string): string | undefined => {
switch (key) {
case "unit":
Expand All @@ -88,6 +94,8 @@ export function SeasonalSearchForm({
?.label;
case "purposes":
return purposeOptions.find((n) => String(n.value) === value)?.label;
case "accessType":
return accessTypeOptions.find((n) => String(n.value) === value)?.label;
default:
return "";
}
Expand All @@ -97,6 +105,7 @@ export function SeasonalSearchForm({
"unit",
"reservationUnitTypes",
"purposes",
"accessType",
] as const;
const hideList = ["id", "order", "sort", "ref"] as const;

Expand Down Expand Up @@ -159,6 +168,14 @@ export function SeasonalSearchForm({
options={purposeOptions}
label={t("searchForm:purposesFilter")}
/>
<ControlledSelect
multiselect
clearable
name="accessType"
control={control}
options={accessTypeOptions}
label={t("searchForm:accessTypeFilter")}
/>
</Filters>
<BottomContainer>
<FilterTagList
Expand Down
55 changes: 37 additions & 18 deletions apps/ui/components/search/SingleSearchForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,8 @@ import SingleLabelInputGroup from "@/components/common/SingleLabelInputGroup";
import { useSearchModify } from "@/hooks/useSearchValues";
import { ControlledSelect } from "common/src/components/form/ControlledSelect";
import {
mapQueryParamToNumber,
mapQueryParamToNumberArray,
mapParamToNumber,
mapSingleBooleanParamToFormValue,
mapSingleParamToFormValue,
} from "@/modules/search";
import {
BottomContainer,
Expand All @@ -26,6 +24,8 @@ import {
StyledSubmitButton,
} from "./styled";
import { useSearchParams, type ReadonlyURLSearchParams } from "next/navigation";
import { AccessType } from "@gql/gql-types";
import { toNumber } from "common/src/helpers";

const StyledCheckBox = styled(Checkbox)`
margin: 0 !important;
Expand All @@ -39,6 +39,7 @@ type FormValues = {
unit: number[];
equipments: number[];
reservationUnitTypes: number[];
accessType: string[];
timeBegin: string | null;
timeEnd: string | null;
startDate: string | null;
Expand All @@ -51,27 +52,29 @@ type FormValues = {
};

function mapQueryToForm(params: ReadonlyURLSearchParams): FormValues {
const dur = mapQueryParamToNumber(params.getAll("duration"));
const dur = toNumber(params.get("duration"));
const duration = dur != null && dur > 0 ? dur : null;
const showOnlyReservable =
mapSingleBooleanParamToFormValue(params.getAll("showOnlyReservable")) ??
true;
return {
purposes: mapQueryParamToNumberArray(params.getAll("purposes")),
unit: mapQueryParamToNumberArray(params.getAll("unit")),
equipments: mapQueryParamToNumberArray(params.getAll("equipments")),
reservationUnitTypes: mapQueryParamToNumberArray(
params.getAll("reservationUnitTypes")
purposes: mapParamToNumber(params.getAll("purposes"), 1),
unit: mapParamToNumber(params.getAll("unit"), 1),
equipments: mapParamToNumber(params.getAll("equipments"), 1),
reservationUnitTypes: mapParamToNumber(
params.getAll("reservationUnitTypes"),
1
),
timeBegin: mapSingleParamToFormValue(params.getAll("timeBegin")),
timeEnd: mapSingleParamToFormValue(params.getAll("timeEnd")),
startDate: mapSingleParamToFormValue(params.getAll("startDate")),
endDate: mapSingleParamToFormValue(params.getAll("endDate")),
accessType: params.getAll("accessType"),
timeBegin: params.get("timeBegin"),
timeEnd: params.get("timeEnd"),
startDate: params.get("startDate"),
endDate: params.get("endDate"),
duration,
minPersons: mapQueryParamToNumber(params.getAll("minPersons")),
maxPersons: mapQueryParamToNumber(params.getAll("maxPersons")),
minPersons: toNumber(params.get("minPersons")),
maxPersons: toNumber(params.get("maxPersons")),
showOnlyReservable,
textSearch: mapSingleParamToFormValue(params.getAll("textSearch")) ?? "",
textSearch: params.get("textSearch") ?? "",
};
}

Expand All @@ -89,12 +92,14 @@ const filterOrder = [
"unit",
"purposes",
"equipments",
"accessType",
] as const;
const multiSelectFilters = [
"unit",
"reservationUnitTypes",
"purposes",
"equipments",
"accessType",
] as const;
// we don't want to show "showOnlyReservable" as a FilterTag, as it has its own checkbox in the form
const hideTagList = ["showOnlyReservable", "order", "sort", "ref"];
Expand All @@ -106,13 +111,13 @@ export function SingleSearchForm({
unitOptions,
equipmentsOptions,
isLoading,
}: {
}: Readonly<{
reservationUnitTypeOptions: Array<{ value: number; label: string }>;
purposeOptions: Array<{ value: number; label: string }>;
unitOptions: Array<{ value: number; label: string }>;
equipmentsOptions: Array<{ value: number; label: string }>;
isLoading: boolean;
}): JSX.Element | null {
}>): JSX.Element | null {
const { handleSearch } = useSearchModify();
const { t } = useTranslation();
const searchValues = useSearchParams();
Expand All @@ -121,6 +126,10 @@ export function SingleSearchForm({
const form = useForm<FormValues>({
values: formValues,
});
const accessTypeOptions = Object.values(AccessType).map((value) => ({
value,
label: t(`reservationUnit:accessTypes.${value}`),
}));

const { handleSubmit, setValue, getValues, control, register } = form;
const unitTypeOptions = reservationUnitTypeOptions;
Expand All @@ -142,6 +151,8 @@ export function SingleSearchForm({
return equipmentsOptions.find((n) => compFn(n, value))?.label;
case "duration":
return durationOptions.find((n) => compFn(n, value))?.label;
case "accessType":
return accessTypeOptions.find((n) => compFn(n, value))?.label;
case "startDate":
case "endDate":
case "timeBegin":
Expand Down Expand Up @@ -288,6 +299,14 @@ export function SingleSearchForm({
}
}}
/>
<ControlledSelect
multiselect
clearable
name="accessType"
control={control}
options={accessTypeOptions}
label={t("searchForm:accessTypeFilter")}
/>
</OptionalFilters>
<Controller
name="showOnlyReservable"
Expand Down
34 changes: 30 additions & 4 deletions apps/ui/components/search/SingleSearchReservationUnitCard.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { IconArrowRight, IconEuroSign, IconGroup, IconSize } from "hds-react";
import {
IconArrowRight,
IconEuroSign,
IconGroup,
IconLock,
IconSize,
} from "hds-react";
import React from "react";
import { useTranslation } from "next-i18next";
import NextImage from "next/image";
Expand Down Expand Up @@ -32,9 +38,9 @@ interface PropsT {

function StatusTag({
data,
}: {
}: Readonly<{
data: { closed: boolean; availableAt: string };
}): JSX.Element {
}>): JSX.Element {
const { t } = useTranslation();
const { closed, availableAt } = data;

Expand Down Expand Up @@ -142,14 +148,20 @@ function ReservationUnitCard({ reservationUnit }: PropsT): JSX.Element {
}
if (unitPrice) {
infos.push({
icon: <IconEuroSign aria-label={t("prices:reservationUnitPriceLabel")} />,
icon: (
<IconEuroSign
aria-hidden="false"
aria-label={t("prices:reservationUnitPriceLabel")}
/>
),
value: unitPrice,
});
}
if (reservationUnit.maxPersons) {
infos.push({
icon: (
<IconGroup
aria-hidden="false"
aria-label={t("reservationUnitCard:maxPersons", {
maxPersons: reservationUnit.maxPersons,
})}
Expand All @@ -161,6 +173,20 @@ function ReservationUnitCard({ reservationUnit }: PropsT): JSX.Element {
}),
});
}
if (reservationUnit.currentAccessType) {
infos.push({
icon: (
<IconLock
aria-hidden="false"
aria-label={t("reservationUnit:accessType")}
size={IconSize.Small}
/>
),
value: t(
`reservationUnit:accessTypes.${reservationUnit.currentAccessType}`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this be null?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above: shouldn't be possible to my knowledge, as all reservation units have an accessType (which currentAccessType defaults to, if it's not specified separately)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this changes in the new backend

),
});
}

const buttons = [
<ButtonLikeLink href={link} key={link} width="full">
Expand Down
Loading