import { Cell, Row, TableRowProps, usePagination, useTable } from "react-table"
import {
InventoryItemDTO,
InventoryLevelDTO,
ProductVariant,
} from "@medusajs/medusa"
import React, { useEffect, useMemo, useState } from "react"
import {
useAdminInventoryItems,
useAdminStockLocations,
useAdminStore,
useAdminUpdateLocationLevel,
useAdminVariant,
} from "medusa-react"
import { useLocation, useNavigate } from "react-router-dom"
import { useTranslation } from "react-i18next"
import Button from "../../fundamentals/button"
import ImagePlaceholder from "../../fundamentals/image-placeholder"
import InputField from "../../molecules/input"
import InputHeader from "../../fundamentals/input-header"
import Modal from "../../molecules/modal"
import { NextSelect } from "../../molecules/select/next-select"
import Spinner from "../../atoms/spinner"
import Table from "../../molecules/table"
import TableContainer from "../../../components/organisms/table-container"
import { getErrorMessage } from "../../../utils/error-messages"
import { isEmpty } from "lodash"
import qs from "qs"
import { useInventoryFilters } from "./use-inventory-filters"
import useInventoryTableColumn from "./use-inventory-column"
import useNotification from "../../../hooks/use-notification"
import useToggleState from "../../../hooks/use-toggle-state"
const DEFAULT_PAGE_SIZE = 15
type InventoryTableProps = {}
const defaultQueryProps = {}
const LocationDropdown = ({
selectedLocation,
onChange,
}: {
selectedLocation: string
onChange: (id: string) => void
}) => {
const { stock_locations: locations, isLoading } = useAdminStockLocations()
useEffect(() => {
if (!selectedLocation && !isLoading && locations?.length) {
onChange(locations[0].id)
}
}, [isLoading, locations, onChange, selectedLocation])
const selectedLocObj = useMemo(() => {
if (!isLoading && locations) {
return locations.find((l) => l.id === selectedLocation)
}
return null
}, [selectedLocation, locations, isLoading])
if (isLoading || !locations || !selectedLocObj) {
return null
}
return (
{
onChange(loc!.value)
}}
options={locations.map((l) => ({
label: l.name,
value: l.id,
}))}
value={{ value: selectedLocObj.id, label: selectedLocObj.name }}
/>
)
}
const InventoryTable: React.FC = () => {
const { store } = useAdminStore()
const location = useLocation()
const { t } = useTranslation()
const { stock_locations, isLoading: locationsLoading } =
useAdminStockLocations()
const defaultQuery = useMemo(() => {
if (store) {
return {
...defaultQueryProps,
location_id: store.default_location_id,
}
}
return defaultQueryProps
}, [store])
const {
reset,
paginate,
setLocationFilter,
setQuery: setFreeText,
queryObject,
representationObject,
} = useInventoryFilters(location.search, defaultQuery)
const offs = parseInt(queryObject.offset) || 0
const limit = parseInt(queryObject.limit)
const [query, setQuery] = useState(queryObject.query)
const [numPages, setNumPages] = useState(0)
const { inventory_items, isLoading, count } = useAdminInventoryItems(
{
...queryObject,
},
{
enabled: !!store,
}
)
useEffect(() => {
const controlledPageCount = Math.ceil(count! / queryObject.limit)
setNumPages(controlledPageCount)
}, [inventory_items])
const updateUrlFromFilter = (obj = {}) => {
const stringified = qs.stringify(obj)
window.history.replaceState(`/a/inventory`, "", `${`?${stringified}`}`)
}
const refreshWithFilters = () => {
const filterObj = representationObject
if (isEmpty(filterObj)) {
updateUrlFromFilter({ offset: 0, limit: DEFAULT_PAGE_SIZE })
} else {
updateUrlFromFilter(filterObj)
}
}
useEffect(() => {
refreshWithFilters()
}, [representationObject])
const [columns] = useInventoryTableColumn({
location_id: queryObject.location_id,
})
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
gotoPage,
canPreviousPage,
canNextPage,
pageCount,
nextPage,
previousPage,
// Get the state from the instance
state: { pageIndex },
} = useTable(
{
columns,
data: inventory_items || [],
manualPagination: true,
initialState: {
pageIndex: Math.floor(offs / limit),
pageSize: limit,
},
pageCount: numPages,
autoResetPage: false,
},
usePagination
)
// Debounced search
useEffect(() => {
const delayDebounceFn = setTimeout(() => {
if (query) {
setFreeText(query)
gotoPage(0)
} else {
if (typeof query !== "undefined") {
// if we delete query string, we reset the table view
reset()
}
}
}, 400)
return () => clearTimeout(delayDebounceFn)
}, [query])
const handleNext = () => {
if (canNextPage) {
paginate(1)
nextPage()
}
}
const handlePrev = () => {
if (canPreviousPage) {
paginate(-1)
previousPage()
}
}
return (
{
setLocationFilter(id)
gotoPage(0)
}}
/>
}
{...getTableProps()}
>
{headerGroups?.map((headerGroup) => {
const { key, ...rest } = headerGroup.getHeaderGroupProps()
return (
{headerGroup.headers.map((col) => {
const { key, ...rest } = col.getHeaderProps()
return (
{col.render("Header")}
)
})}
)
})}
{rows.map((row) => {
prepareRow(row)
const { key, ...rest } = row.getRowProps()
return
})}
{!rows.length && !locationsLoading && !stock_locations?.length && (
You don't have any stock locations. Add one to see inventory.
)}
)
}
const InventoryRow = ({
row,
...rest
}: {
row: Row<
Partial & {
location_levels?: InventoryLevelDTO[] | undefined
variants?: ProductVariant[] | undefined
}
>
} & TableRowProps) => {
const inventory = row.original
const navigate = useNavigate()
const { t } = useTranslation()
const {
state: isShowingAdjustAvailabilityModal,
open: showAdjustAvailabilityModal,
close: closeAdjustAvailabilityModal,
} = useToggleState()
const getRowActionables = () => {
const productId = inventory.variants?.[0]?.product_id
const actions = [
{
label: t(
"inventory-table-actions-adjust-availability",
"Adjust Availability"
),
onClick: showAdjustAvailabilityModal,
},
]
if (productId) {
return [
{
label: t("inventory-table-view-product", "View Product"),
onClick: () => navigate(`/a/products/${productId}`),
},
...actions,
]
}
return actions
}
return (
{row.cells.map((cell: Cell, index: number) => {
const { key, ...rest } = cell.getCellProps()
return (
{cell.render("Cell", { index })}
)
})}
{isShowingAdjustAvailabilityModal && (
)}
)
}
const AdjustAvailabilityModal = ({
inventory,
handleClose,
}: {
inventory: Partial & {
location_levels?: InventoryLevelDTO[] | undefined
variants?: ProductVariant[] | undefined
}
handleClose: () => void
}) => {
const inventoryVariantId = inventory.variants?.[0]?.id
const locationLevel = inventory.location_levels?.[0]
const { t } = useTranslation()
const { variant, isLoading } = useAdminVariant(inventoryVariantId || "")
const {
mutate: updateLocationLevelForInventoryItem,
isLoading: isSubmitting,
} = useAdminUpdateLocationLevel(inventory.id!)
const notification = useNotification()
const [stockedQuantity, setStockedQuantity] = useState(
locationLevel?.stocked_quantity || 0
)
const disableSubmit =
stockedQuantity === (locationLevel?.stocked_quantity || 0) ||
!variant ||
!locationLevel
const onSubmit = () => {
updateLocationLevelForInventoryItem(
{
stockLocationId: locationLevel!.location_id,
stocked_quantity: stockedQuantity,
},
{
onSuccess: () => {
notification(
t("inventory-table-success", "Success"),
t(
"inventory-table-inventory-item-updated-successfully",
"Inventory item updated successfully"
),
"success"
)
handleClose()
},
onError: (error) => {
notification("Error", getErrorMessage(error), "error")
},
}
)
}
return (
{t("inventory-table-adjust-availability", "Adjust availability")}
{isLoading ? (
) : (
{variant?.product?.thumbnail ? (

) : (
)}
{variant?.product?.title}
({inventory.sku})
{variant?.options?.map((o) => (
{o.value}
))}
setStockedQuantity(e.target.valueAsNumber)}
autoFocus
type="number"
placeholder="0"
min={0}
value={stockedQuantity}
/>
)}
)
}
export default InventoryTable