From 5eb779520d0c73f522a156ad18dcf292eca2866a Mon Sep 17 00:00:00 2001 From: Alex Zhang Date: Fri, 10 May 2024 15:36:04 -0700 Subject: [PATCH] Hotfix for partially fulfilled events (#252) * Hotfix for partially fulfilled events * Fix counting bug for fulfilled items using sean's code, add a comment * Update user-facing style to be aligned with Amy's design * Fix bug --- .../PickupOrdersPrepareDisplay/index.tsx | 18 +- src/components/store/OrderSummary/index.tsx | 158 ++++++++++++------ .../store/OrderSummary/style.module.scss | 7 +- .../store/OrderSummary/style.module.scss.d.ts | 1 + src/lib/utils.ts | 16 +- 5 files changed, 129 insertions(+), 71 deletions(-) diff --git a/src/components/admin/store/PickupOrdersPrepareDisplay/index.tsx b/src/components/admin/store/PickupOrdersPrepareDisplay/index.tsx index 7848cdf7..56f8dd11 100644 --- a/src/components/admin/store/PickupOrdersPrepareDisplay/index.tsx +++ b/src/components/admin/store/PickupOrdersPrepareDisplay/index.tsx @@ -1,6 +1,6 @@ import { Typography } from '@/components/common'; -import { PublicOrderItemWithQuantity, PublicOrderWithItems } from '@/lib/types/apiResponses'; -import { getOrderItemQuantities } from '@/lib/utils'; +import { PublicOrderWithItems } from '@/lib/types/apiResponses'; +import { OrderItemQuantity, getOrderItemQuantities } from '@/lib/utils'; import { useMemo } from 'react'; import styles from './style.module.scss'; @@ -8,14 +8,14 @@ interface PickupOrdersDisplayPrepareProps { orders: PublicOrderWithItems[]; } -const itemToString = (item: PublicOrderItemWithQuantity): string => { +const itemToString = (item: OrderItemQuantity): string => { if (item.option.metadata !== null) return `${item.option.item.itemName} (${item.option.metadata.type}: ${item.option.metadata.value})`; return item.option.item.itemName; }; const PickupOrdersPrepareDisplay = ({ orders }: PickupOrdersDisplayPrepareProps) => { - const itemBreakdown: PublicOrderItemWithQuantity[] = useMemo(() => { + const itemBreakdown: OrderItemQuantity[] = useMemo(() => { // Concatenate all items together into one large order to display the item breakdown. const allItems = orders.flatMap(a => a.items); return getOrderItemQuantities(allItems); @@ -34,7 +34,7 @@ const PickupOrdersPrepareDisplay = ({ orders }: PickupOrdersDisplayPrepareProps) {itemBreakdown.map(item => { return ( - + {item.quantity} @@ -65,10 +65,10 @@ const PickupOrdersPrepareDisplay = ({ orders }: PickupOrdersDisplayPrepareProps) diff --git a/src/components/store/OrderSummary/index.tsx b/src/components/store/OrderSummary/index.tsx index f8d9810c..1af3d397 100644 --- a/src/components/store/OrderSummary/index.tsx +++ b/src/components/store/OrderSummary/index.tsx @@ -5,21 +5,17 @@ import PickupEventPreviewModal from '@/components/store/PickupEventPreviewModal' import { config } from '@/lib'; import { PublicOrderPickupEvent, PublicOrderWithItems } from '@/lib/types/apiResponses'; import { OrderStatus } from '@/lib/types/enums'; -import { capitalize, getDefaultOrderItemPhoto, getOrderItemQuantities } from '@/lib/utils'; +import { + OrderItemQuantity, + capitalize, + getDefaultOrderItemPhoto, + getOrderItemQuantities, +} from '@/lib/utils'; import Image from 'next/image'; import Link from 'next/link'; import { useState } from 'react'; import styles from './style.module.scss'; -interface OrderSummaryProps { - order: PublicOrderWithItems; - orderStatus: OrderStatus; - futurePickupEvents: PublicOrderPickupEvent[]; - pickupEvent: PublicOrderPickupEvent; - reschedulePickupEvent: (pickup: PublicOrderPickupEvent) => Promise; - cancelOrder: () => Promise; -} - const isOrderActionable = (status: OrderStatus, pickupEvent: PublicOrderPickupEvent): boolean => { if (status === OrderStatus.CANCELLED || status === OrderStatus.FULFILLED) { // If the order is cancelled by the user or fulfilled, no further action can be taken. @@ -30,16 +26,108 @@ const isOrderActionable = (status: OrderStatus, pickupEvent: PublicOrderPickupEv const now = new Date(); const eventStart = new Date(pickupEvent.start); eventStart.setDate(eventStart.getDate() - 2); - if ( - now > eventStart && - status !== OrderStatus.PICKUP_MISSED && - status !== OrderStatus.PICKUP_CANCELLED - ) { + if (status === OrderStatus.PLACED && now > eventStart) { return false; } return true; }; +interface OrderItemPreviewProps { + item: OrderItemQuantity; + orderStatus: OrderStatus; +} + +const OrderItemPreview = ({ item, orderStatus }: OrderItemPreviewProps) => { + if (item.fulfilled && orderStatus === OrderStatus.PLACED) { + /* + * When a partially fulfilled order is rescheduled, + * the status is changed to PLACED, but all items remain (some as fulfilled). + * These items should be hidden. + */ + return null; + } + + return ( + +
+ Store item picture +
+
+ {item.option.item.itemName} +
+ Price: + +
+
+ Quantity: + {item.quantity} +
+ {item.option.metadata ? ( +
+ {`${capitalize( + item.option.metadata.type + )}: `} + {item.option.metadata.value} +
+ ) : null} +
+ + ); +}; + +interface OrderItemsSummaryProps { + items: OrderItemQuantity[]; + orderStatus: OrderStatus; +} + +const OrderItemsSummary = ({ items, orderStatus }: OrderItemsSummaryProps) => { + if (orderStatus !== OrderStatus.PARTIALLY_FULFILLED) { + return ( + <> + {items.map(item => ( + + ))} + + ); + } + const pickedUpItems = items.filter(item => item.fulfilled); + const notPickedUpItems = items.filter(item => !item.fulfilled); + + return ( +
+ + Items Picked Up: + + {pickedUpItems.map(item => ( + + ))} + +
+ + Awaiting Pickup: + + {notPickedUpItems.map(item => ( + + ))} +
+ ); +}; + +interface OrderSummaryProps { + order: PublicOrderWithItems; + orderStatus: OrderStatus; + futurePickupEvents: PublicOrderPickupEvent[]; + pickupEvent: PublicOrderPickupEvent; + reschedulePickupEvent: (pickup: PublicOrderPickupEvent) => Promise; + cancelOrder: () => Promise; +} + const OrderSummary = ({ order, orderStatus, @@ -73,45 +161,7 @@ const OrderSummary = ({ onClose={() => setCancelModalOpen(false)} cancelOrder={cancelOrder} /> - {items.map(item => ( - -
- Store item picture -
-
- {item.option.item.itemName} -
- Price: - -
-
- Quantity: - {item.quantity} -
- {item.option.metadata ? ( -
- {`${capitalize( - item.option.metadata.type - )}: `} - {item.option.metadata.value} -
- ) : null} -
- - ))} +
diff --git a/src/components/store/OrderSummary/style.module.scss b/src/components/store/OrderSummary/style.module.scss index abf27420..700ca3bb 100644 --- a/src/components/store/OrderSummary/style.module.scss +++ b/src/components/store/OrderSummary/style.module.scss @@ -3,6 +3,10 @@ .container { margin-top: 0.75rem; + .partiallyFulfilledText { + padding: 0.75rem 1.5rem; + } + .itemInfo { display: flex; flex-direction: row; @@ -36,8 +40,7 @@ .divider { border-top: 1px solid var(--theme-elevated-stroke); height: 0.1rem; - margin: 0 1rem; - margin-top: 0.75rem; + margin: 0.75rem 1rem; width: auto; } diff --git a/src/components/store/OrderSummary/style.module.scss.d.ts b/src/components/store/OrderSummary/style.module.scss.d.ts index 49ffe448..4f312057 100644 --- a/src/components/store/OrderSummary/style.module.scss.d.ts +++ b/src/components/store/OrderSummary/style.module.scss.d.ts @@ -7,6 +7,7 @@ export type Styles = { itemInfo: string; itemSummary: string; label: string; + partiallyFulfilledText: string; totalDiamonds: string; totalPrice: string; }; diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 9ab7ac56..b35a2ca1 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,7 +1,7 @@ import defaultProfilePictures from '@/lib/constants/profilePictures'; import ranks from '@/lib/constants/ranks'; import showToast from '@/lib/showToast'; -import type { URL } from '@/lib/types'; +import type { URL, UUID } from '@/lib/types'; import type { ApiResponse, CustomErrorBody, @@ -314,7 +314,7 @@ export const getDefaultMerchItemPhoto = ( return NoImage.src; }; -export const getDefaultOrderItemPhoto = (item: PublicOrderItem): string => { +export const getDefaultOrderItemPhoto = (item: Pick): string => { if (item.option.item.uploadedPhoto) { return item.option.item.uploadedPhoto; } @@ -385,18 +385,22 @@ export const isOrderPickupEvent = ( event: PublicOrderPickupEvent | PublicEvent ): event is PublicOrderPickupEvent => 'status' in event; +export type OrderItemQuantity = Omit & { uuids: UUID[] }; + /** * Condenses a list of ordered items into unique items with quantities. */ -export const getOrderItemQuantities = (items: PublicOrderItem[]): PublicOrderItemWithQuantity[] => { - const itemMap = new Map(); +export const getOrderItemQuantities = (items: PublicOrderItem[]): OrderItemQuantity[] => { + const itemMap = new Map(); items.forEach(item => { - const existingItem = itemMap.get(item.option.uuid); + const hash = `${item.option.uuid} ${item.fulfilled}`; + const existingItem = itemMap.get(hash); if (existingItem) { existingItem.quantity += 1; + existingItem.uuids.push(item.uuid); } else { - itemMap.set(item.option.uuid, { ...item, quantity: 1 }); + itemMap.set(hash, { ...item, quantity: 1, uuids: [item.uuid] }); } });