diff --git a/apps/admin-ui/src/component/filters/ReservationStateFilter.tsx b/apps/admin-ui/src/component/filters/ReservationStateFilter.tsx deleted file mode 100644 index 93f11d1d5..000000000 --- a/apps/admin-ui/src/component/filters/ReservationStateFilter.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import React from "react"; -import { useTranslation } from "react-i18next"; -import { State } from "common/types/gql-types"; -import { SortedSelect } from "@/component/SortedSelect"; -import { OptionType } from "@/common/types"; - -type Props = { - onChange: (units: OptionType[]) => void; - value: OptionType[]; -}; - -const ReservationStateFilter = ({ onChange, value }: Props): JSX.Element => { - const { t } = useTranslation(); - - const opts: OptionType[] = Object.values(State).map((s) => ({ - value: s, - label: t(`RequestedReservation.state.${s}`), - })); - - return ( - - ); -}; - -export default ReservationStateFilter; diff --git a/apps/admin-ui/src/component/filters/ReservationUnitFilter.tsx b/apps/admin-ui/src/component/filters/ReservationUnitFilter.tsx index 0f8b83a9d..c485a4109 100644 --- a/apps/admin-ui/src/component/filters/ReservationUnitFilter.tsx +++ b/apps/admin-ui/src/component/filters/ReservationUnitFilter.tsx @@ -1,26 +1,14 @@ -import React, { useState } from "react"; +import { useState } from "react"; import { useQuery } from "@apollo/client"; -import { useTranslation } from "react-i18next"; import type { Query, QueryReservationUnitsArgs, ReservationUnitNode, } from "common/types/gql-types"; -import { SortedSelect } from "@/component/SortedSelect"; import { GQL_MAX_RESULTS_PER_QUERY } from "@/common/const"; import { RESERVATION_UNITS_FILTER_PARAMS_QUERY } from "./queries"; import { filterNonNullable } from "common/src/helpers"; -type OptionType = { - label: string; - value: number; -}; - -type Props = { - onChange: (reservationUnits: OptionType[]) => void; - value: OptionType[]; -}; - // TODO this should be refactored to use Apollo cache because local state is bad // local state problems: // - two components have separate state so a mutation requires refetch on both @@ -28,10 +16,8 @@ type Props = { // - the fetch (that could include 100s of gql queries) is run for every component // i.e. create dummy data of 10k ReservationUnits, add 100 filter components to the page and // watch the backend break. -const ReservationUnitFilter = ({ onChange, value }: Props): JSX.Element => { - const { t } = useTranslation(); +export function useReservationUnitOptions() { const [resUnits, setResUnits] = useState([]); - // TODO this request is rerun whenever the selection changes (it'll return 0 every time) const offset = resUnits.length > 0 ? resUnits.length : undefined; const { loading } = useQuery( @@ -55,26 +41,10 @@ const ReservationUnitFilter = ({ onChange, value }: Props): JSX.Element => { } ); - const opts = resUnits.map((reservationUnit) => ({ + const options = resUnits.map((reservationUnit) => ({ label: reservationUnit?.nameFi ?? "", value: reservationUnit?.pk ?? 0, })); - // NOTE replaced frontend sort with backend, but this caused the sort to be case sensitive. - // TODO combobox would be preferable since we have like 400 items in it - // but combobox has some weird issue with ghost options being created. - return ( - - ); -}; - -export default ReservationUnitFilter; + return { loading, options }; +} diff --git a/apps/admin-ui/src/component/filters/ReservationUnitStateFilter.tsx b/apps/admin-ui/src/component/filters/ReservationUnitStateFilter.tsx deleted file mode 100644 index dd8dfe4a6..000000000 --- a/apps/admin-ui/src/component/filters/ReservationUnitStateFilter.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import React from "react"; -import { useTranslation } from "react-i18next"; -import { ReservationUnitState } from "common/types/gql-types"; -import { SortedSelect } from "@/component/SortedSelect"; - -type OptionType = { - label: string; - value: ReservationUnitState; -}; -type Props = { - onChange: (units: OptionType[]) => void; - value: OptionType[]; -}; - -const ReservationUnitStateFilter = ({ - onChange, - value, -}: Props): JSX.Element => { - const { t } = useTranslation(); - - const opts: OptionType[] = Object.values(ReservationUnitState).map((s) => ({ - value: s, - label: t(`ReservationUnits.state.${s}`), - })); - - return ( - - ); -}; - -export default ReservationUnitStateFilter; diff --git a/apps/admin-ui/src/component/filters/ReservationUnitTypeFilter.tsx b/apps/admin-ui/src/component/filters/ReservationUnitTypeFilter.tsx index 22b461684..3e4ba4fbd 100644 --- a/apps/admin-ui/src/component/filters/ReservationUnitTypeFilter.tsx +++ b/apps/admin-ui/src/component/filters/ReservationUnitTypeFilter.tsx @@ -1,24 +1,12 @@ import { useQuery } from "@apollo/client"; -import React from "react"; -import { useTranslation } from "react-i18next"; import type { Query, QueryReservationUnitTypesArgs, } from "common/types/gql-types"; -import { SortedSelect } from "@/component/SortedSelect"; import { RESERVATION_UNIT_TYPES_QUERY } from "./queries"; import { filterNonNullable } from "common/src/helpers"; -type OptionType = { - label: string; - value: number; -}; -type Props = { - onChange: (reservationUnitType: OptionType[]) => void; - value: OptionType[]; - style?: React.CSSProperties; -}; - +// TODO move export function useReservationUnitTypes() { const result = useQuery( RESERVATION_UNIT_TYPES_QUERY @@ -36,30 +24,3 @@ export function useReservationUnitTypes() { return { options, ...result }; } - -function ReservationUnitTypeFilter({ - onChange, - value, - style, -}: Props): JSX.Element { - const { t } = useTranslation(); - - const { options, loading } = useReservationUnitTypes(); - - return ( - onChange(units)} - id="type-combobox" - value={value} - /> - ); -} - -export default ReservationUnitTypeFilter; diff --git a/apps/admin-ui/src/component/filters/UnitFilter.tsx b/apps/admin-ui/src/component/filters/UnitFilter.tsx index 1668ccd73..4ffa86e04 100644 --- a/apps/admin-ui/src/component/filters/UnitFilter.tsx +++ b/apps/admin-ui/src/component/filters/UnitFilter.tsx @@ -1,10 +1,8 @@ -import React from "react"; import { gql, useQuery } from "@apollo/client"; -import { useTranslation } from "react-i18next"; import type { Query, QueryUnitsArgs } from "common/types/gql-types"; -import { SortedSelect } from "@/component/SortedSelect"; import { filterNonNullable } from "common/src/helpers"; +// TODO combine with other options queries so we only make a single request for all of them const UNITS_QUERY = gql` query UnitsFilter($offset: Int, $first: Int) { units(onlyWithPermission: true, offset: $offset, first: $first) { @@ -19,15 +17,6 @@ const UNITS_QUERY = gql` } `; -type OptionType = { - label: string; - value: number; -}; -type Props = { - onChange: (units: OptionType[]) => void; - value: OptionType[]; -}; - export function useUnitFilterOptions() { // Copy-paste from ReservationUnitFilter (same issues etc.) const query = useQuery(UNITS_QUERY); @@ -41,23 +30,3 @@ export function useUnitFilterOptions() { return { options, ...query }; } - -export function UnitFilter({ onChange, value }: Props): JSX.Element { - const { t } = useTranslation(); - - const { options, loading } = useUnitFilterOptions(); - - return ( - - ); -} diff --git a/apps/admin-ui/src/component/lists/Tags.tsx b/apps/admin-ui/src/component/lists/Tags.tsx deleted file mode 100644 index 80415f3da..000000000 --- a/apps/admin-ui/src/component/lists/Tags.tsx +++ /dev/null @@ -1,143 +0,0 @@ -import React, { Dispatch } from "react"; -import { Tag as HDSTag } from "hds-react"; -import { get, omit } from "lodash"; -import type { TFunction } from "i18next"; -import styled from "styled-components"; -import { truncate } from "@/helpers"; -import type { OptionType } from "@/common/types"; - -export type Tag = { - key: string; - value: string; - ac: Action; -}; - -const StyledTag = styled(HDSTag)` - border-radius: 30px; - padding: 0 1em; -`; - -const DeleteTag = styled(HDSTag)` - background: transparent; -`; - -export type Action = - | { type: "set"; value: Partial } - | { type: "deleteTag"; field: keyof T; value?: string } - | { type: "reset" }; - -/// @deprecated -- use SearchTags instead -/// this is very type unsafe, it uses a reducer, it doesn't support url params -export const toTags = ( - state: object & T, - t: TFunction, - multivaluedFields: string[], - noLabelFields: string[], - translationPrefix?: string -): Tag[] => { - return (Object.keys(state) as unknown as (keyof T)[]).flatMap((key) => { - if (multivaluedFields.includes(key as string)) { - return (get(state, key) as []).map( - (v: OptionType) => - ({ - key: `${String(key)}.${v.value}`, - value: truncate(v.label, 25), - ac: { type: "deleteTag", field: key, value: v.value }, - }) as Tag - ); - } - - if (typeof state[key] === "string" && String(state[key]) === "") { - return []; - } - return [ - { - key, - value: noLabelFields.includes(String(key)) - ? `"${state[key]}"` - : t(`${translationPrefix || ""}.filters.${String(key)}Tag`, { - value: get(state, key), - }), - - ac: { - type: "deleteTag", - field: key, - }, - } as Tag, - ]; - }); -}; - -export const getReducer = - (emptyState: T) => - (state: T, action: Action): T => { - switch (action.type) { - case "set": { - return { ...state, ...action.value }; - } - - case "reset": { - return emptyState; - } - - case "deleteTag": { - if (!action.value) { - return omit(state as Partial, action.field) as unknown as T; - } - - const filtered = ( - state[action.field] as unknown as [OptionType] - ).filter((e) => e.value !== action.value); - - return { - ...state, - [action.field]: filtered, - }; - } - - default: - return { ...state }; - } - }; - -const Wrapper = styled.div` - display: flex; - gap: var(--spacing-s); - flex-wrap: wrap; -`; - -/// @deprecated -- use SearchTags instead -export default function Tags({ - tags, - dispatch, - t, -}: { - tags: Tag[]; - dispatch: Dispatch>; - t: TFunction; -}): JSX.Element | null { - return tags.length ? ( - - {tags.map((tag) => ( - { - dispatch(tag.ac); - }} - key={tag.key} - > - {tag.value} - - ))} - {tags.length > 0 && ( - dispatch({ type: "reset" })} - theme={{ "--tag-background": "transparent" }} - > - {t("common.clear")} - - )} - - ) : null; -} diff --git a/apps/admin-ui/src/component/reservations/AllReservations.tsx b/apps/admin-ui/src/component/reservations/AllReservations.tsx index 74558cac5..57cbf55b0 100644 --- a/apps/admin-ui/src/component/reservations/AllReservations.tsx +++ b/apps/admin-ui/src/component/reservations/AllReservations.tsx @@ -1,20 +1,20 @@ -import { debounce } from "lodash"; -import React, { useState } from "react"; +import React from "react"; import { useTranslation } from "react-i18next"; import { H1 } from "common/src/common/typography"; -import Filters, { type FilterArguments, emptyState } from "./Filters"; import { ReservationsDataLoader } from "./ReservationsDataLoader"; import BreadcrumbWrapper from "../BreadcrumbWrapper"; import { HR } from "@/component/Table"; import { Container } from "@/styles/layout"; -import { toUIDate } from "common/src/common/util"; +import { Filters } from "./Filters"; function AllReservations(): JSX.Element { - const [filters, setFilters] = useState(emptyState); - const debouncedSearch = debounce((value) => setFilters(value), 300); - const { t } = useTranslation(); + /* TODO + initialFiltering={{ + begin: toUIDate(new Date()) ?? "", + }} + */ return ( <> @@ -23,14 +23,9 @@ function AllReservations(): JSX.Element {

