fix(admin): Add skus to claim menus (#3368)

**What**
- Adds SKU to all tables in claim flow, allowing admins to easily copy them to their clipboard.
This commit is contained in:
Kasper Fabricius Kristensen
2023-03-05 17:17:45 +00:00
committed by GitHub
parent d4e3e119de
commit 67ba8be02b
9 changed files with 91 additions and 35 deletions
@@ -32,14 +32,15 @@ const CopyToClipboard: React.FC<CopyToClipboardProps> = ({
if (isCopied) { if (isCopied) {
notification("Success", "Copied!", "success") notification("Success", "Copied!", "success")
} }
}, [isCopied]) }, [isCopied, notification])
return ( return (
<div className="flex items-center inter-small-regular text-grey-50 gap-x-xsmall"> <div className="inter-small-regular text-grey-50 gap-x-xsmall flex items-center">
<Button <Button
variant="ghost" variant="ghost"
size="small" size="small"
className={clsx("p-0 text-grey-50", { type="button"
className={clsx("text-grey-50 p-0", {
["text-violet-60"]: isCopied, ["text-violet-60"]: isCopied,
})} })}
onClick={handleCopy} onClick={handleCopy}
@@ -104,14 +104,14 @@ const Claim = ({ event }: Props) => {
topNode: Actions, topNode: Actions,
children: [ children: [
<Fragment key={event.id}> <Fragment key={event.id}>
<div className="flex flex-col gap-y-base"> <div className="gap-y-base flex flex-col">
<ClaimStatus event={event} /> <ClaimStatus event={event} />
{renderClaimItems(event)} {renderClaimItems(event)}
{event.claim?.additional_items?.length > 0 && {event.claim?.additional_items?.length > 0 &&
renderReplacementItems(event)} renderReplacementItems(event)}
{shouldHaveButtonActions && ( {shouldHaveButtonActions && (
<div className="flex items-center gap-x-xsmall"> <div className="gap-x-xsmall flex items-center">
{event.claim.return_order.status === "requested" && ( {event.claim.return_order?.status === "requested" && (
<Button <Button
variant="secondary" variant="secondary"
size="small" size="small"
@@ -157,7 +157,7 @@ const Claim = ({ event }: Props) => {
export default Claim export default Claim
const ClaimStatus = ({ event }: Props) => { const ClaimStatus = ({ event }: Props) => {
const divider = <div className="h-11 w-px bg-grey-20" /> const divider = <div className="bg-grey-20 h-11 w-px" />
const shouldHaveFulfillment = const shouldHaveFulfillment =
!!event.claim?.fulfillment_status && !!event.claim?.fulfillment_status &&
@@ -168,7 +168,7 @@ const ClaimStatus = ({ event }: Props) => {
if (event.claim?.type === "replace") { if (event.claim?.type === "replace") {
refundStatus = refundStatus =
event.claim?.return_order.status === "received" event.claim?.return_order?.status === "received"
? "refunded" ? "refunded"
: event.claim?.payment_status : event.claim?.payment_status
} }
@@ -178,15 +178,15 @@ const ClaimStatus = ({ event }: Props) => {
} }
return ( return (
<div className="inter-small-regular flex items-center gap-x-base"> <div className="inter-small-regular gap-x-base flex items-center">
<div className="flex flex-col gap-y-2xsmall"> <div className="gap-y-2xsmall flex flex-col">
<span className="text-grey-50">Refund:</span> <span className="text-grey-50">Refund:</span>
<RefundStatus refundStatus={refundStatus} /> <RefundStatus refundStatus={refundStatus} />
</div> </div>
{shouldHaveReturnStatus && ( {shouldHaveReturnStatus && (
<> <>
{divider} {divider}
<div className="flex flex-col gap-y-2xsmall"> <div className="gap-y-2xsmall flex flex-col">
<span className="text-grey-50">Return:</span> <span className="text-grey-50">Return:</span>
<ReturnStatus returnStatus={event.returnStatus} /> <ReturnStatus returnStatus={event.returnStatus} />
</div> </div>
@@ -195,7 +195,7 @@ const ClaimStatus = ({ event }: Props) => {
{shouldHaveFulfillment && ( {shouldHaveFulfillment && (
<> <>
{divider} {divider}
<div className="flex flex-col gap-y-2xsmall"> <div className="gap-y-2xsmall flex flex-col">
<span className="text-grey-50">Fulfillment:</span> <span className="text-grey-50">Fulfillment:</span>
<FulfillmentStatus <FulfillmentStatus
fulfillmentStatus={event.claim?.fulfillment_status} fulfillmentStatus={event.claim?.fulfillment_status}
@@ -209,7 +209,7 @@ const ClaimStatus = ({ event }: Props) => {
const renderClaimItems = (event: ClaimEvent) => { const renderClaimItems = (event: ClaimEvent) => {
return ( return (
<div className="flex flex-col gap-y-small"> <div className="gap-y-small flex flex-col">
<span className="inter-small-regular text-grey-50">Claim Items</span> <span className="inter-small-regular text-grey-50">Claim Items</span>
<div> <div>
{event.claimItems.map((i, index) => ( {event.claimItems.map((i, index) => (
@@ -222,7 +222,7 @@ const renderClaimItems = (event: ClaimEvent) => {
const renderReplacementItems = (event: ClaimEvent) => { const renderReplacementItems = (event: ClaimEvent) => {
return ( return (
<div className="flex flex-col gap-y-small"> <div className="gap-y-small flex flex-col">
<span className="inter-small-regular text-grey-50"> <span className="inter-small-regular text-grey-50">
Replacement Items Replacement Items
</span> </span>
@@ -245,7 +245,7 @@ const renderClaimActions = (
if (!event.canceledAt && !event.isCanceled) { if (!event.canceledAt && !event.isCanceled) {
if ( if (
event.claim.return_order && event.claim.return_order &&
event.claim.return_order.status === "requested" event.claim.return_order?.status === "requested"
) { ) {
actions.push({ actions.push({
icon: <TrashIcon size={20} />, icon: <TrashIcon size={20} />,
@@ -1,6 +1,7 @@
import { createColumnHelper } from "@tanstack/react-table" import { createColumnHelper } from "@tanstack/react-table"
import React, { ChangeEvent, useCallback, useMemo } from "react" import React, { ChangeEvent, useCallback, useMemo } from "react"
import { Controller, useWatch } from "react-hook-form" import { Controller, useWatch } from "react-hook-form"
import CopyToClipboard from "../../../../components/atoms/copy-to-clipboard"
import Thumbnail from "../../../../components/atoms/thumbnail" import Thumbnail from "../../../../components/atoms/thumbnail"
import IndeterminateCheckbox from "../../../../components/molecules/indeterminate-checkbox" import IndeterminateCheckbox from "../../../../components/molecules/indeterminate-checkbox"
import { NestedForm } from "../../../../utils/nested-form" import { NestedForm } from "../../../../utils/nested-form"
@@ -91,14 +92,27 @@ export const useItemsToReceiveColumns = ({ form, orderCurrency }: Props) => {
columnHelper.accessor("variant_title", { columnHelper.accessor("variant_title", {
header: "Product", header: "Product",
cell: ({ getValue, row: { original } }) => { cell: ({ getValue, row: { original } }) => {
const value = getValue()
return ( return (
<div className="flex items-center gap-x-base py-xsmall"> <div className="gap-x-base py-xsmall flex items-center">
<div> <div>
<Thumbnail src={original.thumbnail} /> <Thumbnail src={original.thumbnail} />
</div> </div>
<div className="inter-small-regular"> <div className="inter-small-regular">
<p>{original.product_title}</p> <div className="gap-x-2xsmall flex items-center">
<p className="text-grey-50">{getValue()}</p> <p>{original.product_title}</p>
{value && <p className="text-grey-50">({value})</p>}
</div>
{original.sku && (
<span>
<CopyToClipboard
value={original.sku}
displayValue={original.sku}
iconSize={14}
/>
</span>
)}
</div> </div>
</div> </div>
) )
@@ -22,6 +22,7 @@ export type ReturnItem = {
thumbnail?: string | null thumbnail?: string | null
product_title: string product_title: string
variant_title: string variant_title: string
sku?: string | null
quantity: number quantity: number
original_quantity: number original_quantity: number
total: number total: number
@@ -75,7 +76,7 @@ const ItemsToReturnForm = ({ form, order, isClaim = false }: Props) => {
}) })
return ( return (
<div className="flex flex-col gap-y-base"> <div className="gap-y-base flex flex-col">
<h2 className="inter-base-semibold"> <h2 className="inter-base-semibold">
Items to {isClaim ? "claim" : "return"} Items to {isClaim ? "claim" : "return"}
</h2> </h2>
@@ -2,6 +2,7 @@ import { createColumnHelper } from "@tanstack/react-table"
import React, { useCallback, useMemo } from "react" import React, { useCallback, useMemo } from "react"
import { Controller, useWatch } from "react-hook-form" import { Controller, useWatch } from "react-hook-form"
import { ItemsToReturnFormType, ReturnItemObject } from "." import { ItemsToReturnFormType, ReturnItemObject } from "."
import CopyToClipboard from "../../../../components/atoms/copy-to-clipboard"
import { Thumbnail } from "../../../../components/atoms/thumbnail/thumbnail" import { Thumbnail } from "../../../../components/atoms/thumbnail/thumbnail"
import IndeterminateCheckbox from "../../../../components/molecules/indeterminate-checkbox" import IndeterminateCheckbox from "../../../../components/molecules/indeterminate-checkbox"
import { NestedForm } from "../../../../utils/nested-form" import { NestedForm } from "../../../../utils/nested-form"
@@ -98,14 +99,27 @@ export const useItemsToReturnColumns = ({ form, orderCurrency }: Props) => {
columnHelper.accessor("variant_title", { columnHelper.accessor("variant_title", {
header: "Product", header: "Product",
cell: ({ getValue, row: { original } }) => { cell: ({ getValue, row: { original } }) => {
const value = getValue()
return ( return (
<div className="flex items-center gap-x-base py-xsmall"> <div className="gap-x-base py-xsmall flex items-center">
<div> <div>
<Thumbnail src={original.thumbnail} /> <Thumbnail src={original.thumbnail} />
</div> </div>
<div className="inter-small-regular"> <div className="inter-small-regular">
<p>{original.product_title}</p> <div className="gap-x-2xsmall flex items-center">
<p className="text-grey-50">{getValue()}</p> <p>{original.product_title}</p>
{value && <p className="text-grey-50">({value})</p>}
</div>
{original.sku && (
<span>
<CopyToClipboard
value={original.sku}
displayValue={original.sku}
iconSize={14}
/>
</span>
)}
</div> </div>
</div> </div>
) )
@@ -1,5 +1,6 @@
import { createColumnHelper } from "@tanstack/react-table" import { createColumnHelper } from "@tanstack/react-table"
import { useCallback, useMemo } from "react" import { useCallback, useMemo } from "react"
import CopyToClipboard from "../../../../components/atoms/copy-to-clipboard"
import Thumbnail from "../../../../components/atoms/thumbnail" import Thumbnail from "../../../../components/atoms/thumbnail"
import Tooltip from "../../../../components/atoms/tooltip" import Tooltip from "../../../../components/atoms/tooltip"
import Button from "../../../../components/fundamentals/button" import Button from "../../../../components/fundamentals/button"
@@ -45,14 +46,24 @@ export const useAdditionalItemsColumns = ({
}, },
}) => { }) => {
return ( return (
<div className="flex items-center gap-base"> <div className="gap-base flex items-center">
<Thumbnail src={thumbnail} /> <Thumbnail src={thumbnail} />
<div> <div className="inter-small-regular">
<p> <div className="gap-x-2xsmall flex items-center">
{product_title}{" "} <p>{product_title}</p>
<span className="text-grey-50">({variant_title})</span> {variant_title && (
</p> <p className="text-grey-50">({variant_title})</p>
{sku && <p className="text-grey-50">{sku}</p>} )}
</div>
{sku && (
<span>
<CopyToClipboard
value={sku}
displayValue={sku}
iconSize={14}
/>
</span>
)}
</div> </div>
</div> </div>
) )
@@ -101,7 +112,7 @@ export const useAdditionalItemsColumns = ({
content="The price has been overridden in a price list, that is applicable to this order." content="The price has been overridden in a price list, that is applicable to this order."
side="top" side="top"
> >
<p className="cursor-default text-grey-40 line-through"> <p className="text-grey-40 cursor-default line-through">
{formatAmountWithSymbol({ {formatAmountWithSymbol({
amount: original_price, amount: original_price,
currency: orderCurrency, currency: orderCurrency,
@@ -15,11 +15,12 @@ type Props = {
} }
export const ClaimSummary = ({ form, order }: Props) => { export const ClaimSummary = ({ form, order }: Props) => {
const { control, getValues } = form const { control } = form
const claimItems = useWatch({ const claimItems = useWatch({
control: control, control: control,
name: "return_items.items", name: "return_items.items",
defaultValue: [],
}) })
const selectedClaimItems = useMemo(() => { const selectedClaimItems = useMemo(() => {
@@ -34,6 +35,7 @@ export const ClaimSummary = ({ form, order }: Props) => {
const replacementItems = useWatch({ const replacementItems = useWatch({
control: control, control: control,
name: "additional_items.items", name: "additional_items.items",
defaultValue: [],
}) })
const replacementItemShipping = useWatch({ const replacementItemShipping = useWatch({
@@ -84,6 +86,7 @@ export const ClaimSummary = ({ form, order }: Props) => {
currencyCode={order.currency_code} currencyCode={order.currency_code}
productTitle={item.product_title} productTitle={item.product_title}
quantity={item.quantity} quantity={item.quantity}
sku={item.sku}
price={item.total / item.original_quantity} price={item.total / item.original_quantity}
total={ total={
(item.total / item.original_quantity) * item.quantity (item.total / item.original_quantity) * item.quantity
@@ -123,6 +126,7 @@ export const ClaimSummary = ({ form, order }: Props) => {
currencyCode={order.currency_code} currencyCode={order.currency_code}
productTitle={item.product_title} productTitle={item.product_title}
quantity={item.quantity} quantity={item.quantity}
sku={item.sku}
price={item.price} price={item.price}
total={item.price * item.quantity} total={item.price * item.quantity}
variantTitle={item.variant_title} variantTitle={item.variant_title}
@@ -1,4 +1,5 @@
import clsx from "clsx" import clsx from "clsx"
import CopyToClipboard from "../../../../components/atoms/copy-to-clipboard"
import Thumbnail from "../../../../components/atoms/thumbnail" import Thumbnail from "../../../../components/atoms/thumbnail"
import { formatAmountWithSymbol } from "../../../../utils/prices" import { formatAmountWithSymbol } from "../../../../utils/prices"
@@ -9,6 +10,7 @@ type SummaryLineProps = {
quantity: number quantity: number
price: number price: number
total: number total: number
sku?: string | null
currencyCode: string currencyCode: string
isFree?: boolean isFree?: boolean
} }
@@ -17,6 +19,7 @@ export const SummaryLineItem = ({
thumbnail, thumbnail,
productTitle, productTitle,
variantTitle, variantTitle,
sku,
quantity, quantity,
price, price,
total, total,
@@ -25,16 +28,23 @@ export const SummaryLineItem = ({
}: SummaryLineProps) => { }: SummaryLineProps) => {
return ( return (
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex items-center gap-x-base"> <div className="gap-x-base flex items-center">
<div> <div>
<Thumbnail src={thumbnail} /> <Thumbnail src={thumbnail} />
</div> </div>
<div className="inter-small-regular"> <div className="inter-small-regular">
<p>{productTitle}</p> <div className="gap-x-2xsmall flex items-center">
<p className="text-grey-50">{variantTitle}</p> <p>{productTitle}</p>
{variantTitle && <p className="text-grey-50">({variantTitle})</p>}
</div>
{sku && (
<span>
<CopyToClipboard value={sku} displayValue={sku} iconSize={14} />
</span>
)}
</div> </div>
</div> </div>
<div className="inter-small-regular flex items-center gap-x-base"> <div className="inter-small-regular gap-x-base flex items-center">
<p className="text-grey-40"> <p className="text-grey-40">
{formatAmountWithSymbol({ {formatAmountWithSymbol({
amount: price, amount: price,
@@ -156,6 +156,7 @@ const getReturnableItemsValues = (order: Order) => {
thumbnail: item.thumbnail, thumbnail: item.thumbnail,
refundable: item.refundable || 0, refundable: item.refundable || 0,
product_title: item.variant.product.title, product_title: item.variant.product.title,
sku: item.variant.sku,
variant_title: item.variant.title, variant_title: item.variant.title,
quantity: returnableQuantity, quantity: returnableQuantity,
original_quantity: item.quantity, original_quantity: item.quantity,