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:
Frane Polić
2024-06-07 10:59:19 +02:00
committed by GitHub
parent 1f1b996f63
commit 987141ab2d
18 changed files with 92 additions and 185 deletions

View File

@@ -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,

View File

@@ -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.",

View File

@@ -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) => {

View File

@@ -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) => {

View File

@@ -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} />

View File

@@ -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 (

View File

@@ -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} />

View File

@@ -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}>

View File

@@ -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}>

View File

@@ -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) => {

View File

@@ -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),
})

View File

@@ -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]
)
}

View File

@@ -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>
)
}

View File

@@ -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 || "",
}))
}

View File

@@ -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}>

View File

@@ -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

View File

@@ -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 (

View File

@@ -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>