From 42e6b7fcd54c470f390ceb1de1a465b452dc2ddf Mon Sep 17 00:00:00 2001 From: Vahid Bazzaz Date: Thu, 21 Sep 2023 18:31:44 +0330 Subject: [PATCH 01/18] create global timeline component --- .../components/Timeline/TimelineContainer.tsx | 60 +++++++++++++++++++ .../Timeline/components/TimelineBullet.tsx | 26 ++++++++ .../Timeline/components/TimelineEntry.tsx | 29 +++++++++ 3 files changed, 115 insertions(+) create mode 100644 react/src/components/Timeline/TimelineContainer.tsx create mode 100644 react/src/components/Timeline/components/TimelineBullet.tsx create mode 100644 react/src/components/Timeline/components/TimelineEntry.tsx diff --git a/react/src/components/Timeline/TimelineContainer.tsx b/react/src/components/Timeline/TimelineContainer.tsx new file mode 100644 index 000000000..f01f3b956 --- /dev/null +++ b/react/src/components/Timeline/TimelineContainer.tsx @@ -0,0 +1,60 @@ +import { Box, Flex, Text } from "@chakra-ui/react"; +import { User } from "types/generated/graphql"; +import TimelineEntry from "./components/TimelineEntry"; + +export interface ITimelineEntry { + action: string | undefined; + createdOn: Date; + createdBy: User; +} + +export interface IGroupedRecordEntry { + date: string; + entries: (ITimelineEntry | null | undefined)[]; +} + +export interface ITimelineProps { + records: IGroupedRecordEntry[]; +} + +function TimelineContainer({ records }: ITimelineProps) { + // eslint-disable-next-line no-console + console.log(records); + + return ( + + {records.map(({ date, entries }, index) => ( + + + + {date} + + + + {entries?.map((entry, indx) => ( + + ))} + + + ))} + + ); +} + +export default TimelineContainer; diff --git a/react/src/components/Timeline/components/TimelineBullet.tsx b/react/src/components/Timeline/components/TimelineBullet.tsx new file mode 100644 index 000000000..589f361a9 --- /dev/null +++ b/react/src/components/Timeline/components/TimelineBullet.tsx @@ -0,0 +1,26 @@ +import { Box } from "@chakra-ui/react"; + +function TimelineBullet() { + return ( + + ); +} + +export default TimelineBullet; diff --git a/react/src/components/Timeline/components/TimelineEntry.tsx b/react/src/components/Timeline/components/TimelineEntry.tsx new file mode 100644 index 000000000..effdc8d31 --- /dev/null +++ b/react/src/components/Timeline/components/TimelineEntry.tsx @@ -0,0 +1,29 @@ +import { Flex, Text } from "@chakra-ui/react"; +import TimelineBullet from "./TimelineBullet"; + +export interface ITimelineEntryProps { + content: string | undefined; + time: string | undefined; +} + +function TimelineEntry({ content, time }: ITimelineEntryProps) { + return ( + + + + + + + {content} + + + {time} + + + + + + ); +} + +export default TimelineEntry; From 5a5039e653568908650b485c853fbb2b12139417 Mon Sep 17 00:00:00 2001 From: Vahid Bazzaz Date: Thu, 21 Sep 2023 18:32:44 +0330 Subject: [PATCH 02/18] create history overlay component --- .../HistoryOverlay/HistoryOverlay.tsx | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 react/src/components/HistoryOverlay/HistoryOverlay.tsx diff --git a/react/src/components/HistoryOverlay/HistoryOverlay.tsx b/react/src/components/HistoryOverlay/HistoryOverlay.tsx new file mode 100644 index 000000000..60a02eca4 --- /dev/null +++ b/react/src/components/HistoryOverlay/HistoryOverlay.tsx @@ -0,0 +1,40 @@ +import { + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, +} from "@chakra-ui/react"; +import TimelineContainer, { IGroupedRecordEntry } from "../Timeline/TimelineContainer"; + +interface IHistoryOverlay { + data: IGroupedRecordEntry[]; + isOpen: boolean; + onClose: () => void; +} + +function HistoryOverlay({ data, isOpen, onClose }: IHistoryOverlay) { + return ( + + + + Box History + + + + + + + + ); +} + +export default HistoryOverlay; From 13a9e3f81b99c167b52d044d03f8307fc778f759 Mon Sep 17 00:00:00 2001 From: Vahid Bazzaz Date: Thu, 21 Sep 2023 18:33:43 +0330 Subject: [PATCH 03/18] make helper function for preparing box history entry --- react/src/utils/helpers.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/react/src/utils/helpers.ts b/react/src/utils/helpers.ts index 37fa36f22..67a727f01 100644 --- a/react/src/utils/helpers.ts +++ b/react/src/utils/helpers.ts @@ -71,3 +71,13 @@ export const formatDateKey = (date: Date): string => { return `${date.toLocaleString("default", { month: "short" })} ${date.getDate()}, ${date.getFullYear()}`; }; + +export const prepareBoxHistoryEntryText = (text: string): string => { + // Remove the last character if it is a semicolon + const trimmedText = text?.endsWith(";") ? text?.slice(0, -1) : text; + + // Replace "box state" with "box status" (ref. trello card https://trello.com/c/ClAikFIk) + const updatedText = trimmedText?.replace("box state", "box status"); + + return updatedText; +}; From f807db3138f2f108c74595804e329faba65aec5d Mon Sep 17 00:00:00 2001 From: Vahid Bazzaz Date: Thu, 21 Sep 2023 18:38:29 +0330 Subject: [PATCH 04/18] add history overlay in the box view - compile box history logs by date - show box history timeline using history overlay --- react/src/views/Box/BoxView.test.tsx | 1 + react/src/views/Box/BoxView.tsx | 46 ++++++++++++++++++- react/src/views/Box/components/BoxCard.tsx | 32 ++++++++++--- react/src/views/Box/components/BoxDetails.tsx | 3 ++ .../views/Box/components/HistoryEntries.tsx | 13 +----- 5 files changed, 77 insertions(+), 18 deletions(-) diff --git a/react/src/views/Box/BoxView.test.tsx b/react/src/views/Box/BoxView.test.tsx index 3103e565b..80faafb9e 100644 --- a/react/src/views/Box/BoxView.test.tsx +++ b/react/src/views/Box/BoxView.test.tsx @@ -777,6 +777,7 @@ it("3.1.10 - No Data or Null Data Fetched for a given Box Label Identifier", asy (); - + const { isOpen: isHistoryOpen, onOpen: onHistoryOpen, onClose: onHistoryClose } = useDisclosure(); const { assignBoxesToShipment, unassignBoxesToShipment, @@ -169,6 +175,38 @@ function BTBox() { ? [BoxState.Receiving, BoxState.MarkedForShipment, BoxState.InTransit].includes(currentBoxState) : false; + // map over each box HistoryEntry to compile its timeline records + const boxLogs: ITimelineEntry[] = (allData.data?.box?.history as HistoryEntry[])?.flatMap( + (histories) => + _.compact([ + histories?.user && { + action: prepareBoxHistoryEntryText(`${histories.user.name} ${histories.changes}`), + createdBy: histories.user as User, + createdOn: new Date(histories.changeDate), + }, + ]), + ) as unknown as ITimelineEntry[]; + + const allLogs = _.orderBy( + _.sortBy(_.concat([...(boxLogs || [])]), "createdOn"), + ["createdOn"], + ["asc", "desc"], + ); + const groupedHistoryEntries: _.Dictionary = _.groupBy( + allLogs, + (log) => `${formatDateKey(log?.createdOn)}`, + ); + + // sort each array of history entries in descending order + const sortedGroupedHistoryEntries = _(groupedHistoryEntries) + .toPairs() + .map(([date, entries]) => ({ + date, + entries: _.orderBy(entries, (entry) => new Date(entry?.createdOn), "desc"), + })) + .orderBy((entry) => new Date(entry.date), "desc") + .value(); + const [updateNumberOfItemsMutation, updateNumberOfItemsMutationStatus] = useMutation< UpdateNumberOfItemsMutation, UpdateNumberOfItemsMutationVariables @@ -596,6 +634,7 @@ function BTBox() { boxData={boxData} boxInTransit={boxInTransit} onPlusOpen={onPlusOpen} + onHistoryOpen={onHistoryOpen} onMinusOpen={onMinusOpen} onMoveToLocationClick={onMoveBoxToLocationClick} onStateChange={onStateChange} @@ -630,6 +669,11 @@ function BTBox() { closeOnOverlayClick={false} redirectToShipmentView /> + ); } diff --git a/react/src/views/Box/components/BoxCard.tsx b/react/src/views/Box/components/BoxCard.tsx index ce74646c1..50ddb399e 100644 --- a/react/src/views/Box/components/BoxCard.tsx +++ b/react/src/views/Box/components/BoxCard.tsx @@ -22,7 +22,9 @@ import { SkeletonCircle, Skeleton, SkeletonText, + Icon, } from "@chakra-ui/react"; +import { MdHistory } from "react-icons/md"; import { NavLink } from "react-router-dom"; import { BoxByLabelIdentifierQuery, @@ -38,6 +40,7 @@ import HistoryEntries from "./HistoryEntries"; export interface IBoxCardProps { boxData: BoxByLabelIdentifierQuery["box"] | UpdateLocationOfBoxMutation["updateBox"]; boxInTransit: boolean; + onHistoryOpen: () => void; onPlusOpen: () => void; onMinusOpen: () => void; onStateChange: (boxState: BoxState) => void; @@ -47,6 +50,7 @@ export interface IBoxCardProps { function BoxCard({ boxData, boxInTransit, + onHistoryOpen, onPlusOpen, onMinusOpen, onStateChange, @@ -327,12 +331,28 @@ function BoxCard({ History:   - {!isLoading && ( - - )} - {isLoading && ( - - )} + + {!isLoading && ( + + )} + {isLoading && ( + + )} + {boxData?.history && boxData?.history?.length > 1 && ( + <> + + } + /> + + )} + diff --git a/react/src/views/Box/components/BoxDetails.tsx b/react/src/views/Box/components/BoxDetails.tsx index 17bfca5bc..71377b6ab 100644 --- a/react/src/views/Box/components/BoxDetails.tsx +++ b/react/src/views/Box/components/BoxDetails.tsx @@ -13,6 +13,7 @@ interface IBoxDetailsProps { boxData: BoxByLabelIdentifierQuery["box"] | UpdateLocationOfBoxMutation["updateBox"]; boxInTransit: boolean; onMoveToLocationClick: (locationId: string) => void; + onHistoryOpen: () => void; onPlusOpen: () => void; onMinusOpen: () => void; onStateChange: (boxState: BoxState) => void; @@ -30,6 +31,7 @@ function BoxDetails({ onMoveToLocationClick, onAssignBoxToDistributionEventClick, onUnassignBoxFromDistributionEventClick, + onHistoryOpen, onPlusOpen, onMinusOpen, onStateChange, @@ -53,6 +55,7 @@ function BoxDetails({ {historyEntry?.user?.name} {" on "} {formatDate(historyEntry?.changeDate)}{" "} - {prepareHistoryEntryText(historyEntry?.changes)} + {prepareBoxHistoryEntryText(historyEntry?.changes)} From 7b852c3c4f00ae55d4174bf0a1d45e1489a1d486 Mon Sep 17 00:00:00 2001 From: Vahid Bazzaz Date: Thu, 21 Sep 2023 19:47:28 +0330 Subject: [PATCH 05/18] add test case 3.1.12 --- .../views/Box/BoxViewHistoryOverlay.test.tsx | 145 ++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 react/src/views/Box/BoxViewHistoryOverlay.test.tsx diff --git a/react/src/views/Box/BoxViewHistoryOverlay.test.tsx b/react/src/views/Box/BoxViewHistoryOverlay.test.tsx new file mode 100644 index 000000000..c4c3911b4 --- /dev/null +++ b/react/src/views/Box/BoxViewHistoryOverlay.test.tsx @@ -0,0 +1,145 @@ +/* eslint-disable */ +import "@testing-library/jest-dom"; +import { screen, render, waitFor } from "tests/test-utils"; +import userEvent from "@testing-library/user-event"; +import { cache } from "queries/cache"; + +import { useErrorHandling } from "hooks/useErrorHandling"; +import { useNotification } from "hooks/useNotification"; + +import { BOX_BY_LABEL_IDENTIFIER_AND_ALL_SHIPMENTS_QUERY } from "queries/queries"; +import { organisation1 } from "mocks/organisations"; +import BTBox from "./BoxView"; + +import { BoxState } from "types/generated/graphql"; +import { history1, history2 } from "mocks/histories"; +import { generateMockBox } from "mocks/boxes"; + +const mockedTriggerError = jest.fn(); +const mockedCreateToast = jest.fn(); +jest.mock("hooks/useErrorHandling"); +jest.mock("hooks/useNotification"); + +cache.reset(); + +const initialQueryForBoxWithHistory = { + request: { + query: BOX_BY_LABEL_IDENTIFIER_AND_ALL_SHIPMENTS_QUERY, + variables: { + labelIdentifier: "123", + }, + notifyOnNetworkStatusChange: true, + }, + result: { + data: { + box: generateMockBox({ + labelIdentifier: "123", + state: BoxState.InStock, + histories: [history1, history2], + }), + }, + }, +}; + +// Test case 3.1.12 +describe("3.1.12 - Box HistoryOverlay on BoxView", () => { + beforeEach(() => { + // we need to mock matchmedia + // https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom + Object.defineProperty(window, "matchMedia", { + writable: true, + value: jest.fn().mockImplementation((query) => ({ + matches: false, + media: query, + onchange: null, + addListener: jest.fn(), // Deprecated + removeListener: jest.fn(), // Deprecated + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + dispatchEvent: jest.fn(), + })), + }); + const mockedUseErrorHandling = jest.mocked(useErrorHandling); + mockedUseErrorHandling.mockReturnValue({ triggerError: mockedTriggerError }); + const mockedUseNotification = jest.mocked(useNotification); + mockedUseNotification.mockReturnValue({ createToast: mockedCreateToast }); + }); + + // Test case 3.1.12.1 + it("3.1.12.1 - When more than one entry is available displays history icon", async () => { + render(, { + routePath: "/bases/:baseId/boxes/:labelIdentifier", + initialUrl: "/bases/1/boxes/123", + additionalRoute: "/bases/1/shipment/1", + mocks: [initialQueryForBoxWithHistory], + addTypename: true, + cache, + globalPreferences: { + dispatch: jest.fn(), + globalPreferences: { + organisation: { id: organisation1.id, name: organisation1.name }, + availableBases: organisation1.bases, + selectedBase: organisation1.bases[0], + }, + }, + }); + + await waitFor(async () => { + expect(await screen.getByRole("heading", { name: /box 123/i })).toBeInTheDocument(); + }); + + await waitFor(async () => { + expect( + await screen.getByRole("button", { + name: /show detail history/i, + }), + ).toBeInTheDocument(); + }); + }, 10000); + + // Test case 3.1.12.2 + it("3.1.12.2 - Click on history icons opens history overlay", async () => { + const user = userEvent.setup(); + + render(, { + routePath: "/bases/:baseId/boxes/:labelIdentifier", + initialUrl: "/bases/1/boxes/123", + additionalRoute: "/bases/1/shipment/1", + mocks: [initialQueryForBoxWithHistory], + addTypename: true, + cache, + globalPreferences: { + dispatch: jest.fn(), + globalPreferences: { + organisation: { id: organisation1.id, name: organisation1.name }, + availableBases: organisation1.bases, + selectedBase: organisation1.bases[0], + }, + }, + }); + + await waitFor(async () => { + expect(await screen.getByRole("heading", { name: /box 123/i })).toBeInTheDocument(); + }); + + const historyButton = await screen.getByRole("button", { + name: /show detail history/i, + }); + + await waitFor(async () => { + expect(historyButton).toBeInTheDocument(); + }); + + await user.click(historyButton); + + await waitFor(async () => { + expect(await screen.getByRole("banner")).toBeInTheDocument(); + expect(screen.getByText(/jan 14, 2023/i)).toBeInTheDocument(); + expect( + screen.getByText(/dev coordinator changed box location from wh men to wh women/i), + ).toBeInTheDocument(); + expect(screen.getByText(/jan 12, 2023/i)).toBeInTheDocument(); + expect(screen.getByText(/dev coordinator created record/i)).toBeInTheDocument(); + }); + }, 10000); +}); From 30b268f6bb3713f36723f4e08031f96a03b60e23 Mon Sep 17 00:00:00 2001 From: Vahid Bazzaz Date: Thu, 21 Sep 2023 19:47:48 +0330 Subject: [PATCH 06/18] clean up --- react/src/components/Timeline/TimelineContainer.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/react/src/components/Timeline/TimelineContainer.tsx b/react/src/components/Timeline/TimelineContainer.tsx index f01f3b956..a3ff3e686 100644 --- a/react/src/components/Timeline/TimelineContainer.tsx +++ b/react/src/components/Timeline/TimelineContainer.tsx @@ -18,9 +18,6 @@ export interface ITimelineProps { } function TimelineContainer({ records }: ITimelineProps) { - // eslint-disable-next-line no-console - console.log(records); - return ( {records.map(({ date, entries }, index) => ( From efc66c4d10aab992576840816b5826df488bd050 Mon Sep 17 00:00:00 2001 From: Vahid Bazzaz Date: Fri, 22 Sep 2023 13:38:32 +0330 Subject: [PATCH 07/18] fix time format in timeline --- .../src/components/Timeline/TimelineContainer.tsx | 4 +--- .../Timeline/components/TimelineEntry.tsx | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/react/src/components/Timeline/TimelineContainer.tsx b/react/src/components/Timeline/TimelineContainer.tsx index a3ff3e686..86632dfef 100644 --- a/react/src/components/Timeline/TimelineContainer.tsx +++ b/react/src/components/Timeline/TimelineContainer.tsx @@ -42,9 +42,7 @@ function TimelineContainer({ records }: ITimelineProps) { ))} diff --git a/react/src/components/Timeline/components/TimelineEntry.tsx b/react/src/components/Timeline/components/TimelineEntry.tsx index effdc8d31..a82ebfec1 100644 --- a/react/src/components/Timeline/components/TimelineEntry.tsx +++ b/react/src/components/Timeline/components/TimelineEntry.tsx @@ -3,10 +3,21 @@ import TimelineBullet from "./TimelineBullet"; export interface ITimelineEntryProps { content: string | undefined; - time: string | undefined; + time: string | Date | undefined; } function TimelineEntry({ content, time }: ITimelineEntryProps) { + function formatTime(date: Date | string): string { + const formattedDate = typeof date === "string" && date !== "" ? new Date(date) : date; + + if (formattedDate instanceof Date) { + const hours = formattedDate.getHours().toString().padStart(2, "0"); + const minutes = formattedDate.getMinutes().toString().padStart(2, "0"); + return `${hours}:${minutes}`; + } + + return ""; + } return ( @@ -17,7 +28,7 @@ function TimelineEntry({ content, time }: ITimelineEntryProps) { {content} - {time} + {time ? formatTime(time) : ""} From daf4beeab7780083d282f953512099a647194e0c Mon Sep 17 00:00:00 2001 From: Vahid Bazzaz Date: Fri, 22 Sep 2023 13:38:54 +0330 Subject: [PATCH 08/18] refactor shipment to use timeline component --- .../components/ShipmentHistory.tsx | 11 ++----- .../components/TimelineBullet.tsx | 26 ----------------- .../ShipmentView/components/TimelineEntry.tsx | 29 ------------------- 3 files changed, 2 insertions(+), 64 deletions(-) delete mode 100644 react/src/views/Transfers/ShipmentView/components/TimelineBullet.tsx delete mode 100644 react/src/views/Transfers/ShipmentView/components/TimelineEntry.tsx diff --git a/react/src/views/Transfers/ShipmentView/components/ShipmentHistory.tsx b/react/src/views/Transfers/ShipmentView/components/ShipmentHistory.tsx index 6582595c3..95e958db2 100644 --- a/react/src/views/Transfers/ShipmentView/components/ShipmentHistory.tsx +++ b/react/src/views/Transfers/ShipmentView/components/ShipmentHistory.tsx @@ -1,6 +1,6 @@ import { Box, Flex, Text } from "@chakra-ui/react"; +import TimelineEntry from "components/Timeline/components/TimelineEntry"; import { IGroupedHistoryEntry, IShipmentHistory, ShipmentActionEvent } from "./ShipmentTabs"; -import TimelineEntry from "./TimelineEntry"; export interface IShipmentHistoryProps { histories: IGroupedHistoryEntry[]; @@ -29,13 +29,6 @@ function ShipmentHistory({ histories }: IShipmentHistoryProps) { return changes; }; - - function formatTime(date: Date): string { - const hours = date.getHours().toString().padStart(2, "0"); - const minutes = date.getMinutes().toString().padStart(2, "0"); - return `${hours}:${minutes}`; - } - return ( {histories.map(({ date, entries }, index) => ( @@ -60,7 +53,7 @@ function ShipmentHistory({ histories }: IShipmentHistoryProps) { ))} diff --git a/react/src/views/Transfers/ShipmentView/components/TimelineBullet.tsx b/react/src/views/Transfers/ShipmentView/components/TimelineBullet.tsx deleted file mode 100644 index 589f361a9..000000000 --- a/react/src/views/Transfers/ShipmentView/components/TimelineBullet.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { Box } from "@chakra-ui/react"; - -function TimelineBullet() { - return ( - - ); -} - -export default TimelineBullet; diff --git a/react/src/views/Transfers/ShipmentView/components/TimelineEntry.tsx b/react/src/views/Transfers/ShipmentView/components/TimelineEntry.tsx deleted file mode 100644 index effdc8d31..000000000 --- a/react/src/views/Transfers/ShipmentView/components/TimelineEntry.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { Flex, Text } from "@chakra-ui/react"; -import TimelineBullet from "./TimelineBullet"; - -export interface ITimelineEntryProps { - content: string | undefined; - time: string | undefined; -} - -function TimelineEntry({ content, time }: ITimelineEntryProps) { - return ( - - - - - - - {content} - - - {time} - - - - - - ); -} - -export default TimelineEntry; From 4b61c8e2d5625048e65be0d3e6c5213a0a96ffce Mon Sep 17 00:00:00 2001 From: Vahid Bazzaz Date: Fri, 22 Sep 2023 13:48:06 +0330 Subject: [PATCH 09/18] fix sending undefined comment value in update box mutation --- react/src/views/BoxEdit/BoxEditView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react/src/views/BoxEdit/BoxEditView.tsx b/react/src/views/BoxEdit/BoxEditView.tsx index 2038073ed..cc25933ae 100644 --- a/react/src/views/BoxEdit/BoxEditView.tsx +++ b/react/src/views/BoxEdit/BoxEditView.tsx @@ -135,7 +135,7 @@ function BoxEditView() { numberOfItems: boxEditFormData.numberOfItems, locationId: parseInt(boxEditFormData.locationId.value, 10), tagIds, - comment: boxEditFormData?.comment, + comment: boxEditFormData?.comment || null, }, }) .then((mutationResult) => { From 3c0d4d19d6472ac8a52da25bb56a88466b988173 Mon Sep 17 00:00:00 2001 From: Vahid Bazzaz Date: Sat, 23 Sep 2023 17:59:49 +0330 Subject: [PATCH 10/18] refactor shipment history to use timeline component --- .../Transfers/ShipmentView/ShipmentView.tsx | 77 ++++++++++++------- .../components/ShipmentHistory.tsx | 66 ---------------- .../ShipmentView/components/ShipmentTabs.tsx | 33 +------- 3 files changed, 53 insertions(+), 123 deletions(-) delete mode 100644 react/src/views/Transfers/ShipmentView/components/ShipmentHistory.tsx diff --git a/react/src/views/Transfers/ShipmentView/ShipmentView.tsx b/react/src/views/Transfers/ShipmentView/ShipmentView.tsx index ab80a1bbf..2baabcf63 100644 --- a/react/src/views/Transfers/ShipmentView/ShipmentView.tsx +++ b/react/src/views/Transfers/ShipmentView/ShipmentView.tsx @@ -45,14 +45,27 @@ import { BoxReconciliationOverlay } from "components/BoxReconciliationOverlay/Bo import { UPDATE_SHIPMENT_WHEN_RECEIVING } from "queries/mutations"; import { boxReconciliationOverlayVar } from "queries/cache"; import { MobileBreadcrumbButton } from "components/BreadcrumbNavigation"; +import { ITimelineEntry } from "components/Timeline/TimelineContainer"; import ShipmentCard from "./components/ShipmentCard"; -import ShipmentTabs, { IShipmentHistory, ShipmentActionEvent } from "./components/ShipmentTabs"; +import ShipmentTabs from "./components/ShipmentTabs"; import ShipmentOverlay, { IShipmentOverlayData } from "./components/ShipmentOverlay"; import ShipmentActionButtons from "./components/ShipmentActionButtons"; import ShipmentReceivingContent from "./components/ShipmentReceivingContent"; import ShipmentReceivingCard from "./components/ShipmentReceivingCard"; -// graphql query and mutations +// eslint-disable-next-line no-shadow +enum ShipmentActionEvent { + ShipmentStarted = "Shipment Started", + ShipmentCanceled = "Shipment Canceled", + ShipmentSent = "Shipment Sent", + ShipmentStartReceiving = "Shipment Being Received", + ShipmentCompleted = "Shipment Completed", + BoxAdded = "Box Added", + BoxRemoved = "Box Removed", + BoxLost = "Box Marked Lost", + BoxReceived = "Box Received", +} + export const SHIPMENT_BY_ID_QUERY = gql` ${SHIPMENT_FIELDS_FRAGMENT} query ShipmentById($id: ID!) { @@ -124,23 +137,18 @@ function ShipmentView() { onOpen: onShipmentOverlayOpen, } = useDisclosure(); - // State to show minus button near boxes when remove button is triggered const [showRemoveIcon, setShowRemoveIcon] = useState(false); const [shipmentState, setShipmentState] = useState(); - // State to pass Data from a row to the Overlay const [shipmentOverlayData, setShipmentOverlayData] = useState(); - // variables in URL const shipmentId = useParams<{ id: string }>().id!; - // fetch shipment data const { loading, error, data } = useQuery( SHIPMENT_BY_ID_QUERY, { variables: { id: shipmentId, }, - // returns cache first, but syncs with server in background fetchPolicy: "cache-and-network", }, ); @@ -152,7 +160,6 @@ function ShipmentView() { }; }, [data]); - // Mutations for shipment actions const [updateShipmentWhenPreparing, updateShipmentWhenPreparingStatus] = useMutation< RemoveBoxFromShipmentMutation, RemoveBoxFromShipmentMutationVariables @@ -182,7 +189,6 @@ function ShipmentView() { UpdateShipmentWhenReceivingMutationVariables >(UPDATE_SHIPMENT_WHEN_RECEIVING); - // shipment actions in the modal const handleShipment = useCallback( (mutation, kind, successMessage = "", failedMessage = "") => () => { @@ -224,7 +230,6 @@ function ShipmentView() { ); const onReceive = handleShipment(startReceivingShipment, "receive"); - // callback function triggered when a state button is clicked. const openShipmentOverlay = useCallback(() => { setShipmentOverlayData({ id: data?.shipment?.id, @@ -356,16 +361,38 @@ function ShipmentView() { updateShipmentWhenReceivingStatus.loading || lostShipmentStatus.loading; - // transform shipment data for UI const shipmentData = data?.shipment! as Shipment; const shipmentContents = (data?.shipment?.details.filter((item) => item.removedOn === null) ?? []) as ShipmentDetail[]; + const changesLabel = (history: any): string => { + let changes = ""; + if ( + [ + ShipmentActionEvent.ShipmentCanceled, + ShipmentActionEvent.ShipmentCompleted, + ShipmentActionEvent.ShipmentSent, + ShipmentActionEvent.ShipmentStartReceiving, + ShipmentActionEvent.ShipmentStarted, + ].includes(history.action) + ) { + changes = `Shipment is ${history.action.toLowerCase().replace("shipment", "")} by ${ + history.createdBy?.name + }`; + } else { + changes = `Box ${history.box} is ${history.action.toLowerCase().replace("box", "")} by ${ + history.createdBy?.name + }`; + } + + return changes; + }; + const generateShipmentHistory = ( entry: Partial>, - ): IShipmentHistory[] => { - const shipmentHistory: IShipmentHistory[] = []; + ): ITimelineEntry[] => { + const shipmentHistory: ITimelineEntry[] = []; Object.entries(entry).forEach(([action, shipmentObj]) => { if (shipmentObj.createdOn) { @@ -380,7 +407,7 @@ function ShipmentView() { return shipmentHistory; }; - const shipmentLogs: IShipmentHistory[] = generateShipmentHistory({ + const shipmentLogs: ITimelineEntry[] = generateShipmentHistory({ [ShipmentActionEvent.ShipmentStarted]: { createdOn: shipmentData?.startedOn, createdBy: shipmentData?.startedBy! as User, @@ -403,10 +430,7 @@ function ShipmentView() { }, }); - // map over each ShipmentDetail to compile its history records - const shipmentDetailLogs: IShipmentHistory[] = ( - data?.shipment?.details! as ShipmentDetail[] - )?.flatMap((detail) => + const shipmentDetailLogs = (data?.shipment?.details! as ShipmentDetail[])?.flatMap((detail) => _.compact([ detail?.createdBy && { box: detail.box.labelIdentifier, @@ -433,29 +457,30 @@ function ShipmentView() { createdOn: new Date(detail?.receivedOn), }, ]), - ) as unknown as IShipmentHistory[]; + ); const allLogs = _.orderBy( _.sortBy(_.concat([...(shipmentLogs || []), ...(shipmentDetailLogs || [])]), "createdOn"), ["createdOn"], ["asc", "desc"], ); - const groupedHistoryEntries: _.Dictionary = _.groupBy( + const groupedHistoryEntries: _.Dictionary = _.groupBy( allLogs, (log) => `${formatDateKey(log?.createdOn)}`, ); - // sort each array of history entries in descending order const sortedGroupedHistoryEntries = _(groupedHistoryEntries) .toPairs() .map(([date, entries]) => ({ date, - entries: _.orderBy(entries, (entry) => new Date(entry?.createdOn), "desc"), + entries: _.orderBy(entries, (entry) => new Date(entry?.createdOn), "desc").map((entry) => ({ + ...entry, + action: changesLabel(entry), + })), })) .orderBy((entry) => new Date(entry.date), "desc") .value(); - // variables for loading dynamic components let shipmentTitle = View Shipment; let shipmentTab; let shipmentCard; @@ -465,7 +490,6 @@ function ShipmentView() { let canLooseShipment = false; let shipmentActionButtons = ; - // error and loading handling if (error) { shipmentTab = ( @@ -484,7 +508,6 @@ function ShipmentView() { (b) => b.id === data?.shipment?.sourceBase?.id, ) !== "undefined"; - // Role Sender // Different State UI Changes if (ShipmentState.Preparing === shipmentState && isSender) { canUpdateShipment = true; canCancelShipment = true; @@ -492,9 +515,7 @@ function ShipmentView() { shipmentTitle = Prepare Shipment; } else if (ShipmentState.Sent === shipmentState && isSender) { canLooseShipment = true; - } - // Role Receiver // Different State UI Changes - else if (ShipmentState.Sent === shipmentState && !isSender) { + } else if (ShipmentState.Sent === shipmentState && !isSender) { canLooseShipment = true; } else if (ShipmentState.Receiving === shipmentState && !isSender) { canLooseShipment = true; diff --git a/react/src/views/Transfers/ShipmentView/components/ShipmentHistory.tsx b/react/src/views/Transfers/ShipmentView/components/ShipmentHistory.tsx deleted file mode 100644 index 95e958db2..000000000 --- a/react/src/views/Transfers/ShipmentView/components/ShipmentHistory.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { Box, Flex, Text } from "@chakra-ui/react"; -import TimelineEntry from "components/Timeline/components/TimelineEntry"; -import { IGroupedHistoryEntry, IShipmentHistory, ShipmentActionEvent } from "./ShipmentTabs"; - -export interface IShipmentHistoryProps { - histories: IGroupedHistoryEntry[]; -} - -function ShipmentHistory({ histories }: IShipmentHistoryProps) { - const changesLabel = (history: IShipmentHistory): string => { - let changes = ""; - if ( - [ - ShipmentActionEvent.ShipmentCanceled, - ShipmentActionEvent.ShipmentCompleted, - ShipmentActionEvent.ShipmentSent, - ShipmentActionEvent.ShipmentStartReceiving, - ShipmentActionEvent.ShipmentStarted, - ].includes(history.action) - ) { - changes = `Shipment is ${history.action.toLowerCase().replace("shipment", "")} by ${ - history.createdBy?.name - }`; - } else { - changes = `Box ${history.box} is ${history.action.toLowerCase().replace("box", "")} by ${ - history.createdBy?.name - }`; - } - - return changes; - }; - return ( - - {histories.map(({ date, entries }, index) => ( - - - - {date} - - - - {entries?.map((entry, indx) => ( - - ))} - - - ))} - - ); -} - -export default ShipmentHistory; diff --git a/react/src/views/Transfers/ShipmentView/components/ShipmentTabs.tsx b/react/src/views/Transfers/ShipmentView/components/ShipmentTabs.tsx index 8a0d01036..b0eaf40e7 100644 --- a/react/src/views/Transfers/ShipmentView/components/ShipmentTabs.tsx +++ b/react/src/views/Transfers/ShipmentView/components/ShipmentTabs.tsx @@ -1,38 +1,13 @@ import { TabList, TabPanels, Tabs, TabPanel, Tab, Center } from "@chakra-ui/react"; +import ShipmentHistory, { IGroupedRecordEntry } from "components/Timeline/TimelineContainer"; import _ from "lodash"; -import { Box, BoxState, ShipmentDetail, ShipmentState, User } from "types/generated/graphql"; +import { Box, BoxState, ShipmentDetail, ShipmentState } from "types/generated/graphql"; import ShipmentContent, { IShipmentContent } from "./ShipmentContent"; -import ShipmentHistory from "./ShipmentHistory"; - -// eslint-disable-next-line no-shadow -export enum ShipmentActionEvent { - ShipmentStarted = "Shipment Started", - ShipmentCanceled = "Shipment Canceled", - ShipmentSent = "Shipment Sent", - ShipmentStartReceiving = "Shipment Being Received", - ShipmentCompleted = "Shipment Completed", - BoxAdded = "Box Added", - BoxRemoved = "Box Removed", - BoxLost = "Box Marked Lost", - BoxReceived = "Box Received", -} - -export interface IShipmentHistory { - box?: string | undefined; - action: ShipmentActionEvent; - createdOn: Date; - createdBy: User; -} - -export interface IGroupedHistoryEntry { - date: string; - entries: (IShipmentHistory | null | undefined)[]; -} export interface IShipmentTabsProps { shipmentState: ShipmentState | undefined; detail: ShipmentDetail[]; - histories: IGroupedHistoryEntry[]; + histories: IGroupedRecordEntry[]; isLoadingMutation: boolean | undefined; showRemoveIcon: Boolean; onRemoveBox: (id: string) => void; @@ -94,7 +69,7 @@ function ShipmentTabs({ /> - + From 109a94f53a2231b8d586d1d7b59176d1b0925d4e Mon Sep 17 00:00:00 2001 From: Vahid Bazzaz Date: Thu, 5 Oct 2023 11:05:09 +0330 Subject: [PATCH 11/18] clean up --- .../views/Transfers/ShipmentView/components/ShipmentTabs.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/react/src/views/Transfers/ShipmentView/components/ShipmentTabs.tsx b/react/src/views/Transfers/ShipmentView/components/ShipmentTabs.tsx index ff816d81e..958c45379 100644 --- a/react/src/views/Transfers/ShipmentView/components/ShipmentTabs.tsx +++ b/react/src/views/Transfers/ShipmentView/components/ShipmentTabs.tsx @@ -1,7 +1,7 @@ import { TabList, TabPanels, Tabs, TabPanel, Tab, Center } from "@chakra-ui/react"; import ShipmentHistory, { IGroupedRecordEntry } from "components/Timeline/TimelineContainer"; import _ from "lodash"; -import { Box, BoxState, ShipmentDetail, ShipmentState, User } from "types/generated/graphql"; +import { Box, ShipmentDetail, ShipmentState } from "types/generated/graphql"; import ShipmentContent, { IShipmentContent } from "./ShipmentContent"; export interface IShipmentTabsProps { @@ -37,7 +37,7 @@ function ShipmentTabs({ size: group[0]?.sourceSize, numberOfItems: shipment.sourceQuantity, product: group[0]?.sourceProduct, - } as Box), + }) as Box, ), })) .orderBy((value) => value.totalLosts, "asc") From ba9a17bae2f0b6dfe6b8b5aad3b3473e0de1d51f Mon Sep 17 00:00:00 2001 From: Vahid Bazzaz Date: Thu, 19 Oct 2023 12:39:14 +0330 Subject: [PATCH 12/18] add helpful comments --- .../src/views/Transfers/ShipmentView/ShipmentView.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/react/src/views/Transfers/ShipmentView/ShipmentView.tsx b/react/src/views/Transfers/ShipmentView/ShipmentView.tsx index 0324b02ac..d751f1529 100644 --- a/react/src/views/Transfers/ShipmentView/ShipmentView.tsx +++ b/react/src/views/Transfers/ShipmentView/ShipmentView.tsx @@ -66,6 +66,7 @@ enum ShipmentActionEvent { BoxReceived = "Box Received", } +// graphql query and mutations export const SHIPMENT_BY_ID_QUERY = gql` ${SHIPMENT_FIELDS_FRAGMENT} query ShipmentById($id: ID!) { @@ -136,19 +137,23 @@ function ShipmentView() { onClose: onShipmentOverlayClose, onOpen: onShipmentOverlayOpen, } = useDisclosure(); - + // State to show minus button near boxes when remove button is triggered const [showRemoveIcon, setShowRemoveIcon] = useState(false); const [shipmentState, setShipmentState] = useState(); + // State to pass Data from a row to the Overlay const [shipmentOverlayData, setShipmentOverlayData] = useState(); + // variables in URL const shipmentId = useParams<{ id: string }>().id!; + // fetch shipment data const { loading, error, data } = useQuery( SHIPMENT_BY_ID_QUERY, { variables: { id: shipmentId, }, + // returns cache first, but syncs with server in background fetchPolicy: "cache-and-network", }, ); @@ -160,6 +165,7 @@ function ShipmentView() { }; }, [data]); + // Mutations for shipment actions const [updateShipmentWhenPreparing, updateShipmentWhenPreparingStatus] = useMutation< RemoveBoxFromShipmentMutation, RemoveBoxFromShipmentMutationVariables @@ -189,6 +195,7 @@ function ShipmentView() { UpdateShipmentWhenReceivingMutationVariables >(UPDATE_SHIPMENT_WHEN_RECEIVING); + // shipment actions in the modal const handleShipment = useCallback( (mutation, kind, successMessage = "", failedMessage = "") => () => { @@ -230,6 +237,7 @@ function ShipmentView() { ); const onReceive = handleShipment(startReceivingShipment, "receive"); + // callback function triggered when a state button is clicked. const openShipmentOverlay = useCallback(() => { setShipmentOverlayData({ id: data?.shipment?.id, @@ -361,6 +369,7 @@ function ShipmentView() { updateShipmentWhenReceivingStatus.loading || lostShipmentStatus.loading; + // transform shipment data for UI const shipmentData = data?.shipment! as Shipment; const shipmentContents = (data?.shipment?.details.filter((item) => item.removedOn === null) ?? From 1427bb16ebdcd1fb213cf9584b839dcb2f211fac Mon Sep 17 00:00:00 2001 From: Vahid Bazzaz Date: Thu, 19 Oct 2023 12:48:36 +0330 Subject: [PATCH 13/18] rename the timeline component --- react/src/components/HistoryOverlay/HistoryOverlay.tsx | 4 ++-- .../Timeline/{TimelineContainer.tsx => Timeline.tsx} | 4 ++-- react/src/views/Box/BoxView.tsx | 2 +- react/src/views/Transfers/ShipmentView/ShipmentView.tsx | 2 +- .../views/Transfers/ShipmentView/components/ShipmentTabs.tsx | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) rename react/src/components/Timeline/{TimelineContainer.tsx => Timeline.tsx} (93%) diff --git a/react/src/components/HistoryOverlay/HistoryOverlay.tsx b/react/src/components/HistoryOverlay/HistoryOverlay.tsx index 60a02eca4..6f853f55a 100644 --- a/react/src/components/HistoryOverlay/HistoryOverlay.tsx +++ b/react/src/components/HistoryOverlay/HistoryOverlay.tsx @@ -7,7 +7,7 @@ import { ModalHeader, ModalOverlay, } from "@chakra-ui/react"; -import TimelineContainer, { IGroupedRecordEntry } from "../Timeline/TimelineContainer"; +import Timeline, { IGroupedRecordEntry } from "../Timeline/Timeline"; interface IHistoryOverlay { data: IGroupedRecordEntry[]; @@ -29,7 +29,7 @@ function HistoryOverlay({ data, isOpen, onClose }: IHistoryOverlay) { Box History - + diff --git a/react/src/components/Timeline/TimelineContainer.tsx b/react/src/components/Timeline/Timeline.tsx similarity index 93% rename from react/src/components/Timeline/TimelineContainer.tsx rename to react/src/components/Timeline/Timeline.tsx index 86632dfef..776d9fa5c 100644 --- a/react/src/components/Timeline/TimelineContainer.tsx +++ b/react/src/components/Timeline/Timeline.tsx @@ -17,7 +17,7 @@ export interface ITimelineProps { records: IGroupedRecordEntry[]; } -function TimelineContainer({ records }: ITimelineProps) { +function Timeline({ records }: ITimelineProps) { return ( {records.map(({ date, entries }, index) => ( @@ -52,4 +52,4 @@ function TimelineContainer({ records }: ITimelineProps) { ); } -export default TimelineContainer; +export default Timeline; diff --git a/react/src/views/Box/BoxView.tsx b/react/src/views/Box/BoxView.tsx index 37750c58f..d75a4501a 100644 --- a/react/src/views/Box/BoxView.tsx +++ b/react/src/views/Box/BoxView.tsx @@ -56,7 +56,7 @@ import { BoxViewSkeleton } from "components/Skeletons"; import { BoxReconciliationOverlay } from "components/BoxReconciliationOverlay/BoxReconciliationOverlay"; import { boxReconciliationOverlayVar } from "queries/cache"; import HistoryOverlay from "components/HistoryOverlay/HistoryOverlay"; -import { ITimelineEntry } from "components/Timeline/TimelineContainer"; +import { ITimelineEntry } from "components/Timeline/Timeline"; import _ from "lodash"; import { formatDateKey, prepareBoxHistoryEntryText } from "utils/helpers"; import BoxDetails from "./components/BoxDetails"; diff --git a/react/src/views/Transfers/ShipmentView/ShipmentView.tsx b/react/src/views/Transfers/ShipmentView/ShipmentView.tsx index d751f1529..a3e855bf2 100644 --- a/react/src/views/Transfers/ShipmentView/ShipmentView.tsx +++ b/react/src/views/Transfers/ShipmentView/ShipmentView.tsx @@ -45,7 +45,7 @@ import { BoxReconciliationOverlay } from "components/BoxReconciliationOverlay/Bo import { UPDATE_SHIPMENT_WHEN_RECEIVING } from "queries/mutations"; import { boxReconciliationOverlayVar } from "queries/cache"; import { MobileBreadcrumbButton } from "components/BreadcrumbNavigation"; -import { ITimelineEntry } from "components/Timeline/TimelineContainer"; +import { ITimelineEntry } from "components/Timeline/Timeline"; import ShipmentCard from "./components/ShipmentCard"; import ShipmentTabs from "./components/ShipmentTabs"; import ShipmentOverlay, { IShipmentOverlayData } from "./components/ShipmentOverlay"; diff --git a/react/src/views/Transfers/ShipmentView/components/ShipmentTabs.tsx b/react/src/views/Transfers/ShipmentView/components/ShipmentTabs.tsx index f03d08a0f..545d93a26 100644 --- a/react/src/views/Transfers/ShipmentView/components/ShipmentTabs.tsx +++ b/react/src/views/Transfers/ShipmentView/components/ShipmentTabs.tsx @@ -1,5 +1,5 @@ import { TabList, TabPanels, Tabs, TabPanel, Tab, Center } from "@chakra-ui/react"; -import ShipmentHistory, { IGroupedRecordEntry } from "components/Timeline/TimelineContainer"; +import ShipmentHistory, { IGroupedRecordEntry } from "components/Timeline/Timeline"; import _ from "lodash"; import { Box, ShipmentDetail, ShipmentState, User } from "types/generated/graphql"; import ShipmentContent, { IShipmentContent } from "./ShipmentContent"; From 82e88f568ac8787192032fad4843f12475fa7236 Mon Sep 17 00:00:00 2001 From: Vahid Bazzaz Date: Thu, 19 Oct 2023 12:54:52 +0330 Subject: [PATCH 14/18] put formatTime method to helpers --- .../Timeline/components/TimelineEntry.tsx | 12 +----------- react/src/utils/helpers.ts | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/react/src/components/Timeline/components/TimelineEntry.tsx b/react/src/components/Timeline/components/TimelineEntry.tsx index a82ebfec1..4d8d9d530 100644 --- a/react/src/components/Timeline/components/TimelineEntry.tsx +++ b/react/src/components/Timeline/components/TimelineEntry.tsx @@ -1,4 +1,5 @@ import { Flex, Text } from "@chakra-ui/react"; +import { formatTime } from "utils/helpers"; import TimelineBullet from "./TimelineBullet"; export interface ITimelineEntryProps { @@ -7,17 +8,6 @@ export interface ITimelineEntryProps { } function TimelineEntry({ content, time }: ITimelineEntryProps) { - function formatTime(date: Date | string): string { - const formattedDate = typeof date === "string" && date !== "" ? new Date(date) : date; - - if (formattedDate instanceof Date) { - const hours = formattedDate.getHours().toString().padStart(2, "0"); - const minutes = formattedDate.getMinutes().toString().padStart(2, "0"); - return `${hours}:${minutes}`; - } - - return ""; - } return ( diff --git a/react/src/utils/helpers.ts b/react/src/utils/helpers.ts index 67a727f01..82b799c8e 100644 --- a/react/src/utils/helpers.ts +++ b/react/src/utils/helpers.ts @@ -81,3 +81,21 @@ export const prepareBoxHistoryEntryText = (text: string): string => { return updatedText; }; + +/** + * Formats a given date or string into a string representation of the time. + * + * @param {Date | string} date - The date or string to be formatted. + * @return {string} The formatted time as a string in the format "HH:MM". + */ +export const formatTime = (date: Date | string): string => { + const formattedDate = typeof date === "string" && date !== "" ? new Date(date) : date; + + if (formattedDate instanceof Date) { + const hours = formattedDate.getHours().toString().padStart(2, "0"); + const minutes = formattedDate.getMinutes().toString().padStart(2, "0"); + return `${hours}:${minutes}`; + } + + return ""; +}; From 773108728068df72f79e8da938c99b33e38c879e Mon Sep 17 00:00:00 2001 From: Vahid Bazzaz Date: Thu, 19 Oct 2023 12:55:21 +0330 Subject: [PATCH 15/18] clean up --- react/src/components/Table/Filter.tsx | 2 +- react/src/hooks/useLabelIdentifierResolver.ts | 2 +- react/src/hooks/useScannedBoxesActions.ts | 6 +++--- .../views/Transfers/CreateShipment/CreateShipmentView.tsx | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/react/src/components/Table/Filter.tsx b/react/src/components/Table/Filter.tsx index b9bf183bf..d91c431a6 100644 --- a/react/src/components/Table/Filter.tsx +++ b/react/src/components/Table/Filter.tsx @@ -52,7 +52,7 @@ export function SelectColumnFilter({ ({ label, value: optionValues[label], - } as ISelectOption), + }) as ISelectOption, ); }, [id, preFilteredRows]); diff --git a/react/src/hooks/useLabelIdentifierResolver.ts b/react/src/hooks/useLabelIdentifierResolver.ts index 3ca9c1b76..d937eb930 100644 --- a/react/src/hooks/useLabelIdentifierResolver.ts +++ b/react/src/hooks/useLabelIdentifierResolver.ts @@ -65,7 +65,7 @@ export const useLabelIdentifierResolver = () => { kind: ILabelIdentifierResolverResultKind.FAIL, labelIdentifier, error: err, - } as ILabelIdentifierResolvedValue), + }) as ILabelIdentifierResolvedValue, ); setLoading(false); return labelIdentifierResolvedValue; diff --git a/react/src/hooks/useScannedBoxesActions.ts b/react/src/hooks/useScannedBoxesActions.ts index a2e8883dd..3229cdd8d 100644 --- a/react/src/hooks/useScannedBoxesActions.ts +++ b/react/src/hooks/useScannedBoxesActions.ts @@ -72,7 +72,7 @@ export const useScannedBoxesActions = () => { (data: IScannedBoxesData) => ({ scannedBoxes: data.scannedBoxes.slice(0, -1), - } as IScannedBoxesData), + }) as IScannedBoxesData, ); }, [apolloClient]); @@ -84,7 +84,7 @@ export const useScannedBoxesActions = () => { (data: IScannedBoxesData) => ({ scannedBoxes: data.scannedBoxes.filter((box) => box.state === BoxState.InStock), - } as IScannedBoxesData), + }) as IScannedBoxesData, ); }, [apolloClient]); @@ -99,7 +99,7 @@ export const useScannedBoxesActions = () => { scannedBoxes: data.scannedBoxes.filter( (box) => !labelIdentifiers.includes(box.labelIdentifier), ), - } as IScannedBoxesData), + }) as IScannedBoxesData, ); }, [apolloClient], diff --git a/react/src/views/Transfers/CreateShipment/CreateShipmentView.tsx b/react/src/views/Transfers/CreateShipment/CreateShipmentView.tsx index e6b8936b5..fad51af92 100644 --- a/react/src/views/Transfers/CreateShipment/CreateShipmentView.tsx +++ b/react/src/views/Transfers/CreateShipment/CreateShipmentView.tsx @@ -144,7 +144,7 @@ function CreateShipmentView() { id: agreement.id, name: agreement.name, bases: agreement.bases, - } as IOrganisationBaseData), + }) as IOrganisationBaseData, ) .reduce((accumulator, currentOrg) => { // Merge options. If there are multiple transfer agreements this step is necessary From d864d20b6a8c3c1d7461f0f55ecef3e7e75ab3e2 Mon Sep 17 00:00:00 2001 From: Vahid Bazzaz Date: Thu, 19 Oct 2023 13:01:19 +0330 Subject: [PATCH 16/18] clean up: remove unknown and undefiend types --- react/src/components/HistoryOverlay/HistoryOverlay.tsx | 2 +- react/src/components/Timeline/Timeline.tsx | 2 +- react/src/views/Box/BoxView.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/react/src/components/HistoryOverlay/HistoryOverlay.tsx b/react/src/components/HistoryOverlay/HistoryOverlay.tsx index 6f853f55a..ee3847dde 100644 --- a/react/src/components/HistoryOverlay/HistoryOverlay.tsx +++ b/react/src/components/HistoryOverlay/HistoryOverlay.tsx @@ -29,7 +29,7 @@ function HistoryOverlay({ data, isOpen, onClose }: IHistoryOverlay) { Box History - + diff --git a/react/src/components/Timeline/Timeline.tsx b/react/src/components/Timeline/Timeline.tsx index 776d9fa5c..7b82437c0 100644 --- a/react/src/components/Timeline/Timeline.tsx +++ b/react/src/components/Timeline/Timeline.tsx @@ -3,7 +3,7 @@ import { User } from "types/generated/graphql"; import TimelineEntry from "./components/TimelineEntry"; export interface ITimelineEntry { - action: string | undefined; + action: string; createdOn: Date; createdBy: User; } diff --git a/react/src/views/Box/BoxView.tsx b/react/src/views/Box/BoxView.tsx index d75a4501a..0f3db2d59 100644 --- a/react/src/views/Box/BoxView.tsx +++ b/react/src/views/Box/BoxView.tsx @@ -185,7 +185,7 @@ function BTBox() { createdOn: new Date(histories.changeDate), }, ]), - ) as unknown as ITimelineEntry[]; + ) as ITimelineEntry[]; const allLogs = _.orderBy( _.sortBy(_.concat([...(boxLogs || [])]), "createdOn"), From a87215554c98db06817c7a698eaf1a612b68a980 Mon Sep 17 00:00:00 2001 From: Vahid Bazzaz Date: Thu, 19 Oct 2023 13:09:11 +0330 Subject: [PATCH 17/18] clean up: import mocks function --- react/src/views/Box/BoxView.test.tsx | 19 ++-------- .../views/Box/BoxViewHistoryOverlay.test.tsx | 18 ++-------- .../Box/BoxViewReconciliationOverlay.test.tsx | 18 ++-------- .../ShipmentView/ShipmentView.test.tsx | 35 +++---------------- 4 files changed, 14 insertions(+), 76 deletions(-) diff --git a/react/src/views/Box/BoxView.test.tsx b/react/src/views/Box/BoxView.test.tsx index 80faafb9e..ca8a88d13 100644 --- a/react/src/views/Box/BoxView.test.tsx +++ b/react/src/views/Box/BoxView.test.tsx @@ -20,7 +20,7 @@ import { tags } from "mocks/tags"; import { selectOptionInSelectField, textContentMatcher } from "tests/helpers"; import BoxDetails from "./components/BoxDetails"; import { generateMockTransferAgreement } from "mocks/transferAgreements"; -import { mockGraphQLError, mockNetworkError } from "mocks/functions"; +import { mockGraphQLError, mockMatchMediaQuery, mockNetworkError } from "mocks/functions"; import { BOX_BY_LABEL_IDENTIFIER_AND_ALL_SHIPMENTS_QUERY } from "queries/queries"; import { organisation1 } from "mocks/organisations"; import { generateMockShipment, shipment1 } from "mocks/shipments"; @@ -307,21 +307,8 @@ const moveLocationOfBoxNetworkFailedMutation = { }; beforeEach(() => { - // we need to mock matchmedia - // https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom - Object.defineProperty(window, "matchMedia", { - writable: true, - value: jest.fn().mockImplementation((query) => ({ - matches: false, - media: query, - onchange: null, - addListener: jest.fn(), // Deprecated - removeListener: jest.fn(), // Deprecated - addEventListener: jest.fn(), - removeEventListener: jest.fn(), - dispatchEvent: jest.fn(), - })), - }); + // setting the screensize to + mockMatchMediaQuery(true); const mockedUseErrorHandling = jest.mocked(useErrorHandling); mockedUseErrorHandling.mockReturnValue({ triggerError: mockedTriggerError }); const mockedUseNotification = jest.mocked(useNotification); diff --git a/react/src/views/Box/BoxViewHistoryOverlay.test.tsx b/react/src/views/Box/BoxViewHistoryOverlay.test.tsx index c4c3911b4..15bb2544a 100644 --- a/react/src/views/Box/BoxViewHistoryOverlay.test.tsx +++ b/react/src/views/Box/BoxViewHistoryOverlay.test.tsx @@ -14,6 +14,7 @@ import BTBox from "./BoxView"; import { BoxState } from "types/generated/graphql"; import { history1, history2 } from "mocks/histories"; import { generateMockBox } from "mocks/boxes"; +import { mockMatchMediaQuery } from "mocks/functions"; const mockedTriggerError = jest.fn(); const mockedCreateToast = jest.fn(); @@ -44,21 +45,8 @@ const initialQueryForBoxWithHistory = { // Test case 3.1.12 describe("3.1.12 - Box HistoryOverlay on BoxView", () => { beforeEach(() => { - // we need to mock matchmedia - // https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom - Object.defineProperty(window, "matchMedia", { - writable: true, - value: jest.fn().mockImplementation((query) => ({ - matches: false, - media: query, - onchange: null, - addListener: jest.fn(), // Deprecated - removeListener: jest.fn(), // Deprecated - addEventListener: jest.fn(), - removeEventListener: jest.fn(), - dispatchEvent: jest.fn(), - })), - }); + // setting the screensize to + mockMatchMediaQuery(true); const mockedUseErrorHandling = jest.mocked(useErrorHandling); mockedUseErrorHandling.mockReturnValue({ triggerError: mockedTriggerError }); const mockedUseNotification = jest.mocked(useNotification); diff --git a/react/src/views/Box/BoxViewReconciliationOverlay.test.tsx b/react/src/views/Box/BoxViewReconciliationOverlay.test.tsx index 71296b46c..2bebb9101 100644 --- a/react/src/views/Box/BoxViewReconciliationOverlay.test.tsx +++ b/react/src/views/Box/BoxViewReconciliationOverlay.test.tsx @@ -18,6 +18,7 @@ import { products } from "mocks/products"; import { tag1, tag2 } from "mocks/tags"; import { generateMockShipment } from "mocks/shipments"; import { ShipmentState } from "types/generated/graphql"; +import { mockMatchMediaQuery } from "mocks/functions"; const mockedTriggerError = jest.fn(); const mockedCreateToast = jest.fn(); @@ -126,21 +127,8 @@ const queryShipmentDetailForBoxReconciliation = { }; beforeEach(() => { - // we need to mock matchmedia - // https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom - Object.defineProperty(window, "matchMedia", { - writable: true, - value: jest.fn().mockImplementation((query) => ({ - matches: false, - media: query, - onchange: null, - addListener: jest.fn(), // Deprecated - removeListener: jest.fn(), // Deprecated - addEventListener: jest.fn(), - removeEventListener: jest.fn(), - dispatchEvent: jest.fn(), - })), - }); + // setting the screensize to + mockMatchMediaQuery(true); const mockedUseErrorHandling = jest.mocked(useErrorHandling); mockedUseErrorHandling.mockReturnValue({ triggerError: mockedTriggerError }); const mockedUseNotification = jest.mocked(useNotification); diff --git a/react/src/views/Transfers/ShipmentView/ShipmentView.test.tsx b/react/src/views/Transfers/ShipmentView/ShipmentView.test.tsx index a606a851d..5a99590b9 100644 --- a/react/src/views/Transfers/ShipmentView/ShipmentView.test.tsx +++ b/react/src/views/Transfers/ShipmentView/ShipmentView.test.tsx @@ -12,6 +12,7 @@ import { BoxState, ShipmentState } from "types/generated/graphql"; import { useErrorHandling } from "hooks/useErrorHandling"; import { useNotification } from "hooks/useNotification"; import userEvent from "@testing-library/user-event"; +import { mockMatchMediaQuery } from "mocks/functions"; import ShipmentView, { SHIPMENT_BY_ID_QUERY } from "./ShipmentView"; const mockedTriggerError = jest.fn(); @@ -154,21 +155,8 @@ const initialRecevingUIAsTargetOrgQuery = { }; beforeEach(() => { - // we need to mock matchmedia - // https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom - Object.defineProperty(window, "matchMedia", { - writable: true, - value: jest.fn().mockImplementation((query) => ({ - matches: false, - media: query, - onchange: null, - addListener: jest.fn(), // Deprecated - removeListener: jest.fn(), // Deprecated - addEventListener: jest.fn(), - removeEventListener: jest.fn(), - dispatchEvent: jest.fn(), - })), - }); + // setting the screensize to + mockMatchMediaQuery(true); const mockedUseErrorHandling = jest.mocked(useErrorHandling); mockedUseErrorHandling.mockReturnValue({ triggerError: mockedTriggerError }); const mockedUseNotification = jest.mocked(useNotification); @@ -177,21 +165,8 @@ beforeEach(() => { describe("4.5 Test Cases", () => { beforeEach(() => { - // we need to mock matchmedia - // https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom - Object.defineProperty(window, "matchMedia", { - writable: true, - value: jest.fn().mockImplementation((query) => ({ - matches: false, - media: query, - onchange: null, - addListener: jest.fn(), // Deprecated - removeListener: jest.fn(), // Deprecated - addEventListener: jest.fn(), - removeEventListener: jest.fn(), - dispatchEvent: jest.fn(), - })), - }); + // setting the screensize to + mockMatchMediaQuery(true); }); // Test case 4.5.1 From 1fcfd624729488e00b0c4f9f4b818c569aa99141 Mon Sep 17 00:00:00 2001 From: Vahid Bazzaz Date: Thu, 19 Oct 2023 13:20:40 +0330 Subject: [PATCH 18/18] refactor: remove waitFor & replace getbyRole with findByRole in the test --- .../views/Box/BoxViewHistoryOverlay.test.tsx | 46 ++++++++----------- 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/react/src/views/Box/BoxViewHistoryOverlay.test.tsx b/react/src/views/Box/BoxViewHistoryOverlay.test.tsx index 15bb2544a..54563dc75 100644 --- a/react/src/views/Box/BoxViewHistoryOverlay.test.tsx +++ b/react/src/views/Box/BoxViewHistoryOverlay.test.tsx @@ -1,6 +1,6 @@ /* eslint-disable */ import "@testing-library/jest-dom"; -import { screen, render, waitFor } from "tests/test-utils"; +import { screen, render } from "tests/test-utils"; import userEvent from "@testing-library/user-event"; import { cache } from "queries/cache"; @@ -72,17 +72,14 @@ describe("3.1.12 - Box HistoryOverlay on BoxView", () => { }, }); - await waitFor(async () => { - expect(await screen.getByRole("heading", { name: /box 123/i })).toBeInTheDocument(); - }); + const heading = await screen.findByRole("heading", { name: /box 123/i }); + expect(heading).toBeInTheDocument(); - await waitFor(async () => { - expect( - await screen.getByRole("button", { - name: /show detail history/i, - }), - ).toBeInTheDocument(); + const showHistoryButton = await screen.findByRole("button", { + name: /show detail history/i, }); + + expect(showHistoryButton).toBeInTheDocument(); }, 10000); // Test case 3.1.12.2 @@ -106,28 +103,25 @@ describe("3.1.12 - Box HistoryOverlay on BoxView", () => { }, }); - await waitFor(async () => { - expect(await screen.getByRole("heading", { name: /box 123/i })).toBeInTheDocument(); - }); + const heading = await screen.findByRole("heading", { name: /box 123/i }); + expect(heading).toBeInTheDocument(); - const historyButton = await screen.getByRole("button", { + const historyButton = await screen.findByRole("button", { name: /show detail history/i, }); - await waitFor(async () => { - expect(historyButton).toBeInTheDocument(); - }); + expect(historyButton).toBeInTheDocument(); await user.click(historyButton); - await waitFor(async () => { - expect(await screen.getByRole("banner")).toBeInTheDocument(); - expect(screen.getByText(/jan 14, 2023/i)).toBeInTheDocument(); - expect( - screen.getByText(/dev coordinator changed box location from wh men to wh women/i), - ).toBeInTheDocument(); - expect(screen.getByText(/jan 12, 2023/i)).toBeInTheDocument(); - expect(screen.getByText(/dev coordinator created record/i)).toBeInTheDocument(); - }); + const banner = await screen.findByRole("banner"); + + expect(banner).toBeInTheDocument(); + expect(screen.getByText(/jan 14, 2023/i)).toBeInTheDocument(); + expect( + screen.getByText(/dev coordinator changed box location from wh men to wh women/i), + ).toBeInTheDocument(); + expect(screen.getByText(/jan 12, 2023/i)).toBeInTheDocument(); + expect(screen.getByText(/dev coordinator created record/i)).toBeInTheDocument(); }, 10000); });