chore: Product page shows list of categories associated with it (#3400)
This commit is contained in:
1 parent
ed0f777431
commit
bca731a148
5 files changed
+90
-56
No files matched your search
@@ -0,0 +1,44 @@
|
|||||||
|
import React from "react"
|
||||||
|
import Tooltip from "../../atoms/tooltip"
|
||||||
|
|
||||||
|
type DelimitedListProps = {
|
||||||
|
list: string[]
|
||||||
|
delimit?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const DelimitedList: React.FC<DelimitedListProps> = ({ list, delimit = 1 }) => {
|
||||||
|
if (!list.length) {
|
||||||
|
return <></>
|
||||||
|
}
|
||||||
|
|
||||||
|
const itemsToDisplay = list.slice(0, delimit).join(", ")
|
||||||
|
const showExtraItemsInTooltip = list.length > delimit
|
||||||
|
const extraItemsInToolTipCount = list.length - delimit
|
||||||
|
|
||||||
|
const ToolTipContent = () => {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col">
|
||||||
|
{list.slice(delimit).map((listItem) => (
|
||||||
|
<span key={listItem}>{listItem}</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className="inter-small-regular">
|
||||||
|
{itemsToDisplay}
|
||||||
|
|
||||||
|
{showExtraItemsInTooltip && (
|
||||||
|
<Tooltip content={<ToolTipContent />}>
|
||||||
|
<span className="text-grey-40">
|
||||||
|
{" "}
|
||||||
|
+ {extraItemsInToolTipCount} more
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DelimitedList
|
||||||
@@ -13,12 +13,12 @@ const SalesChannelsDisplay = ({ channels = [] }: Props) => {
|
|||||||
const remainder = Math.max(channels.length - 3, 0)
|
const remainder = Math.max(channels.length - 3, 0)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-y-small">
|
<div className="gap-y-small flex flex-col">
|
||||||
{channels.length > 0 && (
|
{channels.length > 0 && (
|
||||||
<div className="flex gap-x-1">
|
<div className="flex gap-x-1">
|
||||||
<div className="flex gap-x-1 max-w-[600px] overflow-clip">
|
<div className="flex max-w-[600px] gap-x-1 overflow-clip">
|
||||||
{channels.slice(0, 3).map((sc) => (
|
{channels.slice(0, 3).map((sc) => (
|
||||||
<SalesChannelBadge channel={sc} />
|
<SalesChannelBadge key={sc.id} channel={sc} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
{remainder > 0 && (
|
{remainder > 0 && (
|
||||||
@@ -32,7 +32,7 @@ const SalesChannelsDisplay = ({ channels = [] }: Props) => {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Badge variant="ghost" className="px-3 py-1.5">
|
<Badge variant="ghost" className="px-3 py-1.5">
|
||||||
<div className="flex items-center h-full inter-small-regular text-grey-50">
|
<div className="inter-small-regular text-grey-50 flex h-full items-center">
|
||||||
+ {remainder} more
|
+ {remainder} more
|
||||||
</div>
|
</div>
|
||||||
</Badge>
|
</Badge>
|
||||||
|
|||||||
+10
-32
@@ -1,8 +1,8 @@
|
|||||||
import clsx from "clsx"
|
import clsx from "clsx"
|
||||||
import { useAdminStore } from "medusa-react"
|
import { useAdminStore } from "medusa-react"
|
||||||
import React, { useMemo } from "react"
|
import { useMemo } from "react"
|
||||||
import { defaultChannelsSorter } from "../../../utils/sales-channel-compare-operator"
|
import { defaultChannelsSorter } from "../../../utils/sales-channel-compare-operator"
|
||||||
import Tooltip from "../../atoms/tooltip"
|
import DelimitedList from "../../molecules/delimited-list"
|
||||||
import ListIcon from "../../fundamentals/icons/list-icon"
|
import ListIcon from "../../fundamentals/icons/list-icon"
|
||||||
import TileIcon from "../../fundamentals/icons/tile-icon"
|
import TileIcon from "../../fundamentals/icons/tile-icon"
|
||||||
import ImagePlaceholder from "../../fundamentals/image-placeholder"
|
import ImagePlaceholder from "../../fundamentals/image-placeholder"
|
||||||
@@ -27,33 +27,11 @@ const useProductTableColumn = ({ setTileView, setListView, showList }) => {
|
|||||||
const { store } = useAdminStore()
|
const { store } = useAdminStore()
|
||||||
|
|
||||||
const getProductSalesChannels = (salesChannels) => {
|
const getProductSalesChannels = (salesChannels) => {
|
||||||
if (salesChannels?.length) {
|
;(salesChannels || []).sort(
|
||||||
salesChannels.sort(
|
defaultChannelsSorter(store?.default_sales_channel_id || "")
|
||||||
defaultChannelsSorter(store?.default_sales_channel_id || "")
|
)
|
||||||
)
|
|
||||||
return (
|
return <DelimitedList list={salesChannels.map((sc) => sc.name)} />
|
||||||
<span className="inter-small-regular">
|
|
||||||
{salesChannels[0].name}
|
|
||||||
{salesChannels.length > 1 && (
|
|
||||||
<Tooltip
|
|
||||||
content={
|
|
||||||
<div className="flex flex-col">
|
|
||||||
{salesChannels.slice(1).map((sc) => (
|
|
||||||
<span>{sc.name}</span>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<span className="text-grey-40">
|
|
||||||
{" "}
|
|
||||||
+ {salesChannels.length - 1} more
|
|
||||||
</span>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return <></>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const columns = useMemo(
|
const columns = useMemo(
|
||||||
@@ -64,11 +42,11 @@ const useProductTableColumn = ({ setTileView, setListView, showList }) => {
|
|||||||
Cell: ({ row: { original } }) => {
|
Cell: ({ row: { original } }) => {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<div className="h-[40px] w-[30px] my-1.5 flex items-center mr-4">
|
<div className="my-1.5 mr-4 flex h-[40px] w-[30px] items-center">
|
||||||
{original.thumbnail ? (
|
{original.thumbnail ? (
|
||||||
<img
|
<img
|
||||||
src={original.thumbnail}
|
src={original.thumbnail}
|
||||||
className="h-full object-cover rounded-soft"
|
className="rounded-soft h-full object-cover"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<ImagePlaceholder />
|
<ImagePlaceholder />
|
||||||
@@ -110,7 +88,7 @@ const useProductTableColumn = ({ setTileView, setListView, showList }) => {
|
|||||||
{
|
{
|
||||||
accessor: "col-3",
|
accessor: "col-3",
|
||||||
Header: (
|
Header: (
|
||||||
<div className="text-right flex justify-end">
|
<div className="flex justify-end text-right">
|
||||||
<span
|
<span
|
||||||
onClick={setListView}
|
onClick={setListView}
|
||||||
className={clsx("hover:bg-grey-5 cursor-pointer rounded p-0.5", {
|
className={clsx("hover:bg-grey-5 cursor-pointer rounded p-0.5", {
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import { Product, SalesChannel } from "@medusajs/medusa"
|
import { Product } from "@medusajs/medusa"
|
||||||
import React from "react"
|
|
||||||
import Badge from "../../../../../components/fundamentals/badge"
|
|
||||||
import FeatureToggle from "../../../../../components/fundamentals/feature-toggle"
|
import FeatureToggle from "../../../../../components/fundamentals/feature-toggle"
|
||||||
import ChannelsIcon from "../../../../../components/fundamentals/icons/channels-icon"
|
import ChannelsIcon from "../../../../../components/fundamentals/icons/channels-icon"
|
||||||
import EditIcon from "../../../../../components/fundamentals/icons/edit-icon"
|
import EditIcon from "../../../../../components/fundamentals/icons/edit-icon"
|
||||||
@@ -8,9 +6,13 @@ import TrashIcon from "../../../../../components/fundamentals/icons/trash-icon"
|
|||||||
import { ActionType } from "../../../../../components/molecules/actionables"
|
import { ActionType } from "../../../../../components/molecules/actionables"
|
||||||
import SalesChannelsDisplay from "../../../../../components/molecules/sales-channels-display"
|
import SalesChannelsDisplay from "../../../../../components/molecules/sales-channels-display"
|
||||||
import StatusSelector from "../../../../../components/molecules/status-selector"
|
import StatusSelector from "../../../../../components/molecules/status-selector"
|
||||||
|
import DelimitedList from "../../../../../components/molecules/delimited-list"
|
||||||
import Section from "../../../../../components/organisms/section"
|
import Section from "../../../../../components/organisms/section"
|
||||||
|
import {
|
||||||
|
useFeatureFlag,
|
||||||
|
FeatureFlag,
|
||||||
|
} from "../../../../../providers/feature-flag-provider"
|
||||||
import useToggleState from "../../../../../hooks/use-toggle-state"
|
import useToggleState from "../../../../../hooks/use-toggle-state"
|
||||||
import { useFeatureFlag } from "../../../../../providers/feature-flag-provider"
|
|
||||||
import useEditProductActions from "../../hooks/use-edit-product-actions"
|
import useEditProductActions from "../../hooks/use-edit-product-actions"
|
||||||
import ChannelsModal from "./channels-modal"
|
import ChannelsModal from "./channels-modal"
|
||||||
import GeneralModal from "./general-modal"
|
import GeneralModal from "./general-modal"
|
||||||
@@ -95,19 +97,33 @@ const GeneralSection = ({ product }: Props) => {
|
|||||||
|
|
||||||
type DetailProps = {
|
type DetailProps = {
|
||||||
title: string
|
title: string
|
||||||
value?: string | null
|
value?: string[] | string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
const Detail = ({ title, value }: DetailProps) => {
|
const Detail = ({ title, value }: DetailProps) => {
|
||||||
|
const DetailValue = () => {
|
||||||
|
if (!Array.isArray(value)) {
|
||||||
|
return <p>{value ? value : "–"}</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.length) {
|
||||||
|
return <DelimitedList list={value} delimit={2} />
|
||||||
|
}
|
||||||
|
|
||||||
|
return <p>–</p>
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="inter-base-regular text-grey-50 flex items-center justify-between">
|
<div className="inter-base-regular text-grey-50 flex items-center justify-between">
|
||||||
<p>{title}</p>
|
<p>{title}</p>
|
||||||
<p>{value ? value : "–"}</p>
|
<DetailValue />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProductDetails = ({ product }: Props) => {
|
const ProductDetails = ({ product }: Props) => {
|
||||||
|
const { isFeatureEnabled } = useFeatureFlag()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-8 flex flex-col gap-y-3">
|
<div className="mt-8 flex flex-col gap-y-3">
|
||||||
<h2 className="inter-base-semibold">Details</h2>
|
<h2 className="inter-base-semibold">Details</h2>
|
||||||
@@ -115,6 +131,12 @@ const ProductDetails = ({ product }: Props) => {
|
|||||||
<Detail title="Handle" value={product.handle} />
|
<Detail title="Handle" value={product.handle} />
|
||||||
<Detail title="Type" value={product.type?.value} />
|
<Detail title="Type" value={product.type?.value} />
|
||||||
<Detail title="Collection" value={product.collection?.title} />
|
<Detail title="Collection" value={product.collection?.title} />
|
||||||
|
{isFeatureEnabled(FeatureFlag.PRODUCT_CATEGORIES) && (
|
||||||
|
<Detail
|
||||||
|
title="Category"
|
||||||
|
value={product.categories.map((c) => c.name)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<Detail
|
<Detail
|
||||||
title="Discountable"
|
title="Discountable"
|
||||||
value={product.discountable ? "True" : "False"}
|
value={product.discountable ? "True" : "False"}
|
||||||
@@ -141,20 +163,6 @@ const ProductTags = ({ product }: Props) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
type SalesChannelBadgeProps = {
|
|
||||||
channel: SalesChannel
|
|
||||||
}
|
|
||||||
|
|
||||||
const SalesChannelBadge: React.FC<SalesChannelBadgeProps> = ({ channel }) => {
|
|
||||||
return (
|
|
||||||
<Badge variant="ghost" className="px-3 py-1.5">
|
|
||||||
<div className="flex items-center">
|
|
||||||
<span className="inter-small-regular text-grey-90">{channel.name}</span>
|
|
||||||
</div>
|
|
||||||
</Badge>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const ProductSalesChannels = ({ product }: Props) => {
|
const ProductSalesChannels = ({ product }: Props) => {
|
||||||
return (
|
return (
|
||||||
<FeatureToggle featureFlag="sales_channels">
|
<FeatureToggle featureFlag="sales_channels">
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ import React, {
|
|||||||
useState,
|
useState,
|
||||||
} from "react"
|
} from "react"
|
||||||
|
|
||||||
|
export enum FeatureFlag {
|
||||||
|
PRODUCT_CATEGORIES = "product_categories",
|
||||||
|
}
|
||||||
|
|
||||||
const defaultFeatureFlagContext: {
|
const defaultFeatureFlagContext: {
|
||||||
featureToggleList: Record<string, boolean>
|
featureToggleList: Record<string, boolean>
|
||||||
isFeatureEnabled: (flag: string) => boolean
|
isFeatureEnabled: (flag: string) => boolean
|
||||||
|
|||||||
Reference in new issue
Block a user