fix(dashboard) editor columns (#7534)
* fix: reorg variant editor columns, auto select default sales channel when creating products * fix: remove return * fix: add placeholders on options create inputs, add placeholder for chip input * fix: gaps on details pages * fix: address feedback
This commit is contained in:
@@ -97,7 +97,7 @@ export function getFieldsInRange(
|
||||
}
|
||||
|
||||
export function convertArrayToPrimitive<
|
||||
T extends "boolean" | "number" | "string",
|
||||
T extends "boolean" | "number" | "string"
|
||||
>(values: string[], type: T) {
|
||||
const convertedValues: any[] = []
|
||||
|
||||
@@ -172,7 +172,7 @@ type DataGridHelperColumnsProps<TData> = {
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
disableHidding?: boolean
|
||||
disableHiding?: boolean
|
||||
}
|
||||
|
||||
export function createDataGridHelper<TData>() {
|
||||
@@ -186,13 +186,13 @@ export function createDataGridHelper<TData>() {
|
||||
cell,
|
||||
type = "string",
|
||||
asString,
|
||||
disableHidding = false,
|
||||
disableHiding = false,
|
||||
}: DataGridHelperColumnsProps<TData>) =>
|
||||
columnHelper.display({
|
||||
id,
|
||||
header,
|
||||
cell,
|
||||
enableHiding: !disableHidding,
|
||||
enableHiding: !disableHiding,
|
||||
meta: {
|
||||
type,
|
||||
asString,
|
||||
|
||||
@@ -184,6 +184,12 @@
|
||||
"header": "Variants",
|
||||
"subHeadingTitle": "Yes, this is a product with variants",
|
||||
"subHeadingDescription": "When unchecked we will create a default variant for you",
|
||||
"optionTitle": {
|
||||
"placeholder": "Size"
|
||||
},
|
||||
"optionValues": {
|
||||
"placeholder": "Small, Medium, Large"
|
||||
},
|
||||
"productVariants": {
|
||||
"label": "Product variants",
|
||||
"hint": "This ranking will affect how the variants are ranked in your frontend.",
|
||||
|
||||
@@ -43,7 +43,7 @@ export const CampaignDetail = () => {
|
||||
)
|
||||
})}
|
||||
<div className="flex flex-col gap-x-4 xl:flex-row xl:items-start">
|
||||
<div className="flex w-full flex-col gap-y-2">
|
||||
<div className="flex w-full flex-col gap-y-3">
|
||||
<CampaignGeneralSection campaign={campaign} />
|
||||
<CampaignPromotionSection campaign={campaign} />
|
||||
{after.widgets.map((w, i) => {
|
||||
|
||||
@@ -44,7 +44,7 @@ export const CategoryDetail = () => {
|
||||
)
|
||||
})}
|
||||
<div className="flex flex-col gap-x-4 xl:flex-row xl:items-start">
|
||||
<div className="flex w-full flex-col gap-y-2">
|
||||
<div className="flex w-full flex-col gap-y-3">
|
||||
<CategoryGeneralSection category={product_category} />
|
||||
<CategoryProductSection category={product_category} />
|
||||
{after.widgets.map((w, i) => {
|
||||
|
||||
@@ -45,7 +45,7 @@ export const InventoryDetail = () => {
|
||||
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-2">
|
||||
<div className="flex w-full flex-col gap-y-3">
|
||||
<InventoryItemGeneralSection inventoryItem={inventory_item} />
|
||||
<InventoryItemLocationLevelsSection inventoryItem={inventory_item} />
|
||||
<InventoryItemReservationsSection inventoryItem={inventory_item} />
|
||||
|
||||
@@ -52,7 +52,7 @@ export const LocationDetails = () => {
|
||||
)
|
||||
})}
|
||||
<div className="flex flex-col gap-x-4 lg:flex-row xl:items-start">
|
||||
<div className="flex w-full flex-col gap-y-2">
|
||||
<div className="flex w-full flex-col gap-y-3">
|
||||
<LocationGeneralSection location={location} />
|
||||
{after.widgets.map((w, i) => {
|
||||
return (
|
||||
|
||||
@@ -36,7 +36,7 @@ export const OrderFulfillmentSection = ({
|
||||
const fulfillments = order.fulfillments || []
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-y-2">
|
||||
<div className="flex flex-col gap-y-3">
|
||||
<UnfulfilledItemBreakdown order={order} />
|
||||
{fulfillments.map((f, index) => (
|
||||
<Fulfillment key={f.id} index={index} fulfillment={f} order={order} />
|
||||
|
||||
@@ -39,7 +39,7 @@ export const OrderDetail = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-y-2">
|
||||
<div className="flex flex-col gap-y-3">
|
||||
{before.widgets.map((w, i) => {
|
||||
return (
|
||||
<div key={i}>
|
||||
@@ -48,7 +48,7 @@ export const OrderDetail = () => {
|
||||
)
|
||||
})}
|
||||
<div className="flex flex-col gap-x-4 lg:flex-row xl:items-start">
|
||||
<div className="flex w-full flex-col gap-y-2">
|
||||
<div className="flex w-full flex-col gap-y-3">
|
||||
<OrderGeneralSection order={order} />
|
||||
<OrderSummarySection order={order} />
|
||||
{/* <OrderPaymentSection order={order} />*/}
|
||||
@@ -64,7 +64,7 @@ export const OrderDetail = () => {
|
||||
<JsonViewSection data={order} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-2 flex w-full max-w-[100%] flex-col gap-y-2 xl:mt-0 xl:max-w-[400px]">
|
||||
<div className="mt-2 flex w-full max-w-[100%] flex-col gap-y-3 xl:mt-0 xl:max-w-[400px]">
|
||||
{sideBefore.widgets.map((w, i) => {
|
||||
return (
|
||||
<div key={i}>
|
||||
|
||||
@@ -5,7 +5,7 @@ import before from "virtual:medusa/widgets/order/list/before"
|
||||
|
||||
export const OrderList = () => {
|
||||
return (
|
||||
<div className="flex w-full flex-col gap-y-2">
|
||||
<div className="flex w-full flex-col gap-y-3">
|
||||
{before.widgets.map((w, i) => {
|
||||
return (
|
||||
<div key={i}>
|
||||
|
||||
@@ -34,7 +34,7 @@ export const PricingDetail = () => {
|
||||
)
|
||||
})}
|
||||
<div className="flex flex-col gap-x-4 lg:flex-row xl:items-start">
|
||||
<div className="flex w-full flex-col gap-y-2">
|
||||
<div className="flex w-full flex-col gap-y-3">
|
||||
<PricingGeneralSection priceList={price_list} />
|
||||
<PricingProductSection priceList={price_list} />
|
||||
{after.widgets.map((w, i) => {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Button, ProgressStatus, ProgressTabs, toast } from "@medusajs/ui"
|
||||
import { useEffect, useMemo, useState } from "react"
|
||||
import { useForm, useWatch } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { SalesChannelDTO } from "@medusajs/types"
|
||||
import {
|
||||
RouteFocusModal,
|
||||
useRouteModal,
|
||||
@@ -35,7 +36,11 @@ const SAVE_DRAFT_BUTTON = "save-draft-button"
|
||||
|
||||
let LAST_VISITED_TAB: Tab | null = null
|
||||
|
||||
export const ProductCreateForm = () => {
|
||||
type ProductCreateFormProps = { defaultChannel?: SalesChannelDTO }
|
||||
|
||||
export const ProductCreateForm = ({
|
||||
defaultChannel,
|
||||
}: ProductCreateFormProps) => {
|
||||
const [tab, setTab] = useState<Tab>(Tab.DETAILS)
|
||||
const [tabState, setTabState] = useState<TabState>({
|
||||
[Tab.DETAILS]: "in-progress",
|
||||
@@ -48,7 +53,12 @@ export const ProductCreateForm = () => {
|
||||
const { handleSuccess } = useRouteModal()
|
||||
|
||||
const form = useForm<ProductCreateSchemaType>({
|
||||
defaultValues: PRODUCT_CREATE_FORM_DEFAULTS,
|
||||
defaultValues: {
|
||||
...PRODUCT_CREATE_FORM_DEFAULTS,
|
||||
sales_channels: defaultChannel
|
||||
? [{ id: defaultChannel.id, name: defaultChannel.name }]
|
||||
: [],
|
||||
},
|
||||
resolver: zodResolver(ProductCreateSchema),
|
||||
})
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ProductVariantDTO } from "@medusajs/types"
|
||||
import { ProductVariantDTO, RegionDTO } from "@medusajs/types"
|
||||
import { useMemo } from "react"
|
||||
import { UseFormReturn, useWatch } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
@@ -11,6 +11,7 @@ import { createDataGridHelper } from "../../../../../components/data-grid/utils"
|
||||
import { DataGridCurrencyCell } from "../../../../../components/data-grid/data-grid-cells/data-grid-currency-cell"
|
||||
import { DataGridBooleanCell } from "../../../../../components/data-grid/data-grid-cells/data-grid-boolean-cell"
|
||||
import { DataGridCountrySelectCell } from "../../../../../components/data-grid/data-grid-cells/data-grid-country-select-cell"
|
||||
import { useRegions } from "../../../../../hooks/api/regions"
|
||||
|
||||
type ProductCreateVariantsFormProps = {
|
||||
form: UseFormReturn<ProductCreateSchemaType>
|
||||
@@ -19,9 +20,10 @@ type ProductCreateVariantsFormProps = {
|
||||
export const ProductCreateVariantsForm = ({
|
||||
form,
|
||||
}: ProductCreateVariantsFormProps) => {
|
||||
const { regions } = useRegions({ limit: 9999 })
|
||||
|
||||
const { store, isPending, isError, error } = useStore({
|
||||
fields: "supported_currency_codes",
|
||||
limit: 9999,
|
||||
})
|
||||
|
||||
const variants = useWatch({
|
||||
@@ -39,6 +41,7 @@ export const ProductCreateVariantsForm = ({
|
||||
const columns = useColumns({
|
||||
options,
|
||||
currencies: store?.supported_currency_codes,
|
||||
regions,
|
||||
})
|
||||
|
||||
const variantData = useMemo(
|
||||
@@ -66,9 +69,11 @@ const columnHelper = createDataGridHelper<ProductVariantDTO>()
|
||||
const useColumns = ({
|
||||
options,
|
||||
currencies = [],
|
||||
regions = [],
|
||||
}: {
|
||||
options: any // CreateProductOptionSchemaType[]
|
||||
currencies?: string[]
|
||||
regions: RegionDTO[]
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
@@ -90,7 +95,7 @@ const useColumns = ({
|
||||
</DataGridReadOnlyCell>
|
||||
)
|
||||
},
|
||||
disableHidding: true,
|
||||
disableHiding: true,
|
||||
}),
|
||||
columnHelper.column({
|
||||
id: "title",
|
||||
@@ -118,45 +123,6 @@ const useColumns = ({
|
||||
)
|
||||
},
|
||||
}),
|
||||
columnHelper.column({
|
||||
id: "ean",
|
||||
name: t("fields.ean"),
|
||||
header: t("fields.ean"),
|
||||
cell: (context) => {
|
||||
return (
|
||||
<DataGridTextCell
|
||||
context={context}
|
||||
field={`variants.${context.row.index}.ean`}
|
||||
/>
|
||||
)
|
||||
},
|
||||
}),
|
||||
columnHelper.column({
|
||||
id: "upc",
|
||||
name: t("fields.upc"),
|
||||
header: t("fields.upc"),
|
||||
cell: (context) => {
|
||||
return (
|
||||
<DataGridTextCell
|
||||
context={context}
|
||||
field={`variants.${context.row.index}.upc`}
|
||||
/>
|
||||
)
|
||||
},
|
||||
}),
|
||||
columnHelper.column({
|
||||
id: "barcode",
|
||||
name: t("fields.barcode"),
|
||||
header: t("fields.barcode"),
|
||||
cell: (context) => {
|
||||
return (
|
||||
<DataGridTextCell
|
||||
context={context}
|
||||
field={`variants.${context.row.index}.barcode`}
|
||||
/>
|
||||
)
|
||||
},
|
||||
}),
|
||||
|
||||
columnHelper.column({
|
||||
id: "manage_inventory",
|
||||
@@ -189,7 +155,7 @@ const useColumns = ({
|
||||
|
||||
columnHelper.column({
|
||||
id: "inventory_kit",
|
||||
name: t("fields.allowBackorder"),
|
||||
name: t("fields.inventoryKit"),
|
||||
header: t("fields.inventoryKit"),
|
||||
cell: (context) => {
|
||||
return (
|
||||
@@ -220,111 +186,23 @@ const useColumns = ({
|
||||
})
|
||||
}),
|
||||
|
||||
columnHelper.column({
|
||||
id: "mid_code",
|
||||
name: t("fields.midCode"),
|
||||
header: t("fields.midCode"),
|
||||
cell: (context) => {
|
||||
return (
|
||||
<DataGridTextCell
|
||||
context={context}
|
||||
field={`variants.${context.row.index}.mid_code`}
|
||||
/>
|
||||
)
|
||||
},
|
||||
}),
|
||||
columnHelper.column({
|
||||
id: "hs_code",
|
||||
name: t("fields.hsCode"),
|
||||
header: t("fields.hsCode"),
|
||||
cell: (context) => {
|
||||
return (
|
||||
<DataGridTextCell
|
||||
context={context}
|
||||
field={`variants.${context.row.index}.hs_code`}
|
||||
/>
|
||||
)
|
||||
},
|
||||
}),
|
||||
columnHelper.column({
|
||||
id: "width",
|
||||
name: t("fields.width"),
|
||||
header: t("fields.width"),
|
||||
cell: (context) => {
|
||||
return (
|
||||
<DataGridTextCell
|
||||
context={context}
|
||||
field={`variants.${context.row.index}.width`}
|
||||
/>
|
||||
)
|
||||
},
|
||||
}),
|
||||
columnHelper.column({
|
||||
id: "length",
|
||||
name: t("fields.length"),
|
||||
header: t("fields.length"),
|
||||
cell: (context) => {
|
||||
return (
|
||||
<DataGridTextCell
|
||||
context={context}
|
||||
field={`variants.${context.row.index}.length`}
|
||||
/>
|
||||
)
|
||||
},
|
||||
}),
|
||||
columnHelper.column({
|
||||
id: "height",
|
||||
name: t("fields.height"),
|
||||
header: t("fields.height"),
|
||||
cell: (context) => {
|
||||
return (
|
||||
<DataGridTextCell
|
||||
context={context}
|
||||
field={`variants.${context.row.index}.height`}
|
||||
/>
|
||||
)
|
||||
},
|
||||
}),
|
||||
columnHelper.column({
|
||||
id: "weight",
|
||||
name: t("fields.weight"),
|
||||
header: t("fields.weight"),
|
||||
cell: (context) => {
|
||||
return (
|
||||
<DataGridTextCell
|
||||
context={context}
|
||||
field={`variants.${context.row.index}.weight`}
|
||||
/>
|
||||
)
|
||||
},
|
||||
}),
|
||||
columnHelper.column({
|
||||
id: "material",
|
||||
name: t("fields.material"),
|
||||
header: t("fields.material"),
|
||||
cell: (context) => {
|
||||
return (
|
||||
<DataGridTextCell
|
||||
context={context}
|
||||
field={`variants.${context.row.index}.material`}
|
||||
/>
|
||||
)
|
||||
},
|
||||
}),
|
||||
columnHelper.column({
|
||||
id: "origin_country",
|
||||
name: t("fields.countryOfOrigin"),
|
||||
header: t("fields.countryOfOrigin"),
|
||||
cell: (context) => {
|
||||
return (
|
||||
<DataGridCountrySelectCell
|
||||
context={context}
|
||||
field={`variants.${context.row.index}.origin_country`}
|
||||
/>
|
||||
)
|
||||
},
|
||||
...regions.map((region) => {
|
||||
return columnHelper.column({
|
||||
id: `price_${region.id}`,
|
||||
name: `Price ${region.name}`,
|
||||
header: `Price ${region.name}`,
|
||||
cell: (context) => {
|
||||
return (
|
||||
<DataGridCurrencyCell
|
||||
code={region.currency_code}
|
||||
context={context}
|
||||
field={`variants.${context.row.index}.prices.${region.id}`}
|
||||
/>
|
||||
)
|
||||
},
|
||||
})
|
||||
}),
|
||||
],
|
||||
[currencies, options, t]
|
||||
[currencies, regions, options, t]
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,25 @@
|
||||
import { RouteFocusModal } from "../../../components/route-modal"
|
||||
import { ProductCreateForm } from "./components/product-create-form/product-create-form"
|
||||
import { useStore } from "../../../hooks/api/store"
|
||||
import { useSalesChannel } from "../../../hooks/api/sales-channels"
|
||||
|
||||
export const ProductCreate = () => {
|
||||
const { store } = useStore({
|
||||
fields: "default_sales_channel",
|
||||
})
|
||||
|
||||
const { sales_channel, isPending } = useSalesChannel(
|
||||
store?.default_sales_channel_id,
|
||||
{
|
||||
enabled: !!store,
|
||||
}
|
||||
)
|
||||
|
||||
const canDisplayForm = store && !isPending
|
||||
|
||||
return (
|
||||
<RouteFocusModal>
|
||||
<ProductCreateForm />
|
||||
{canDisplayForm && <ProductCreateForm defaultChannel={sales_channel} />}
|
||||
</RouteFocusModal>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -56,10 +56,19 @@ export const normalizeVariants = (
|
||||
manage_inventory: variant.manage_inventory || undefined,
|
||||
allow_backorder: variant.allow_backorder || undefined,
|
||||
// TODO: inventory - should be added to the workflow
|
||||
prices: Object.entries(variant.prices || {}).map(([key, value]: any) => ({
|
||||
currency_code: key,
|
||||
amount: getDbAmount(castNumber(value), key),
|
||||
})),
|
||||
prices: Object.entries(variant.prices || {})
|
||||
.map(([key, value]: any) => {
|
||||
if (key.startsWith("reg_")) {
|
||||
// TODO: route needs to accept region prices as well
|
||||
return undefined
|
||||
} else {
|
||||
return {
|
||||
currency_code: key,
|
||||
amount: getDbAmount(castNumber(value), key),
|
||||
}
|
||||
}
|
||||
})
|
||||
.filter((v) => !!v),
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -70,19 +79,8 @@ export const decorateVariantsWithDefaultValues = (
|
||||
...variant,
|
||||
title: variant.title || "",
|
||||
sku: variant.sku || "",
|
||||
ean: variant.ean || "",
|
||||
upc: variant.upc || "",
|
||||
barcode: variant.barcode || "",
|
||||
manage_inventory: variant.manage_inventory || false,
|
||||
allow_backorder: variant.allow_backorder || false,
|
||||
inventory_kit: variant.inventory_kit || false,
|
||||
mid_code: variant.mid_code || "",
|
||||
hs_code: variant.hs_code || "",
|
||||
width: variant.width || "",
|
||||
height: variant.height || "",
|
||||
weight: variant.weight || "",
|
||||
length: variant.length || "",
|
||||
material: variant.material || "",
|
||||
origin_country: variant.origin_country || "",
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -48,8 +48,8 @@ export const ProductDetail = () => {
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
<div className="flex flex-col gap-x-4 xl:flex-row xl:items-start">
|
||||
<div className="flex w-full flex-col gap-y-2">
|
||||
<div className="flex flex-col gap-x-4 gap-y-3 xl:flex-row xl:items-start">
|
||||
<div className="flex w-full flex-col gap-y-3">
|
||||
<ProductGeneralSection product={product} />
|
||||
<ProductMediaSection product={product} />
|
||||
<ProductOptionSection product={product} />
|
||||
@@ -65,7 +65,7 @@ export const ProductDetail = () => {
|
||||
<JsonViewSection data={product} root="product" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-2 flex w-full max-w-[100%] flex-col gap-y-2 xl:mt-0 xl:max-w-[400px]">
|
||||
<div className="flex w-full max-w-[100%] flex-col gap-y-3 xl:mt-0 xl:max-w-[400px]">
|
||||
{sideBefore.widgets.map((w, i) => {
|
||||
return (
|
||||
<div key={i}>
|
||||
|
||||
@@ -41,7 +41,7 @@ export const PromotionDetail = () => {
|
||||
)
|
||||
})}
|
||||
<div className="flex flex-col gap-x-4 xl:flex-row xl:items-start">
|
||||
<div className="flex w-full flex-col gap-y-2">
|
||||
<div className="flex w-full flex-col gap-y-3">
|
||||
<PromotionGeneralSection promotion={promotion} />
|
||||
<PromotionConditionsSection rules={rules || []} ruleType={"rules"} />
|
||||
<PromotionConditionsSection
|
||||
|
||||
@@ -44,7 +44,7 @@ export const ReservationDetail = () => {
|
||||
)
|
||||
})}
|
||||
<div className="flex flex-col gap-x-4 xl:flex-row xl:items-start">
|
||||
<div className="flex w-full flex-col gap-y-2">
|
||||
<div className="flex w-full flex-col gap-y-3">
|
||||
<ReservationGeneralSection reservation={reservation} />
|
||||
{after.widgets.map((w, i) => {
|
||||
return (
|
||||
|
||||
@@ -29,7 +29,7 @@ export const InLayout: Story = {
|
||||
<div className="border-ui-border-base w-full max-w-[216px] border-r p-4">
|
||||
<Heading level="h3">Menubar</Heading>
|
||||
</div>
|
||||
<div className="flex w-full flex-col gap-y-2 px-8 pb-8 pt-6">
|
||||
<div className="flex w-full flex-col gap-y-3 px-8 pb-8 pt-6">
|
||||
<Container>
|
||||
<Heading>Section 1</Heading>
|
||||
</Container>
|
||||
|
||||
Reference in New Issue
Block a user