{t("Reservations.allReservationListHeading")}

{t("Reservations.allReservationListDescription")}

- +
- + ); diff --git a/apps/admin-ui/src/component/reservations/Filters.tsx b/apps/admin-ui/src/component/reservations/Filters.tsx index 68eda8600..cf6d1975a 100644 --- a/apps/admin-ui/src/component/reservations/Filters.tsx +++ b/apps/admin-ui/src/component/reservations/Filters.tsx @@ -1,44 +1,20 @@ -import React, { useEffect, useReducer } from "react"; -import { DateInput, NumberInput, TextInput } from "hds-react"; +import React from "react"; +import { DateInput } from "hds-react"; import { useTranslation } from "react-i18next"; import styled from "styled-components"; -import i18next from "i18next"; import ShowAllContainer from "common/src/components/ShowAllContainer"; -import type { OptionType } from "@/common/types"; -import ReservationUnitTypeFilter from "../filters/ReservationUnitTypeFilter"; -import Tags, { type Action, getReducer, toTags } from "../lists/Tags"; -import { UnitFilter } from "../filters/UnitFilter"; -import ReservationUnitFilter from "../filters/ReservationUnitFilter"; -import ReservationStateFilter from "../filters/ReservationStateFilter"; -import PaymentStatusFilter from "./PaymentStatusFilter"; +import { useReservationUnitTypes } from "../filters/ReservationUnitTypeFilter"; +import { useUnitFilterOptions } from "../filters/UnitFilter"; +import { useReservationUnitOptions } from "../filters/ReservationUnitFilter"; import { AutoGrid } from "@/styles/layout"; - -export type FilterArguments = { - reservationUnitType: Array<{ label: string; value: number }>; - unit: Array<{ label: string; value: number }>; - reservationUnit: Array<{ label: string; value: number }>; - reservationState: OptionType[]; - paymentStatuses: OptionType[]; - textSearch: string; - begin: string; - end: string; - minPrice: string; - maxPrice: string; -}; - -const multivaluedFields = [ - "unit", - "reservationUnit", - "reservationUnitType", - "reservationUnitStates", - "reservationState", - "paymentStatuses", -]; - -type Props = { - onSearch: (args: FilterArguments) => void; - initialFiltering?: Partial; -}; +import { + MultiSelectFilter, + RangeNumberFilter, + SearchFilter, +} from "../QueryParamFilters"; +import { useSearchParams } from "react-router-dom"; +import { SearchTags } from "../SearchTags"; +import { OrderStatus, State } from "common/types/gql-types"; const Wrapper = styled.div` display: flex; @@ -52,121 +28,86 @@ const MoreWrapper = styled(ShowAllContainer)` } `; -export const emptyState: FilterArguments = { - reservationUnitType: [], - unit: [], - reservationUnit: [], - paymentStatuses: [], - reservationState: [], - textSearch: "", - begin: "", - end: "", - minPrice: "", - maxPrice: "", -}; +function DateInputFilter({ name }: { name: string }) { + const { t } = useTranslation(); + const [searchParams, setParams] = useSearchParams(); -const MyTextInput = ({ - id, - value, - dispatch, -}: { - id: keyof FilterArguments; - value: string; - dispatch: React.Dispatch>; -}) => ( - { - if (e.target.value.length > 0) { - dispatch({ - type: "set", - value: { [id]: e.target.value }, - }); - } else { - dispatch({ - type: "deleteTag", - field: id, - }); - } - }} - value={value || ""} - placeholder={i18next.t("ReservationsSearch.textSearchPlaceholder")} - /> -); + const filter = searchParams.get(name); -function Filters({ onSearch, initialFiltering }: Props): JSX.Element { - const { t } = useTranslation(); - const initialEmptyState = { ...emptyState, ...initialFiltering }; + const handleChange = (val: string) => { + const params = new URLSearchParams(searchParams); + if (val.length > 0) { + params.set(name, val); + setParams(params, { replace: true }); + } else { + setParams(params, { replace: true }); + } + }; - const [state, dispatch] = useReducer( - getReducer(initialEmptyState), - initialEmptyState + const label = t(`filters.label.${name}`); + // TODO make the translation empty. no placeholder on purpose + const placeholder = t(`filters.placeholder.${name}`); + return ( + handleChange(val)} + value={filter ?? ""} + /> ); +} - useEffect(() => { - onSearch(state); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [state]); +export function Filters(): JSX.Element { + const { t } = useTranslation(); - const tags = toTags( - state, - t, - multivaluedFields, - ["textSearch"], - "ReservationsSearch" - ); + const { options: reservationUnitTypeOptions } = useReservationUnitTypes(); + const reservationStateOptions = Object.values(State).map((s) => ({ + value: s, + label: t(`RequestedReservation.state.${s}`), + })); + const paymentStatusOptions = Object.values(OrderStatus).map((s) => ({ + value: s, + label: t(`Payment.status.${s}`), + })); + + const { options: unitOptions } = useUnitFilterOptions(); + const { options: reservationUnitOptions } = useReservationUnitOptions(); + + // TODO implement + function translateTag(tag: string, val: string): string { + switch (tag) { + default: + return val; + } + } return ( - - dispatch({ type: "set", value: { reservationUnitType } }) - } - value={state.reservationUnitType} - /> - - dispatch({ type: "set", value: { reservationState } }) - } - value={state.reservationState} - /> - dispatch({ type: "set", value: { unit } })} - value={state.unit} + - - - dispatch({ type: "set", value: { paymentStatuses } }) - } - value={state.paymentStatuses || []} + + + - - dispatch({ type: "set", value: { reservationUnit } }) - } - value={state.reservationUnit} - /> - dispatch({ type: "set", value: { begin } })} - value={state.begin} - /> - dispatch({ type: "set", value: { end } })} - value={state.end} + + + - - dispatch({ - type: "set", - value: { minPrice: e.target.value }, - }) - } - /> - { - dispatch({ - type: "set", - value: { - maxPrice: e.target.value, - }, - }); - }} + - + ); } - -export default Filters; diff --git a/apps/admin-ui/src/component/reservations/PaymentStatusFilter.tsx b/apps/admin-ui/src/component/reservations/PaymentStatusFilter.tsx deleted file mode 100644 index ac8ad04b2..000000000 --- a/apps/admin-ui/src/component/reservations/PaymentStatusFilter.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import React from "react"; -import { useTranslation } from "react-i18next"; -import { SortedSelect } from "@/component/SortedSelect"; -import { OptionType } from "@/common/types"; - -type Props = { - onChange: (units: OptionType[]) => void; - value: OptionType[]; -}; - -const PaymentStatuses = [ - "DRAFT", - "EXPIRED", - "CANCELLED", - "PAID", - "PAID_MANUALLY", - "REFUNDED", -]; - -const PaymentStatusFilter = ({ onChange, value }: Props): JSX.Element => { - const { t } = useTranslation(); - - const opts: OptionType[] = PaymentStatuses.map((s) => ({ - value: s, - label: t(`Payment.status.${s}`), - })); - - return ( - - ); -}; - -export default PaymentStatusFilter; diff --git a/apps/admin-ui/src/component/reservations/RequestedReservations.tsx b/apps/admin-ui/src/component/reservations/RequestedReservations.tsx index d442e218c..53c59e9fd 100644 --- a/apps/admin-ui/src/component/reservations/RequestedReservations.tsx +++ b/apps/admin-ui/src/component/reservations/RequestedReservations.tsx @@ -1,20 +1,21 @@ -import { debounce } from "lodash"; -import React, { useState } from "react"; +import React from "react"; import { useTranslation } from "react-i18next"; import { H1 } from "common/src/common/typography"; -import Filters, { type FilterArguments, emptyState } from "./Filters"; +import { Filters } from "./Filters"; import { ReservationsDataLoader } from "./ReservationsDataLoader"; import BreadcrumbWrapper from "../BreadcrumbWrapper"; import { HR } from "@/component/Table"; import { Container } from "@/styles/layout"; -import { toUIDate } from "common/src/common/util"; function Reservations(): JSX.Element { - const [search, setSearch] = useState(emptyState); - const debouncedSearch = debounce((value) => setSearch(value), 300); - const { t } = useTranslation(); + /* TODO add default filters + defaultFiltering={{ + state: ["DENIED", "CONFIRMED", "REQUIRES_HANDLING"], + }} + */ + return ( <> @@ -23,19 +24,9 @@ function Reservations(): JSX.Element {

{t("Reservations.reservationListHeading")}

{t("Reservations.reservationListDescription")}

- +
- + ); diff --git a/apps/admin-ui/src/component/reservations/ReservationsDataLoader.tsx b/apps/admin-ui/src/component/reservations/ReservationsDataLoader.tsx index fab99cf38..f209b4607 100644 --- a/apps/admin-ui/src/component/reservations/ReservationsDataLoader.tsx +++ b/apps/admin-ui/src/component/reservations/ReservationsDataLoader.tsx @@ -1,6 +1,5 @@ import React, { useState } from "react"; import { type ApolloError, useQuery } from "@apollo/client"; -import { values } from "lodash"; import { type Query, type QueryReservationsArgs, @@ -10,65 +9,75 @@ import { More } from "@/component/More"; import { LIST_PAGE_SIZE } from "@/common/const"; import { useNotification } from "@/context/NotificationContext"; import Loader from "../Loader"; -import { FilterArguments } from "./Filters"; import { RESERVATIONS_QUERY } from "./queries"; import { ReservationsTable } from "./ReservationsTable"; import { fromUIDate, toApiDate } from "common/src/common/util"; -import { filterNonNullable } from "common/src/helpers"; - -type Props = { - filters: FilterArguments; - defaultFiltering: QueryReservationsArgs; -}; - -function mapFilterParams( - params: FilterArguments, - defaultParams: QueryReservationsArgs -): QueryReservationsArgs { - const emptySearch = - values(params).filter((v) => !(v === "" || v.length === 0)).length === 0; - - // only use defaults if search is "empty" - const defaults = emptySearch ? defaultParams : {}; - - const states = filterNonNullable( - params.reservationState.map((ru) => ru.value?.toString()) - ); - const state = states.length > 0 ? states : defaults.state; - - const begin = fromUIDate(params.begin); - const end = fromUIDate(params.end); - const beginDate = begin ? toApiDate(begin) : defaults.beginDate; - const endDate = end ? toApiDate(end) : defaults.endDate; +import { filterNonNullable, toNumber } from "common/src/helpers"; +import { useSearchParams } from "react-router-dom"; + +// TODO add defaultParams (using query params on page load) +function mapFilterParams(searchParams: URLSearchParams): QueryReservationsArgs { + const reservationUnitTypes = searchParams + .getAll("reservationUnitType") + .map(Number) + .filter(Number.isInteger); + + const unit = searchParams.getAll("unit").map(Number).filter(Number.isInteger); + const paymentStatus = searchParams + .getAll("paymentStatus") + .map(Number) + .filter(Number.isInteger); + const reservationUnit = searchParams + .getAll("reservationUnit") + .map(Number) + .filter(Number.isInteger); + const reservationState = searchParams + .getAll("reservationState") + .map(Number) + .filter(Number.isInteger); + const textSearch = searchParams.get("search"); + + const uiBegin = searchParams.get("begin"); + const uiEnd = searchParams.get("end"); + + const minPrice = searchParams.get("minPrice"); + const maxPrice = searchParams.get("maxPrice"); + + const begin = uiBegin ? fromUIDate(uiBegin) : undefined; + const end = uiEnd ? fromUIDate(uiEnd) : undefined; + const beginDate = begin ? toApiDate(begin) : undefined; + const endDate = end ? toApiDate(end) : undefined; return { - unit: filterNonNullable(params.unit?.map((u) => u.value?.toString())), - reservationUnitType: filterNonNullable( - params.reservationUnitType?.map((u) => u.value?.toString()) - ), - reservationUnit: filterNonNullable( - params.reservationUnit?.map((ru) => ru.value?.toString()) - ), - state, - textSearch: params.textSearch || undefined, + unit: unit.map((u) => u.toString()), + reservationUnitType: reservationUnitTypes.map((u) => u.toString()), + reservationUnit: reservationUnit.map((ru) => ru.toString()), + orderStatus: paymentStatus.map((status) => status.toString()), + state: reservationState.map((status) => status.toString()), + textSearch, beginDate, endDate, - priceGte: params.minPrice !== "" ? params.minPrice : undefined, - priceLte: params.maxPrice !== "" ? params.maxPrice : undefined, - orderStatus: filterNonNullable( - params.paymentStatuses?.map((status) => status.value?.toString()) - ), + priceGte: minPrice ? toNumber(minPrice)?.toString() : undefined, + priceLte: maxPrice ? toNumber(maxPrice)?.toString() : undefined, }; } -function useReservations( - filters: FilterArguments, - defaultFiltering: QueryReservationsArgs, - sort: string -) { +export function ReservationsDataLoader(): JSX.Element { + const [sort, setSort] = useState("-state"); + const onSortChanged = (sortField: string) => { + if (sort === sortField) { + setSort(`-${sortField}`); + } else { + setSort(sortField); + } + }; + const { notifyError } = useNotification(); + // TODO the sort string should be in the url const orderBy = transformSortString(sort); + const [searchParams] = useSearchParams(); + const { fetchMore, loading, data, previousData } = useQuery< Query, QueryReservationsArgs @@ -79,7 +88,7 @@ function useReservations( variables: { orderBy, first: LIST_PAGE_SIZE, - ...mapFilterParams(filters, defaultFiltering), + ...mapFilterParams(searchParams), }, onError: (err: ApolloError) => { notifyError(err.message); @@ -87,53 +96,28 @@ function useReservations( }); const currData = data ?? previousData; + const reservations = filterNonNullable( currData?.reservations?.edges.map((edge) => edge?.node) ); + const totalCount = currData?.reservations?.totalCount; + const offset = currData?.reservations?.edges.length; - return { - fetchMore, - loading, - data: reservations, - totalCount: data?.reservations?.totalCount, - offset: data?.reservations?.edges?.length, - }; -} - -export function ReservationsDataLoader({ - filters, - defaultFiltering, -}: Props): JSX.Element { - const [sort, setSort] = useState("-state"); - const onSortChanged = (sortField: string) => { - if (sort === sortField) { - setSort(`-${sortField}`); - } else { - setSort(sortField); - } - }; - - const { fetchMore, loading, data, totalCount, offset } = useReservations( - filters, - defaultFiltering, - sort - ); - - if (loading && data.length === 0) { + if (loading && reservations.length === 0) { return ; } return ( <> fetchMore({ variables: { offset } })} /> diff --git a/apps/admin-ui/src/i18n/messages.ts b/apps/admin-ui/src/i18n/messages.ts index 69708fd88..91132aebd 100644 --- a/apps/admin-ui/src/i18n/messages.ts +++ b/apps/admin-ui/src/i18n/messages.ts @@ -1737,13 +1737,7 @@ const translations: ITranslations = { showInCalendar: ["Näytä kalenterissa"], }, ReservationsSearch: { - textSearch: ["Hae varausta"], - textSearchPlaceholder: ["Hae nimellä tai idllä"], - minPrice: ["Hinta vähintään"], - maxPrice: ["Hinta enintään"], - begin: ["Alkaen"], - end: ["Asti"], - paymentStatus: ["Maksutila"], + // textSearchPlaceholder: ["Hae nimellä tai idllä"], filters: { minPriceTag: ["Hinta vähintään: {{value}}"], maxPriceTag: ["Hinta enintään: {{value}}"], @@ -1796,6 +1790,7 @@ const translations: ITranslations = { selectUnits: ["Valitse toimipisteet"], priority: ["Aikatoive"], search: ["Hae hakemusta"], + searchReservation: ["Hae varausta"], ageGroup: ["Ikäryhmä"], purpose: ["Käyttötarkoitus"], homeCity: ["Kotikunta"], @@ -1803,6 +1798,14 @@ const translations: ITranslations = { order: ["Varausyksiköiden toivejärjestys"], reservationUnitType: ["Varausyksikön tyyppi"], reservationUnitState: ["Varausyksikön tila"], + price: ["Hinta"], + /* + minPrice: ["Hinta vähintään"], + maxPrice: ["Hinta enintään"], + */ + begin: ["Alkaen"], + end: ["Asti"], + paymentStatus: ["Maksutila"], }, // weird values that don't fit under placeholder or label (custom options in this case) reservationUnitApplication: ["Tilatoive"],