Skip to content

Commit

Permalink
Merge pull request #50 from NYPL/SCC-3776/item-table-component
Browse files Browse the repository at this point in the history
SCC-3776 - Item Table Component
  • Loading branch information
dgcohen authored Dec 21, 2023
2 parents 4614937 + 903a570 commit 8e584fc
Show file tree
Hide file tree
Showing 27 changed files with 583 additions and 26 deletions.
5 changes: 4 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
PLATFORM_API_CLIENT_ID=[secret]
PLATFORM_API_CLIENT_SECRET=[secret]
NYPL_HEADER_URL=https://ds-header.nypl.org
NYPL_HEADER_URL=https://ds-header.nypl.org
NEXT_PUBLIC_CLOSED_LOCATIONS=""
NEXT_PUBLIC_RECAP_CLOSED_LOCATIONS=""
NEXT_PUBLIC_NON_RECAP_CLOSED_LOCATIONS=""
9 changes: 6 additions & 3 deletions ENVIRONMENTVARS.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,12 @@ If an environment variable is updated, make sure to restart the server for the a

These environment variables control how certain elements on the page render and where to fetch data.

| Variable | Type | Value Example | Description |
| ----------------- | ------ | ---------------------------- | ------------------------------------------------------------------------ |
| `NYPL_HEADER_URL` | string | "https://ds-header.nypl.org" | The base URL of the NYPL environment-specific header and footer scripts. |
| Variable | Type | Value Example | Description |
| ---------------------------------------- | ------ | ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `NYPL_HEADER_URL` | string | "https://ds-header.nypl.org" | The base URL of the NYPL environment-specific header and footer scripts. |
| `NEXT_PUBLIC_CLOSED_LOCATIONS` | string | "all;Library of the Performing Arts" | A semicolon-delimited list of strings. Include quotes around the string. All locations beginning with any string in this list will be removed from the list of request options in the `ElectronicDelivery`, `HoldRequest`, and `ItemTableRow` components. Currently used physical locations: `Schwarzman;Science;Library for the Performing Arts;Schomburg`. To close all locations, add `all`. This will also remove EDD as a request option, the 'Request' buttons, and also disable the hold request/edd forms. If `all` is not present, EDD and 'Request' buttons will still be available. |
| `NEXT_PUBLIC_RECAP_CLOSED_LOCATIONS` | string | "" | A semicolon-delimited list of closed locations that are recap. |
| `NEXT_PUBLIC_NON_RECAP_CLOSED_LOCATIONS` | string | "" | A semicolon-delimited list of closed locations that are not recap. |

## AWS ECS Environment Variables

Expand Down
57 changes: 57 additions & 0 deletions __test__/fixtures/itemFixtures.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { ItemLocationEndpoint } from "../../src/types/itemTypes"

export const itemPhysicallyRequestable = {
"@id": "res:i10572545",
"@type": ["bf:Item"],
Expand All @@ -16,6 +18,7 @@ export const itemPhysicallyRequestable = {
prefLabel: "book, limited circ, MaRLI",
},
],
dueDate: ["2023-09-03"],
eddRequestable: false,
enumerationChronology: ["no. 4 (2001)"],
formatLiteral: ["Text"],
Expand Down Expand Up @@ -282,6 +285,60 @@ export const itemNYPLReCAP = {
},
}

export const itemAvailableOnsite = {
"@id": "res:i14119377",
"@type": ["bf:Item"],
accessMessage: [
{
"@id": "accessMessage:1",
prefLabel: "Use in library",
},
],
catalogItemType: [
{
"@id": "catalogItemType:55",
prefLabel: "book, limited circ, MaRLI",
},
],
eddRequestable: true,
formatLiteral: ["Text"],
holdingLocation: [
{
"@id": "loc:mal92",
prefLabel: "Schwarzman Building M2 - General Research Room 315",
endpoint: "schwarzman" as ItemLocationEndpoint,
},
],
idBarcode: ["33433048828085"],
identifier: [
{
"@type": "bf:ShelfMark",
"@value": "JFE 93-253",
},
{
"@type": "bf:Barcode",
"@value": "33433048828085",
},
],
m2CustomerCode: ["XF"],
physRequestable: true,
physicalLocation: ["JFE 93-253"],
requestable: [true],
shelfMark: ["JFE 93-253"],
specRequestable: false,
status: [
{
"@id": "status:a",
prefLabel: "Available",
},
],
uri: "i14119377",
idNyplSourceId: {
"@type": "SierraNypl",
"@value": "14119377",
},
}

