diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a8b8758..591607d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,14 @@ ========== Changelog ========== ++++++++++ +v1.0.0 (24/03/2026) ++++++++++ + +**Added** + +- Display tag if sample is in inventory +- Narrow down shipping instructions based on user choice +++++++++ v0.20.2 (12/03/2026) diff --git a/src/app/proposals/[proposalId]/sessions/[visitNumber]/shipments/[shipmentId]/(default)/import-samples/page.tsx b/src/app/proposals/[proposalId]/sessions/[visitNumber]/shipments/[shipmentId]/(default)/import-samples/page.tsx index 12f016b..f3f1dbd 100644 --- a/src/app/proposals/[proposalId]/sessions/[visitNumber]/shipments/[shipmentId]/(default)/import-samples/page.tsx +++ b/src/app/proposals/[proposalId]/sessions/[visitNumber]/shipments/[shipmentId]/(default)/import-samples/page.tsx @@ -11,7 +11,6 @@ const SubmissionOverview = async (props: { params: Promise; searchParams: Promise<{ new: string }>; }) => { - const searchParams = await props.searchParams; const params = await props.params; return ( diff --git a/src/app/proposals/[proposalId]/sessions/[visitNumber]/shipments/[shipmentId]/(default)/submitted/page.test.tsx b/src/app/proposals/[proposalId]/sessions/[visitNumber]/shipments/[shipmentId]/(default)/submitted/page.test.tsx index 49c865b..1b9f405 100644 --- a/src/app/proposals/[proposalId]/sessions/[visitNumber]/shipments/[shipmentId]/(default)/submitted/page.test.tsx +++ b/src/app/proposals/[proposalId]/sessions/[visitNumber]/shipments/[shipmentId]/(default)/submitted/page.test.tsx @@ -16,28 +16,6 @@ describe("Sample Collection Submission Overview", () => { expect(screen.getAllByText(/2/i)).toHaveLength(2); }); - it("should display toast if shipment request creation fails", async () => { - server.use( - http.post( - "http://localhost/api/shipments/:shipmentId/request", - () => HttpResponse.json({}, { status: 424 }), - { once: true }, - ), - ); - - render(await SubmissionOverview(baseShipmentParams)); - - fireEvent.click(screen.getByText("Arrange Shipping")); - fireEvent.click(screen.getByText(/continue/i)); - - await waitFor(() => - expect(toastMock).toHaveBeenCalledWith({ - status: "error", - title: "Unable to create shipment request", - }), - ); - }); - it("should display warning if shipment contents are 'locked'", async () => { server.use( http.get( diff --git a/src/app/proposals/[proposalId]/sessions/[visitNumber]/shipments/[shipmentId]/(default)/submitted/page.tsx b/src/app/proposals/[proposalId]/sessions/[visitNumber]/shipments/[shipmentId]/(default)/submitted/page.tsx index b82da6e..d0cdff0 100644 --- a/src/app/proposals/[proposalId]/sessions/[visitNumber]/shipments/[shipmentId]/(default)/submitted/page.tsx +++ b/src/app/proposals/[proposalId]/sessions/[visitNumber]/shipments/[shipmentId]/(default)/submitted/page.tsx @@ -17,8 +17,8 @@ import { VStack, } from "@chakra-ui/react"; import { Metadata } from "next"; -import { ArrangeShipmentButton } from "@/components/navigation/ArrangeShipmentButton"; import { getShipmentData } from "@/utils/server/shipment"; +import { ShippingInstructions } from "./pageContent"; export const metadata: Metadata = { title: "Sample Collection Submitted - Scaup", @@ -101,39 +101,7 @@ const SubmissionOverview = async (props: { params: Promise }) => - - If you do not plan to use Diamond's own courier (DHL, on Diamond's - account), you do not need to arrange shipping through Diamond. When using your - own courier, ensure the labels provided by your courier are securely affixed. - - - If you plan to arrange shipping through Diamond,{" "} - - print the tracking labels after you're finished setting up your shipping details - - . This can be done on the{" "} - - sample collection summary page - - . You will be automatically redirected to that page once you finish setting up - shipping. - - - Tracking labels must be securely affixed to the outside of both dewars and - dewar cases, even if using your own courier. - - - - - - - + )} diff --git a/src/app/proposals/[proposalId]/sessions/[visitNumber]/shipments/[shipmentId]/(default)/submitted/pageContent.test.tsx b/src/app/proposals/[proposalId]/sessions/[visitNumber]/shipments/[shipmentId]/(default)/submitted/pageContent.test.tsx new file mode 100644 index 0000000..dc8785d --- /dev/null +++ b/src/app/proposals/[proposalId]/sessions/[visitNumber]/shipments/[shipmentId]/(default)/submitted/pageContent.test.tsx @@ -0,0 +1,50 @@ +import { fireEvent, render, screen, waitFor } from "@testing-library/react"; + +import { toastMock } from "@/../vitest.setup"; +import { server } from "@/mocks/server"; +import { HttpResponse, http } from "msw"; +import { ShippingInstructions } from "./pageContent"; + +const params = { + proposalId: "cm1", + visitNumber: "1", + shipmentId: "1", +}; + +describe("Shipping Instructions", () => { + it("should select 'yes' for shipping through Diamond if already booked", async () => { + render(); + + expect(screen.getByText("Yes")).toHaveAttribute("data-checked"); + }); + + it("should display label printing instructions if 'no' is selected", async () => { + render(); + + fireEvent.click(screen.getByText("No")); + expect(screen.getByText("Print Tracking Labels")).toBeInTheDocument(); + }); + + it("should display toast if shipment request creation fails", async () => { + server.use( + http.post( + "http://localhost/api/shipments/:shipmentId/request", + () => HttpResponse.json({}, { status: 424 }), + { once: true }, + ), + ); + + render(); + + fireEvent.click(screen.getByText("Yes")); + fireEvent.click(screen.getByText("Arrange Shipping")); + fireEvent.click(screen.getByText(/continue/i)); + + await waitFor(() => + expect(toastMock).toHaveBeenCalledWith({ + status: "error", + title: "Unable to create shipment request", + }), + ); + }); +}); diff --git a/src/app/proposals/[proposalId]/sessions/[visitNumber]/shipments/[shipmentId]/(default)/submitted/pageContent.tsx b/src/app/proposals/[proposalId]/sessions/[visitNumber]/shipments/[shipmentId]/(default)/submitted/pageContent.tsx new file mode 100644 index 0000000..5e65087 --- /dev/null +++ b/src/app/proposals/[proposalId]/sessions/[visitNumber]/shipments/[shipmentId]/(default)/submitted/pageContent.tsx @@ -0,0 +1,91 @@ +"use client"; +import { ShipmentParams } from "@/types/generic"; +import { + Box, + Button, + Divider, + Heading, + HStack, + Link, + Radio, + RadioGroup, + Stack, + Text, +} from "@chakra-ui/react"; +import { ArrangeShipmentButton } from "@/components/navigation/ArrangeShipmentButton"; +import { useState } from "react"; +import NextLink from "next/link"; + +export interface ShippingInstructionsProps { + params: ShipmentParams; + isBooked: boolean; +} + +export const ShippingInstructions = ({ params, isBooked }: ShippingInstructionsProps) => { + const [isBookedThroughDiamond, setIsBookedThroughDiamond] = useState(null); + return ( + <> + Shipping Options + + + I will be shipping my dewar using{" "} + Diamond's own courier (DHL, on Diamond's account) + + + + + Yes + + + No + + + + + {isBookedThroughDiamond !== null || isBooked ? ( + + {" "} + {isBookedThroughDiamond === "false" && !isBooked ? ( + <> + + When using your own courier, ensure the labels provided by your courier are securely + affixed + + + Tracking labels must be securely affixed to the outside of both dewars and + dewar cases, even if using your own courier. + + + + + + ) : ( + <> + + + Print the tracking labels after you're finished setting up your shipping + details + + . This can be done on the{" "} + + booking and labels page + + . You will be automatically redirected to that page once you finish setting up + shipping. + + + + + + )} + + ) : null} + + ); +}; diff --git a/src/components/navigation/SampleCard.test.tsx b/src/components/navigation/SampleCard.test.tsx index 9b2fdf5..029aab7 100644 --- a/src/components/navigation/SampleCard.test.tsx +++ b/src/components/navigation/SampleCard.test.tsx @@ -117,6 +117,23 @@ describe("Sample Card", () => { ); }); + it("should display tag if item is in inventory", () => { + renderWithProviders( + , + ); + + expect(screen.getByText("In Inventory")).toBeInTheDocument(); + }); + it("should display multiple parents", () => { renderWithProviders( { {sample.containerId && sample.containerName ? ( In {sample.containerName}{" "} + {sample.isInternal && ( + + In Inventory + + )} {sample.location && `, slot ${sample.location}`} ) : ( diff --git a/src/types/schema.d.ts b/src/types/schema.d.ts index 5da212a..f675f70 100644 --- a/src/types/schema.d.ts +++ b/src/types/schema.d.ts @@ -564,6 +564,26 @@ export interface paths { patch?: never; trace?: never; }; + "/internal-containers/preloaded-dewars": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Create Preloaded Inventory Dewar + * @description Create preloaded inventory dewar with pucks and grid boxes + */ + post: operations["create_preloaded_inventory_dewar_internal_containers_preloaded_dewars_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/sessions": { parameters: { query?: never; @@ -657,7 +677,7 @@ export interface components { /** Comments */ comments?: string | null; /** Shipmentid */ - shipmentId: number; + shipmentId?: number | null; /** Id */ id: number; /** Type */ @@ -768,10 +788,10 @@ export interface components { * @description Base top level container name. If name is not provided, the container's type followedby the container index is used */ name?: string | null; - /** Type */ - type?: string | null; /** Code */ code?: string | null; + /** Type */ + type?: string | null; /** Barcode */ barCode?: string | null; }; @@ -860,6 +880,11 @@ export interface components { */ isLocked: boolean; }; + /** PreloadedInventoryDewar */ + PreloadedInventoryDewar: { + /** Name */ + name: string; + }; /** SampleIn */ SampleIn: { /** Containerid */ @@ -913,7 +938,7 @@ export interface components { /** Id */ id: number; /** Shipmentid */ - shipmentId: number; + shipmentId?: number | null; /** Proteinid */ proteinId: number; /** Containername */ @@ -924,6 +949,8 @@ export interface components { dataCollectionGroupId?: number | null; /** Parentshipmentname */ parentShipmentName?: string | null; + /** Isinternal */ + isInternal?: boolean | null; /** Originsamples */ originSamples?: components["schemas"]["SampleOut"][] | null; /** Derivedsamples */ @@ -1043,11 +1070,11 @@ export interface components { /** Origin Url */ origin_url: string; /** Journey Type */ - journey_type: string; + journey_type?: string | null; /** Status */ status: string; /** Tracking Number */ - tracking_number: string; + tracking_number?: string | null; /** Pickup Confirmation Code */ pickup_confirmation_code?: string | null; /** Pickup Confirmation Timestamp */ @@ -1084,10 +1111,12 @@ export interface components { * @description Base top level container name. If name is not provided, the container's type followedby the container index is used */ name?: string | null; - /** Type */ - type: string; /** Code */ code?: string | null; + /** Type */ + type: string; + /** Manufacturerserialnumber */ + manufacturerSerialNumber?: string | null; /** * Isinternal * @default false @@ -1107,6 +1136,8 @@ export interface components { * @description Base top level container name. If name is not provided, the container's type followedby the container index is used */ name?: string | null; + /** Code */ + code?: string | null; /** Id */ id: number; /** Type */ @@ -1386,7 +1417,12 @@ export interface operations { }; create_sample_shipments__shipmentId__samples_post: { parameters: { - query?: never; + query?: { + /** @description Push sample to external DB. May create orphan samples (samples without a container) */ + pushToExternalDb?: boolean; + /** @description Include ordinal suffix in sample's name */ + includeSuffix?: boolean; + }; header?: never; path: { shipmentId: number; @@ -2325,9 +2361,43 @@ export interface operations { }; }; }; + create_preloaded_inventory_dewar_internal_containers_preloaded_dewars_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["PreloadedInventoryDewar"]; + }; + }; + responses: { + /** @description Successful Response */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TopLevelContainerOut"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; get_sessions_sessions_get: { parameters: { query?: { + /** @description Minimum session end date */ minEndDate?: string | null; /** @description Page number/Results to skip. Negative numbers count backwards from the last page */ page?: number;