From 4e86caba3060508a2512de17e8021ae1fb9697b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Frane=20Poli=C4=87?= <16856471+fPolic@users.noreply.github.com> Date: Mon, 17 Jun 2024 11:23:18 +0200 Subject: [PATCH] feat(dashboard): display inventory levels in variants table (#7694) * feat: display inventory levels in variants table * fix: display conditions and translations * fix: invalidate inventory lists when products are created * fix: translation, fix link definition * fix: revert link * feat: navigation actions * fix: action, refactor * fix: refactor, add check for manage quantity flag * fix: update label --- .../dashboard/src/hooks/api/products.tsx | 5 + .../dashboard/src/i18n/translations/en.json | 8 ++ .../components/use-inventory-table-query.tsx | 19 +++- .../product-variant-section.tsx | 2 +- .../use-variant-table-columns.tsx | 107 ++++++++++++++++-- .../types/src/http/inventory/admin/queries.ts | 1 + 6 files changed, 128 insertions(+), 14 deletions(-) diff --git a/packages/admin-next/dashboard/src/hooks/api/products.tsx b/packages/admin-next/dashboard/src/hooks/api/products.tsx index f25353eb22..eb1bdd85c3 100644 --- a/packages/admin-next/dashboard/src/hooks/api/products.tsx +++ b/packages/admin-next/dashboard/src/hooks/api/products.tsx @@ -9,6 +9,7 @@ import { import { sdk } from "../../lib/client" import { queryClient } from "../../lib/query-client" import { queryKeysFactory } from "../../lib/query-key-factory" +import { inventoryItemsQueryKeys } from "./inventory.tsx" const PRODUCTS_QUERY_KEY = "products" as const export const productsQueryKeys = queryKeysFactory(PRODUCTS_QUERY_KEY) @@ -254,6 +255,10 @@ export const useCreateProduct = ( sdk.admin.product.create(payload), onSuccess: (data: any, variables: any, context: any) => { queryClient.invalidateQueries({ queryKey: productsQueryKeys.lists() }) + // if `manage_inventory` is true on created variants that will create inventory items automatically + queryClient.invalidateQueries({ + queryKey: inventoryItemsQueryKeys.lists(), + }) options?.onSuccess?.(data, variables, context) }, ...options, diff --git a/packages/admin-next/dashboard/src/i18n/translations/en.json b/packages/admin-next/dashboard/src/i18n/translations/en.json index cb6186c910..ebb2bce571 100644 --- a/packages/admin-next/dashboard/src/i18n/translations/en.json +++ b/packages/admin-next/dashboard/src/i18n/translations/en.json @@ -324,7 +324,15 @@ "create": { "header": "Create Variant" }, + "tableItemAvailable": "{{availableCount}} available", + "tableItem_one": "{{availableCount}} available at {{locationCount}} location", + "tableItem_other": "{{availableCount}} available at {{locationCount}} locations", "inventory": { + "notManaged": "Not managed", + "actions": { + "inventoryItems": "Go to inventory item", + "inventoryKit": "Show inventory items" + }, "header": "Stock & Inventory", "editItemDetails": "Edit item details", "manageInventoryLabel": "Manage inventory", diff --git a/packages/admin-next/dashboard/src/routes/inventory/inventory-list/components/use-inventory-table-query.tsx b/packages/admin-next/dashboard/src/routes/inventory/inventory-list/components/use-inventory-table-query.tsx index d02f52f891..acf886a72c 100644 --- a/packages/admin-next/dashboard/src/routes/inventory/inventory-list/components/use-inventory-table-query.tsx +++ b/packages/admin-next/dashboard/src/routes/inventory/inventory-list/components/use-inventory-table-query.tsx @@ -1,4 +1,5 @@ -import { AdminGetInventoryItemsParams } from "@medusajs/medusa" +import { HttpTypes } from "@medusajs/types" + import { useQueryParams } from "../../../../hooks/use-query-params" export const useInventoryTableQuery = ({ @@ -10,14 +11,17 @@ export const useInventoryTableQuery = ({ }) => { const raw = useQueryParams( [ + "id", "location_id", "q", "order", "requires_shipping", "offset", "sku", + "origin_country", "material", "mid_code", + "hs_code", "order", "weight", "width", @@ -37,7 +41,7 @@ export const useInventoryTableQuery = ({ ...params } = raw - const searchParams: AdminGetInventoryItemsParams = { + const searchParams: HttpTypes.AdminInventoryItemParams = { limit: pageSize, offset: offset ? parseInt(offset) : undefined, weight: weight ? JSON.parse(weight) : undefined, @@ -47,7 +51,16 @@ export const useInventoryTableQuery = ({ requires_shipping: requires_shipping ? JSON.parse(requires_shipping) : undefined, - ...params, + q: params.q, + sku: params.sku, + order: params.order, + mid_code: params.mid_code, + hs_code: params.hs_code, + material: params.material, + location_levels: { + location_id: params.location_id || [], + }, + id: params.id ? params.id.split(",") : undefined, } return { diff --git a/packages/admin-next/dashboard/src/routes/products/product-detail/components/product-variant-section/product-variant-section.tsx b/packages/admin-next/dashboard/src/routes/products/product-detail/components/product-variant-section/product-variant-section.tsx index 6444b40234..80ac2bab6f 100644 --- a/packages/admin-next/dashboard/src/routes/products/product-detail/components/product-variant-section/product-variant-section.tsx +++ b/packages/admin-next/dashboard/src/routes/products/product-detail/components/product-variant-section/product-variant-section.tsx @@ -2,6 +2,7 @@ import { PencilSquare, Plus } from "@medusajs/icons" import { Container, Heading } from "@medusajs/ui" import { useTranslation } from "react-i18next" import { keepPreviousData } from "@tanstack/react-query" +import { HttpTypes } from "@medusajs/types" import { ActionMenu } from "../../../../../components/common/action-menu" import { DataTable } from "../../../../../components/table/data-table" @@ -10,7 +11,6 @@ import { useProductVariantTableColumns } from "./use-variant-table-columns" import { useProductVariantTableFilters } from "./use-variant-table-filters" import { useProductVariantTableQuery } from "./use-variant-table-query" import { useProductVariants } from "../../../../../hooks/api/products" -import { HttpTypes } from "@medusajs/types" type ProductVariantSectionProps = { product: HttpTypes.AdminProduct diff --git a/packages/admin-next/dashboard/src/routes/products/product-detail/components/product-variant-section/use-variant-table-columns.tsx b/packages/admin-next/dashboard/src/routes/products/product-detail/components/product-variant-section/use-variant-table-columns.tsx index 51126447c3..bb6e8419de 100644 --- a/packages/admin-next/dashboard/src/routes/products/product-detail/components/product-variant-section/use-variant-table-columns.tsx +++ b/packages/admin-next/dashboard/src/routes/products/product-detail/components/product-variant-section/use-variant-table-columns.tsx @@ -1,25 +1,31 @@ -import { PencilSquare, Trash } from "@medusajs/icons" -import { Badge, usePrompt } from "@medusajs/ui" +import { Buildings, Component, PencilSquare, Trash } from "@medusajs/icons" +import { Badge, usePrompt, clx } from "@medusajs/ui" import { createColumnHelper } from "@tanstack/react-table" -import { useMemo } from "react" +import { HttpTypes, InventoryItemDTO } from "@medusajs/types" import { useTranslation } from "react-i18next" +import { useMemo } from "react" import { ActionMenu } from "../../../../../components/common/action-menu" import { PlaceholderCell } from "../../../../../components/table/table-cells/common/placeholder-cell" import { useDeleteVariant } from "../../../../../hooks/api/products" -import { HttpTypes } from "@medusajs/types" const VariantActions = ({ variant, product, }: { - variant: HttpTypes.AdminProductVariant + variant: HttpTypes.AdminProductVariant & { + inventory_items: { inventory: InventoryItemDTO }[] + } product: HttpTypes.AdminProduct }) => { const { mutateAsync } = useDeleteVariant(product.id, variant.id) const { t } = useTranslation() const prompt = usePrompt() + const inventoryItemsCount = variant.inventory_items?.length || 0 + const hasInventoryItem = inventoryItemsCount === 1 + const hasInventoryKit = inventoryItemsCount > 1 + const handleDelete = async () => { const res = await prompt({ title: t("general.areYouSure"), @@ -37,6 +43,23 @@ const VariantActions = ({ await mutateAsync() } + const [inventoryItemLink, inventoryKitLink] = useMemo(() => { + if (!variant.inventory_items?.length) { + return ["", ""] + } + + const itemId = variant.inventory_items![0].inventory.id + const itemLink = `/inventory/${itemId}` + + const itemIds = variant.inventory_items!.map((i) => i.inventory.id) + const params = { id: itemIds } + const query = new URLSearchParams(params).toString() + + const kitLink = `/inventory?${query}` + + return [itemLink, kitLink] + }, [variant.inventory_items]) + return ( , }, - ], - }, - { - actions: [ { label: t("actions.delete"), onClick: handleDelete, icon: , }, - ], + hasInventoryItem + ? { + label: t("products.variant.inventory.actions.inventoryItems"), + to: inventoryItemLink, + icon: , + } + : false, + hasInventoryKit + ? { + label: t("products.variant.inventory.actions.inventoryKit"), + to: inventoryKitLink, + icon: , + } + : false, + ].filter(Boolean), }, ]} /> @@ -140,6 +173,60 @@ export const useProductVariantTableColumns = ( }, }), ...optionColumns, + columnHelper.accessor("inventory_items", { + header: () => ( +
+ {t("fields.inventory")} +
+ ), + cell: ({ getValue, row }) => { + const variant = row.original + + if (!variant.manage_inventory) { + return t("products.variant.inventory.notManaged") + } + + const inventory: InventoryItemDTO[] = getValue().map( + (i) => i.inventory + ) + + const hasInventoryKit = inventory.length > 1 + + const locations = {} + + inventory.forEach((i) => { + i.location_levels.forEach((l) => { + locations[l.id] = true + }) + }) + + const locationCount = Object.keys(locations).length + + const text = hasInventoryKit + ? t("products.variant.tableItemAvailable", { + availableCount: variant.inventory_quantity, + }) + : t("products.variant.tableItem", { + availableCount: variant.inventory_quantity, + locationCount, + count: locationCount, + }) + + return ( +
+ {hasInventoryKit && } + + {text} + +
+ ) + }, + }), columnHelper.display({ id: "actions", cell: ({ row, table }) => { diff --git a/packages/core/types/src/http/inventory/admin/queries.ts b/packages/core/types/src/http/inventory/admin/queries.ts index 82420cecf7..9dedaec5c5 100644 --- a/packages/core/types/src/http/inventory/admin/queries.ts +++ b/packages/core/types/src/http/inventory/admin/queries.ts @@ -6,6 +6,7 @@ export interface AdminInventoryItemParams extends FindParams { q?: string sku?: string | string[] origin_country?: string | string[] + mid_code?: string | string[] hs_code?: string | string[] material?: string | string[] requires_shipping?: boolean