feat(dashboard,types,js-sdk,ui): Tax Regions UI (#7935)
This commit is contained in:
committed by
GitHub
parent
75e7047243
commit
046a34bdfc
@@ -1,59 +0,0 @@
|
||||
import { PropsWithChildren, createContext } from "react"
|
||||
|
||||
type ConditionOperator =
|
||||
| "eq"
|
||||
| "ne"
|
||||
| "gt"
|
||||
| "lt"
|
||||
| "gte"
|
||||
| "lte"
|
||||
| "in"
|
||||
| "nin"
|
||||
|
||||
type ConditionBlockValue<TValue> = {
|
||||
attribute: string
|
||||
operator: ConditionOperator
|
||||
value: TValue
|
||||
}
|
||||
|
||||
type ConditionBlockState<TValue> = {
|
||||
defaultValue?: ConditionBlockValue<TValue>
|
||||
value?: ConditionBlockValue<TValue>
|
||||
onChange: (value: ConditionBlockValue<TValue>) => void
|
||||
}
|
||||
|
||||
const ConditionBlockContext = createContext<ConditionBlockState<any> | null>(
|
||||
null
|
||||
)
|
||||
|
||||
const useConditionBlock = () => {
|
||||
const context = ConditionBlockContext
|
||||
|
||||
if (!context) {
|
||||
throw new Error("useConditionBlock must be used within a ConditionBlock")
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
|
||||
type ConditionBlockProps<TValue> = PropsWithChildren<
|
||||
ConditionBlockState<TValue>
|
||||
>
|
||||
|
||||
const Root = <TValue,>({ children, ...props }: ConditionBlockProps<TValue>) => {
|
||||
return (
|
||||
<ConditionBlockContext.Provider value={props}>
|
||||
{children}
|
||||
</ConditionBlockContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
const Divider = () => {}
|
||||
|
||||
const Operator = () => {}
|
||||
|
||||
const Item = () => {}
|
||||
|
||||
export const ConditionBlock = Object.assign(Root, {
|
||||
Divider,
|
||||
})
|
||||
@@ -3,6 +3,7 @@ import { PropsWithChildren } from "react"
|
||||
|
||||
type IconAvatarProps = PropsWithChildren<{
|
||||
className?: string
|
||||
size?: "small" | "large" | "xlarge"
|
||||
}>
|
||||
|
||||
/**
|
||||
@@ -10,12 +11,24 @@ type IconAvatarProps = PropsWithChildren<{
|
||||
*
|
||||
* The `<Avatar/>` component from `@medusajs/ui` does not support passing an icon as a child.
|
||||
*/
|
||||
export const IconAvatar = ({ children, className }: IconAvatarProps) => {
|
||||
export const IconAvatar = ({
|
||||
size = "small",
|
||||
children,
|
||||
className,
|
||||
}: IconAvatarProps) => {
|
||||
return (
|
||||
<div
|
||||
className={clx(
|
||||
"shadow-borders-base bg-ui-bg-base flex size-7 items-center justify-center rounded-md",
|
||||
"[&>div]:bg-ui-bg-field [&>div]:text-ui-fg-subtle [&>div]:flex [&>div]:size-6 [&>div]:items-center [&>div]:justify-center [&>div]:rounded-[4px]",
|
||||
"shadow-borders-base flex size-7 items-center justify-center",
|
||||
"[&>div]:bg-ui-bg-field [&>div]:text-ui-fg-subtle [&>div]:flex [&>div]:size-6 [&>div]:items-center [&>div]:justify-center",
|
||||
{
|
||||
"size-7 rounded-md [&>div]:size-6 [&>div]:rounded-[4px]":
|
||||
size === "small",
|
||||
"size-10 rounded-lg [&>div]:size-9 [&>div]:rounded-[6px]":
|
||||
size === "large",
|
||||
"size-12 rounded-xl [&>div]:size-11 [&>div]:rounded-[10px]":
|
||||
size === "xlarge",
|
||||
},
|
||||
className
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -136,6 +136,23 @@ export const GeneralSectionSkeleton = ({
|
||||
)
|
||||
}
|
||||
|
||||
export const TableFooterSkeleton = ({ layout }: { layout: "fill" | "fit" }) => {
|
||||
return (
|
||||
<div
|
||||
className={clx("flex items-center justify-between p-4", {
|
||||
"border-t": layout === "fill",
|
||||
})}
|
||||
>
|
||||
<Skeleton className="h-7 w-[138px]" />
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Skeleton className="h-7 w-24" />
|
||||
<Skeleton className="h-7 w-11" />
|
||||
<Skeleton className="h-7 w-11" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
type TableSkeletonProps = {
|
||||
rowCount?: number
|
||||
search?: boolean
|
||||
@@ -182,20 +199,7 @@ export const TableSkeleton = ({
|
||||
<Skeleton key={row} className="h-10 w-full rounded-none" />
|
||||
))}
|
||||
</div>
|
||||
{pagination && (
|
||||
<div
|
||||
className={clx("flex items-center justify-between p-4", {
|
||||
"border-t": layout === "fill",
|
||||
})}
|
||||
>
|
||||
<Skeleton className="h-7 w-[138px]" />
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Skeleton className="h-7 w-24" />
|
||||
<Skeleton className="h-7 w-11" />
|
||||
<Skeleton className="h-7 w-11" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{pagination && <TableFooterSkeleton layout={layout} />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { clx } from "@medusajs/ui"
|
||||
import { ComponentPropsWithoutRef, forwardRef, memo } from "react"
|
||||
import { Controller } from "react-hook-form"
|
||||
|
||||
import { countries } from "../../../lib/countries"
|
||||
import { countries } from "../../../lib/data/countries"
|
||||
import { useDataGridCell } from "../hooks"
|
||||
import { DataGridCellProps } from "../types"
|
||||
import { DataGridCellContainer } from "./data-grid-cell-container"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import CurrencyInput from "react-currency-input-field"
|
||||
import { Controller } from "react-hook-form"
|
||||
|
||||
import { currencies } from "../../../lib/currencies"
|
||||
import { currencies } from "../../../lib/data/currencies"
|
||||
import { useDataGridCell } from "../hooks"
|
||||
import { DataGridCellProps } from "../types"
|
||||
import { DataGridCellContainer } from "./data-grid-cell-container"
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
import { TrianglesMini } from "@medusajs/icons"
|
||||
import { clx } from "@medusajs/ui"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { countries } from "../../../lib/countries"
|
||||
import { countries } from "../../../lib/data/countries"
|
||||
|
||||
export const CountrySelect = forwardRef<
|
||||
HTMLSelectElement,
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { Input, Text } from "@medusajs/ui"
|
||||
import { Input, Text, clx } from "@medusajs/ui"
|
||||
import { ComponentProps, ElementRef, forwardRef } from "react"
|
||||
import Primitive from "react-currency-input-field"
|
||||
|
||||
export const PercentageInput = forwardRef<
|
||||
/**
|
||||
* @deprecated Use `PercentageInput` instead
|
||||
*/
|
||||
export const DeprecatedPercentageInput = forwardRef<
|
||||
ElementRef<typeof Input>,
|
||||
Omit<ComponentProps<typeof Input>, "type">
|
||||
>(({ min = 0, max = 100, step = 0.0001, ...props }, ref) => {
|
||||
@@ -29,4 +33,43 @@ export const PercentageInput = forwardRef<
|
||||
</div>
|
||||
)
|
||||
})
|
||||
PercentageInput.displayName = "HandleInput"
|
||||
DeprecatedPercentageInput.displayName = "PercentageInput"
|
||||
|
||||
export const PercentageInput = forwardRef<
|
||||
ElementRef<"input">,
|
||||
ComponentProps<typeof Primitive>
|
||||
>(({ min = 0, decimalScale = 2, className, ...props }, ref) => {
|
||||
return (
|
||||
<div className="relative">
|
||||
<Primitive
|
||||
ref={ref as any} // dependency is typed incorrectly
|
||||
min={min}
|
||||
autoComplete="off"
|
||||
decimalScale={decimalScale}
|
||||
decimalsLimit={decimalScale}
|
||||
{...props}
|
||||
className={clx(
|
||||
"caret-ui-fg-base bg-ui-bg-field shadow-buttons-neutral transition-fg txt-compact-small flex w-full select-none appearance-none items-center justify-between rounded-md px-2 py-1.5 pr-10 text-right outline-none",
|
||||
"placeholder:text-ui-fg-muted text-ui-fg-base",
|
||||
"hover:bg-ui-bg-field-hover",
|
||||
"focus-visible:shadow-borders-interactive-with-active data-[state=open]:!shadow-borders-interactive-with-active",
|
||||
"aria-[invalid=true]:border-ui-border-error aria-[invalid=true]:shadow-borders-error",
|
||||
"invalid::border-ui-border-error invalid:shadow-borders-error",
|
||||
"disabled:!bg-ui-bg-disabled disabled:!text-ui-fg-disabled",
|
||||
className
|
||||
)}
|
||||
/>
|
||||
<div className="absolute inset-y-0 right-0 z-10 flex w-8 items-center justify-center border-l">
|
||||
<Text
|
||||
className="text-ui-fg-muted"
|
||||
size="small"
|
||||
leading="compact"
|
||||
weight="plus"
|
||||
>
|
||||
%
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
PercentageInput.displayName = "PercentageInput"
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./province-select"
|
||||
@@ -0,0 +1,111 @@
|
||||
import {
|
||||
ComponentPropsWithoutRef,
|
||||
forwardRef,
|
||||
useImperativeHandle,
|
||||
useRef,
|
||||
} from "react"
|
||||
|
||||
import { TrianglesMini } from "@medusajs/icons"
|
||||
import { clx } from "@medusajs/ui"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { getCountryProvinceObjectByIso2 } from "../../../lib/data/country-states"
|
||||
|
||||
interface ProvinceSelectProps extends ComponentPropsWithoutRef<"select"> {
|
||||
/**
|
||||
* ISO 3166-1 alpha-2 country code
|
||||
*/
|
||||
country_code: string
|
||||
/**
|
||||
* Whether to use the ISO 3166-1 alpha-2 code or the name of the province as the value
|
||||
*
|
||||
* @default "iso_2"
|
||||
*/
|
||||
valueAs?: "iso_2" | "name"
|
||||
placeholder?: string
|
||||
}
|
||||
|
||||
export const ProvinceSelect = forwardRef<
|
||||
HTMLSelectElement,
|
||||
ProvinceSelectProps
|
||||
>(
|
||||
(
|
||||
{
|
||||
className,
|
||||
disabled,
|
||||
placeholder,
|
||||
country_code,
|
||||
valueAs = "iso_2",
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const { t } = useTranslation()
|
||||
const innerRef = useRef<HTMLSelectElement>(null)
|
||||
|
||||
useImperativeHandle(ref, () => innerRef.current as HTMLSelectElement)
|
||||
|
||||
const isPlaceholder = innerRef.current?.value === ""
|
||||
|
||||
const provinceObject = getCountryProvinceObjectByIso2(country_code)
|
||||
|
||||
if (!provinceObject) {
|
||||
disabled = true
|
||||
}
|
||||
|
||||
const options = Object.entries(provinceObject?.options ?? {}).map(
|
||||
([iso2, name]) => {
|
||||
return (
|
||||
<option key={iso2} value={valueAs === "iso_2" ? iso2 : name}>
|
||||
{name}
|
||||
</option>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
const placeholderText = provinceObject
|
||||
? t(`taxRegions.fields.sublevels.placeholders.${provinceObject.type}`)
|
||||
: ""
|
||||
|
||||
const placeholderOption = provinceObject ? (
|
||||
<option value="" disabled className="text-ui-fg-muted">
|
||||
{placeholder || placeholderText}
|
||||
</option>
|
||||
) : null
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<TrianglesMini
|
||||
className={clx(
|
||||
"text-ui-fg-muted transition-fg pointer-events-none absolute right-2 top-1/2 -translate-y-1/2",
|
||||
{
|
||||
"text-ui-fg-disabled": disabled,
|
||||
}
|
||||
)}
|
||||
/>
|
||||
<select
|
||||
disabled={disabled}
|
||||
className={clx(
|
||||
"bg-ui-bg-field shadow-buttons-neutral transition-fg txt-compact-small flex w-full select-none appearance-none items-center justify-between rounded-md px-2 py-1.5 outline-none",
|
||||
"placeholder:text-ui-fg-muted text-ui-fg-base",
|
||||
"hover:bg-ui-bg-field-hover",
|
||||
"focus-visible:shadow-borders-interactive-with-active data-[state=open]:!shadow-borders-interactive-with-active",
|
||||
"aria-[invalid=true]:border-ui-border-error aria-[invalid=true]:shadow-borders-error",
|
||||
"invalid::border-ui-border-error invalid:shadow-borders-error",
|
||||
"disabled:!bg-ui-bg-disabled disabled:!text-ui-fg-disabled",
|
||||
{
|
||||
"text-ui-fg-muted": isPlaceholder,
|
||||
},
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
ref={innerRef}
|
||||
>
|
||||
{/* Add an empty option so the first option is preselected */}
|
||||
{placeholderOption}
|
||||
{options}
|
||||
</select>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
ProvinceSelect.displayName = "CountrySelect"
|
||||
@@ -107,10 +107,7 @@ export const NavItem = ({
|
||||
<ul>
|
||||
{items.map((item) => {
|
||||
return (
|
||||
<li
|
||||
key={item.to}
|
||||
className="flex h-[32px] items-center gap-x-1 pl-2"
|
||||
>
|
||||
<li key={item.to} className="flex h-[32px] items-center pl-1">
|
||||
<div
|
||||
role="presentation"
|
||||
className="flex h-full w-5 items-center justify-center"
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./single-column-page"
|
||||
export * from "./two-column-page"
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./single-column-page"
|
||||
@@ -0,0 +1,38 @@
|
||||
import { Outlet } from "react-router-dom"
|
||||
import { JsonViewSection } from "../../../common/json-view-section"
|
||||
import { PageProps } from "../types"
|
||||
|
||||
export const SingleColumnPage = <TData,>({
|
||||
children,
|
||||
widgets,
|
||||
data,
|
||||
hasOutlet,
|
||||
showJSON,
|
||||
}: PageProps<TData>) => {
|
||||
const { before, after } = widgets
|
||||
const widgetProps = { data }
|
||||
|
||||
if (showJSON && !data) {
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
console.warn(
|
||||
"`showJSON` is true but no data is provided. To display JSON, provide data prop."
|
||||
)
|
||||
}
|
||||
|
||||
showJSON = false
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-y-3">
|
||||
{before.widgets.map((w, i) => {
|
||||
return <w.Component {...widgetProps} key={i} />
|
||||
})}
|
||||
{children}
|
||||
{after.widgets.map((w, i) => {
|
||||
return <w.Component {...widgetProps} key={i} />
|
||||
})}
|
||||
{showJSON && <JsonViewSection data={data!} />}
|
||||
{hasOutlet && <Outlet />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./two-column-page"
|
||||
@@ -0,0 +1,123 @@
|
||||
import { clx } from "@medusajs/ui"
|
||||
import { Children, ComponentPropsWithoutRef } from "react"
|
||||
import { Outlet } from "react-router-dom"
|
||||
import { JsonViewSection } from "../../../common/json-view-section"
|
||||
import { PageProps, WidgetImport, WidgetProps } from "../types"
|
||||
|
||||
interface TwoColumnWidgetProps extends WidgetProps {
|
||||
sideBefore: WidgetImport
|
||||
sideAfter: WidgetImport
|
||||
}
|
||||
|
||||
interface TwoColumnPageProps<TData> extends PageProps<TData> {
|
||||
widgets: TwoColumnWidgetProps
|
||||
}
|
||||
|
||||
const Root = <TData,>({
|
||||
children,
|
||||
/**
|
||||
* Widgets to be rendered in the main content area and sidebar.
|
||||
*/
|
||||
widgets,
|
||||
/**
|
||||
* Data to be passed to widgets and the JSON view.
|
||||
*/
|
||||
data,
|
||||
/**
|
||||
* Whether to show JSON view of the data. Defaults to true.
|
||||
*/
|
||||
showJSON = false,
|
||||
/**
|
||||
* Whether to render an outlet for children routes. Defaults to true.
|
||||
*/
|
||||
hasOutlet = true,
|
||||
}: TwoColumnPageProps<TData>) => {
|
||||
const widgetProps = { data }
|
||||
const { before, after, sideBefore, sideAfter } = widgets
|
||||
|
||||
if (showJSON && !data) {
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
console.warn(
|
||||
"`showJSON` is true but no data is provided. To display JSON, provide data prop."
|
||||
)
|
||||
}
|
||||
|
||||
showJSON = false
|
||||
}
|
||||
|
||||
const childrenArray = Children.toArray(children)
|
||||
|
||||
if (childrenArray.length !== 2) {
|
||||
throw new Error("TwoColumnPage expects exactly two children")
|
||||
}
|
||||
|
||||
const [main, sidebar] = childrenArray
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-y-2">
|
||||
{before.widgets.map((w, i) => {
|
||||
return <w.Component {...widgetProps} key={i} />
|
||||
})}
|
||||
<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">
|
||||
{main}
|
||||
{after.widgets.map((w, i) => {
|
||||
return <w.Component {...widgetProps} key={i} />
|
||||
})}
|
||||
{showJSON && (
|
||||
<div className="hidden xl:block">
|
||||
<JsonViewSection data={data!} root="product" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex w-full max-w-[100%] flex-col gap-y-3 xl:mt-0 xl:max-w-[440px]">
|
||||
{sideBefore.widgets.map((w, i) => {
|
||||
return <w.Component {...widgetProps} key={i} />
|
||||
})}
|
||||
{sidebar}
|
||||
{sideAfter.widgets.map((w, i) => {
|
||||
return <w.Component {...widgetProps} key={i} />
|
||||
})}
|
||||
{showJSON && (
|
||||
<div className="xl:hidden">
|
||||
<JsonViewSection data={data!} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{hasOutlet && <Outlet />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const Main = ({
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
}: ComponentPropsWithoutRef<"div">) => {
|
||||
return (
|
||||
<div className={clx("flex w-full flex-col gap-y-3", className)} {...props}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const Sidebar = ({
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
}: ComponentPropsWithoutRef<"div">) => {
|
||||
return (
|
||||
<div
|
||||
className={clx(
|
||||
"flex w-full max-w-[100%] flex-col gap-y-3 xl:mt-0 xl:max-w-[400px]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const TwoColumnPage = Object.assign(Root, { Main, Sidebar })
|
||||
@@ -0,0 +1,22 @@
|
||||
import { ReactNode } from "react"
|
||||
|
||||
export type Widget = {
|
||||
Component: React.ComponentType<any>
|
||||
}
|
||||
|
||||
export type WidgetImport = {
|
||||
widgets: Widget[]
|
||||
}
|
||||
|
||||
export interface WidgetProps {
|
||||
before: WidgetImport
|
||||
after: WidgetImport
|
||||
}
|
||||
|
||||
export interface PageProps<TData> {
|
||||
children: ReactNode
|
||||
widgets: WidgetProps
|
||||
data?: TData
|
||||
showJSON?: boolean
|
||||
hasOutlet?: boolean
|
||||
}
|
||||
@@ -42,8 +42,8 @@ const useSettingRoutes = (): NavItemProps[] => {
|
||||
to: "/settings/regions",
|
||||
},
|
||||
{
|
||||
label: "Taxes",
|
||||
to: "/settings/taxes",
|
||||
label: t("taxRegions.domain"),
|
||||
to: "/settings/tax-regions",
|
||||
},
|
||||
{
|
||||
label: t("salesChannels.domain"),
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
Keyboard,
|
||||
MagnifyingGlass,
|
||||
SidebarLeft,
|
||||
TriangleRightMini,
|
||||
User as UserIcon,
|
||||
} from "@medusajs/icons"
|
||||
import {
|
||||
@@ -92,18 +93,17 @@ const Breadcrumbs = () => {
|
||||
})
|
||||
|
||||
return (
|
||||
<ol className={clx("text-ui-fg-muted flex select-none items-center")}>
|
||||
<ol
|
||||
className={clx(
|
||||
"text-ui-fg-muted txt-compact-small-plus flex select-none items-center"
|
||||
)}
|
||||
>
|
||||
{crumbs.map((crumb, index) => {
|
||||
const isLast = index === crumbs.length - 1
|
||||
const isSingle = crumbs.length === 1
|
||||
|
||||
return (
|
||||
<li
|
||||
key={index}
|
||||
className={clx("txt-compact-small-plus flex items-center", {
|
||||
"text-ui-fg-subtle": isLast,
|
||||
})}
|
||||
>
|
||||
<li key={index} className={clx("flex items-center")}>
|
||||
{!isLast ? (
|
||||
<Link
|
||||
className="transition-fg hover:text-ui-fg-subtle"
|
||||
@@ -124,7 +124,11 @@ const Breadcrumbs = () => {
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{!isLast && <span className="mx-2 -mt-0.5">›</span>}
|
||||
{!isLast && (
|
||||
<span className="mx-2">
|
||||
<TriangleRightMini />
|
||||
</span>
|
||||
)}
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
|
||||
@@ -66,7 +66,7 @@ const Content = forwardRef<
|
||||
return (
|
||||
<FocusModal.Content
|
||||
ref={ref}
|
||||
className={clx("top-6", className)}
|
||||
className={clx("!top-6", className)}
|
||||
overlayProps={{
|
||||
className: "bg-transparent",
|
||||
}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ArrowUpDown } from "@medusajs/icons"
|
||||
import { DescendingSorting } from "@medusajs/icons"
|
||||
import { DropdownMenu, IconButton } from "@medusajs/ui"
|
||||
import { useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
@@ -107,7 +107,7 @@ export const DataTableOrderBy = <TData,>({
|
||||
<DropdownMenu>
|
||||
<DropdownMenu.Trigger asChild>
|
||||
<IconButton size="small">
|
||||
<ArrowUpDown />
|
||||
<DescendingSorting />
|
||||
</IconButton>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content className="z-[1]" align="end">
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Tooltip } from "@medusajs/ui"
|
||||
import format from "date-fns/format"
|
||||
import { format } from "date-fns/format"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { PlaceholderCell } from "../placeholder-cell"
|
||||
|
||||
type DateCellProps = {
|
||||
date: Date | string | null
|
||||
date?: Date | string | null
|
||||
}
|
||||
|
||||
export const DateCell = ({ date }: DateCellProps) => {
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import type { Discount } from "@medusajs/medusa"
|
||||
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
type DiscountCellProps = {
|
||||
discount: Discount
|
||||
}
|
||||
|
||||
export const CodeCell = ({ discount }: DiscountCellProps) => {
|
||||
return (
|
||||
<div className="flex h-full w-full items-center gap-x-3 overflow-hidden">
|
||||
{/* // TODO: border color inversion*/}
|
||||
<span className="bg-ui-tag-neutral-bg truncate rounded-md border border-neutral-200 p-1 text-xs">
|
||||
{discount.code}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const CodeHeader = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className=" flex h-full w-full items-center ">
|
||||
<span>{t("fields.code")}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./code-cell"
|
||||
@@ -1,25 +0,0 @@
|
||||
import type { Discount } from "@medusajs/medusa"
|
||||
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
type DiscountCellProps = {
|
||||
discount: Discount
|
||||
}
|
||||
|
||||
export const DescriptionCell = ({ discount }: DiscountCellProps) => {
|
||||
return (
|
||||
<div className="flex h-full w-full items-center gap-x-3 overflow-hidden">
|
||||
<span className="truncate">{discount.rule.description}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const DescriptionHeader = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className=" flex h-full w-full items-center ">
|
||||
<span>{t("fields.description")}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./description-cell"
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./redemption-cell.tsx"
|
||||
@@ -1,23 +0,0 @@
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
type DiscountCellProps = {
|
||||
redemptions: number
|
||||
}
|
||||
|
||||
export const RedemptionCell = ({ redemptions }: DiscountCellProps) => {
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-end gap-x-3 text-right">
|
||||
<span>{redemptions}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const RedemptionHeader = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-end text-right">
|
||||
<span className="truncate">{t("fields.totalRedemptions")}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./status-cell"
|
||||
@@ -1,45 +0,0 @@
|
||||
import { Discount } from "@medusajs/medusa"
|
||||
|
||||
import { StatusCell as StatusCell_ } from "../../common/status-cell"
|
||||
|
||||
import { useTranslation } from "react-i18next"
|
||||
import {
|
||||
getDiscountStatus,
|
||||
PromotionStatus,
|
||||
} from "../../../../../lib/discounts.ts"
|
||||
|
||||
type DiscountCellProps = {
|
||||
discount: Discount
|
||||
}
|
||||
|
||||
export const StatusCell = ({ discount }: DiscountCellProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const [color, text] = {
|
||||
[PromotionStatus.DISABLED]: [
|
||||
"grey",
|
||||
t("discounts.discountStatus.disabled"),
|
||||
],
|
||||
[PromotionStatus.ACTIVE]: ["green", t("discounts.discountStatus.active")],
|
||||
[PromotionStatus.SCHEDULED]: [
|
||||
"orange",
|
||||
t("discounts.discountStatus.scheduled"),
|
||||
],
|
||||
[PromotionStatus.EXPIRED]: ["red", t("discounts.discountStatus.expired")],
|
||||
}[getDiscountStatus(discount)] as [
|
||||
"grey" | "orange" | "green" | "red",
|
||||
string
|
||||
]
|
||||
|
||||
return <StatusCell_ color={color}>{text}</StatusCell_>
|
||||
}
|
||||
|
||||
export const StatusHeader = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className=" flex h-full w-full items-center ">
|
||||
<span>{t("fields.status")}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./value-cell.tsx"
|
||||
@@ -1,35 +0,0 @@
|
||||
import { DiscountRule } from "@medusajs/medusa"
|
||||
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { MoneyAmountCell } from "../../common/money-amount-cell"
|
||||
|
||||
type DiscountCellProps = {
|
||||
rule: DiscountRule
|
||||
currencyCode: string
|
||||
}
|
||||
|
||||
export const ValueCell = ({ currencyCode, rule }: DiscountCellProps) => {
|
||||
const isFixed = rule.type === "fixed"
|
||||
const isPercentage = rule.type === "percentage"
|
||||
const isFreeShipping = rule.type === "free_shipping"
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full items-center gap-x-3 overflow-hidden">
|
||||
{isFreeShipping && <span>Free shipping</span>}
|
||||
{isPercentage && <span className="">{rule.value}%</span>}
|
||||
{isFixed && (
|
||||
<MoneyAmountCell currencyCode={currencyCode} amount={rule.value} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const ValueHeader = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className=" flex h-full w-full items-center ">
|
||||
<span>{t("fields.value")}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { RegionCountryDTO } from "@medusajs/types"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import { PlaceholderCell } from "../../common/placeholder-cell"
|
||||
import { countries as COUNTRIES } from "../../../../../lib/data/countries"
|
||||
import { ListSummary } from "../../../../common/list-summary"
|
||||
import { countries as COUNTRIES } from "../../../../../lib/countries"
|
||||
import { PlaceholderCell } from "../../common/placeholder-cell"
|
||||
|
||||
type CountriesCellProps = {
|
||||
countries?: RegionCountryDTO[] | null
|
||||
|
||||
@@ -21,3 +21,5 @@ export const I18n = () => {
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export { i18n }
|
||||
|
||||
Reference in New Issue
Block a user