feat(dashboard,types,js-sdk,ui): Tax Regions UI (#7935)
This commit is contained in:
-59
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
+1
-1
@@ -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
-1
@@ -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"
|
||||
|
||||
+1
-1
@@ -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,
|
||||
|
||||
+46
-3
@@ -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"
|
||||
+111
@@ -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"
|
||||
+38
@@ -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"
|
||||
+123
@@ -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
|
||||
}
|
||||
+2
-2
@@ -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>
|
||||
)
|
||||
})}
|
||||
|
||||
+1
-1
@@ -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",
|
||||
}}
|
||||
|
||||
+2
-2
@@ -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">
|
||||
|
||||
+2
-2
@@ -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) => {
|
||||
|
||||
-28
@@ -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
@@ -1 +0,0 @@
|
||||
export * from "./code-cell"
|
||||
-25
@@ -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
@@ -1 +0,0 @@
|
||||
export * from "./description-cell"
|
||||
-1
@@ -1 +0,0 @@
|
||||
export * from "./redemption-cell.tsx"
|
||||
-23
@@ -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
@@ -1 +0,0 @@
|
||||
export * from "./status-cell"
|
||||
-45
@@ -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
@@ -1 +0,0 @@
|
||||
export * from "./value-cell.tsx"
|
||||
-35
@@ -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>
|
||||
)
|
||||
}
|
||||
+3
-3
@@ -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 }
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { FetchError } from "@medusajs/js-sdk"
|
||||
import { HttpTypes, PaginatedResponse } from "@medusajs/types"
|
||||
import {
|
||||
QueryKey,
|
||||
@@ -25,7 +26,7 @@ export const useCustomerGroup = (
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
{ customer_group: HttpTypes.AdminCustomerGroup },
|
||||
Error,
|
||||
FetchError,
|
||||
{ customer_group: HttpTypes.AdminCustomerGroup },
|
||||
QueryKey
|
||||
>,
|
||||
@@ -46,7 +47,7 @@ export const useCustomerGroups = (
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
PaginatedResponse<{ customer_groups: HttpTypes.AdminCustomerGroup[] }>,
|
||||
Error,
|
||||
FetchError,
|
||||
PaginatedResponse<{ customer_groups: HttpTypes.AdminCustomerGroup[] }>,
|
||||
QueryKey
|
||||
>,
|
||||
@@ -65,7 +66,7 @@ export const useCustomerGroups = (
|
||||
export const useCreateCustomerGroup = (
|
||||
options?: UseMutationOptions<
|
||||
{ customer_group: HttpTypes.AdminCustomerGroup },
|
||||
Error,
|
||||
FetchError,
|
||||
z.infer<typeof CreateCustomerGroupSchema>
|
||||
>
|
||||
) => {
|
||||
@@ -85,7 +86,7 @@ export const useUpdateCustomerGroup = (
|
||||
id: string,
|
||||
options?: UseMutationOptions<
|
||||
{ customer_group: HttpTypes.AdminCustomerGroup },
|
||||
Error,
|
||||
FetchError,
|
||||
z.infer<typeof EditCustomerGroupSchema>
|
||||
>
|
||||
) => {
|
||||
@@ -109,7 +110,7 @@ export const useDeleteCustomerGroup = (
|
||||
id: string,
|
||||
options?: UseMutationOptions<
|
||||
{ id: string; object: "customer-group"; deleted: boolean },
|
||||
Error,
|
||||
FetchError,
|
||||
void
|
||||
>
|
||||
) => {
|
||||
@@ -133,7 +134,7 @@ export const useAddCustomersToGroup = (
|
||||
id: string,
|
||||
options?: UseMutationOptions<
|
||||
{ customer_group: HttpTypes.AdminCustomerGroup },
|
||||
Error,
|
||||
FetchError,
|
||||
{ customer_ids: string[] }
|
||||
>
|
||||
) => {
|
||||
@@ -160,7 +161,7 @@ export const useRemoveCustomersFromGroup = (
|
||||
id: string,
|
||||
options?: UseMutationOptions<
|
||||
{ customer_group: HttpTypes.AdminCustomerGroup },
|
||||
Error,
|
||||
FetchError,
|
||||
{ customer_ids: string[] }
|
||||
>
|
||||
) => {
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
export * from "./api-keys"
|
||||
export * from "./auth"
|
||||
export * from "./campaigns"
|
||||
export * from "./categories"
|
||||
export * from "./collections"
|
||||
export * from "./currencies"
|
||||
export * from "./customer-groups"
|
||||
export * from "./customers"
|
||||
export * from "./fulfillment"
|
||||
export * from "./fulfillment-providers"
|
||||
export * from "./fulfillment-sets"
|
||||
export * from "./inventory"
|
||||
export * from "./invites"
|
||||
export * from "./orders"
|
||||
export * from "./payments"
|
||||
export * from "./price-lists"
|
||||
export * from "./product-types"
|
||||
export * from "./products"
|
||||
export * from "./promotions"
|
||||
export * from "./regions"
|
||||
export * from "./reservations"
|
||||
export * from "./sales-channels"
|
||||
export * from "./shipping-options"
|
||||
export * from "./shipping-profiles"
|
||||
export * from "./stock-locations"
|
||||
export * from "./store"
|
||||
export * from "./tags"
|
||||
export * from "./tax-rates"
|
||||
export * from "./tax-regions"
|
||||
export * from "./users"
|
||||
export * from "./workflow-executions"
|
||||
@@ -1,7 +1,8 @@
|
||||
import { FetchError } from "@medusajs/js-sdk"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { QueryKey, UseQueryOptions, useQuery } from "@tanstack/react-query"
|
||||
import { client } from "../../lib/client"
|
||||
import { sdk } from "../../lib/client"
|
||||
import { queryKeysFactory } from "../../lib/query-key-factory"
|
||||
import { TagsListRes, TagRes } from "../../types/api-responses"
|
||||
|
||||
const TAGS_QUERY_KEY = "tags" as const
|
||||
export const tagsQueryKeys = queryKeysFactory(TAGS_QUERY_KEY)
|
||||
@@ -9,13 +10,18 @@ export const tagsQueryKeys = queryKeysFactory(TAGS_QUERY_KEY)
|
||||
export const useTag = (
|
||||
id: string,
|
||||
options?: Omit<
|
||||
UseQueryOptions<TagRes, Error, TagRes, QueryKey>,
|
||||
UseQueryOptions<
|
||||
HttpTypes.AdminProductTagResponse,
|
||||
FetchError,
|
||||
HttpTypes.AdminProductTagResponse,
|
||||
QueryKey
|
||||
>,
|
||||
"queryFn" | "queryKey"
|
||||
>
|
||||
) => {
|
||||
const { data, ...rest } = useQuery({
|
||||
queryKey: tagsQueryKeys.detail(id),
|
||||
queryFn: async () => client.tags.retrieve(id),
|
||||
queryFn: async () => sdk.admin.productTag.retrieve(id),
|
||||
...options,
|
||||
})
|
||||
|
||||
@@ -23,15 +29,20 @@ export const useTag = (
|
||||
}
|
||||
|
||||
export const useTags = (
|
||||
query?: Record<string, any>,
|
||||
query?: HttpTypes.AdminProductTagListParams,
|
||||
options?: Omit<
|
||||
UseQueryOptions<TagsListRes, Error, TagsListRes, QueryKey>,
|
||||
UseQueryOptions<
|
||||
HttpTypes.AdminProductTagListResponse,
|
||||
FetchError,
|
||||
HttpTypes.AdminProductTagListResponse,
|
||||
QueryKey
|
||||
>,
|
||||
"queryFn" | "queryKey"
|
||||
>
|
||||
) => {
|
||||
const { data, ...rest } = useQuery({
|
||||
queryKey: tagsQueryKeys.list(query),
|
||||
queryFn: async () => client.tags.list(query),
|
||||
queryFn: async () => sdk.admin.productTag.list(query),
|
||||
...options,
|
||||
})
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { FetchError } from "@medusajs/js-sdk"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import {
|
||||
QueryKey,
|
||||
@@ -9,6 +10,7 @@ import {
|
||||
import { sdk } from "../../lib/client"
|
||||
import { queryClient } from "../../lib/query-client"
|
||||
import { queryKeysFactory } from "../../lib/query-key-factory"
|
||||
import { taxRegionsQueryKeys } from "./tax-regions"
|
||||
|
||||
const TAX_RATES_QUERY_KEY = "tax_rates" as const
|
||||
export const taxRatesQueryKeys = queryKeysFactory(TAX_RATES_QUERY_KEY)
|
||||
@@ -19,7 +21,7 @@ export const useTaxRate = (
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
HttpTypes.AdminTaxRateResponse,
|
||||
Error,
|
||||
FetchError,
|
||||
HttpTypes.AdminTaxRateResponse,
|
||||
QueryKey
|
||||
>,
|
||||
@@ -72,6 +74,8 @@ export const useUpdateTaxRate = (
|
||||
queryKey: taxRatesQueryKeys.detail(id),
|
||||
})
|
||||
|
||||
queryClient.invalidateQueries({ queryKey: taxRegionsQueryKeys.details() })
|
||||
|
||||
options?.onSuccess?.(data, variables, context)
|
||||
},
|
||||
...options,
|
||||
@@ -81,7 +85,7 @@ export const useUpdateTaxRate = (
|
||||
export const useCreateTaxRate = (
|
||||
options?: UseMutationOptions<
|
||||
HttpTypes.AdminTaxRateResponse,
|
||||
Error,
|
||||
FetchError,
|
||||
HttpTypes.AdminCreateTaxRate
|
||||
>
|
||||
) => {
|
||||
@@ -89,6 +93,9 @@ export const useCreateTaxRate = (
|
||||
mutationFn: (payload) => sdk.admin.taxRate.create(payload),
|
||||
onSuccess: (data, variables, context) => {
|
||||
queryClient.invalidateQueries({ queryKey: taxRatesQueryKeys.lists() })
|
||||
|
||||
queryClient.invalidateQueries({ queryKey: taxRegionsQueryKeys.details() })
|
||||
|
||||
options?.onSuccess?.(data, variables, context)
|
||||
},
|
||||
...options,
|
||||
@@ -111,6 +118,8 @@ export const useDeleteTaxRate = (
|
||||
queryKey: taxRatesQueryKeys.detail(id),
|
||||
})
|
||||
|
||||
queryClient.invalidateQueries({ queryKey: taxRegionsQueryKeys.details() })
|
||||
|
||||
options?.onSuccess?.(data, variables, context)
|
||||
},
|
||||
...options,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { FetchError } from "@medusajs/js-sdk"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import {
|
||||
QueryKey,
|
||||
@@ -15,11 +16,11 @@ export const taxRegionsQueryKeys = queryKeysFactory(TAX_REGIONS_QUERY_KEY)
|
||||
|
||||
export const useTaxRegion = (
|
||||
id: string,
|
||||
query?: Record<string, any>,
|
||||
query?: HttpTypes.AdminTaxRegionParams,
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
HttpTypes.AdminTaxRegionResponse,
|
||||
Error,
|
||||
FetchError,
|
||||
HttpTypes.AdminTaxRegionResponse,
|
||||
QueryKey
|
||||
>,
|
||||
@@ -36,11 +37,11 @@ export const useTaxRegion = (
|
||||
}
|
||||
|
||||
export const useTaxRegions = (
|
||||
query?: Record<string, any>,
|
||||
query?: HttpTypes.AdminTaxRegionListParams,
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
HttpTypes.AdminTaxRegionListResponse,
|
||||
Error,
|
||||
FetchError,
|
||||
HttpTypes.AdminTaxRegionListResponse,
|
||||
QueryKey
|
||||
>,
|
||||
@@ -59,7 +60,7 @@ export const useTaxRegions = (
|
||||
export const useCreateTaxRegion = (
|
||||
options?: UseMutationOptions<
|
||||
HttpTypes.AdminTaxRegionResponse,
|
||||
Error,
|
||||
FetchError,
|
||||
HttpTypes.AdminCreateTaxRegion
|
||||
>
|
||||
) => {
|
||||
@@ -77,7 +78,7 @@ export const useDeleteTaxRegion = (
|
||||
id: string,
|
||||
options?: UseMutationOptions<
|
||||
HttpTypes.AdminTaxRegionDeleteResponse,
|
||||
Error,
|
||||
FetchError,
|
||||
void
|
||||
>
|
||||
) => {
|
||||
@@ -89,6 +90,9 @@ export const useDeleteTaxRegion = (
|
||||
queryKey: taxRegionsQueryKeys.detail(id),
|
||||
})
|
||||
|
||||
// Invalidate all detail queries, as the deleted tax region may have been a sublevel region
|
||||
queryClient.invalidateQueries({ queryKey: taxRegionsQueryKeys.details() })
|
||||
|
||||
options?.onSuccess?.(data, variables, context)
|
||||
},
|
||||
...options,
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
export * from "./use-campaign-table-columns"
|
||||
export * from "./use-collection-table-columns"
|
||||
export * from "./use-customer-group-table-columns"
|
||||
export * from "./use-customer-table-columns"
|
||||
export * from "./use-order-table-columns"
|
||||
export * from "./use-product-table-columns"
|
||||
export * from "./use-product-tag-table-columns"
|
||||
export * from "./use-product-type-table-columns"
|
||||
export * from "./use-region-table-columns"
|
||||
export * from "./use-sales-channel-table-columns"
|
||||
export * from "./use-shipping-option-table-columns"
|
||||
export * from "./use-tax-rates-table-columns"
|
||||
+5
-9
@@ -2,8 +2,7 @@ import { HttpTypes } from "@medusajs/types"
|
||||
import { createColumnHelper } from "@tanstack/react-table"
|
||||
import { useMemo } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import { CollectionRowActions } from "./collection-row-actions"
|
||||
import { TextCell } from "../../../components/table/table-cells/common/text-cell"
|
||||
|
||||
const columnHelper = createColumnHelper<HttpTypes.AdminCollection>()
|
||||
|
||||
@@ -14,23 +13,20 @@ export const useCollectionTableColumns = () => {
|
||||
() => [
|
||||
columnHelper.accessor("title", {
|
||||
header: t("fields.title"),
|
||||
cell: ({ getValue }) => <TextCell text={getValue()} />,
|
||||
}),
|
||||
columnHelper.accessor("handle", {
|
||||
header: t("fields.handle"),
|
||||
cell: ({ getValue }) => `/${getValue()}`,
|
||||
cell: ({ getValue }) => <TextCell text={`/${getValue()}`} />,
|
||||
}),
|
||||
columnHelper.accessor("products", {
|
||||
header: t("fields.products"),
|
||||
cell: ({ getValue }) => {
|
||||
const count = getValue()?.length
|
||||
const count = getValue()?.length || undefined
|
||||
|
||||
return <span>{count || "-"}</span>
|
||||
return <TextCell text={count} />
|
||||
},
|
||||
}),
|
||||
columnHelper.display({
|
||||
id: "actions",
|
||||
cell: ({ row }) => <CollectionRowActions collection={row.original} />,
|
||||
}),
|
||||
],
|
||||
[t]
|
||||
)
|
||||
@@ -1,65 +0,0 @@
|
||||
import { useMemo } from "react"
|
||||
import { ColumnDef, createColumnHelper } from "@tanstack/react-table"
|
||||
|
||||
import type { Discount } from "@medusajs/medusa"
|
||||
|
||||
import {
|
||||
CodeCell,
|
||||
CodeHeader,
|
||||
} from "../../../components/table/table-cells/discount/code-cell"
|
||||
import {
|
||||
DescriptionHeader,
|
||||
DescriptionCell,
|
||||
} from "../../../components/table/table-cells/discount/description-cell"
|
||||
import {
|
||||
ValueCell,
|
||||
ValueHeader,
|
||||
} from "../../../components/table/table-cells/discount/value-cell"
|
||||
import {
|
||||
RedemptionCell,
|
||||
RedemptionHeader,
|
||||
} from "../../../components/table/table-cells/discount/redemption-cell"
|
||||
import {
|
||||
StatusHeader,
|
||||
StatusCell,
|
||||
} from "../../../components/table/table-cells/discount/status-cell"
|
||||
|
||||
const columnHelper = createColumnHelper<Discount>()
|
||||
|
||||
export const useDiscountTableColumns = () => {
|
||||
return useMemo(
|
||||
() => [
|
||||
columnHelper.display({
|
||||
id: "discount",
|
||||
header: () => <CodeHeader />,
|
||||
cell: ({ row }) => <CodeCell discount={row.original} />,
|
||||
}),
|
||||
columnHelper.accessor("rule.description", {
|
||||
header: () => <DescriptionHeader />,
|
||||
cell: ({ row }) => <DescriptionCell discount={row.original} />,
|
||||
}),
|
||||
|
||||
columnHelper.accessor("rule.value", {
|
||||
header: () => <ValueHeader />,
|
||||
cell: ({ row }) => (
|
||||
<ValueCell
|
||||
rule={row.original.rule}
|
||||
currencyCode={row.original.regions[0]?.currency_code}
|
||||
/>
|
||||
),
|
||||
}),
|
||||
columnHelper.display({
|
||||
id: "status",
|
||||
header: () => <StatusHeader />,
|
||||
cell: ({ row }) => <StatusCell discount={row.original} />,
|
||||
}),
|
||||
columnHelper.accessor("usage_count", {
|
||||
header: () => <RedemptionHeader />,
|
||||
cell: ({ row }) => (
|
||||
<RedemptionCell redemptions={row.original.usage_count} />
|
||||
),
|
||||
}),
|
||||
],
|
||||
[]
|
||||
) as ColumnDef<Discount>[]
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { createColumnHelper } from "@tanstack/react-table"
|
||||
import { useMemo } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { DateCell } from "../../../components/table/table-cells/common/date-cell"
|
||||
import { TextCell } from "../../../components/table/table-cells/common/text-cell"
|
||||
|
||||
const columnHelper = createColumnHelper<HttpTypes.AdminProductTag>()
|
||||
|
||||
export const useProductTagTableColumns = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return useMemo(
|
||||
() => [
|
||||
columnHelper.accessor("value", {
|
||||
header: () => t("fields.value"),
|
||||
cell: ({ getValue }) => <TextCell text={getValue()} />,
|
||||
}),
|
||||
columnHelper.accessor("created_at", {
|
||||
header: () => t("fields.createdAt"),
|
||||
|
||||
cell: ({ getValue }) => {
|
||||
return <DateCell date={getValue()} />
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor("updated_at", {
|
||||
header: () => t("fields.updatedAt"),
|
||||
cell: ({ getValue }) => {
|
||||
return <DateCell date={getValue()} />
|
||||
},
|
||||
}),
|
||||
],
|
||||
[t]
|
||||
)
|
||||
}
|
||||
+7
-14
@@ -2,8 +2,9 @@ import { HttpTypes } from "@medusajs/types"
|
||||
import { createColumnHelper } from "@tanstack/react-table"
|
||||
import { useMemo } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { ProductTypeRowActions } from "./product-table-row-actions"
|
||||
import { DateCell } from "../../../../../components/table/table-cells/common/date-cell"
|
||||
|
||||
import { DateCell } from "../../../components/table/table-cells/common/date-cell"
|
||||
import { TextCell } from "../../../components/table/table-cells/common/text-cell"
|
||||
|
||||
const columnHelper = createColumnHelper<HttpTypes.AdminProductType>()
|
||||
|
||||
@@ -13,28 +14,20 @@ export const useProductTypeTableColumns = () => {
|
||||
return useMemo(
|
||||
() => [
|
||||
columnHelper.accessor("value", {
|
||||
header: () => t("productTypes.fields.value"),
|
||||
cell: ({ getValue }) => getValue(),
|
||||
header: () => t("fields.value"),
|
||||
cell: ({ getValue }) => <TextCell text={getValue()} />,
|
||||
}),
|
||||
columnHelper.accessor("created_at", {
|
||||
header: () => t("fields.createdAt"),
|
||||
|
||||
cell: ({ getValue }) => {
|
||||
const date = new Date(getValue())
|
||||
return <DateCell date={date} />
|
||||
return <DateCell date={getValue()} />
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor("updated_at", {
|
||||
header: () => t("fields.updatedAt"),
|
||||
cell: ({ getValue }) => {
|
||||
const date = new Date(getValue())
|
||||
return <DateCell date={date} />
|
||||
},
|
||||
}),
|
||||
columnHelper.display({
|
||||
id: "actions",
|
||||
cell: ({ row }) => {
|
||||
return <ProductTypeRowActions productType={row.original} />
|
||||
return <DateCell date={getValue()} />
|
||||
},
|
||||
}),
|
||||
],
|
||||
@@ -0,0 +1,13 @@
|
||||
export * from "./use-collection-table-filters"
|
||||
export * from "./use-customer-group-table-filters"
|
||||
export * from "./use-customer-table-filters"
|
||||
export * from "./use-date-table-filters"
|
||||
export * from "./use-order-table-filters"
|
||||
export * from "./use-product-table-filters"
|
||||
export * from "./use-product-tag-table-filters"
|
||||
export * from "./use-product-type-table-filters"
|
||||
export * from "./use-promotion-table-filters"
|
||||
export * from "./use-region-table-filters"
|
||||
export * from "./use-sales-channel-table-filters"
|
||||
export * from "./use-shipping-option-table-filters"
|
||||
export * from "./use-tax-rate-table-filters"
|
||||
@@ -0,0 +1,7 @@
|
||||
import { useDateTableFilters } from "./use-date-table-filters"
|
||||
|
||||
export const useCollectionTableFilters = () => {
|
||||
const dateFilters = useDateTableFilters()
|
||||
|
||||
return dateFilters
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Filter } from "../../../components/table/data-table"
|
||||
|
||||
export const useDiscountTableFilters = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
let filters: Filter[] = [
|
||||
{ label: t("fields.createdAt"), key: "created_at" },
|
||||
{ label: t("fields.updatedAt"), key: "updated_at" },
|
||||
].map((f) => ({
|
||||
key: f.key,
|
||||
label: f.label,
|
||||
type: "date",
|
||||
}))
|
||||
|
||||
const isDisabledFilter: Filter = {
|
||||
key: "is_disabled",
|
||||
label: t("fields.disabled"),
|
||||
type: "select",
|
||||
options: [
|
||||
{
|
||||
label: t("fields.true"),
|
||||
value: "true",
|
||||
},
|
||||
{
|
||||
label: t("fields.false"),
|
||||
value: "false",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const isDynamicFilter: Filter = {
|
||||
key: "is_dynamic",
|
||||
label: t("fields.type"),
|
||||
type: "select",
|
||||
options: [
|
||||
{
|
||||
label: t("fields.dynamic"),
|
||||
value: "true",
|
||||
},
|
||||
{
|
||||
label: t("fields.normal"),
|
||||
value: "false",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
filters = [...filters, isDisabledFilter, isDynamicFilter]
|
||||
|
||||
return filters
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import { useDateTableFilters } from "./use-date-table-filters"
|
||||
|
||||
export const useProductTagTableFilters = () => {
|
||||
const dateFilters = useDateTableFilters()
|
||||
|
||||
return dateFilters
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { useDateTableFilters } from "../../../../../hooks/table/filters/use-date-table-filters"
|
||||
import { useDateTableFilters } from "./use-date-table-filters"
|
||||
|
||||
export const useProductTypeTableFilters = () => {
|
||||
const dateFilters = useDateTableFilters()
|
||||
@@ -0,0 +1,14 @@
|
||||
export * from "./use-campaign-table-query"
|
||||
export * from "./use-collection-table-query"
|
||||
export * from "./use-customer-group-table-query"
|
||||
export * from "./use-customer-table-query"
|
||||
export * from "./use-order-table-query"
|
||||
export * from "./use-product-table-query"
|
||||
export * from "./use-product-tag-table-query"
|
||||
export * from "./use-product-type-table-query"
|
||||
export * from "./use-region-table-query"
|
||||
export * from "./use-sales-channel-table-query"
|
||||
export * from "./use-shipping-option-table-query"
|
||||
export * from "./use-tax-rate-table-query"
|
||||
export * from "./use-tax-region-table-query"
|
||||
export * from "./use-user-invite-table-query"
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
import { AdminCollectionFilters, FindParams } from "@medusajs/types"
|
||||
import { useQueryParams } from "../../../../../hooks/use-query-params"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { useQueryParams } from "../../use-query-params"
|
||||
|
||||
type UseCollectionTableQueryProps = {
|
||||
prefix?: string
|
||||
@@ -17,7 +17,7 @@ export const useCollectionTableQuery = ({
|
||||
|
||||
const { offset, created_at, updated_at, q, order } = queryObject
|
||||
|
||||
const searchParams: FindParams & AdminCollectionFilters = {
|
||||
const searchParams: HttpTypes.AdminCollectionListParams = {
|
||||
limit: pageSize,
|
||||
offset: offset ? Number(offset) : 0,
|
||||
order,
|
||||
@@ -1,45 +0,0 @@
|
||||
import { AdminGetDiscountsParams } from "@medusajs/medusa"
|
||||
|
||||
import { useQueryParams } from "../../use-query-params"
|
||||
|
||||
type UseDiscountTableQueryProps = {
|
||||
prefix?: string
|
||||
pageSize?: number
|
||||
}
|
||||
|
||||
export const useDiscountTableQuery = ({
|
||||
prefix,
|
||||
pageSize = 20,
|
||||
}: UseDiscountTableQueryProps) => {
|
||||
const queryObject = useQueryParams(
|
||||
[
|
||||
"offset",
|
||||
"order",
|
||||
"q",
|
||||
"is_dynamic",
|
||||
"is_disabled",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
],
|
||||
prefix
|
||||
)
|
||||
|
||||
const { offset, order, q, is_dynamic, is_disabled, created_at, updated_at } =
|
||||
queryObject
|
||||
|
||||
const searchParams: AdminGetDiscountsParams = {
|
||||
limit: pageSize,
|
||||
is_disabled: is_disabled ? is_disabled === "true" : undefined,
|
||||
is_dynamic: is_dynamic ? is_dynamic === "true" : undefined,
|
||||
created_at: created_at ? JSON.parse(created_at) : undefined,
|
||||
updated_at: updated_at ? JSON.parse(updated_at) : undefined,
|
||||
offset: offset ? Number(offset) : 0,
|
||||
order,
|
||||
q,
|
||||
}
|
||||
|
||||
return {
|
||||
searchParams,
|
||||
raw: queryObject,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { useQueryParams } from "../../use-query-params"
|
||||
|
||||
type UseProductTagTableQueryProps = {
|
||||
prefix?: string
|
||||
pageSize?: number
|
||||
}
|
||||
|
||||
export const useProductTagTableQuery = ({
|
||||
prefix,
|
||||
pageSize = 20,
|
||||
}: UseProductTagTableQueryProps) => {
|
||||
const queryObject = useQueryParams(
|
||||
["offset", "q", "order", "created_at", "updated_at"],
|
||||
prefix
|
||||
)
|
||||
|
||||
const { offset, q, order, created_at, updated_at } = queryObject
|
||||
const searchParams: HttpTypes.AdminProductTagListParams = {
|
||||
limit: pageSize,
|
||||
offset: offset ? Number(offset) : 0,
|
||||
order,
|
||||
created_at: created_at ? JSON.parse(created_at) : undefined,
|
||||
updated_at: updated_at ? JSON.parse(updated_at) : undefined,
|
||||
q,
|
||||
}
|
||||
|
||||
return {
|
||||
searchParams,
|
||||
raw: queryObject,
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -1,4 +1,5 @@
|
||||
import { useQueryParams } from "../../../../../hooks/use-query-params"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { useQueryParams } from "../../use-query-params"
|
||||
|
||||
type UseProductTypeTableQueryProps = {
|
||||
prefix?: string
|
||||
@@ -15,7 +16,7 @@ export const useProductTypeTableQuery = ({
|
||||
)
|
||||
|
||||
const { offset, q, order, created_at, updated_at } = queryObject
|
||||
const searchParams = {
|
||||
const searchParams: HttpTypes.AdminProductTypeListParams = {
|
||||
limit: pageSize,
|
||||
offset: offset ? Number(offset) : 0,
|
||||
order,
|
||||
@@ -98,7 +98,8 @@
|
||||
"add": "Add",
|
||||
"select": "Select",
|
||||
"browse": "Browse",
|
||||
"logout": "Logout"
|
||||
"logout": "Logout",
|
||||
"hide": "Hide"
|
||||
},
|
||||
"operators": {
|
||||
"in": "In"
|
||||
@@ -1049,127 +1050,239 @@
|
||||
"type": "Enter shipping profile type, for example: Heavy, Oversized, Freight-only, etc."
|
||||
}
|
||||
},
|
||||
"discounts": {
|
||||
"domain": "Discounts",
|
||||
"startDate": "Start date",
|
||||
"createDiscountTitle": "Create Discount",
|
||||
"validDuration": "Duration of the discount",
|
||||
"redemptionsLimit": "Redemptions limit",
|
||||
"endDate": "End date",
|
||||
"type": "Discount type",
|
||||
"percentageDiscount": "Percentage discount",
|
||||
"freeShipping": "Free shipping",
|
||||
"fixedDiscount": "Fixed discount",
|
||||
"fixedAmount": "Fixed amount",
|
||||
"validRegions": "Valid regions",
|
||||
"deleteWarning": "You are about to delete the discount {{code}}. This action cannot be undone.",
|
||||
"editDiscountDetails": "Edit Discount Details",
|
||||
"editDiscountConfiguration": "Edit Discount Configuration",
|
||||
"hasStartDate": "Discount has a start date",
|
||||
"hasEndDate": "Discount has an expiry date",
|
||||
"startDateHint": "Schedule the discount to activate in the future.",
|
||||
"endDateHint": "Schedule the discount to deactivate in the future.",
|
||||
"codeHint": "Discount code applies from when you hit the publish button and forever if left untouched.",
|
||||
"hasUsageLimit": "Limit the number of redemptions?",
|
||||
"usageLimitHint": "Limit applies across all customers, not per customer.",
|
||||
"titleHint": "The code your customers will enter during checkout. This will appear on your customer's invoice.\nUppercase letters and numbers only.",
|
||||
"hasDurationLimit": "Availability duration",
|
||||
"durationHint": "Set the duration of the discount",
|
||||
"chooseValidRegions": "Choose valid regions",
|
||||
"conditionsHint": "Create conditions to apply on the discount",
|
||||
"isTemplateDiscount": "Is this a template discount?",
|
||||
"percentageDescription": "Discount applied in %",
|
||||
"fixedDescription": "Amount discount",
|
||||
"shippingDescription": "Override delivery amount",
|
||||
"selectRegionFirst": "Select a region first",
|
||||
"templateHint": "Template discounts allow you to define a set of rules that can be used across a group of discounts. This is useful in campaigns that should generate unique codes for each user, but where the rules for all unique codes should be the same.",
|
||||
"conditions": {
|
||||
"editHeader": "Edit Discount Conditions",
|
||||
"editHint": "Specify conditions for when the discount can be applied to a cart.",
|
||||
"manageTypesAction": "Manage condition types",
|
||||
"including": {
|
||||
"products_one": "Discount applies to <0/> product",
|
||||
"products_other": "Discount applies to <0/> products",
|
||||
"customer_groups_one": "Discount applies to <0/> customer group",
|
||||
"customer_groups_other": "Discount applies to <0/> customer groups",
|
||||
"product_tags_one": "Discount applies to <0/> tag",
|
||||
"product_tags_other": "Discount applies to <0/> tags",
|
||||
"product_collections_one": "Discount applies to <0/> product collection",
|
||||
"product_collections_other": "Discount applies to <0/> product collections",
|
||||
"product_types_one": "Discount applies to <0/> product type",
|
||||
"product_types_other": "Discount applies to <0/> product types"
|
||||
},
|
||||
"excluding": {
|
||||
"products": "Discount applies to <1>all</1> products except <0/>",
|
||||
"customer_groups": "Discount applies to <1>all</1> customer groups except <0/>",
|
||||
"product_tags": "Discount applies to <1>all</1> product tags except <0/>",
|
||||
"product_collections": "Discount applies to <1>all</1> product collections except <0/>",
|
||||
"product_types": "Discount applies to <1>all</1> product types except <0/>"
|
||||
},
|
||||
"edit": {
|
||||
"appliesTo": "Discount applies to",
|
||||
"except": {
|
||||
"products_one": "product, except",
|
||||
"products_other": "products, except",
|
||||
"product_tags_one": "product tag, except",
|
||||
"product_tags_other": "product tags, except",
|
||||
"product_types_one": "product type, except",
|
||||
"product_types_other": "product types, except",
|
||||
"product_collections_one": "product collection, except",
|
||||
"product_collections_other": "product collections, except",
|
||||
"customer_groups_one": "customer group, except",
|
||||
"customer_groups_other": "customer groups, except"
|
||||
}
|
||||
}
|
||||
},
|
||||
"discountStatus": {
|
||||
"scheduled": "Scheduled",
|
||||
"expired": "Expired",
|
||||
"active": "Active",
|
||||
"disabled": "Disabled"
|
||||
}
|
||||
},
|
||||
"taxRates": {
|
||||
"domain": "Tax Rates",
|
||||
"fields": {
|
||||
"isCombinable": "Is combinable?",
|
||||
"appliesTo": "Tax Rate applies to",
|
||||
"customer_groups": "Customer Group",
|
||||
"product_collections": "Product Collection",
|
||||
"product_tags": "Product Tag",
|
||||
"product_types": "Product Type",
|
||||
"products": "Product"
|
||||
},
|
||||
"edit": {
|
||||
"title": "Edit Tax Rate",
|
||||
"description": "Edits the tax rate of a tax region"
|
||||
},
|
||||
"create": {
|
||||
"title": "Create Tax Rate Override",
|
||||
"description": "Create a tax rate that overrides the default tax rates for selected conditions."
|
||||
}
|
||||
},
|
||||
"taxRegions": {
|
||||
"domain": "Tax Regions",
|
||||
"subtitle": "Manage tax rates and settings for each region.",
|
||||
"description": "Manage your region's tax structure",
|
||||
"list": {
|
||||
"hint": "Manage what you charge your customers when they shop from different countries and regions."
|
||||
},
|
||||
"delete": {
|
||||
"confirmation": "You are about to delete a tax region. This action cannot be undone.",
|
||||
"successToast": "The tax region was successfully deleted."
|
||||
},
|
||||
"create": {
|
||||
"title": "Create Tax Region",
|
||||
"description": "Creates a tax region with a default tax rate."
|
||||
},
|
||||
"create-child": {
|
||||
"title": "Create Default Rate for Province",
|
||||
"description": "Creates a tax region for a province with default tax rate"
|
||||
},
|
||||
"removeWarning": "You are about to remove {{tax_region_name}}. This action cannot be undone.",
|
||||
"fields": {
|
||||
"rate": {
|
||||
"name": "Rate",
|
||||
"hint": "Tax rate to apply for a region or province"
|
||||
"header": "Create Tax Region",
|
||||
"hint": "Create a new tax region to define tax rates for a specific country.",
|
||||
"errors": {
|
||||
"rateIsRequired": "Tax rate is required when creating a default tax rate.",
|
||||
"nameIsRequired": "Name is required when creating a default tax rate."
|
||||
},
|
||||
"is_combinable": {
|
||||
"name": "Is combinable",
|
||||
"hint": "Whether this tax rate can be combined with the default rate from province or parent tax region."
|
||||
"successToast": "The tax region was successfully created."
|
||||
},
|
||||
"province": {
|
||||
"header": "Provinces",
|
||||
"create": {
|
||||
"header": "Create Province Tax Region",
|
||||
"hint": "Create a new tax region to define tax rates for a specific province."
|
||||
}
|
||||
},
|
||||
"state": {
|
||||
"header": "States",
|
||||
"create": {
|
||||
"header": "Create State Tax Region",
|
||||
"hint": "Create a new tax region to define tax rates for a specific state."
|
||||
}
|
||||
},
|
||||
"stateOrTerritory": {
|
||||
"header": "States or Territories",
|
||||
"create": {
|
||||
"header": "Create State/Territory Tax Region",
|
||||
"hint": "Create a new tax region to define tax rates for a specific state/territory."
|
||||
}
|
||||
},
|
||||
"county": {
|
||||
"header": "Counties",
|
||||
"create": {
|
||||
"header": "Create County Tax Region",
|
||||
"hint": "Create a new tax region to define tax rates for a specific county."
|
||||
}
|
||||
},
|
||||
"region": {
|
||||
"header": "Regions",
|
||||
"create": {
|
||||
"header": "Create Region Tax Region",
|
||||
"hint": "Create a new tax region to define tax rates for a specific region."
|
||||
}
|
||||
},
|
||||
"department": {
|
||||
"header": "Departments",
|
||||
"create": {
|
||||
"header": "Create Department Tax Region",
|
||||
"hint": "Create a new tax region to define tax rates for a specific department."
|
||||
}
|
||||
},
|
||||
"territory": {
|
||||
"header": "Territories",
|
||||
"create": {
|
||||
"header": "Create Territory Tax Region",
|
||||
"hint": "Create a new tax region to define tax rates for a specific territory."
|
||||
}
|
||||
},
|
||||
"prefecture": {
|
||||
"header": "Prefectures",
|
||||
"create": {
|
||||
"header": "Create Prefecture Tax Region",
|
||||
"hint": "Create a new tax region to define tax rates for a specific prefecture."
|
||||
}
|
||||
},
|
||||
"district": {
|
||||
"header": "Districts",
|
||||
"create": {
|
||||
"header": "Create District Tax Region",
|
||||
"hint": "Create a new tax region to define tax rates for a specific district."
|
||||
}
|
||||
},
|
||||
"governorate": {
|
||||
"header": "Governorates",
|
||||
"create": {
|
||||
"header": "Create Governorate Tax Region",
|
||||
"hint": "Create a new tax region to define tax rates for a specific governorate."
|
||||
}
|
||||
},
|
||||
"canton": {
|
||||
"header": "Cantons",
|
||||
"create": {
|
||||
"header": "Create Canton Tax Region",
|
||||
"hint": "Create a new tax region to define tax rates for a specific canton."
|
||||
}
|
||||
},
|
||||
"emirate": {
|
||||
"header": "Emirates",
|
||||
"create": {
|
||||
"header": "Create Emirate Tax Region",
|
||||
"hint": "Create a new tax region to define tax rates for a specific emirate."
|
||||
}
|
||||
},
|
||||
"sublevel": {
|
||||
"header": "Sublevels",
|
||||
"create": {
|
||||
"header": "Create Sublevel Tax Region",
|
||||
"hint": "Create a new tax region to define tax rates for a specific sublevel."
|
||||
}
|
||||
},
|
||||
"taxOverrides": {
|
||||
"header": "Overrides",
|
||||
"create": {
|
||||
"header": "Create Override",
|
||||
"hint": "Create a tax rate that overrides the default tax rates for selected conditions."
|
||||
},
|
||||
"edit": {
|
||||
"header": "Edit Override",
|
||||
"hint": "Edit the tax rate that overrides the default tax rates for selected conditions."
|
||||
}
|
||||
},
|
||||
"taxRates": {
|
||||
"create": {
|
||||
"header": "Create Tax Rate",
|
||||
"hint": "Create a new tax rate to define the tax rate for a region.",
|
||||
"successToast": "Tax rate was successfully created."
|
||||
},
|
||||
"edit": {
|
||||
"header": "Edit Tax Rate",
|
||||
"hint": "Edit the tax rate to define the tax rate for a region.",
|
||||
"successToast": "Tax rate was successfully updated."
|
||||
},
|
||||
"delete": {
|
||||
"confirmation": "You are about to delete the tax rate {{name}}. This action cannot be undone.",
|
||||
"successToast": "Tax rate was successfully deleted."
|
||||
}
|
||||
},
|
||||
"fields": {
|
||||
"isCombinable": {
|
||||
"label": "Combinable",
|
||||
"hint": "Whether this tax rate can be combined with the default rate from the tax region.",
|
||||
"true": "Combinable",
|
||||
"false": "Not combinable"
|
||||
},
|
||||
"defaultTaxRate": {
|
||||
"label": "Default tax rate",
|
||||
"tooltip": "The default tax rate for this region. An example is the standard VAT rate for a country or region.",
|
||||
"action": "Create default tax rate"
|
||||
},
|
||||
"taxRate": "Tax rate",
|
||||
"taxCode": "Tax code",
|
||||
"targets": {
|
||||
"label": "Targets",
|
||||
"hint": "Select the targets that this tax rate will apply to.",
|
||||
"options": {
|
||||
"product": "Products",
|
||||
"productCollection": "Product collections",
|
||||
"productTag": "Product tags",
|
||||
"productType": "Product types",
|
||||
"customerGroup": "Customer groups"
|
||||
},
|
||||
"operators": {
|
||||
"in": "in",
|
||||
"on": "on",
|
||||
"and": "and"
|
||||
},
|
||||
"placeholders": {
|
||||
"product": "Search for products",
|
||||
"productCollection": "Search for product collections",
|
||||
"productTag": "Search for product tags",
|
||||
"productType": "Search for product types",
|
||||
"customerGroup": "Search for customer groups"
|
||||
},
|
||||
"tags": {
|
||||
"product": "Product",
|
||||
"productCollection": "Product collection",
|
||||
"productTag": "Product tag",
|
||||
"productType": "Product type",
|
||||
"customerGroup": "Customer group"
|
||||
},
|
||||
"modal": {
|
||||
"header": "Add targets"
|
||||
},
|
||||
"values_one": "{{count}} value",
|
||||
"values_other": "{{count}} values",
|
||||
"numberOfTargets_one": "{{count}} target",
|
||||
"numberOfTargets_other": "{{count}} targets",
|
||||
"additionalValues_one": "and {{count}} more value",
|
||||
"additionalValues_other": "and {{count}} more values",
|
||||
"action": "Add target"
|
||||
},
|
||||
"sublevels": {
|
||||
"labels": {
|
||||
"province": "Province",
|
||||
"state": "State",
|
||||
"region": "Region",
|
||||
"stateOrTerritory": "State/Territory",
|
||||
"department": "Department",
|
||||
"county": "County",
|
||||
"territory": "Territory",
|
||||
"prefecture": "Prefecture",
|
||||
"district": "District",
|
||||
"governorate": "Governorate",
|
||||
"emirate": "Emirate",
|
||||
"canton": "Canton",
|
||||
"sublevel": "Sublevel code"
|
||||
},
|
||||
"placeholders": {
|
||||
"province": "Select province",
|
||||
"state": "Select state",
|
||||
"region": "Select region",
|
||||
"stateOrTerritory": "Select state/territory",
|
||||
"department": "Select department",
|
||||
"county": "Select county",
|
||||
"territory": "Select territory",
|
||||
"prefecture": "Select prefecture",
|
||||
"district": "Select district",
|
||||
"governorate": "Select governorate",
|
||||
"emirate": "Select emirate",
|
||||
"canton": "Select canton"
|
||||
},
|
||||
"tooltips": {
|
||||
"sublevel": "Enter the ISO 3166-2 code for the sublevel tax region.",
|
||||
"notPartOfCountry": "{{province}} does not appear to be part of {{country}}. Please double-check if this is correct."
|
||||
},
|
||||
"alert": {
|
||||
"header": "Sublevel regions are disabled for this tax region",
|
||||
"description": "Sublevel regions are disabled for this region by default. You can enable them to create sublevel regions like provinces, states, or territories.",
|
||||
"action": "Enable sublevel regions"
|
||||
}
|
||||
},
|
||||
"noDefaultRate": {
|
||||
"label": "No default rate",
|
||||
"tooltip": "This tax region does not have a default tax rate. If there is a standard rate, such as a country's VAT, please add it to this region."
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1985,8 +2098,6 @@
|
||||
"note": "Note",
|
||||
"automaticTaxes": "Automatic Taxes",
|
||||
"taxInclusivePricing": "Tax inclusive pricing",
|
||||
"taxRate": "Tax Rate",
|
||||
"taxCode": "Tax Code",
|
||||
"currency": "Currency",
|
||||
"address": "Address",
|
||||
"address2": "Apartment, suite, etc.",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { AddressDTO } from "@medusajs/types"
|
||||
|
||||
import { countries } from "./countries"
|
||||
import { countries } from "./data/countries"
|
||||
|
||||
export const isSameAddress = (a: AddressDTO | null, b: AddressDTO | null) => {
|
||||
if (!a || !b) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,32 +0,0 @@
|
||||
import { Discount } from "@medusajs/medusa"
|
||||
import { end, parse } from "iso8601-duration"
|
||||
|
||||
export enum PromotionStatus {
|
||||
SCHEDULED = "SCHEDULED",
|
||||
EXPIRED = "EXPIRED",
|
||||
ACTIVE = "ACTIVE",
|
||||
DISABLED = "DISABLED",
|
||||
}
|
||||
|
||||
export const getDiscountStatus = (discount: Discount) => {
|
||||
if (discount.is_disabled) {
|
||||
return PromotionStatus.DISABLED
|
||||
}
|
||||
|
||||
const date = new Date()
|
||||
if (new Date(discount.starts_at) > date) {
|
||||
return PromotionStatus.SCHEDULED
|
||||
}
|
||||
|
||||
if (
|
||||
(discount.ends_at && new Date(discount.ends_at) < date) ||
|
||||
(discount.valid_duration &&
|
||||
date >
|
||||
end(parse(discount.valid_duration), new Date(discount.starts_at))) ||
|
||||
discount.usage_count === discount.usage_limit
|
||||
) {
|
||||
return PromotionStatus.EXPIRED
|
||||
}
|
||||
|
||||
return PromotionStatus.ACTIVE
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { currencies } from "./currencies"
|
||||
import { currencies } from "./data/currencies"
|
||||
|
||||
export const getDecimalDigits = (currency: string) => {
|
||||
return currencies[currency.toUpperCase()]?.decimal_digits ?? 0
|
||||
@@ -15,7 +15,7 @@ export const getDecimalDigits = (currency: string) => {
|
||||
* getFormattedAmount(10, "usd") // '10,00 $' if the browser's locale is fr-FR
|
||||
*/
|
||||
export const getLocaleAmount = (amount: number, currencyCode: string) => {
|
||||
const formatter = new Intl.NumberFormat(undefined, {
|
||||
const formatter = new Intl.NumberFormat([], {
|
||||
style: "currency",
|
||||
currencyDisplay: "narrowSymbol",
|
||||
currency: currencyCode,
|
||||
@@ -25,7 +25,7 @@ export const getLocaleAmount = (amount: number, currencyCode: string) => {
|
||||
}
|
||||
|
||||
export const getNativeSymbol = (currencyCode: string) => {
|
||||
const formatted = new Intl.NumberFormat(undefined, {
|
||||
const formatted = new Intl.NumberFormat([], {
|
||||
style: "currency",
|
||||
currency: currencyCode,
|
||||
currencyDisplay: "narrowSymbol",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const formatter = new Intl.NumberFormat(undefined, {
|
||||
const formatter = new Intl.NumberFormat([], {
|
||||
style: "percent",
|
||||
minimumFractionDigits: 2,
|
||||
})
|
||||
|
||||
@@ -220,7 +220,7 @@ export const useGlobalShortcuts = () => {
|
||||
},
|
||||
label: t("app.keyboardShortcuts.goToTaxRegions"),
|
||||
type: "settingShortcut",
|
||||
callback: () => navigate("/settings/taxes"),
|
||||
callback: () => navigate("/settings/tax-regions"),
|
||||
},
|
||||
{
|
||||
keys: {
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import {
|
||||
AdminApiKeyResponse,
|
||||
AdminProductCategoryResponse,
|
||||
AdminTaxRateResponse,
|
||||
AdminTaxRegionResponse,
|
||||
HttpTypes,
|
||||
SalesChannelDTO,
|
||||
UserDTO,
|
||||
} from "@medusajs/types"
|
||||
import { Outlet, RouteObject } from "react-router-dom"
|
||||
|
||||
@@ -15,6 +13,12 @@ import { SettingsLayout } from "../../components/layout/settings-layout"
|
||||
import { ErrorBoundary } from "../../components/utilities/error-boundary"
|
||||
import { PriceListRes } from "../../types/api-responses"
|
||||
|
||||
import { getCountryByIso2 } from "../../lib/data/countries"
|
||||
import {
|
||||
getProvinceByIso2,
|
||||
isProvinceInCountry,
|
||||
} from "../../lib/data/country-states"
|
||||
import { taxRegionLoader } from "../../routes/tax-regions/tax-region-detail/loader"
|
||||
import { RouteExtensions } from "./route-extensions"
|
||||
import { SettingsExtensions } from "./settings-extensions"
|
||||
|
||||
@@ -1074,60 +1078,116 @@ export const RouteMap: RouteObject[] = [
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "taxes",
|
||||
path: "tax-regions",
|
||||
element: <Outlet />,
|
||||
handle: {
|
||||
crumb: () => "Taxes",
|
||||
crumb: () => "Tax Regions",
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
lazy: () => import("../../routes/taxes/tax-region-list"),
|
||||
lazy: () => import("../../routes/tax-regions/tax-region-list"),
|
||||
children: [
|
||||
{
|
||||
path: "create",
|
||||
lazy: () => import("../../routes/taxes/tax-region-create"),
|
||||
children: [],
|
||||
lazy: () =>
|
||||
import("../../routes/tax-regions/tax-region-create"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: ":id",
|
||||
lazy: () => import("../../routes/taxes/tax-region-detail"),
|
||||
Component: Outlet,
|
||||
loader: taxRegionLoader,
|
||||
handle: {
|
||||
crumb: (data: AdminTaxRegionResponse) => {
|
||||
return data.tax_region.country_code
|
||||
return (
|
||||
getCountryByIso2(data.tax_region.country_code)
|
||||
?.display_name ||
|
||||
data.tax_region.country_code?.toUpperCase()
|
||||
)
|
||||
},
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "create-default",
|
||||
path: "",
|
||||
lazy: () =>
|
||||
import("../../routes/taxes/tax-province-create"),
|
||||
children: [],
|
||||
},
|
||||
{
|
||||
path: "create-override",
|
||||
lazy: () => import("../../routes/taxes/tax-rate-create"),
|
||||
children: [],
|
||||
},
|
||||
{
|
||||
path: "tax-rates",
|
||||
import("../../routes/tax-regions/tax-region-detail"),
|
||||
children: [
|
||||
{
|
||||
path: ":taxRateId",
|
||||
children: [
|
||||
{
|
||||
path: "edit",
|
||||
lazy: () =>
|
||||
import("../../routes/taxes/tax-rate-edit"),
|
||||
handle: {
|
||||
crumb: (data: AdminTaxRateResponse) => {
|
||||
return data.tax_rate.code
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
path: "provinces/create",
|
||||
lazy: () =>
|
||||
import(
|
||||
"../../routes/tax-regions/tax-region-province-create"
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "overrides/create",
|
||||
lazy: () =>
|
||||
import(
|
||||
"../../routes/tax-regions/tax-region-tax-override-create"
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "overrides/:tax_rate_id/edit",
|
||||
lazy: () =>
|
||||
import(
|
||||
"../../routes/tax-regions/tax-region-tax-override-edit"
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "tax-rates/create",
|
||||
lazy: () =>
|
||||
import(
|
||||
"../../routes/tax-regions/tax-region-tax-rate-create"
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "tax-rates/:tax_rate_id/edit",
|
||||
lazy: () =>
|
||||
import(
|
||||
"../../routes/tax-regions/tax-region-tax-rate-edit"
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "provinces/:province_id",
|
||||
lazy: () =>
|
||||
import(
|
||||
"../../routes/tax-regions/tax-region-province-detail"
|
||||
),
|
||||
handle: {
|
||||
crumb: (data: AdminTaxRegionResponse) => {
|
||||
const countryCode =
|
||||
data.tax_region.country_code?.toUpperCase()
|
||||
const provinceCode =
|
||||
data.tax_region.province_code?.toUpperCase()
|
||||
|
||||
const isValid = isProvinceInCountry(
|
||||
countryCode,
|
||||
provinceCode
|
||||
)
|
||||
|
||||
return isValid
|
||||
? getProvinceByIso2(provinceCode)
|
||||
: provinceCode
|
||||
},
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "tax-rates/create",
|
||||
lazy: () =>
|
||||
import(
|
||||
"../../routes/tax-regions/tax-region-tax-rate-create"
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "tax-rates/:tax_rate_id/edit",
|
||||
lazy: () =>
|
||||
import(
|
||||
"../../routes/tax-regions/tax-region-tax-rate-edit"
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ import * as zod from "zod"
|
||||
import { Form } from "../../../../../components/common/form"
|
||||
import { RouteDrawer, useRouteModal } from "../../../../../components/modals"
|
||||
import { useUpdateCampaign } from "../../../../../hooks/api/campaigns"
|
||||
import { getCurrencySymbol } from "../../../../../lib/currencies"
|
||||
import { getCurrencySymbol } from "../../../../../lib/data/currencies"
|
||||
|
||||
type EditCampaignBudgetFormProps = {
|
||||
campaign: CampaignResponse
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ import { useNavigate } from "react-router-dom"
|
||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||
import { formatDate } from "../../../../../components/common/date"
|
||||
import { useDeleteCampaign } from "../../../../../hooks/api/campaigns"
|
||||
import { currencies } from "../../../../../lib/currencies"
|
||||
import { currencies } from "../../../../../lib/data/currencies"
|
||||
import {
|
||||
campaignStatus,
|
||||
statusColor,
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ import { useTranslation } from "react-i18next"
|
||||
|
||||
import { Form } from "../../../../../components/common/form"
|
||||
import { useStore } from "../../../../../hooks/api/store"
|
||||
import { currencies, getCurrencySymbol } from "../../../../../lib/currencies"
|
||||
import { currencies, getCurrencySymbol } from "../../../../../lib/data/currencies"
|
||||
|
||||
export const CreateCampaignFormFields = ({ form, fieldScope = "" }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
+25
-4
@@ -2,13 +2,17 @@ import { Button, Container, Heading, Text } from "@medusajs/ui"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Link } from "react-router-dom"
|
||||
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { keepPreviousData } from "@tanstack/react-query"
|
||||
import { createColumnHelper } from "@tanstack/react-table"
|
||||
import { useMemo } from "react"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { useCollections } from "../../../../../hooks/api/collections"
|
||||
import { useCollectionTableColumns } from "../../../../../hooks/table/columns/use-collection-table-columns"
|
||||
import { useCollectionTableFilters } from "../../../../../hooks/table/filters"
|
||||
import { useCollectionTableQuery } from "../../../../../hooks/table/query"
|
||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
import { useCollectionTableColumns } from "./use-collection-table-columns"
|
||||
import { useCollectionTableFilters } from "./use-collection-table-filters"
|
||||
import { useCollectionTableQuery } from "./use-collection-table-query"
|
||||
import { CollectionRowActions } from "./collection-row-actions"
|
||||
|
||||
const PAGE_SIZE = 20
|
||||
|
||||
@@ -26,7 +30,7 @@ export const CollectionListTable = () => {
|
||||
)
|
||||
|
||||
const filters = useCollectionTableFilters()
|
||||
const columns = useCollectionTableColumns()
|
||||
const columns = useColumns()
|
||||
|
||||
const { table } = useDataTable({
|
||||
data: collections ?? [],
|
||||
@@ -71,3 +75,20 @@ export const CollectionListTable = () => {
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
const columnHelper = createColumnHelper<HttpTypes.AdminCollection>()
|
||||
|
||||
const useColumns = () => {
|
||||
const base = useCollectionTableColumns()
|
||||
|
||||
return useMemo(
|
||||
() => [
|
||||
...base,
|
||||
columnHelper.display({
|
||||
id: "actions",
|
||||
cell: ({ row }) => <CollectionRowActions collection={row.original} />,
|
||||
}),
|
||||
],
|
||||
[base]
|
||||
)
|
||||
}
|
||||
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Filter } from "../../../../../components/table/data-table"
|
||||
|
||||
export const useCollectionTableFilters = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
let filters: Filter[] = []
|
||||
|
||||
const dateFilters: Filter[] = [
|
||||
{ label: t("fields.createdAt"), key: "created_at" },
|
||||
{ label: t("fields.updatedAt"), key: "updated_at" },
|
||||
].map((f) => ({
|
||||
key: f.key,
|
||||
label: f.label,
|
||||
type: "date",
|
||||
}))
|
||||
|
||||
filters = [...filters, ...dateFilters]
|
||||
|
||||
return filters
|
||||
}
|
||||
+1
-1
@@ -17,7 +17,7 @@ import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
import {
|
||||
StaticCountry,
|
||||
countries as staticCountries,
|
||||
} from "../../../../../lib/countries"
|
||||
} from "../../../../../lib/data/countries"
|
||||
import { useCountries } from "../../../../regions/common/hooks/use-countries"
|
||||
import { useCountryTableColumns } from "../../../../regions/common/hooks/use-country-table-columns"
|
||||
import { useCountryTableQuery } from "../../../../regions/common/hooks/use-country-table-query"
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ import { getFormattedAddress } from "../../../../../lib/addresses"
|
||||
import {
|
||||
StaticCountry,
|
||||
countries as staticCountries,
|
||||
} from "../../../../../lib/countries"
|
||||
} from "../../../../../lib/data/countries"
|
||||
import { formatProvider } from "../../../../../lib/format-provider"
|
||||
import {
|
||||
isOptionEnabledInStore,
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ import {
|
||||
useRouteModal,
|
||||
} from "../../../../../components/modals"
|
||||
import { useUpdateFulfillmentSetServiceZone } from "../../../../../hooks/api/fulfillment-sets"
|
||||
import { countries } from "../../../../../lib/countries"
|
||||
import { countries } from "../../../../../lib/data/countries"
|
||||
import { GeoZoneForm } from "../../../common/components/geo-zone-form"
|
||||
|
||||
const EditeServiceZoneSchema = z.object({
|
||||
|
||||
+2
-2
@@ -42,7 +42,7 @@ export const PriceListEditForm = ({ priceList }: PriceListEditFormProps) => {
|
||||
resolver: zodResolver(PriceListEditSchema),
|
||||
})
|
||||
|
||||
const { mutateAsync } = useUpdatePriceList(priceList.id)
|
||||
const { mutateAsync, isPending } = useUpdatePriceList(priceList.id)
|
||||
|
||||
const handleSubmit = form.handleSubmit(async (values) => {
|
||||
await mutateAsync(values, {
|
||||
@@ -172,7 +172,7 @@ export const PriceListEditForm = ({ priceList }: PriceListEditFormProps) => {
|
||||
{t("actions.cancel")}
|
||||
</Button>
|
||||
</RouteDrawer.Close>
|
||||
<Button size="small" type="submit">
|
||||
<Button size="small" type="submit" isLoading={isPending}>
|
||||
{t("actions.save")}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
+28
-4
@@ -1,13 +1,18 @@
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { Button, Container, Heading, Text } from "@medusajs/ui"
|
||||
import { keepPreviousData } from "@tanstack/react-query"
|
||||
import { createColumnHelper } from "@tanstack/react-table"
|
||||
import { useMemo } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Link } from "react-router-dom"
|
||||
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { useProductTypes } from "../../../../../hooks/api/product-types"
|
||||
import { useProductTypeTableColumns } from "../../../../../hooks/table/columns/use-product-type-table-columns"
|
||||
import { useProductTypeTableFilters } from "../../../../../hooks/table/filters/use-product-type-table-filters"
|
||||
import { useProductTypeTableQuery } from "../../../../../hooks/table/query/use-product-type-table-query"
|
||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
import { useProductTypeTableColumns } from "./use-product-type-table-columns"
|
||||
import { useProductTypeTableFilters } from "./use-product-type-table-filters"
|
||||
import { useProductTypeTableQuery } from "./use-product-type-table-query"
|
||||
import { ProductTypeRowActions } from "./product-table-row-actions"
|
||||
|
||||
const PAGE_SIZE = 20
|
||||
|
||||
@@ -25,7 +30,7 @@ export const ProductTypeListTable = () => {
|
||||
)
|
||||
|
||||
const filters = useProductTypeTableFilters()
|
||||
const columns = useProductTypeTableColumns()
|
||||
const columns = useColumns()
|
||||
|
||||
const { table } = useDataTable({
|
||||
columns,
|
||||
@@ -68,3 +73,22 @@ export const ProductTypeListTable = () => {
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
const columnHelper = createColumnHelper<HttpTypes.AdminProductType>()
|
||||
|
||||
const useColumns = () => {
|
||||
const base = useProductTypeTableColumns()
|
||||
|
||||
return useMemo(
|
||||
() => [
|
||||
...base,
|
||||
columnHelper.display({
|
||||
id: "actions",
|
||||
cell: ({ row }) => {
|
||||
return <ProductTypeRowActions productType={row.original} />
|
||||
},
|
||||
}),
|
||||
],
|
||||
[base]
|
||||
)
|
||||
}
|
||||
|
||||
+3
-3
@@ -26,14 +26,14 @@ import {
|
||||
} from "@medusajs/types"
|
||||
import { Divider } from "../../../../../components/common/divider"
|
||||
import { Form } from "../../../../../components/common/form"
|
||||
import { PercentageInput } from "../../../../../components/inputs/percentage-input"
|
||||
import { DeprecatedPercentageInput } from "../../../../../components/inputs/percentage-input"
|
||||
import {
|
||||
RouteFocusModal,
|
||||
useRouteModal,
|
||||
} from "../../../../../components/modals"
|
||||
import { useCampaigns } from "../../../../../hooks/api/campaigns"
|
||||
import { useCreatePromotion } from "../../../../../hooks/api/promotions"
|
||||
import { getCurrencySymbol } from "../../../../../lib/currencies"
|
||||
import { getCurrencySymbol } from "../../../../../lib/data/currencies"
|
||||
import { defaultCampaignValues } from "../../../../campaigns/campaign-create/components/create-campaign-form"
|
||||
import { RulesFormField } from "../../../common/edit-rules/components/rules-form-field"
|
||||
import { AddCampaignPromotionFields } from "../../../promotion-add-campaign/components/add-campaign-promotion-form"
|
||||
@@ -661,7 +661,7 @@ export const CreatePromotionForm = () => {
|
||||
disabled={!currencyCode}
|
||||
/>
|
||||
) : (
|
||||
<PercentageInput
|
||||
<DeprecatedPercentageInput
|
||||
key="amount"
|
||||
className="text-right"
|
||||
min={0}
|
||||
|
||||
+9
-9
@@ -13,13 +13,13 @@ import { Trans, useTranslation } from "react-i18next"
|
||||
import * as zod from "zod"
|
||||
|
||||
import { Form } from "../../../../../components/common/form"
|
||||
import { PercentageInput } from "../../../../../components/inputs/percentage-input"
|
||||
import { DeprecatedPercentageInput } from "../../../../../components/inputs/percentage-input"
|
||||
import {
|
||||
RouteDrawer,
|
||||
useRouteModal,
|
||||
} from "../../../../../components/modals"
|
||||
import { useUpdatePromotion } from "../../../../../hooks/api/promotions"
|
||||
import { getCurrencySymbol } from "../../../../../lib/currencies"
|
||||
import { getCurrencySymbol } from "../../../../../lib/data/currencies"
|
||||
|
||||
type EditPromotionFormProps = {
|
||||
promotion: PromotionDTO
|
||||
@@ -99,7 +99,7 @@ export const EditPromotionDetailsForm = ({
|
||||
>
|
||||
<RadioGroup.ChoiceBox
|
||||
className={clx("basis-1/2", {
|
||||
"border-2 border-ui-border-interactive":
|
||||
"border-ui-border-interactive border-2":
|
||||
"false" === field.value,
|
||||
})}
|
||||
value={"false"}
|
||||
@@ -110,7 +110,7 @@ export const EditPromotionDetailsForm = ({
|
||||
/>
|
||||
<RadioGroup.ChoiceBox
|
||||
className={clx("basis-1/2", {
|
||||
"border-2 border-ui-border-interactive":
|
||||
"border-ui-border-interactive border-2":
|
||||
"true" === field.value,
|
||||
})}
|
||||
value={"true"}
|
||||
@@ -172,7 +172,7 @@ export const EditPromotionDetailsForm = ({
|
||||
>
|
||||
<RadioGroup.ChoiceBox
|
||||
className={clx("basis-1/2", {
|
||||
"border-2 border-ui-border-interactive":
|
||||
"border-ui-border-interactive border-2":
|
||||
"fixed" === field.value,
|
||||
})}
|
||||
value={"fixed"}
|
||||
@@ -184,7 +184,7 @@ export const EditPromotionDetailsForm = ({
|
||||
|
||||
<RadioGroup.ChoiceBox
|
||||
className={clx("basis-1/2", {
|
||||
"border-2 border-ui-border-interactive":
|
||||
"border-ui-border-interactive border-2":
|
||||
"percentage" === field.value,
|
||||
})}
|
||||
value={"percentage"}
|
||||
@@ -227,7 +227,7 @@ export const EditPromotionDetailsForm = ({
|
||||
value={field.value}
|
||||
/>
|
||||
) : (
|
||||
<PercentageInput
|
||||
<DeprecatedPercentageInput
|
||||
key="amount"
|
||||
min={0}
|
||||
max={100}
|
||||
@@ -264,7 +264,7 @@ export const EditPromotionDetailsForm = ({
|
||||
>
|
||||
<RadioGroup.ChoiceBox
|
||||
className={clx("basis-1/2", {
|
||||
"border-2 border-ui-border-interactive":
|
||||
"border-ui-border-interactive border-2":
|
||||
"each" === field.value,
|
||||
})}
|
||||
value={"each"}
|
||||
@@ -276,7 +276,7 @@ export const EditPromotionDetailsForm = ({
|
||||
|
||||
<RadioGroup.ChoiceBox
|
||||
className={clx("basis-1/2", {
|
||||
"border-2 border-ui-border-interactive":
|
||||
"border-ui-border-interactive border-2":
|
||||
"across" === field.value,
|
||||
})}
|
||||
value={"across"}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { RegionCountryDTO } from "@medusajs/types"
|
||||
import { json } from "react-router-dom"
|
||||
import { StaticCountry } from "../../../../lib/countries"
|
||||
import { StaticCountry } from "../../../../lib/data/countries"
|
||||
|
||||
const acceptedOrderKeys = ["name", "code"]
|
||||
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ import { createColumnHelper } from "@tanstack/react-table"
|
||||
import { useMemo } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import { StaticCountry } from "../../../../lib/countries"
|
||||
import { StaticCountry } from "../../../../lib/data/countries"
|
||||
|
||||
const columnHelper = createColumnHelper<StaticCountry>()
|
||||
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ import {
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { useUpdateRegion } from "../../../../../hooks/api/regions"
|
||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
import { countries as staticCountries } from "../../../../../lib/countries"
|
||||
import { countries as staticCountries } from "../../../../../lib/data/countries"
|
||||
import { useCountries } from "../../../common/hooks/use-countries"
|
||||
import { useCountryTableColumns } from "../../../common/hooks/use-country-table-columns"
|
||||
import { useCountryTableQuery } from "../../../common/hooks/use-country-table-query"
|
||||
|
||||
+2
-2
@@ -29,8 +29,8 @@ import {
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { useCreateRegion } from "../../../../../hooks/api/regions"
|
||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
import { countries as staticCountries } from "../../../../../lib/countries"
|
||||
import { CurrencyInfo } from "../../../../../lib/currencies"
|
||||
import { countries as staticCountries } from "../../../../../lib/data/countries"
|
||||
import { CurrencyInfo } from "../../../../../lib/data/currencies"
|
||||
import { formatProvider } from "../../../../../lib/format-provider"
|
||||
import { useCountries } from "../../../common/hooks/use-countries"
|
||||
import { useCountryTableColumns } from "../../../common/hooks/use-country-table-columns"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { RouteFocusModal } from "../../../components/modals/route-focus-modal"
|
||||
import { usePaymentProviders } from "../../../hooks/api/payments"
|
||||
import { useStore } from "../../../hooks/api/store"
|
||||
import { currencies } from "../../../lib/currencies"
|
||||
import { currencies } from "../../../lib/data/currencies"
|
||||
import { CreateRegionForm } from "./components/create-region-form"
|
||||
|
||||
export const RegionCreate = () => {
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ import { useNavigate } from "react-router-dom"
|
||||
import { ActionMenu } from "../../../../../components/common/action-menu/index.ts"
|
||||
import { ListSummary } from "../../../../../components/common/list-summary/index.ts"
|
||||
import { useDeleteRegion } from "../../../../../hooks/api/regions.tsx"
|
||||
import { currencies } from "../../../../../lib/currencies.ts"
|
||||
import { currencies } from "../../../../../lib/data/currencies.ts"
|
||||
import { formatProvider } from "../../../../../lib/format-provider.ts"
|
||||
import { SectionRow } from "../../../../../components/common/section/section-row.tsx"
|
||||
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ import {
|
||||
useRouteModal,
|
||||
} from "../../../../../components/modals/index.ts"
|
||||
import { useUpdateRegion } from "../../../../../hooks/api/regions.tsx"
|
||||
import { CurrencyInfo } from "../../../../../lib/currencies.ts"
|
||||
import { CurrencyInfo } from "../../../../../lib/data/currencies.ts"
|
||||
import { formatProvider } from "../../../../../lib/format-provider.ts"
|
||||
|
||||
type EditRegionFormProps = {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { RouteDrawer } from "../../../components/modals"
|
||||
import { usePaymentProviders } from "../../../hooks/api/payments"
|
||||
import { useRegion } from "../../../hooks/api/regions"
|
||||
import { useStore } from "../../../hooks/api/store"
|
||||
import { currencies } from "../../../lib/currencies"
|
||||
import { currencies } from "../../../lib/data/currencies"
|
||||
import { EditRegionForm } from "./components/edit-region-form"
|
||||
import { usePricePreferences } from "../../../hooks/api/price-preferences"
|
||||
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
export * from "./target-form"
|
||||
+789
@@ -0,0 +1,789 @@
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { Button, Checkbox } from "@medusajs/ui"
|
||||
import { keepPreviousData } from "@tanstack/react-query"
|
||||
import {
|
||||
OnChangeFn,
|
||||
RowSelectionState,
|
||||
createColumnHelper,
|
||||
} from "@tanstack/react-table"
|
||||
import { useEffect, useMemo, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import { useSearchParams } from "react-router-dom"
|
||||
import {
|
||||
StackedDrawer,
|
||||
StackedFocusModal,
|
||||
} from "../../../../../components/modals"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import {
|
||||
useCollections,
|
||||
useCustomerGroups,
|
||||
useProductTypes,
|
||||
useProducts,
|
||||
useTags,
|
||||
} from "../../../../../hooks/api"
|
||||
import {
|
||||
useCollectionTableColumns,
|
||||
useCustomerGroupTableColumns,
|
||||
useProductTableColumns,
|
||||
useProductTagTableColumns,
|
||||
useProductTypeTableColumns,
|
||||
} from "../../../../../hooks/table/columns"
|
||||
import {
|
||||
useCollectionTableFilters,
|
||||
useCustomerGroupTableFilters,
|
||||
useProductTableFilters,
|
||||
useProductTagTableFilters,
|
||||
useProductTypeTableFilters,
|
||||
} from "../../../../../hooks/table/filters"
|
||||
import {
|
||||
useCollectionTableQuery,
|
||||
useCustomerGroupTableQuery,
|
||||
useProductTableQuery,
|
||||
useProductTagTableQuery,
|
||||
useProductTypeTableQuery,
|
||||
} from "../../../../../hooks/table/query"
|
||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
import { TaxRateRuleReferenceType } from "../../constants"
|
||||
import { TaxRateRuleReference } from "../../schemas"
|
||||
|
||||
type TargetFormProps = {
|
||||
referenceType: TaxRateRuleReferenceType
|
||||
type: "focus" | "drawer"
|
||||
state: TaxRateRuleReference[]
|
||||
setState: (state: TaxRateRuleReference[]) => void
|
||||
}
|
||||
|
||||
function initRowSelection(state: TaxRateRuleReference[]) {
|
||||
return state.reduce((acc, reference) => {
|
||||
acc[reference.value] = true
|
||||
return acc
|
||||
}, {} as RowSelectionState)
|
||||
}
|
||||
|
||||
export const TargetForm = ({
|
||||
referenceType,
|
||||
type,
|
||||
setState,
|
||||
state,
|
||||
}: TargetFormProps) => {
|
||||
const { t } = useTranslation()
|
||||
const Component = type === "focus" ? StackedFocusModal : StackedDrawer
|
||||
|
||||
const [intermediate, setIntermediate] =
|
||||
useState<TaxRateRuleReference[]>(state)
|
||||
|
||||
const handleSave = () => {
|
||||
setState(intermediate)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex size-full flex-col overflow-hidden">
|
||||
<Component.Body className="min-h-0 p-0">
|
||||
<Table
|
||||
referenceType={referenceType}
|
||||
initialRowState={initRowSelection(state)}
|
||||
intermediate={intermediate}
|
||||
setIntermediate={setIntermediate}
|
||||
/>
|
||||
</Component.Body>
|
||||
<Component.Footer>
|
||||
<Component.Close asChild>
|
||||
<Button variant="secondary" size="small" type="button">
|
||||
{t("actions.cancel")}
|
||||
</Button>
|
||||
</Component.Close>
|
||||
<Button type="button" size="small" onClick={handleSave}>
|
||||
{t("actions.save")}
|
||||
</Button>
|
||||
</Component.Footer>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
type TableProps = {
|
||||
referenceType: TaxRateRuleReferenceType
|
||||
initialRowState: RowSelectionState
|
||||
intermediate: TaxRateRuleReference[]
|
||||
setIntermediate: (state: TaxRateRuleReference[]) => void
|
||||
}
|
||||
|
||||
const Table = ({ referenceType, ...props }: TableProps) => {
|
||||
switch (referenceType) {
|
||||
case TaxRateRuleReferenceType.CUSTOMER_GROUP:
|
||||
return <CustomerGroupTable {...props} />
|
||||
case TaxRateRuleReferenceType.PRODUCT:
|
||||
return <ProductTable {...props} />
|
||||
case TaxRateRuleReferenceType.PRODUCT_COLLECTION:
|
||||
return <ProductCollectionTable {...props} />
|
||||
case TaxRateRuleReferenceType.PRODUCT_TYPE:
|
||||
return <ProductTypeTable {...props} />
|
||||
case TaxRateRuleReferenceType.PRODUCT_TAG:
|
||||
return <ProductTagTable {...props} />
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
type TableImplementationProps = {
|
||||
initialRowState: RowSelectionState
|
||||
intermediate: TaxRateRuleReference[]
|
||||
setIntermediate: (state: TaxRateRuleReference[]) => void
|
||||
}
|
||||
|
||||
const PAGE_SIZE = 50
|
||||
|
||||
const PREFIX_CUSTOMER_GROUP = "cg"
|
||||
|
||||
const CustomerGroupTable = ({
|
||||
initialRowState,
|
||||
intermediate,
|
||||
setIntermediate,
|
||||
}: TableImplementationProps) => {
|
||||
const [rowSelection, setRowSelection] =
|
||||
useState<RowSelectionState>(initialRowState)
|
||||
|
||||
useCleanupSearchParams()
|
||||
|
||||
const { searchParams, raw } = useCustomerGroupTableQuery({
|
||||
pageSize: PAGE_SIZE,
|
||||
prefix: PREFIX_CUSTOMER_GROUP,
|
||||
})
|
||||
const { customer_groups, count, isLoading, isError, error } =
|
||||
useCustomerGroups(searchParams, {
|
||||
placeholderData: keepPreviousData,
|
||||
})
|
||||
|
||||
const updater: OnChangeFn<RowSelectionState> = (value) => {
|
||||
const state = typeof value === "function" ? value(rowSelection) : value
|
||||
const currentIds = Object.keys(rowSelection)
|
||||
|
||||
const ids = Object.keys(state)
|
||||
|
||||
const newIds = ids.filter((id) => !currentIds.includes(id))
|
||||
const removedIds = currentIds.filter((id) => !ids.includes(id))
|
||||
|
||||
const newCustomerGroups =
|
||||
customer_groups
|
||||
?.filter((cg) => newIds.includes(cg.id))
|
||||
.map((cg) => ({ value: cg.id, label: cg.name! })) || []
|
||||
|
||||
const filteredIntermediate = intermediate.filter(
|
||||
(cg) => !removedIds.includes(cg.value)
|
||||
)
|
||||
|
||||
setIntermediate([...filteredIntermediate, ...newCustomerGroups])
|
||||
setRowSelection(state)
|
||||
}
|
||||
|
||||
const filters = useCustomerGroupTableFilters()
|
||||
const columns = useGroupColumns()
|
||||
|
||||
const { table } = useDataTable({
|
||||
data: customer_groups || [],
|
||||
columns,
|
||||
count,
|
||||
enablePagination: true,
|
||||
enableRowSelection: true,
|
||||
getRowId: (row) => row.id,
|
||||
rowSelection: {
|
||||
state: rowSelection,
|
||||
updater,
|
||||
},
|
||||
pageSize: PAGE_SIZE,
|
||||
prefix: PREFIX_CUSTOMER_GROUP,
|
||||
})
|
||||
|
||||
if (isError) {
|
||||
throw error
|
||||
}
|
||||
|
||||
return (
|
||||
<DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
pageSize={PAGE_SIZE}
|
||||
count={count}
|
||||
isLoading={isLoading}
|
||||
filters={filters}
|
||||
orderBy={["name", "created_at", "updated_at"]}
|
||||
layout="fill"
|
||||
pagination
|
||||
search
|
||||
prefix={PREFIX_CUSTOMER_GROUP}
|
||||
queryObject={raw}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const cgColumnHelper = createColumnHelper<HttpTypes.AdminCustomerGroup>()
|
||||
|
||||
const useGroupColumns = () => {
|
||||
const base = useCustomerGroupTableColumns()
|
||||
|
||||
return useMemo(
|
||||
() => [
|
||||
cgColumnHelper.display({
|
||||
id: "select",
|
||||
header: ({ table }) => {
|
||||
return (
|
||||
<Checkbox
|
||||
checked={
|
||||
table.getIsSomePageRowsSelected()
|
||||
? "indeterminate"
|
||||
: table.getIsAllPageRowsSelected()
|
||||
}
|
||||
onCheckedChange={(value) =>
|
||||
table.toggleAllPageRowsSelected(!!value)
|
||||
}
|
||||
/>
|
||||
)
|
||||
},
|
||||
cell: ({ row }) => {
|
||||
return (
|
||||
<Checkbox
|
||||
checked={row.getIsSelected()}
|
||||
onCheckedChange={(value) => row.toggleSelected(!!value)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
}}
|
||||
/>
|
||||
)
|
||||
},
|
||||
}),
|
||||
...base,
|
||||
],
|
||||
[base]
|
||||
)
|
||||
}
|
||||
|
||||
const PREFIX_PRODUCT = "p"
|
||||
|
||||
const ProductTable = ({
|
||||
initialRowState,
|
||||
intermediate,
|
||||
setIntermediate,
|
||||
}: TableImplementationProps) => {
|
||||
const [rowSelection, setRowSelection] =
|
||||
useState<RowSelectionState>(initialRowState)
|
||||
|
||||
useCleanupSearchParams()
|
||||
|
||||
const { searchParams, raw } = useProductTableQuery({
|
||||
pageSize: PAGE_SIZE,
|
||||
prefix: PREFIX_PRODUCT,
|
||||
})
|
||||
|
||||
const { products, count, isLoading, isError, error } = useProducts(
|
||||
searchParams,
|
||||
{
|
||||
placeholderData: keepPreviousData,
|
||||
}
|
||||
)
|
||||
|
||||
const updater: OnChangeFn<RowSelectionState> = (value) => {
|
||||
const state = typeof value === "function" ? value(rowSelection) : value
|
||||
const currentIds = Object.keys(rowSelection)
|
||||
|
||||
const ids = Object.keys(state)
|
||||
|
||||
const newIds = ids.filter((id) => !currentIds.includes(id))
|
||||
const removedIds = currentIds.filter((id) => !ids.includes(id))
|
||||
|
||||
const newProducts =
|
||||
products
|
||||
?.filter((p) => newIds.includes(p.id))
|
||||
.map((p) => ({
|
||||
value: p.id,
|
||||
label: p.title!,
|
||||
})) || []
|
||||
|
||||
const filteredIntermediate = intermediate.filter(
|
||||
(p) => !removedIds.includes(p.value)
|
||||
)
|
||||
|
||||
setIntermediate([...filteredIntermediate, ...newProducts])
|
||||
setRowSelection(state)
|
||||
}
|
||||
|
||||
const filters = useProductTableFilters()
|
||||
const columns = useProductColumns()
|
||||
|
||||
const { table } = useDataTable({
|
||||
data: products || [],
|
||||
columns,
|
||||
count,
|
||||
enablePagination: true,
|
||||
enableRowSelection: true,
|
||||
getRowId: (row) => row.id,
|
||||
rowSelection: {
|
||||
state: rowSelection,
|
||||
updater,
|
||||
},
|
||||
pageSize: PAGE_SIZE,
|
||||
prefix: PREFIX_PRODUCT,
|
||||
})
|
||||
|
||||
if (isError) {
|
||||
throw error
|
||||
}
|
||||
|
||||
return (
|
||||
<DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
pageSize={PAGE_SIZE}
|
||||
count={count}
|
||||
isLoading={isLoading}
|
||||
filters={filters}
|
||||
orderBy={["title", "created_at", "updated_at"]}
|
||||
layout="fill"
|
||||
pagination
|
||||
search
|
||||
prefix={PREFIX_PRODUCT}
|
||||
queryObject={raw}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const pColumnHelper = createColumnHelper<HttpTypes.AdminProduct>()
|
||||
|
||||
const useProductColumns = () => {
|
||||
const base = useProductTableColumns()
|
||||
|
||||
return useMemo(
|
||||
() => [
|
||||
pColumnHelper.display({
|
||||
id: "select",
|
||||
header: ({ table }) => {
|
||||
return (
|
||||
<Checkbox
|
||||
checked={
|
||||
table.getIsSomePageRowsSelected()
|
||||
? "indeterminate"
|
||||
: table.getIsAllPageRowsSelected()
|
||||
}
|
||||
onCheckedChange={(value) =>
|
||||
table.toggleAllPageRowsSelected(!!value)
|
||||
}
|
||||
/>
|
||||
)
|
||||
},
|
||||
cell: ({ row }) => {
|
||||
return (
|
||||
<Checkbox
|
||||
checked={row.getIsSelected()}
|
||||
onCheckedChange={(value) => row.toggleSelected(!!value)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
}}
|
||||
/>
|
||||
)
|
||||
},
|
||||
}),
|
||||
...base,
|
||||
],
|
||||
[base]
|
||||
)
|
||||
}
|
||||
|
||||
const PREFIX_PRODUCT_COLLECTION = "pc"
|
||||
|
||||
const ProductCollectionTable = ({
|
||||
initialRowState,
|
||||
intermediate,
|
||||
setIntermediate,
|
||||
}: TableImplementationProps) => {
|
||||
const [rowSelection, setRowSelection] =
|
||||
useState<RowSelectionState>(initialRowState)
|
||||
|
||||
useCleanupSearchParams()
|
||||
|
||||
const { searchParams, raw } = useCollectionTableQuery({
|
||||
pageSize: PAGE_SIZE,
|
||||
prefix: PREFIX_PRODUCT_COLLECTION,
|
||||
})
|
||||
|
||||
const { collections, count, isLoading, isError, error } = useCollections(
|
||||
searchParams,
|
||||
{
|
||||
placeholderData: keepPreviousData,
|
||||
}
|
||||
)
|
||||
|
||||
const updater: OnChangeFn<RowSelectionState> = (value) => {
|
||||
const state = typeof value === "function" ? value(rowSelection) : value
|
||||
const currentIds = Object.keys(rowSelection)
|
||||
|
||||
const ids = Object.keys(state)
|
||||
|
||||
const newIds = ids.filter((id) => !currentIds.includes(id))
|
||||
const removedIds = currentIds.filter((id) => !ids.includes(id))
|
||||
|
||||
const newCollections =
|
||||
collections
|
||||
?.filter((p) => newIds.includes(p.id))
|
||||
.map((p) => ({
|
||||
value: p.id,
|
||||
label: p.title,
|
||||
})) || []
|
||||
|
||||
const filteredIntermediate = intermediate.filter(
|
||||
(p) => !removedIds.includes(p.value)
|
||||
)
|
||||
|
||||
setIntermediate([...filteredIntermediate, ...newCollections])
|
||||
setRowSelection(state)
|
||||
}
|
||||
|
||||
const filters = useCollectionTableFilters()
|
||||
const columns = useCollectionColumns()
|
||||
|
||||
const { table } = useDataTable({
|
||||
data: collections || [],
|
||||
columns,
|
||||
count,
|
||||
enablePagination: true,
|
||||
enableRowSelection: true,
|
||||
getRowId: (row) => row.id,
|
||||
rowSelection: {
|
||||
state: rowSelection,
|
||||
updater,
|
||||
},
|
||||
pageSize: PAGE_SIZE,
|
||||
prefix: PREFIX_PRODUCT_COLLECTION,
|
||||
})
|
||||
|
||||
if (isError) {
|
||||
throw error
|
||||
}
|
||||
|
||||
return (
|
||||
<DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
pageSize={PAGE_SIZE}
|
||||
count={count}
|
||||
isLoading={isLoading}
|
||||
filters={filters}
|
||||
orderBy={["title", "created_at", "updated_at"]}
|
||||
layout="fill"
|
||||
pagination
|
||||
search
|
||||
prefix={PREFIX_PRODUCT_COLLECTION}
|
||||
queryObject={raw}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const pcColumnHelper = createColumnHelper<HttpTypes.AdminCollection>()
|
||||
|
||||
const useCollectionColumns = () => {
|
||||
const base = useCollectionTableColumns()
|
||||
|
||||
return useMemo(
|
||||
() => [
|
||||
pcColumnHelper.display({
|
||||
id: "select",
|
||||
header: ({ table }) => {
|
||||
return (
|
||||
<Checkbox
|
||||
checked={
|
||||
table.getIsSomePageRowsSelected()
|
||||
? "indeterminate"
|
||||
: table.getIsAllPageRowsSelected()
|
||||
}
|
||||
onCheckedChange={(value) =>
|
||||
table.toggleAllPageRowsSelected(!!value)
|
||||
}
|
||||
/>
|
||||
)
|
||||
},
|
||||
cell: ({ row }) => {
|
||||
return (
|
||||
<Checkbox
|
||||
checked={row.getIsSelected()}
|
||||
onCheckedChange={(value) => row.toggleSelected(!!value)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
}}
|
||||
/>
|
||||
)
|
||||
},
|
||||
}),
|
||||
...base,
|
||||
],
|
||||
[base]
|
||||
)
|
||||
}
|
||||
|
||||
const PREFIX_PRODUCT_TYPE = "pt"
|
||||
|
||||
const ProductTypeTable = ({
|
||||
initialRowState,
|
||||
intermediate,
|
||||
setIntermediate,
|
||||
}: TableImplementationProps) => {
|
||||
const [rowSelection, setRowSelection] =
|
||||
useState<RowSelectionState>(initialRowState)
|
||||
|
||||
useCleanupSearchParams()
|
||||
|
||||
const { searchParams, raw } = useProductTypeTableQuery({
|
||||
pageSize: PAGE_SIZE,
|
||||
prefix: PREFIX_PRODUCT_TYPE,
|
||||
})
|
||||
|
||||
const { product_types, count, isLoading, isError, error } = useProductTypes(
|
||||
searchParams,
|
||||
{
|
||||
placeholderData: keepPreviousData,
|
||||
}
|
||||
)
|
||||
|
||||
const updater: OnChangeFn<RowSelectionState> = (value) => {
|
||||
const state = typeof value === "function" ? value(rowSelection) : value
|
||||
const currentIds = Object.keys(rowSelection)
|
||||
|
||||
const ids = Object.keys(state)
|
||||
|
||||
const newIds = ids.filter((id) => !currentIds.includes(id))
|
||||
const removedIds = currentIds.filter((id) => !ids.includes(id))
|
||||
|
||||
const newTypes =
|
||||
product_types
|
||||
?.filter((p) => newIds.includes(p.id))
|
||||
.map((p) => ({
|
||||
value: p.id,
|
||||
label: p.value,
|
||||
})) || []
|
||||
|
||||
const filteredIntermediate = intermediate.filter(
|
||||
(p) => !removedIds.includes(p.value)
|
||||
)
|
||||
|
||||
setIntermediate([...filteredIntermediate, ...newTypes])
|
||||
setRowSelection(state)
|
||||
}
|
||||
|
||||
const filters = useProductTypeTableFilters()
|
||||
const columns = useProductTypeColumns()
|
||||
|
||||
const { table } = useDataTable({
|
||||
data: product_types || [],
|
||||
columns,
|
||||
count,
|
||||
enablePagination: true,
|
||||
enableRowSelection: true,
|
||||
getRowId: (row) => row.id,
|
||||
rowSelection: {
|
||||
state: rowSelection,
|
||||
updater,
|
||||
},
|
||||
pageSize: PAGE_SIZE,
|
||||
prefix: PREFIX_PRODUCT_TYPE,
|
||||
})
|
||||
|
||||
if (isError) {
|
||||
throw error
|
||||
}
|
||||
|
||||
return (
|
||||
<DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
pageSize={PAGE_SIZE}
|
||||
count={count}
|
||||
isLoading={isLoading}
|
||||
filters={filters}
|
||||
orderBy={["value", "created_at", "updated_at"]}
|
||||
layout="fill"
|
||||
pagination
|
||||
search
|
||||
prefix={PREFIX_PRODUCT_TYPE}
|
||||
queryObject={raw}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const ptColumnHelper = createColumnHelper<HttpTypes.AdminProductType>()
|
||||
|
||||
const useProductTypeColumns = () => {
|
||||
const base = useProductTypeTableColumns()
|
||||
|
||||
return useMemo(
|
||||
() => [
|
||||
ptColumnHelper.display({
|
||||
id: "select",
|
||||
header: ({ table }) => {
|
||||
return (
|
||||
<Checkbox
|
||||
checked={
|
||||
table.getIsSomePageRowsSelected()
|
||||
? "indeterminate"
|
||||
: table.getIsAllPageRowsSelected()
|
||||
}
|
||||
onCheckedChange={(value) =>
|
||||
table.toggleAllPageRowsSelected(!!value)
|
||||
}
|
||||
/>
|
||||
)
|
||||
},
|
||||
cell: ({ row }) => {
|
||||
return (
|
||||
<Checkbox
|
||||
checked={row.getIsSelected()}
|
||||
onCheckedChange={(value) => row.toggleSelected(!!value)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
}}
|
||||
/>
|
||||
)
|
||||
},
|
||||
}),
|
||||
...base,
|
||||
],
|
||||
[base]
|
||||
)
|
||||
}
|
||||
|
||||
const PREFIX_PRODUCT_TAG = "ptag"
|
||||
|
||||
const ProductTagTable = ({
|
||||
initialRowState,
|
||||
intermediate,
|
||||
setIntermediate,
|
||||
}: TableImplementationProps) => {
|
||||
const [rowSelection, setRowSelection] =
|
||||
useState<RowSelectionState>(initialRowState)
|
||||
|
||||
useCleanupSearchParams()
|
||||
|
||||
const { searchParams, raw } = useProductTagTableQuery({
|
||||
pageSize: PAGE_SIZE,
|
||||
prefix: PREFIX_PRODUCT_TAG,
|
||||
})
|
||||
|
||||
const { product_tags, count, isLoading, isError, error } = useTags(
|
||||
searchParams,
|
||||
{
|
||||
placeholderData: keepPreviousData,
|
||||
}
|
||||
)
|
||||
|
||||
const updater: OnChangeFn<RowSelectionState> = (value) => {
|
||||
const state = typeof value === "function" ? value(rowSelection) : value
|
||||
const currentIds = Object.keys(rowSelection)
|
||||
|
||||
const ids = Object.keys(state)
|
||||
|
||||
const newIds = ids.filter((id) => !currentIds.includes(id))
|
||||
const removedIds = currentIds.filter((id) => !ids.includes(id))
|
||||
|
||||
const newTags =
|
||||
product_tags
|
||||
?.filter((p) => newIds.includes(p.id))
|
||||
.map((p) => ({
|
||||
value: p.id,
|
||||
label: p.value,
|
||||
})) || []
|
||||
|
||||
const filteredIntermediate = intermediate.filter(
|
||||
(p) => !removedIds.includes(p.value)
|
||||
)
|
||||
|
||||
setIntermediate([...filteredIntermediate, ...newTags])
|
||||
setRowSelection(state)
|
||||
}
|
||||
|
||||
const filters = useProductTagTableFilters()
|
||||
const columns = useProductTagColumns()
|
||||
|
||||
const { table } = useDataTable({
|
||||
data: product_tags || [],
|
||||
columns,
|
||||
count,
|
||||
enablePagination: true,
|
||||
enableRowSelection: true,
|
||||
getRowId: (row) => row.id,
|
||||
rowSelection: {
|
||||
state: rowSelection,
|
||||
updater,
|
||||
},
|
||||
pageSize: PAGE_SIZE,
|
||||
prefix: PREFIX_PRODUCT_TAG,
|
||||
})
|
||||
|
||||
if (isError) {
|
||||
throw error
|
||||
}
|
||||
|
||||
return (
|
||||
<DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
pageSize={PAGE_SIZE}
|
||||
count={count}
|
||||
isLoading={isLoading}
|
||||
filters={filters}
|
||||
orderBy={["value", "created_at", "updated_at"]}
|
||||
layout="fill"
|
||||
pagination
|
||||
search
|
||||
prefix={PREFIX_PRODUCT_TAG}
|
||||
queryObject={raw}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const ptagColumnHelper = createColumnHelper<HttpTypes.AdminProductTag>()
|
||||
|
||||
const useProductTagColumns = () => {
|
||||
const base = useProductTagTableColumns()
|
||||
|
||||
return useMemo(
|
||||
() => [
|
||||
ptagColumnHelper.display({
|
||||
id: "select",
|
||||
header: ({ table }) => {
|
||||
return (
|
||||
<Checkbox
|
||||
checked={
|
||||
table.getIsSomePageRowsSelected()
|
||||
? "indeterminate"
|
||||
: table.getIsAllPageRowsSelected()
|
||||
}
|
||||
onCheckedChange={(value) =>
|
||||
table.toggleAllPageRowsSelected(!!value)
|
||||
}
|
||||
/>
|
||||
)
|
||||
},
|
||||
cell: ({ row }) => {
|
||||
return (
|
||||
<Checkbox
|
||||
checked={row.getIsSelected()}
|
||||
onCheckedChange={(value) => row.toggleSelected(!!value)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
}}
|
||||
/>
|
||||
)
|
||||
},
|
||||
}),
|
||||
...base,
|
||||
],
|
||||
[base]
|
||||
)
|
||||
}
|
||||
|
||||
const useCleanupSearchParams = () => {
|
||||
const [_, setSearchParams] = useSearchParams()
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
setSearchParams({})
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
export * from "./target-item"
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
import { XMarkMini } from "@medusajs/icons"
|
||||
import { IconButton, Text } from "@medusajs/ui"
|
||||
|
||||
type TargetItemProps = {
|
||||
index: number
|
||||
onRemove: (index: number) => void
|
||||
label: string
|
||||
}
|
||||
|
||||
export const TargetItem = ({ index, label, onRemove }: TargetItemProps) => {
|
||||
return (
|
||||
<div className="bg-ui-bg-field-component shadow-borders-base flex items-center justify-between gap-2 rounded-md px-2 py-0.5">
|
||||
<Text size="small" leading="compact">
|
||||
{label}
|
||||
</Text>
|
||||
<IconButton
|
||||
size="small"
|
||||
variant="transparent"
|
||||
type="button"
|
||||
onClick={() => onRemove(index)}
|
||||
>
|
||||
<XMarkMini />
|
||||
</IconButton>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
export * from "./tax-override-card"
|
||||
+378
@@ -0,0 +1,378 @@
|
||||
import {
|
||||
ArrowDownRightMini,
|
||||
PencilSquare,
|
||||
Trash,
|
||||
TriangleRightMini,
|
||||
} from "@medusajs/icons"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { Badge, IconButton, StatusBadge, Text, Tooltip } from "@medusajs/ui"
|
||||
import * as Collapsible from "@radix-ui/react-collapsible"
|
||||
import { ComponentPropsWithoutRef } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import { FetchError } from "@medusajs/js-sdk"
|
||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||
import { Divider } from "../../../../../components/common/divider"
|
||||
import { useCollections } from "../../../../../hooks/api/collections"
|
||||
import { useCustomerGroups } from "../../../../../hooks/api/customer-groups"
|
||||
import { useProductTypes } from "../../../../../hooks/api/product-types"
|
||||
import { useProducts } from "../../../../../hooks/api/products"
|
||||
import { useTags } from "../../../../../hooks/api/tags"
|
||||
import { formatPercentage } from "../../../../../lib/percentage-helpers"
|
||||
import { TaxRateRuleReferenceType } from "../../constants"
|
||||
import { useDeleteTaxRateAction } from "../../hooks"
|
||||
|
||||
interface TaxOverrideCardProps extends ComponentPropsWithoutRef<"div"> {
|
||||
taxRate: HttpTypes.AdminTaxRate
|
||||
}
|
||||
|
||||
export const TaxOverrideCard = ({ taxRate }: TaxOverrideCardProps) => {
|
||||
const { t } = useTranslation()
|
||||
const handleDelete = useDeleteTaxRateAction(taxRate)
|
||||
|
||||
if (taxRate.is_default) {
|
||||
return null
|
||||
}
|
||||
|
||||
const groupedRules = taxRate.rules.reduce((acc, rule) => {
|
||||
if (!acc[rule.reference]) {
|
||||
acc[rule.reference] = []
|
||||
}
|
||||
|
||||
acc[rule.reference].push(rule.reference_id)
|
||||
|
||||
return acc
|
||||
}, {} as Record<string, string[]>)
|
||||
|
||||
const validKeys = Object.values(TaxRateRuleReferenceType)
|
||||
const numberOfTargets = Object.keys(groupedRules).map((key) =>
|
||||
validKeys.includes(key as TaxRateRuleReferenceType)
|
||||
).length
|
||||
|
||||
return (
|
||||
<Collapsible.Root>
|
||||
<div className="flex items-center justify-between px-6 py-3">
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Collapsible.Trigger asChild>
|
||||
<IconButton size="2xsmall" variant="transparent" className="group">
|
||||
<TriangleRightMini className="text-ui-fg-muted transition-transform group-data-[state='open']:rotate-90" />
|
||||
</IconButton>
|
||||
</Collapsible.Trigger>
|
||||
<div className="flex items-center gap-x-1.5">
|
||||
<Text size="small" weight="plus" leading="compact">
|
||||
{taxRate.name}
|
||||
</Text>
|
||||
{taxRate.code && (
|
||||
<div className="text-ui-fg-subtle flex items-center gap-x-1.5">
|
||||
<Text size="small" leading="compact">
|
||||
·
|
||||
</Text>
|
||||
<Text size="small" leading="compact">
|
||||
{taxRate.code}
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-x-3">
|
||||
<Text size="small" leading="compact" className="text-ui-fg-subtle">
|
||||
{t("taxRegions.fields.targets.numberOfTargets", {
|
||||
count: numberOfTargets,
|
||||
})}
|
||||
</Text>
|
||||
<div className="bg-ui-border-base h-3 w-px" />
|
||||
<StatusBadge color={taxRate.is_combinable ? "green" : "grey"}>
|
||||
{taxRate.is_combinable
|
||||
? t("taxRegions.fields.isCombinable.true")
|
||||
: t("taxRegions.fields.isCombinable.false")}
|
||||
</StatusBadge>
|
||||
<ActionMenu
|
||||
groups={[
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
label: t("actions.edit"),
|
||||
icon: <PencilSquare />,
|
||||
to: `overrides/${taxRate.id}/edit`,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
label: t("actions.delete"),
|
||||
icon: <Trash />,
|
||||
onClick: handleDelete,
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Collapsible.Content>
|
||||
<div className="bg-ui-bg-subtle">
|
||||
<Divider variant="dashed" />
|
||||
<div className="px-6 py-3">
|
||||
<div className="flex items-center gap-x-3">
|
||||
<div className="text-ui-fg-muted flex size-5 items-center justify-center">
|
||||
<ArrowDownRightMini />
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-x-1.5 gap-y-2">
|
||||
<Badge size="2xsmall">{formatPercentage(taxRate.rate)}</Badge>
|
||||
<Text
|
||||
size="small"
|
||||
leading="compact"
|
||||
className="text-ui-fg-subtle"
|
||||
>
|
||||
{t("taxRegions.fields.targets.operators.on")}
|
||||
</Text>
|
||||
{Object.entries(groupedRules).map(([reference, ids], index) => {
|
||||
return (
|
||||
<div
|
||||
key={reference}
|
||||
className="flex items-center gap-x-1.5"
|
||||
>
|
||||
<Reference
|
||||
key={reference}
|
||||
reference={reference as TaxRateRuleReferenceType}
|
||||
ids={ids}
|
||||
/>
|
||||
{index < Object.keys(groupedRules).length - 1 && (
|
||||
<Text
|
||||
size="small"
|
||||
leading="compact"
|
||||
className="text-ui-fg-subtle"
|
||||
>
|
||||
{t("taxRegions.fields.targets.operators.and")}
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Collapsible.Content>
|
||||
</Collapsible.Root>
|
||||
)
|
||||
}
|
||||
|
||||
const Reference = ({
|
||||
reference,
|
||||
ids,
|
||||
}: {
|
||||
reference: TaxRateRuleReferenceType
|
||||
ids: string[]
|
||||
}) => {
|
||||
return (
|
||||
<div className="flex items-center gap-x-1.5">
|
||||
<ReferenceBadge reference={reference} />
|
||||
<ReferenceValues type={reference} ids={ids} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const ReferenceBadge = ({
|
||||
reference,
|
||||
}: {
|
||||
reference: TaxRateRuleReferenceType
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
let label: string | null = null
|
||||
|
||||
switch (reference) {
|
||||
case TaxRateRuleReferenceType.PRODUCT:
|
||||
label = t("taxRegions.fields.targets.tags.product")
|
||||
break
|
||||
case TaxRateRuleReferenceType.PRODUCT_COLLECTION:
|
||||
label = t("taxRegions.fields.targets.tags.productCollection")
|
||||
break
|
||||
case TaxRateRuleReferenceType.PRODUCT_TAG:
|
||||
label = t("taxRegions.fields.targets.tags.productTag")
|
||||
break
|
||||
case TaxRateRuleReferenceType.PRODUCT_TYPE:
|
||||
label = t("taxRegions.fields.targets.tags.productType")
|
||||
break
|
||||
case TaxRateRuleReferenceType.CUSTOMER_GROUP:
|
||||
label = t("taxRegions.fields.targets.tags.customerGroup")
|
||||
break
|
||||
}
|
||||
|
||||
if (!label) {
|
||||
return null
|
||||
}
|
||||
|
||||
return <Badge size="2xsmall">{label}</Badge>
|
||||
}
|
||||
|
||||
const ReferenceValues = ({
|
||||
type,
|
||||
ids,
|
||||
}: {
|
||||
type: TaxRateRuleReferenceType
|
||||
ids: string[]
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { isPending, additional, labels, isError, error } = useReferenceValues(
|
||||
type,
|
||||
ids
|
||||
)
|
||||
|
||||
if (isError) {
|
||||
throw error
|
||||
}
|
||||
|
||||
if (isPending) {
|
||||
return (
|
||||
<div className="bg-ui-tag-neutral-bg border-ui-tag-neutral-border h-5 w-14 animate-pulse rounded-md" />
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
content={
|
||||
<ul>
|
||||
{labels?.map((label: string, index) => (
|
||||
<li key={index}>{label}</li>
|
||||
))}
|
||||
{additional > 0 && (
|
||||
<li>
|
||||
{t("taxRegions.fields.targets.additionalValues", {
|
||||
count: additional,
|
||||
})}
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
}
|
||||
>
|
||||
<Badge size="2xsmall">
|
||||
{t("taxRegions.fields.targets.values", {
|
||||
count: ids.length,
|
||||
})}
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
const useReferenceValues = (
|
||||
type: TaxRateRuleReferenceType,
|
||||
ids: string[]
|
||||
): {
|
||||
labels: string[] | undefined
|
||||
isPending: boolean
|
||||
additional: number
|
||||
isError: boolean
|
||||
error: FetchError | null
|
||||
} => {
|
||||
const products = useProducts(
|
||||
{
|
||||
id: ids,
|
||||
limit: 10,
|
||||
},
|
||||
{
|
||||
enabled: !!ids.length && type === TaxRateRuleReferenceType.PRODUCT,
|
||||
}
|
||||
)
|
||||
|
||||
const tags = useTags(
|
||||
{
|
||||
id: ids,
|
||||
limit: 10,
|
||||
},
|
||||
{
|
||||
enabled: !!ids.length && type === TaxRateRuleReferenceType.PRODUCT_TAG,
|
||||
}
|
||||
)
|
||||
|
||||
const productTypes = useProductTypes(
|
||||
{
|
||||
id: ids,
|
||||
limit: 10,
|
||||
},
|
||||
{
|
||||
enabled: !!ids.length && type === TaxRateRuleReferenceType.PRODUCT_TYPE,
|
||||
}
|
||||
)
|
||||
|
||||
const collections = useCollections(
|
||||
{
|
||||
id: ids,
|
||||
limit: 10,
|
||||
},
|
||||
{
|
||||
enabled:
|
||||
!!ids.length && type === TaxRateRuleReferenceType.PRODUCT_COLLECTION,
|
||||
}
|
||||
)
|
||||
|
||||
const customerGroups = useCustomerGroups(
|
||||
{
|
||||
id: ids,
|
||||
limit: 10,
|
||||
},
|
||||
{
|
||||
enabled: !!ids.length && type === TaxRateRuleReferenceType.CUSTOMER_GROUP,
|
||||
}
|
||||
)
|
||||
|
||||
switch (type) {
|
||||
case TaxRateRuleReferenceType.PRODUCT:
|
||||
return {
|
||||
labels: products.products?.map((product) => product.title),
|
||||
isPending: products.isPending,
|
||||
additional:
|
||||
products.products && products.count
|
||||
? products.count - products.products.length
|
||||
: 0,
|
||||
isError: products.isError,
|
||||
error: products.error,
|
||||
}
|
||||
case TaxRateRuleReferenceType.PRODUCT_TAG:
|
||||
return {
|
||||
labels: tags.product_tags?.map((tag: any) => tag.value),
|
||||
isPending: tags.isPending,
|
||||
additional:
|
||||
tags.product_tags && tags.count
|
||||
? tags.count - tags.product_tags.length
|
||||
: 0,
|
||||
isError: tags.isError,
|
||||
error: tags.error,
|
||||
}
|
||||
case TaxRateRuleReferenceType.PRODUCT_TYPE:
|
||||
return {
|
||||
labels: productTypes.product_types?.map((type) => type.value),
|
||||
isPending: productTypes.isPending,
|
||||
additional:
|
||||
productTypes.product_types && productTypes.count
|
||||
? productTypes.count - productTypes.product_types.length
|
||||
: 0,
|
||||
isError: productTypes.isError,
|
||||
error: productTypes.error,
|
||||
}
|
||||
case TaxRateRuleReferenceType.PRODUCT_COLLECTION:
|
||||
return {
|
||||
labels: collections.collections?.map((collection) => collection.title!),
|
||||
isPending: collections.isPending,
|
||||
additional:
|
||||
collections.collections && collections.count
|
||||
? collections.count - collections.collections.length
|
||||
: 0,
|
||||
isError: collections.isError,
|
||||
error: collections.error,
|
||||
}
|
||||
case TaxRateRuleReferenceType.CUSTOMER_GROUP:
|
||||
return {
|
||||
labels: customerGroups.customer_groups?.map((group) => group.name!),
|
||||
isPending: customerGroups.isPending,
|
||||
additional:
|
||||
customerGroups.customer_groups && customerGroups.count
|
||||
? customerGroups.count - customerGroups.customer_groups.length
|
||||
: 0,
|
||||
isError: customerGroups.isError,
|
||||
error: customerGroups.error,
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
export * from "./tax-override-table"
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { Button } from "@medusajs/ui"
|
||||
import { Table } from "@tanstack/react-table"
|
||||
import { ReactNode } from "react"
|
||||
import { Link } from "react-router-dom"
|
||||
import {
|
||||
NoRecords,
|
||||
NoResults,
|
||||
} from "../../../../../components/common/empty-table-content"
|
||||
import { TableFooterSkeleton } from "../../../../../components/common/skeleton"
|
||||
import { LocalizedTablePagination } from "../../../../../components/localization/localized-table-pagination"
|
||||
import { DataTableOrderBy } from "../../../../../components/table/data-table/data-table-order-by"
|
||||
import { DataTableSearch } from "../../../../../components/table/data-table/data-table-search"
|
||||
import { TaxOverrideCard } from "../tax-override-card"
|
||||
|
||||
type TaxOverrideTableProps = {
|
||||
isPending: boolean
|
||||
queryObject: Record<string, any>
|
||||
count?: number
|
||||
table: Table<HttpTypes.AdminTaxRate>
|
||||
action: { label: string; to: string }
|
||||
prefix?: string
|
||||
children?: ReactNode
|
||||
}
|
||||
|
||||
export const TaxOverrideTable = ({
|
||||
isPending,
|
||||
action,
|
||||
count = 0,
|
||||
table,
|
||||
queryObject,
|
||||
prefix,
|
||||
children,
|
||||
}: TaxOverrideTableProps) => {
|
||||
if (isPending) {
|
||||
return (
|
||||
<div className="flex flex-col divide-y">
|
||||
{Array.from({ length: 3 }).map((_, index) => {
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className="bg-ui-bg-field-component h-[52px] w-full animate-pulse"
|
||||
/>
|
||||
)
|
||||
})}
|
||||
<TableFooterSkeleton layout="fit" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const noQuery =
|
||||
Object.values(queryObject).filter((v) => Boolean(v)).length === 0
|
||||
const noResults = !isPending && count === 0 && !noQuery
|
||||
const noRecords = !isPending && count === 0 && noQuery
|
||||
|
||||
const { pageIndex, pageSize } = table.getState().pagination
|
||||
|
||||
return (
|
||||
<div className="flex flex-col divide-y">
|
||||
<div className="flex flex-col justify-between gap-x-4 gap-y-3 px-6 py-4 md:flex-row md:items-center">
|
||||
<div>{children}</div>
|
||||
<div className="flex items-center gap-x-2">
|
||||
{!noRecords && (
|
||||
<div className="flex w-full items-center gap-x-2 md:w-fit">
|
||||
<div className="w-full md:w-fit">
|
||||
<DataTableSearch prefix={prefix} />
|
||||
</div>
|
||||
<DataTableOrderBy
|
||||
keys={["name", "rate", "code", "updated_at", "created_at"]}
|
||||
prefix={prefix}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<Link to={action.to}>
|
||||
<Button size="small" variant="secondary">
|
||||
{action.label}
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
{noResults && <NoResults />}
|
||||
{noRecords && <NoRecords />}
|
||||
{!noRecords && !noResults
|
||||
? !isPending
|
||||
? table.getRowModel().rows.map((row) => {
|
||||
return (
|
||||
<TaxOverrideCard
|
||||
key={row.id}
|
||||
taxRate={row.original}
|
||||
role="row"
|
||||
aria-rowindex={row.index}
|
||||
/>
|
||||
)
|
||||
})
|
||||
: Array.from({ length: 3 }).map((_, index) => {
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className="bg-ui-bg-field-component h-[60px] w-full animate-pulse"
|
||||
/>
|
||||
)
|
||||
})
|
||||
: null}
|
||||
{!noRecords && (
|
||||
<LocalizedTablePagination
|
||||
prefix={prefix}
|
||||
canNextPage={table.getCanNextPage()}
|
||||
canPreviousPage={table.getCanPreviousPage()}
|
||||
count={count}
|
||||
nextPage={table.nextPage}
|
||||
previousPage={table.previousPage}
|
||||
pageCount={table.getPageCount()}
|
||||
pageIndex={pageIndex}
|
||||
pageSize={pageSize}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
export * from "./tax-rate-line"
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
import { PencilSquare, Trash } from "@medusajs/icons"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { StatusBadge, Text } from "@medusajs/ui"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||
import { formatPercentage } from "../../../../../lib/percentage-helpers"
|
||||
import { useDeleteTaxRateAction } from "../../hooks"
|
||||
|
||||
type TaxRateLineProps = {
|
||||
taxRate: HttpTypes.AdminTaxRate
|
||||
isSublevelTaxRate?: boolean
|
||||
}
|
||||
|
||||
export const TaxRateLine = ({
|
||||
taxRate,
|
||||
isSublevelTaxRate,
|
||||
}: TaxRateLineProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className="text-ui-fg-subtle grid grid-cols-[1fr_1fr_auto] items-center gap-4 px-6 py-4">
|
||||
<div className="flex items-center gap-x-1.5">
|
||||
<Text size="small" weight="plus" leading="compact">
|
||||
{taxRate.name}
|
||||
</Text>
|
||||
{taxRate.code && (
|
||||
<div className="flex items-center gap-x-1.5">
|
||||
<Text size="small" leading="compact">
|
||||
·
|
||||
</Text>
|
||||
<Text size="small" leading="compact">
|
||||
{taxRate.code}
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Text size="small" leading="compact">
|
||||
{formatPercentage(taxRate.rate)}
|
||||
</Text>
|
||||
<div className="flex items-center justify-end gap-x-2">
|
||||
{isSublevelTaxRate && (
|
||||
<StatusBadge color={taxRate.is_combinable ? "green" : "grey"}>
|
||||
{taxRate.is_combinable
|
||||
? t("taxRegions.fields.isCombinable.true")
|
||||
: t("taxRegions.fields.isCombinable.false")}
|
||||
</StatusBadge>
|
||||
)}
|
||||
<TaxRateActions taxRate={taxRate} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const TaxRateActions = ({ taxRate }: { taxRate: HttpTypes.AdminTaxRate }) => {
|
||||
const { t } = useTranslation()
|
||||
const handleDelete = useDeleteTaxRateAction(taxRate)
|
||||
|
||||
return (
|
||||
<ActionMenu
|
||||
groups={[
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
label: t("actions.edit"),
|
||||
icon: <PencilSquare />,
|
||||
to: `tax-rates/${taxRate.id}/edit`,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
label: t("actions.delete"),
|
||||
icon: <Trash />,
|
||||
onClick: handleDelete,
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
export * from "./tax-region-card"
|
||||
+200
@@ -0,0 +1,200 @@
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { Heading, Text, Tooltip, clx } from "@medusajs/ui"
|
||||
import ReactCountryFlag from "react-country-flag"
|
||||
|
||||
import { ExclamationCircle, MapPin, Plus, Trash } from "@medusajs/icons"
|
||||
import { ComponentPropsWithoutRef, ReactNode } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Link } from "react-router-dom"
|
||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||
import { IconAvatar } from "../../../../../components/common/icon-avatar"
|
||||
import { getCountryByIso2 } from "../../../../../lib/data/countries"
|
||||
import {
|
||||
getProvinceByIso2,
|
||||
isProvinceInCountry,
|
||||
} from "../../../../../lib/data/country-states"
|
||||
import { useDeleteTaxRegionAction } from "../../hooks"
|
||||
|
||||
interface TaxRegionCardProps extends ComponentPropsWithoutRef<"div"> {
|
||||
taxRegion: HttpTypes.AdminTaxRegion
|
||||
type?: "header" | "list"
|
||||
variant?: "country" | "province"
|
||||
asLink?: boolean
|
||||
badge?: ReactNode
|
||||
}
|
||||
|
||||
export const TaxRegionCard = ({
|
||||
taxRegion,
|
||||
type = "list",
|
||||
variant = "country",
|
||||
asLink = true,
|
||||
badge,
|
||||
}: TaxRegionCardProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { id, country_code, province_code } = taxRegion
|
||||
|
||||
const country = getCountryByIso2(country_code)
|
||||
const province = getProvinceByIso2(province_code)
|
||||
|
||||
let name = "N/A"
|
||||
let misconfiguredSublevelTooltip: string | null = null
|
||||
|
||||
if (province || province_code) {
|
||||
name = province ? province : province_code!.toUpperCase()
|
||||
} else if (country || country_code) {
|
||||
name = country ? country.display_name : country_code!.toUpperCase()
|
||||
}
|
||||
|
||||
if (
|
||||
country_code &&
|
||||
province_code &&
|
||||
!isProvinceInCountry(country_code, province_code)
|
||||
) {
|
||||
name = province_code.toUpperCase()
|
||||
misconfiguredSublevelTooltip = t(
|
||||
"taxRegions.fields.sublevels.tooltips.notPartOfCountry",
|
||||
{
|
||||
country: country?.display_name,
|
||||
province: province_code.toUpperCase(),
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const showCreateDefaultTaxRate =
|
||||
!taxRegion.tax_rates.filter((tr) => tr.is_default).length &&
|
||||
type === "header"
|
||||
|
||||
const Component = (
|
||||
<div
|
||||
className={clx(
|
||||
"group-data-[link=true]:hover:bg-ui-bg-base-hover transition-fg flex flex-col justify-between gap-y-4 px-6 md:flex-row md:items-center md:gap-y-0",
|
||||
{
|
||||
"py-4": type === "header",
|
||||
"py-3": type === "list",
|
||||
}
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-x-4">
|
||||
<IconAvatar size={type === "list" ? "small" : "large"}>
|
||||
{country_code && !province_code ? (
|
||||
<div
|
||||
className={clx(
|
||||
"flex size-fit items-center justify-center overflow-hidden rounded-[1px]",
|
||||
{
|
||||
"rounded-sm": type === "header",
|
||||
}
|
||||
)}
|
||||
>
|
||||
<ReactCountryFlag
|
||||
countryCode={country_code}
|
||||
svg
|
||||
style={
|
||||
type === "list"
|
||||
? { width: "12px", height: "9px" }
|
||||
: { width: "16px", height: "12px" }
|
||||
}
|
||||
aria-label={country?.display_name}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<MapPin className="text-ui-fg-subtle" />
|
||||
)}
|
||||
</IconAvatar>
|
||||
<div>
|
||||
{type === "list" ? (
|
||||
<Text size="small" weight="plus" leading="compact">
|
||||
{name}
|
||||
</Text>
|
||||
) : (
|
||||
<Heading>{name}</Heading>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex size-fit items-center gap-x-2 md:hidden">
|
||||
{misconfiguredSublevelTooltip && (
|
||||
<Tooltip content={misconfiguredSublevelTooltip}>
|
||||
<ExclamationCircle className="text-ui-tag-orange-icon" />
|
||||
</Tooltip>
|
||||
)}
|
||||
{badge}
|
||||
<TaxRegionCardActions
|
||||
taxRegion={taxRegion}
|
||||
showCreateDefaultTaxRate={showCreateDefaultTaxRate}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="hidden size-fit items-center gap-x-2 md:flex">
|
||||
{misconfiguredSublevelTooltip && (
|
||||
<Tooltip content={misconfiguredSublevelTooltip}>
|
||||
<ExclamationCircle className="text-ui-tag-orange-icon" />
|
||||
</Tooltip>
|
||||
)}
|
||||
{badge}
|
||||
<TaxRegionCardActions
|
||||
taxRegion={taxRegion}
|
||||
showCreateDefaultTaxRate={showCreateDefaultTaxRate}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
if (asLink) {
|
||||
return (
|
||||
<Link
|
||||
to={variant === "country" ? `${id}` : `provinces/${id}`}
|
||||
data-link="true"
|
||||
className="group block"
|
||||
>
|
||||
{Component}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
return Component
|
||||
}
|
||||
|
||||
const TaxRegionCardActions = ({
|
||||
taxRegion,
|
||||
showCreateDefaultTaxRate,
|
||||
}: {
|
||||
taxRegion: HttpTypes.AdminTaxRegion
|
||||
showCreateDefaultTaxRate?: boolean
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const to = taxRegion.parent_id
|
||||
? `/settings/tax-regions/${taxRegion.parent_id}`
|
||||
: undefined
|
||||
const handleDelete = useDeleteTaxRegionAction({ taxRegion, to })
|
||||
|
||||
return (
|
||||
<ActionMenu
|
||||
groups={[
|
||||
...(showCreateDefaultTaxRate
|
||||
? [
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
icon: <Plus />,
|
||||
label: t("taxRegions.fields.defaultTaxRate.action"),
|
||||
to: `tax-rates/create`,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
icon: <Trash />,
|
||||
label: t("actions.delete"),
|
||||
onClick: handleDelete,
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
export * from "./tax-region-table"
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { Button } from "@medusajs/ui"
|
||||
import { Table } from "@tanstack/react-table"
|
||||
import { ReactNode } from "react"
|
||||
import { Link } from "react-router-dom"
|
||||
import {
|
||||
NoRecords,
|
||||
NoResults,
|
||||
} from "../../../../../components/common/empty-table-content"
|
||||
import { TableFooterSkeleton } from "../../../../../components/common/skeleton"
|
||||
import { LocalizedTablePagination } from "../../../../../components/localization/localized-table-pagination"
|
||||
import { DataTableOrderBy } from "../../../../../components/table/data-table/data-table-order-by"
|
||||
import { TaxRegionCard } from "../tax-region-card"
|
||||
|
||||
type TaxRegionTableProps = {
|
||||
variant?: "country" | "province"
|
||||
isPending: boolean
|
||||
queryObject: Record<string, any>
|
||||
count?: number
|
||||
table: Table<HttpTypes.AdminTaxRegion>
|
||||
action: { label: string; to: string }
|
||||
prefix?: string
|
||||
children?: ReactNode
|
||||
}
|
||||
|
||||
export const TaxRegionTable = ({
|
||||
variant = "country",
|
||||
isPending,
|
||||
action,
|
||||
count = 0,
|
||||
table,
|
||||
queryObject,
|
||||
prefix,
|
||||
children,
|
||||
}: TaxRegionTableProps) => {
|
||||
if (isPending) {
|
||||
return (
|
||||
<div className="flex flex-col divide-y">
|
||||
{Array.from({ length: 3 }).map((_, index) => {
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className="bg-ui-bg-field-component h-[52px] w-full animate-pulse"
|
||||
/>
|
||||
)
|
||||
})}
|
||||
<TableFooterSkeleton layout="fit" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const noQuery =
|
||||
Object.values(queryObject).filter((v) => Boolean(v)).length === 0
|
||||
const noResults = !isPending && count === 0 && !noQuery
|
||||
const noRecords = !isPending && count === 0 && noQuery
|
||||
|
||||
const { pageIndex, pageSize } = table.getState().pagination
|
||||
|
||||
return (
|
||||
<div className="flex flex-col divide-y">
|
||||
<div className="flex flex-col justify-between gap-x-4 gap-y-3 px-6 py-4 md:flex-row md:items-center">
|
||||
<div>{children}</div>
|
||||
<div className="flex items-center gap-x-2">
|
||||
{!noRecords && (
|
||||
<div className="flex w-full items-center gap-x-2 md:w-fit">
|
||||
{/* Re-enable when we allow searching tax regions by country name rather than country_code */}
|
||||
{/* <div className="w-full md:w-fit">
|
||||
<DataTableSearch prefix={prefix} />
|
||||
</div> */}
|
||||
<DataTableOrderBy
|
||||
keys={["updated_at", "created_at"]}
|
||||
prefix={prefix}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<Link to={action.to}>
|
||||
<Button size="small" variant="secondary">
|
||||
{action.label}
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
{noResults && <NoResults />}
|
||||
{noRecords && <NoRecords />}
|
||||
{!noRecords && !noResults
|
||||
? !isPending
|
||||
? table.getRowModel().rows.map((row) => {
|
||||
return (
|
||||
<TaxRegionCard
|
||||
variant={variant}
|
||||
key={row.id}
|
||||
taxRegion={row.original}
|
||||
role="row"
|
||||
aria-rowindex={row.index}
|
||||
/>
|
||||
)
|
||||
})
|
||||
: Array.from({ length: 3 }).map((_, index) => {
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className="bg-ui-bg-field-component h-[60px] w-full animate-pulse"
|
||||
/>
|
||||
)
|
||||
})
|
||||
: null}
|
||||
{!noRecords && (
|
||||
<LocalizedTablePagination
|
||||
prefix={prefix}
|
||||
canNextPage={table.getCanNextPage()}
|
||||
canPreviousPage={table.getCanPreviousPage()}
|
||||
count={count}
|
||||
nextPage={table.nextPage}
|
||||
previousPage={table.previousPage}
|
||||
pageCount={table.getPageCount()}
|
||||
pageIndex={pageIndex}
|
||||
pageSize={pageSize}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
+2
-6
@@ -1,11 +1,7 @@
|
||||
export enum ConditionEntities {
|
||||
export enum TaxRateRuleReferenceType {
|
||||
PRODUCT = "products",
|
||||
PRODUCT_TYPE = "product_types",
|
||||
PRODUCT_COLLECTION = "product_collections",
|
||||
PRODUCT_TAG = "product_tags",
|
||||
PRODUCT_TYPE = "product_types",
|
||||
CUSTOMER_GROUP = "customer_groups",
|
||||
}
|
||||
|
||||
export enum Operators {
|
||||
IN = "in",
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { toast, usePrompt } from "@medusajs/ui"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import { useDeleteTaxRate } from "../../../hooks/api/tax-rates"
|
||||
import { useDeleteTaxRegion } from "../../../hooks/api/tax-regions"
|
||||
|
||||
export const useDeleteTaxRegionAction = ({
|
||||
taxRegion,
|
||||
to = "/settings/tax-regions",
|
||||
}: {
|
||||
taxRegion: HttpTypes.AdminTaxRegion
|
||||
to?: string
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const navigate = useNavigate()
|
||||
const prompt = usePrompt()
|
||||
|
||||
const { mutateAsync } = useDeleteTaxRegion(taxRegion.id)
|
||||
|
||||
const handleDelete = async () => {
|
||||
const res = await prompt({
|
||||
title: t("general.areYouSure"),
|
||||
description: t("taxRegions.delete.confirmation"),
|
||||
confirmText: t("actions.delete"),
|
||||
cancelText: t("actions.cancel"),
|
||||
})
|
||||
|
||||
if (!res) {
|
||||
return
|
||||
}
|
||||
|
||||
await mutateAsync(undefined, {
|
||||
onSuccess: () => {
|
||||
toast.success(t("general.success"), {
|
||||
description: t("taxRegions.delete.successToast"),
|
||||
dismissable: true,
|
||||
dismissLabel: t("actions.close"),
|
||||
})
|
||||
|
||||
navigate(to, { replace: true })
|
||||
},
|
||||
onError: (e) => {
|
||||
toast.error(t("general.error"), {
|
||||
description: e.message,
|
||||
dismissable: true,
|
||||
dismissLabel: t("actions.close"),
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return handleDelete
|
||||
}
|
||||
|
||||
export const useDeleteTaxRateAction = (taxRate: HttpTypes.AdminTaxRate) => {
|
||||
const { t } = useTranslation()
|
||||
const prompt = usePrompt()
|
||||
|
||||
const { mutateAsync } = useDeleteTaxRate(taxRate.id)
|
||||
|
||||
const handleDelete = async () => {
|
||||
const res = await prompt({
|
||||
title: t("general.areYouSure"),
|
||||
description: t("taxRegions.taxRates.delete.confirmation", {
|
||||
name: taxRate.name,
|
||||
}),
|
||||
confirmText: t("actions.delete"),
|
||||
cancelText: t("actions.cancel"),
|
||||
})
|
||||
|
||||
if (!res) {
|
||||
return
|
||||
}
|
||||
|
||||
await mutateAsync(undefined, {
|
||||
onSuccess: () => {
|
||||
toast.success(t("taxRegions.taxRates.delete.successToast"))
|
||||
},
|
||||
onError: (e) => {
|
||||
toast.error(e.message)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return handleDelete
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user