export const itemNoShelfMark = {
"@id": "res:i10572546",
"@type": ["bf:Item"],
Expand Down
6 changes: 3 additions & 3 deletions __test__/fixtures/searchResultPhysicalItems.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export const searchResultPhysicalItems = {
},
],
eddRequestable: true,
formatLiteral: ["Text"],
formatLiteral: ["Format"],
holdingLocation: [
{
"@id": "loc:mal82",
Expand Down Expand Up @@ -119,7 +119,7 @@ export const searchResultPhysicalItems = {
},
],
eddRequestable: true,
formatLiteral: ["Text"],
formatLiteral: ["Format"],
holdingLocation: [
{
"@id": "loc:rc2ma",
Expand Down Expand Up @@ -177,7 +177,7 @@ export const searchResultPhysicalItems = {
materialType: [
{
"@id": "resourcetypes:txt",
prefLabel: "Text",
prefLabel: "Material",
},
],
mediaType: [
Expand Down
1 change: 0 additions & 1 deletion pages/search/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ export default function Search({ results }) {

// Map Search Results Elements from response to SearchResultBib objects
const searchResultBibs = mapElementsToSearchResultsBibs(searchResultsElements)

// Map DRB Works from response to DRBResult objects
const drbResults = mapWorksToDRBResults(drbWorks)

Expand Down
2 changes: 1 addition & 1 deletion src/components/ItemFilters/FiltersContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {

import styles from "../../../styles/components/ItemFilters.module.scss"
import type { ItemAggregation } from "../../types/filterTypes"
import { ItemFilterData, LocationFilterData } from "../../models/itemFilterData"
import { ItemFilterData, LocationFilterData } from "../../models/ItemFilterData"
import ItemFilter from "./ItemFilter"
import {
buildAppliedFiltersString,
Expand Down
2 changes: 1 addition & 1 deletion src/components/ItemFilters/ItemFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
import type { Dispatch } from "react"
import { useCallback, useEffect, useState } from "react"

import type { ItemFilterData } from "../../models/itemFilterData"
import type { ItemFilterData } from "../../models/ItemFilterData"
import type { Option, AppliedFilters } from "../../types/filterTypes"
import styles from "../../../styles/components/ItemFilters.module.scss"

Expand Down
67 changes: 67 additions & 0 deletions src/components/ItemTable/ItemAvailability.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import React from "react"
import { render, screen } from "@testing-library/react"
import ItemAvailability from "./ItemAvailability"
import Item from "../../models/Item"
import SearchResultsBib from "../../models/SearchResultsBib"
import {
itemNYPLReCAP,
itemPhysicallyRequestable,
itemAvailableOnsite,
itemUnavailable,
} from "../../../__test__/fixtures/itemFixtures"
import { searchResultPhysicalItems } from "../../../__test__/fixtures/searchResultPhysicalItems"

const parentBib = new SearchResultsBib(searchResultPhysicalItems)

describe("ItemAvailability", () => {
it("renders the correct link when item is available, is reCAP, and does not have an aeon url", async () => {
const item = new Item(itemNYPLReCAP, parentBib)
render(<ItemAvailability item={item} />)
expect(
screen.getByRole("link", {
name: "How do I pick up this item and when will it be ready?",
})
).toHaveAttribute(
"href",
"https://www.nypl.org/help/request-research-materials"
)
})
it("renders the correct text when item is available, has an aeon url, and has a location endpoint", async () => {
const item = new Item(itemPhysicallyRequestable, parentBib)
render(<ItemAvailability item={item} />)
expect(screen.getByText("Available by appointment")).toBeInTheDocument()
expect(
screen.getByRole("link", {
name: "Schwarzman Building - Main Reading Room 315",
})
).toHaveAttribute("href", "https://www.nypl.org/locations/schwarzman")
})
it("renders the correct text for an available onsite item", async () => {
const item = new Item(itemAvailableOnsite, parentBib)
render(<ItemAvailability item={item} />)
expect(screen.getByText("Available")).toBeInTheDocument()
expect(
screen.getByText("- Can be used on site. Please visit", { exact: false })
).toBeInTheDocument()
expect(
screen.getByRole("link", {
name: "New York Public Library - Schwarzman Building M2",
})
).toHaveAttribute("href", "https://www.nypl.org/locations/schwarzman")
expect(
screen.getByText("to submit a request in person.", { exact: false })
).toBeInTheDocument()
})
it("renders the correct text for unavailable items", async () => {
const item = new Item(itemUnavailable, parentBib)
render(<ItemAvailability item={item} />)
expect(screen.getByText("Not available")).toBeInTheDocument()
expect(screen.getByText("- Please", { exact: false })).toBeInTheDocument()
screen.getByRole("button", {
name: "contact a librarian",
})
expect(
screen.getByText("for assistance.", { exact: false })
).toBeInTheDocument()
})
})
93 changes: 93 additions & 0 deletions src/components/ItemTable/ItemAvailability.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { Text, Link, Button, Box } from "@nypl/design-system-react-components"

import { appConfig } from "../../config/config"
import type Item from "../../models/Item"

interface ItemAvailabilityProps {
item: Item
}

/**
* The ItemAvailability component appears below the Item table and displays
* info about an item's availability.
* TODO: Add Feedback box, Due date, Available font styles
*/
const ItemAvailability = ({ item }: ItemAvailabilityProps) => {
// TODO: Move this logic into a getter function in the Item class that returns an availability status key
// and replace this nested If with a simple switch statement
if (item.isAvailable) {
if (item.isReCAP && !item.aeonUrl) {
// Available ReCAP item
return (
<Link
href={appConfig.externalUrls.researchMaterialsHelp}
target="_blank"
fontSize="sm"
>
How do I pick up this item and when will it be ready?
</Link>
)
} else if (item.aeonUrl && item.location?.endpoint) {
return (
<Text>
<Box as="span" color="ui.success.primary">
Available by appointment
</Box>
{!item.isReCAP ? (
<>
{" at "}
<Link
href={`${appConfig.externalUrls.locations}${item.location.endpoint}`}
target="_blank"
>
{item.location.prefLabel}
</Link>
</>
) : null}
</Text>
)
} else {
// Available Onsite item
const locationShort = item.location.prefLabel.split("-")[0]
return (
<Text>
<Box as="span" color="ui.success.primary">
Available
</Box>
{" - Can be used on site. Please visit "}
<Link
href={`${appConfig.externalUrls.locations}${item.location.endpoint}`}
target="_blank"
>
{`New York Public Library - ${locationShort}`}
</Link>
{" to submit a request in person."}
</Text>
)
}
} else {
// Not available
return (
<Text>
<Box as="span" color="ui.warning.primary">
Not available
</Box>
{item.dueDate && ` - In use until ${item.dueDate}`}
{" - Please "}
<Button
id="contact-librarian"
buttonType="link"
sx={{ display: "inline", fontWeight: "inherit", fontSize: "inherit" }}
onClick={() => {
console.log("TODO: Trigger Feedback box")
}}
>
contact a librarian
</Button>
{" for assistance."}
</Text>
)
}
}

export default ItemAvailability
43 changes: 43 additions & 0 deletions src/components/ItemTable/ItemTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Box, Table } from "@nypl/design-system-react-components"

import type ItemTableData from "../../models/ItemTableData"
import RequestButtons from "./RequestButtons"
import ItemAvailability from "./ItemAvailability"

interface ItemTableProps {
itemTableData: ItemTableData
}

/**
* The ItemTable displays item details, the RequestButtons
*/
const ItemTable = ({ itemTableData }: ItemTableProps) => {
return (
<>
<Table
columnHeaders={itemTableData.tableHeadings}
tableData={itemTableData.tableData}
mb="s"
// TODO: These styles approximate those of the old app.
// Design will VQA the component with the DS defaults, but leaving this for reference.
// sx={{
// tableLayout: "fixed",
// width: "full",
// tr: { border: "0 !important", padding: 0 },
// "th, td, th > span, td > span": {
// paddingTop: "xs",
// paddingBottom: "xs",
// },
// }}
/>
{!itemTableData.isBibPage && (
<Box mb="s">
<RequestButtons item={itemTableData.items[0]} />
<ItemAvailability item={itemTableData.items[0]} />
</Box>
)}
</>
)
}

export default ItemTable
Loading

0 comments on commit 8e584fc

Please sign in to comment.