Skip to content

Commit

Permalink
Split availability hook into two separate hooks
Browse files Browse the repository at this point in the history
 Two major factors to be aware of here:

 * Three hooks are introduced instead of one: One main hook and one for
 physical and one for online materials, to get a better overview of
 the rules and the logic.
 * Apparently we cannot make use of `onSuccess` handlers on queries if
 the request have alredy been performed and the data is delivered the
 second time by the `QueryClient` cache. And that is the case of the
 availability labels on a material page, where they are used both in the
 header and in the "udagver" section later on the page.
  • Loading branch information
spaceo committed Jul 31, 2024
1 parent 06f5296 commit 4280787
Show file tree
Hide file tree
Showing 7 changed files with 217 additions and 141 deletions.
3 changes: 2 additions & 1 deletion src/components/availability-label/availability-label.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import { useText } from "../../core/utils/text";
import LinkNoStyle from "../atoms/links/LinkNoStyle";
import { useStatistics } from "../../core/statistics/useStatistics";
import { statistics } from "../../core/statistics/statistics";
import { getParentAvailabilityLabelClass, useAvailabilityData } from "./helper";
import { getParentAvailabilityLabelClass } from "./helper";
import {
Access,
AccessTypeCode
} from "../../core/dbc-gateway/generated/graphql";
import AvailabilityLabelInside from "./availability-label-inside";
import { FaustId } from "../../core/utils/types/ids";
import useAvailabilityData from "./useAvailabilityData";

