fix(dashboard,admin-shared): Fixes to inventory page (#8941)
**What** - Adds qty column to reservations table. - Adds missing injections zones - Fixes layout on long description field. Resolves CC-121
This commit is contained in:
committed by
GitHub
parent
29555ae518
commit
c6ff9efdea
@@ -189,6 +189,15 @@ const RETURN_REASON_INJECTION_ZONES = [
|
||||
"return_reason.list.after",
|
||||
] as const
|
||||
|
||||
const INVENTORY_ITEM_INJECTION_ZONES = [
|
||||
"inventory_item.details.before",
|
||||
"inventory_item.details.after",
|
||||
"inventory_item.details.side.before",
|
||||
"inventory_item.details.side.after",
|
||||
"inventory_item.list.before",
|
||||
"inventory_item.list.after",
|
||||
] as const
|
||||
|
||||
/**
|
||||
* All valid injection zones in the admin panel. An injection zone is a specific place
|
||||
* in the admin panel where a plugin can inject custom widgets.
|
||||
@@ -220,4 +229,5 @@ export const INJECTION_ZONES = [
|
||||
...PRODUCT_TYPE_INJECTION_ZONES,
|
||||
...PRODUCT_TAG_INJECTION_ZONES,
|
||||
...RETURN_REASON_INJECTION_ZONES,
|
||||
...INVENTORY_ITEM_INJECTION_ZONES,
|
||||
] as const
|
||||
|
||||
@@ -1,28 +1,48 @@
|
||||
import { clx } from "@medusajs/ui"
|
||||
import { ConditionalTooltip } from "../../../../common/conditional-tooltip"
|
||||
import { PlaceholderCell } from "../placeholder-cell"
|
||||
|
||||
type CellProps = {
|
||||
text?: string | number
|
||||
align?: "left" | "center" | "right"
|
||||
maxWidth?: number
|
||||
}
|
||||
|
||||
type HeaderProps = {
|
||||
text: string
|
||||
align?: "left" | "center" | "right"
|
||||
}
|
||||
|
||||
export const TextCell = ({ text }: CellProps) => {
|
||||
export const TextCell = ({ text, align = "left", maxWidth = 220 }: CellProps) => {
|
||||
if (!text) {
|
||||
return <PlaceholderCell />
|
||||
}
|
||||
|
||||
const stringLength = text.toString().length
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full items-center gap-x-3 overflow-hidden">
|
||||
<ConditionalTooltip content={text} showTooltip={stringLength > 20}>
|
||||
<div className={clx("flex h-full w-full items-center gap-x-3 overflow-hidden", {
|
||||
"justify-start text-start": align === "left",
|
||||
"justify-center text-center": align === "center",
|
||||
"justify-end text-end": align === "right",
|
||||
})}
|
||||
style={{
|
||||
maxWidth: maxWidth,
|
||||
}}>
|
||||
<span className="truncate">{text}</span>
|
||||
</div>
|
||||
</ConditionalTooltip>
|
||||
)
|
||||
}
|
||||
|
||||
export const TextHeader = ({ text }: HeaderProps) => {
|
||||
export const TextHeader = ({ text, align = "left" }: HeaderProps) => {
|
||||
return (
|
||||
<div className="flex h-full w-full items-center">
|
||||
<div className={clx("flex h-full w-full items-center", {
|
||||
"justify-start text-start": align === "left",
|
||||
"justify-center text-center": align === "center",
|
||||
"justify-end text-end": align === "right",
|
||||
})}>
|
||||
<span className="truncate">{text}</span>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Container, Heading, IconButton } from "@medusajs/ui"
|
||||
import { TriangleRightMini } from "@medusajs/icons"
|
||||
import { Container, Heading } from "@medusajs/ui"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { ArrowUpRightOnBox } from "@medusajs/icons"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import { Link } from "react-router-dom"
|
||||
|
||||
import { ProductVariantDTO } from "@medusajs/types"
|
||||
import { Thumbnail } from "../../../../../components/common/thumbnail"
|
||||
@@ -14,7 +14,6 @@ export const InventoryItemVariantsSection = ({
|
||||
variants,
|
||||
}: InventoryItemVariantsSectionProps) => {
|
||||
const { t } = useTranslation()
|
||||
const navigate = useNavigate()
|
||||
|
||||
if (!variants?.length) {
|
||||
return null
|
||||
@@ -27,38 +26,46 @@ export const InventoryItemVariantsSection = ({
|
||||
</div>
|
||||
|
||||
<div className="txt-small flex flex-col gap-2 px-2 pb-2">
|
||||
{variants.map((variant) => (
|
||||
<div
|
||||
key={variant.id}
|
||||
className="shadow-elevation-card-rest bg-ui-bg-component rounded-md px-4 py-2"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="shadow-elevation-card-rest rounded-md">
|
||||
<Thumbnail src={variant.product?.thumbnail} />
|
||||
{variants.map((variant) => {
|
||||
const link = variant.product
|
||||
? `/products/${variant.product.id}/variants/${variant.id}`
|
||||
: null
|
||||
|
||||
const Inner = (
|
||||
<div className="shadow-elevation-card-rest bg-ui-bg-component rounded-md px-4 py-2 transition-colors">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="shadow-elevation-card-rest rounded-md">
|
||||
<Thumbnail src={variant.product?.thumbnail} />
|
||||
</div>
|
||||
<div className="flex flex-1 flex-col">
|
||||
<span className="text-ui-fg-base font-medium">
|
||||
{variant.title}
|
||||
</span>
|
||||
<span className="text-ui-fg-subtle">
|
||||
{variant.options.map((o) => o.value).join(" ⋅ ")}
|
||||
</span>
|
||||
</div>
|
||||
<div className="size-7 flex items-center justify-center">
|
||||
<TriangleRightMini className="text-ui-fg-muted" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-1 flex-col">
|
||||
<span className="text-ui-fg-base font-medium">
|
||||
{variant.title}
|
||||
</span>
|
||||
<span className="text-ui-fg-subtle">
|
||||
{variant.options.map((o) => o.value).join(" ⋅ ")}
|
||||
</span>
|
||||
</div>
|
||||
<IconButton
|
||||
size="2xsmall"
|
||||
variant="transparent"
|
||||
type="button"
|
||||
onClick={() =>
|
||||
navigate(
|
||||
`/products/${variant.product.id}/variants/${variant.id}`
|
||||
)
|
||||
}
|
||||
>
|
||||
<ArrowUpRightOnBox className="text-ui-fg-muted" />
|
||||
</IconButton>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
)
|
||||
|
||||
if (!link) {
|
||||
return <div key={variant.id}>{Inner}</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<Link
|
||||
to={link}
|
||||
key={variant.id}
|
||||
className="outline-none focus-within:shadow-borders-interactive-with-focus rounded-md [&:hover>div]:bg-ui-bg-component-hover"
|
||||
>
|
||||
{Inner}
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</Container>
|
||||
)
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { PlaceholderCell } from "../../../../../components/table/table-cells/common/placeholder-cell"
|
||||
import { ReservationActions } from "./reservation-actions"
|
||||
import { createColumnHelper } from "@tanstack/react-table"
|
||||
import { useMemo } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { CreatedAtCell } from "../../../../../components/table/table-cells/common/created-at-cell"
|
||||
import { PlaceholderCell } from "../../../../../components/table/table-cells/common/placeholder-cell"
|
||||
import { TextCell, TextHeader } from "../../../../../components/table/table-cells/common/text-cell"
|
||||
import { ReservationActions } from "./reservation-actions"
|
||||
|
||||
/**
|
||||
* Adds missing properties to the InventoryItemDTO type.
|
||||
@@ -22,17 +23,16 @@ export const useReservationTableColumn = ({ sku }: { sku: string }) => {
|
||||
return useMemo(
|
||||
() => [
|
||||
columnHelper.display({
|
||||
header: t("fields.sku"),
|
||||
id: "sku",
|
||||
header: () => <TextHeader text={t("fields.sku")} />,
|
||||
cell: () => {
|
||||
return (
|
||||
<div className="flex size-full items-center overflow-hidden">
|
||||
<span className="truncate">{sku}</span>
|
||||
</div>
|
||||
<TextCell text={sku} />
|
||||
)
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor("line_item.order_id", {
|
||||
header: t("inventory.reservation.orderID"),
|
||||
header: () => <TextHeader text={t("inventory.reservation.orderID")} />,
|
||||
cell: ({ getValue }) => {
|
||||
const orderId = getValue()
|
||||
|
||||
@@ -41,14 +41,12 @@ export const useReservationTableColumn = ({ sku }: { sku: string }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex size-full items-center overflow-hidden">
|
||||
<span className="truncate">{orderId}</span>
|
||||
</div>
|
||||
<TextCell text={orderId} />
|
||||
)
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor("description", {
|
||||
header: t("fields.description"),
|
||||
header: () => <TextHeader text={t("fields.description")} />,
|
||||
cell: ({ getValue }) => {
|
||||
const description = getValue()
|
||||
|
||||
@@ -57,14 +55,12 @@ export const useReservationTableColumn = ({ sku }: { sku: string }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex size-full items-center overflow-hidden">
|
||||
<span className="truncate">{description}</span>
|
||||
</div>
|
||||
<TextCell text={description} />
|
||||
)
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor("location.name", {
|
||||
header: t("inventory.reservation.location"),
|
||||
header: () => <TextHeader text={t("inventory.reservation.location")} />,
|
||||
cell: ({ getValue }) => {
|
||||
const location = getValue()
|
||||
|
||||
@@ -73,16 +69,20 @@ export const useReservationTableColumn = ({ sku }: { sku: string }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex size-full items-center overflow-hidden">
|
||||
<span className="truncate">{location}</span>
|
||||
</div>
|
||||
<TextCell text={location} />
|
||||
)
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor("created_at", {
|
||||
header: t("fields.createdAt"),
|
||||
header: () => <TextHeader text={t("fields.createdAt")} />,
|
||||
cell: ({ getValue }) => <CreatedAtCell date={getValue()} />,
|
||||
}),
|
||||
columnHelper.accessor("quantity", {
|
||||
header: () => <TextHeader text={t("fields.quantity")} align="right" />,
|
||||
cell: ({ getValue }) => {
|
||||
return <TextCell text={getValue()} align="right" />
|
||||
},
|
||||
}),
|
||||
columnHelper.display({
|
||||
id: "actions",
|
||||
cell: ({ row }) => <ReservationActions reservation={row.original} />,
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
import { Outlet, json, useLoaderData, useParams } from "react-router-dom"
|
||||
import { useLoaderData, useParams } from "react-router-dom"
|
||||
|
||||
import { TwoColumnPageSkeleton } from "../../../components/common/skeleton"
|
||||
import { TwoColumnPage } from "../../../components/layout/pages"
|
||||
import { useInventoryItem } from "../../../hooks/api/inventory"
|
||||
import { InventoryItemAttributeSection } from "./components/inventory-item-attributes/attributes-section"
|
||||
import { InventoryItemGeneralSection } from "./components/inventory-item-general-section"
|
||||
import { InventoryItemLocationLevelsSection } from "./components/inventory-item-location-levels"
|
||||
import { InventoryItemReservationsSection } from "./components/inventory-item-reservations"
|
||||
import { JsonViewSection } from "../../../components/common/json-view-section"
|
||||
import { useInventoryItem } from "../../../hooks/api/inventory"
|
||||
import { InventoryItemVariantsSection } from "./components/inventory-item-variants/variants-section"
|
||||
import { inventoryItemLoader } from "./loader"
|
||||
|
||||
import after from "virtual:medusa/widgets/inventory_item/details/after"
|
||||
import before from "virtual:medusa/widgets/inventory_item/details/before"
|
||||
import sideAfter from "virtual:medusa/widgets/inventory_item/details/side/after"
|
||||
import sideBefore from "virtual:medusa/widgets/inventory_item/details/side/before"
|
||||
|
||||
export const InventoryDetail = () => {
|
||||
const { id } = useParams()
|
||||
|
||||
@@ -31,37 +37,45 @@ export const InventoryDetail = () => {
|
||||
}
|
||||
)
|
||||
|
||||
if (isLoading) {
|
||||
return <div>Loading...</div>
|
||||
if (isLoading || !inventory_item) {
|
||||
return (
|
||||
<TwoColumnPageSkeleton
|
||||
showJSON
|
||||
showMetadata
|
||||
mainSections={3}
|
||||
sidebarSections={2}
|
||||
/>
|
||||
)
|
||||
}
|
||||
if (isError || !inventory_item) {
|
||||
if (error) {
|
||||
throw error
|
||||
}
|
||||
|
||||
throw json("An unknown error occurred", 500)
|
||||
if (isError) {
|
||||
throw error
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-y-2">
|
||||
<div className="flex flex-col gap-x-4 lg:flex-row lg:items-start">
|
||||
<div className="flex w-full flex-col gap-y-3">
|
||||
<InventoryItemGeneralSection inventoryItem={inventory_item} />
|
||||
<InventoryItemLocationLevelsSection inventoryItem={inventory_item} />
|
||||
<InventoryItemReservationsSection inventoryItem={inventory_item} />
|
||||
<div className="hidden lg:block">
|
||||
<JsonViewSection data={inventory_item} />
|
||||
</div>
|
||||
<Outlet />
|
||||
</div>
|
||||
<div className="mt-2 flex w-full max-w-[100%] flex-col gap-y-2 lg:mt-0 lg:max-w-[400px]">
|
||||
<InventoryItemVariantsSection variants={inventory_item.variants} />
|
||||
<InventoryItemAttributeSection inventoryItem={inventory_item} />
|
||||
<div className="lg:hidden">
|
||||
<JsonViewSection data={inventory_item} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<TwoColumnPage
|
||||
widgets={{
|
||||
after,
|
||||
before,
|
||||
sideAfter,
|
||||
sideBefore,
|
||||
}}
|
||||
data={inventory_item}
|
||||
showJSON
|
||||
showMetadata
|
||||
hasOutlet
|
||||
>
|
||||
<TwoColumnPage.Main>
|
||||
<InventoryItemGeneralSection inventoryItem={inventory_item} />
|
||||
<InventoryItemLocationLevelsSection inventoryItem={inventory_item} />
|
||||
<InventoryItemReservationsSection inventoryItem={inventory_item} />
|
||||
</TwoColumnPage.Main>
|
||||
<TwoColumnPage.Sidebar>
|
||||
<InventoryItemVariantsSection
|
||||
variants={(inventory_item as any).variants}
|
||||
/>
|
||||
<InventoryItemAttributeSection inventoryItem={inventory_item as any} />
|
||||
</TwoColumnPage.Sidebar>
|
||||
</TwoColumnPage>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user