export interface AvailabilityLabelProps {
manifestText: string;
Expand Down
142 changes: 4 additions & 138 deletions src/components/availability-label/helper.ts
Original file line number Diff line number Diff line change
@@ -1,148 +1,14 @@
import { useEffect, useState } from "react";
import clsx from "clsx";
import {
useGetV1LoanstatusIdentifier,
useGetV1ProductsIdentifier
} from "../../core/publizon/publizon";
import { useConfig } from "../../core/utils/config";
import {
Access,
AccessTypeCode
} from "../../core/dbc-gateway/generated/graphql";
import { FaustId } from "../../core/utils/types/ids";
import useGetAvailability from "../../core/utils/useGetAvailability";
import { publizonProductStatuses } from "./types";
import { ManifestationMaterialType } from "../../core/utils/types/material-type";

export const useAvailabilityData = ({
accessTypes,
access,
faustIds,
isbn,
manifestText
}: {
accessTypes: AccessTypeCode[];
access: Access["__typename"][];
faustIds: FaustId[] | null;
isbn: string | null;
manifestText: string;
}) => {
const [isAvailable, setIsAvailable] = useState<null | boolean>(null);
const config = useConfig();
const isOnline = accessTypes?.includes(AccessTypeCode.Online) ?? false;
const [isCostFree, setIsCostFree] = useState<null | boolean>(null);
const [isLoading, setIsLoading] = useState<null | boolean>(null);

useEffect(() => {
// An online material is by default always available.
if (isOnline) {
setIsAvailable(true);
}
}, [isOnline]);

useEffect(() => {
// Articles are always available.
if (
manifestText === ManifestationMaterialType.article &&
isAvailable === null
) {
setIsAvailable(true);
}
}, [manifestText, isAvailable]);

const { isLoading: isLoadingIdentifier } = useGetV1ProductsIdentifier(
isbn ?? "",
{
query: {
// Publizon / useGetV1ProductsIdentifier is responsible for online
// materials. It requires an ISBN to do lookups.
enabled:
isOnline &&
isbn !== null &&
manifestText !== ManifestationMaterialType.article,
onSuccess: (res) => {
// If an online material isn't cost-free we need to check whether there is a queue
// to reserve it (via useGetV1LoanstatusIdentifier below in the code)
if (res?.product?.costFree === false) {
setIsCostFree(false);
return;
}
setIsCostFree(true);
}
}
}
);

const { isLoading: isLoadingProductInfo } = useGetV1LoanstatusIdentifier(
isbn || "",
{
// Publizon / useGetV1LoanstatusIdentifier shows loan status per material.
// This status is only available for products found on Ereol. Other online
// materials are always supposed to be shown as "available"
enabled:
isOnline &&
!!isbn &&
isCostFree === false &&
access.some((acc) => acc === "Ereol") &&
manifestText !== ManifestationMaterialType.article,
onSuccess: (res) => {
if (res && res.loanStatus) {
setIsAvailable(publizonProductStatuses[res.loanStatus].isAvailable);
return;
}
// In case the load status data doesn't exist we assume it isn't available
setIsAvailable(false);
}
}
);

const { isLoading: isLoadingAvailability } = useGetAvailability({
faustIds: faustIds ?? [],
config,
options: {
query: {
// FBS / useGetAvailabilityV3 is responsible for handling availability
// for physical items. This will be the majority of all materials so we
// use this for everything except materials that are explicitly online.
enabled:
!isOnline &&
faustIds !== null &&
manifestText !== ManifestationMaterialType.article,
onSuccess: (data) => {
if (data?.some((item) => item.available)) {
setIsAvailable(true);
}
}
}
}
});

useEffect(() => {
// Articles are always available, thus no need to load.
if (manifestText !== ManifestationMaterialType.article) {
setIsLoading(
(isLoadingAvailability ||
isLoadingIdentifier ||
isLoadingProductInfo) &&
isAvailable === null
);
}
}, [
isLoadingAvailability,
isLoadingIdentifier,
isLoadingProductInfo,
isAvailable,
manifestText
]);

return { isLoading, isAvailable };
};
import { AccessTypeCode } from "../../core/dbc-gateway/generated/graphql";

type GetParrentAvailabilityLabelClassProps = {
selected?: boolean;
cursorPointer?: boolean;
};

export const isOnline = (accessTypes: AccessTypeCode[]): boolean =>
accessTypes?.includes(AccessTypeCode.Online) ?? false;

export const getParentAvailabilityLabelClass = ({
selected,
cursorPointer
Expand Down
44 changes: 44 additions & 0 deletions src/components/availability-label/useAvailabilityData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import {
Access,
AccessTypeCode
} from "../../core/dbc-gateway/generated/graphql";
import { FaustId } from "../../core/utils/types/ids";
import { isOnline } from "./helper";
import useOnlineAvailabilityData from "./useOnlineAvailabilityData";
import usePhysicalAvailabilityData from "./usePhysicalAvailabilityData";

const useAvailabilityData = ({
accessTypes,
access,
faustIds,
manifestText,
isbn
}: {
accessTypes: AccessTypeCode[];
access: Access["__typename"][];
faustIds: FaustId[];
manifestText: string;
isbn: string | null;
}) => {
const availabilityOnline = useOnlineAvailabilityData({
enabled: isOnline(accessTypes),
access,
faustIds,
isbn,
manifestText
});

const availabilityPhysical = usePhysicalAvailabilityData({
enabled: !isOnline(accessTypes),
faustIds,
manifestText
});

if (isOnline(accessTypes)) {
return availabilityOnline;
}

return availabilityPhysical;
};

export default useAvailabilityData;
99 changes: 99 additions & 0 deletions src/components/availability-label/useOnlineAvailabilityData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { useEffect, useState } from "react";
import {
useGetV1LoanstatusIdentifier,
useGetV1ProductsIdentifier
} from "../../core/publizon/publizon";
import { Access } from "../../core/dbc-gateway/generated/graphql";
import { FaustId } from "../../core/utils/types/ids";
import { publizonProductStatuses } from "./types";

const useOnlineAvailabilityData = ({
enabled,
access,
faustIds,
isbn,
manifestText
}: {
enabled: boolean;
access: Access["__typename"][];
faustIds: FaustId[] | null;
isbn: string | null;
manifestText: string;
}) => {
const [isAvailable, setIsAvailable] = useState<null | boolean>(null);

// Find out if the material is cost free.
const { isLoading: isLoadingIdentifier, data: dataIdentifier } =
useGetV1ProductsIdentifier(isbn ?? "", {
query: {
// Publizon / useGetV1ProductsIdentifier is responsible for online
// materials. It requires an ISBN to do lookups.
enabled: enabled && isAvailable === null && isbn !== null
}
});

// Ereol request.
const { isLoading: isLoadingEreolData, data: dataEreol } =
useGetV1LoanstatusIdentifier(isbn || "", {
// Publizon / useGetV1LoanstatusIdentifier shows loan status per material.
// This status is only available for products found on Ereol. Other online
// materials are always supposed to be shown as "available"
enabled:
enabled &&
isAvailable === null &&
!!isbn &&
// If the material is free (I think it is called blue material btw.)
// we should not load the loan status because then we know that it is available.
// So If the material is not free and we know it is an "Ereol" material we should load the loan status.
dataIdentifier?.product?.costFree === false &&
access.some((acc) => acc === "Ereol")
});

useEffect(() => {
if (
!enabled ||
isAvailable !== null ||
isLoadingIdentifier !== false ||
isLoadingEreolData !== false
) {
return;
}

// If we have ereol data, we can use that to determine the availability.
if (dataEreol && dataEreol.loanStatus) {
setIsAvailable(publizonProductStatuses[dataEreol.loanStatus].isAvailable);
}
}, [
isLoadingIdentifier,
isAvailable,
manifestText,
faustIds,
enabled,
dataEreol,
isLoadingEreolData
]);

// If hook is not enabled make it clear that the loading and availability status is unknown.
if (!enabled) {
return {
isLoading: null,
isAvailable: null
};
}

// An online material is by default always available if the availability status has not been set yet.
if (isAvailable === null) {
return {
isLoading: false,
isAvailable: true
};
}

// Return the availability status.
return {
isLoading: isLoadingIdentifier && isLoadingEreolData,
isAvailable
};
};

export default useOnlineAvailabilityData;
65 changes: 65 additions & 0 deletions src/components/availability-label/usePhysicalAvailabilityData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { useConfig } from "../../core/utils/config";
import { FaustId } from "../../core/utils/types/ids";
import useGetAvailability from "../../core/utils/useGetAvailability";
import { ManifestationMaterialType } from "../../core/utils/types/material-type";

const usePhysicalAvailabilityData = ({
enabled,
faustIds,
manifestText
}: {
enabled: boolean;
faustIds: FaustId[] | null;
manifestText: string;
}) => {
const config = useConfig();

const response = useGetAvailability({
faustIds: faustIds ?? [],
config,
options: {
query: {
// FBS / useGetAvailabilityV3 is responsible for handling availability
// for physical items. This will be the majority of all materials so we
// use this for everything except materials that are explicitly online.
enabled:
enabled &&
faustIds !== null &&
manifestText !== ManifestationMaterialType.article
}
}
});

const { isLoading, data } = response;

// Articles are always available.
if (manifestText === ManifestationMaterialType.article) {
return {
isLoading: false,
isAvailable: true
};
}

// If we do not have data yet, return loading state.
if (!data) {
return {
isLoading,
isAvailable: null
};
}

// If we have data, check if any of the items are available.
if (data?.some((item) => item.available)) {
return {
isLoading: false,
isAvailable: true
};
}
// Otherwise the manifestation is not available.
return {
isLoading: false,
isAvailable: false
};
};

export default usePhysicalAvailabilityData;
1 change: 1 addition & 0 deletions src/components/material/MaterialMainfestationItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ const MaterialMainfestationItem: FC<MaterialMainfestationItemProps> = ({
<div className="material-manifestation-item">
<div className="material-manifestation-item__availability">
<AvailabilityLabel
key={`${faustId}-material-manifestation-item`}
manifestText={materialTypes[0]?.materialTypeSpecific.display}
faustIds={[faustId]}
isbns={identifiers.map((identifier) => identifier.value)}
Expand Down
Loading

0 comments on commit 4280787

Please sign in to comment.