feat(ui,dashboard): Add DataTable block (#10024)
**What** - Adds opinionated DataTable block to `@medusajs/ui` - Adds new DataTable to `@medusajs/dashboard` that uses the above mentioned block as the primitive. The PR also replaces the table on /customer-groups and the variants table on /products/:id with the new DataTable, to provide an example of it's usage. The previous DataTable component has been renamed to `_DataTable` and has been deprecated. **Note** This PR has a lot of LOC. 5,346 of these changes are the fr.json file, which wasn't formatted correctly before. When adding the new translations needed for this PR the file was formatted which caused each line to change to have the proper indentation. Resolves CMRC-333
This commit is contained in:
committed by
GitHub
parent
c3976a312b
commit
147c0e5a35
6
.changeset/wild-zebras-hammer.md
Normal file
6
.changeset/wild-zebras-hammer.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@medusajs/ui": patch
|
||||
"@medusajs/dashboard": patch
|
||||
---
|
||||
|
||||
feat(ui,dashboard): Add new DataTable block
|
||||
19
.eslintrc.js
19
.eslintrc.js
@@ -87,6 +87,11 @@ module.exports = {
|
||||
"./packages/admin/admin-bundler/tsconfig.json",
|
||||
"./packages/admin/admin-vite-plugin/tsconfig.json",
|
||||
|
||||
"./packages/design-system/ui/tsconfig.json",
|
||||
"./packages/design-system/icons/tsconfig.json",
|
||||
"./packages/design-system/ui-preset/tsconfig.json",
|
||||
"./packages/design-system/toolbox/tsconfig.json",
|
||||
|
||||
"./packages/cli/create-medusa-app/tsconfig.json",
|
||||
"./packages/cli/medusa-cli/tsconfig.spec.json",
|
||||
"./packages/cli/oas/medusa-oas-cli/tsconfig.spec.json",
|
||||
@@ -167,7 +172,10 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["packages/design-system/ui/**/*.{ts,tsx}"],
|
||||
files: [
|
||||
"./packages/design-system/ui/**/*.ts",
|
||||
"./packages/design-system/ui/**/*.tsx",
|
||||
],
|
||||
extends: [
|
||||
"plugin:react/recommended",
|
||||
"plugin:storybook/recommended",
|
||||
@@ -196,7 +204,10 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["packages/design-system/icons/**/*.{ts,tsx}"],
|
||||
files: [
|
||||
"./packages/design-system/icons/**/*.ts",
|
||||
"./packages/design-system/icons/**/*.tsx",
|
||||
],
|
||||
extends: [
|
||||
"plugin:react/recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
@@ -223,8 +234,8 @@ module.exports = {
|
||||
},
|
||||
{
|
||||
files: [
|
||||
"packages/admin/dashboard/**/*.ts",
|
||||
"packages/admin/dashboard/**/*.tsx",
|
||||
"./packages/admin/dashboard/**/*.ts",
|
||||
"./packages/admin/dashboard/**/*.tsx",
|
||||
],
|
||||
plugins: ["unused-imports", "react-refresh"],
|
||||
extends: [
|
||||
|
||||
@@ -7,7 +7,13 @@
|
||||
"arrowParens": "always",
|
||||
"overrides": [
|
||||
{
|
||||
"files": "./packages/admin-ui/**/*.{js,jsx,ts,tsx}",
|
||||
"files": "./packages/admin/dashboard/src/**/*.{ts,tsx}",
|
||||
"options": {
|
||||
"plugins": ["prettier-plugin-tailwindcss"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": "./packages/design-system/ui/src/**/*.{ts,tsx}",
|
||||
"options": {
|
||||
"plugins": ["prettier-plugin-tailwindcss"]
|
||||
}
|
||||
|
||||
@@ -30,11 +30,16 @@ export type ActionGroup = {
|
||||
|
||||
type ActionMenuProps = PropsWithChildren<{
|
||||
groups: ActionGroup[]
|
||||
variant?: "transparent" | "primary"
|
||||
}>
|
||||
|
||||
export const ActionMenu = ({ groups, children }: ActionMenuProps) => {
|
||||
export const ActionMenu = ({
|
||||
groups,
|
||||
variant = "transparent",
|
||||
children,
|
||||
}: ActionMenuProps) => {
|
||||
const inner = children ?? (
|
||||
<IconButton size="small" variant="transparent">
|
||||
<IconButton size="small" variant={variant}>
|
||||
<EllipsisHorizontal />
|
||||
</IconButton>
|
||||
)
|
||||
|
||||
@@ -0,0 +1,404 @@
|
||||
import {
|
||||
Button,
|
||||
DataTableColumnDef,
|
||||
DataTableCommand,
|
||||
DataTableEmptyStateProps,
|
||||
DataTableFilter,
|
||||
DataTableFilteringState,
|
||||
DataTablePaginationState,
|
||||
DataTableRowSelectionState,
|
||||
DataTableSortingState,
|
||||
Heading,
|
||||
DataTable as Primitive,
|
||||
useDataTable,
|
||||
} from "@medusajs/ui"
|
||||
import React, { ReactNode, useCallback, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Link, useNavigate, useSearchParams } from "react-router-dom"
|
||||
|
||||
import { useQueryParams } from "../../hooks/use-query-params"
|
||||
import { ActionMenu } from "../common/action-menu"
|
||||
|
||||
type DataTableActionProps = {
|
||||
label: string
|
||||
disabled?: boolean
|
||||
} & (
|
||||
| {
|
||||
to: string
|
||||
}
|
||||
| {
|
||||
onClick: () => void
|
||||
}
|
||||
)
|
||||
|
||||
type DataTableActionMenuActionProps = {
|
||||
label: string
|
||||
icon: ReactNode
|
||||
disabled?: boolean
|
||||
} & (
|
||||
| {
|
||||
to: string
|
||||
}
|
||||
| {
|
||||
onClick: () => void
|
||||
}
|
||||
)
|
||||
|
||||
type DataTableActionMenuGroupProps = {
|
||||
actions: DataTableActionMenuActionProps[]
|
||||
}
|
||||
|
||||
type DataTableActionMenuProps = {
|
||||
groups: DataTableActionMenuGroupProps[]
|
||||
}
|
||||
|
||||
interface DataTableProps<TData> {
|
||||
data?: TData[]
|
||||
columns: DataTableColumnDef<TData, any>[]
|
||||
filters?: DataTableFilter[]
|
||||
commands?: DataTableCommand[]
|
||||
action?: DataTableActionProps
|
||||
actionMenu?: DataTableActionMenuProps
|
||||
rowCount?: number
|
||||
getRowId: (row: TData) => string
|
||||
enablePagination?: boolean
|
||||
enableSearch?: boolean
|
||||
autoFocusSearch?: boolean
|
||||
rowHref?: (row: TData) => string
|
||||
emptyState?: DataTableEmptyStateProps
|
||||
heading: string
|
||||
prefix?: string
|
||||
pageSize?: number
|
||||
isLoading?: boolean
|
||||
rowSelection?: {
|
||||
state: DataTableRowSelectionState
|
||||
onRowSelectionChange: (value: DataTableRowSelectionState) => void
|
||||
}
|
||||
}
|
||||
|
||||
export const DataTable = <TData,>({
|
||||
data = [],
|
||||
columns,
|
||||
filters,
|
||||
commands,
|
||||
action,
|
||||
actionMenu,
|
||||
getRowId,
|
||||
rowCount = 0,
|
||||
enablePagination = true,
|
||||
enableSearch = true,
|
||||
autoFocusSearch = false,
|
||||
rowHref,
|
||||
heading,
|
||||
prefix,
|
||||
pageSize = 10,
|
||||
emptyState,
|
||||
rowSelection,
|
||||
isLoading = false,
|
||||
}: DataTableProps<TData>) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const enableFiltering = filters && filters.length > 0
|
||||
const enableCommands = commands && commands.length > 0
|
||||
const enableSorting = columns.some((column) => column.enableSorting)
|
||||
|
||||
const filterIds = filters?.map((f) => f.id) ?? []
|
||||
const prefixedFilterIds = filterIds.map((id) => getQueryParamKey(id, prefix))
|
||||
|
||||
const { offset, order, q, ...filterParams } = useQueryParams(
|
||||
[
|
||||
...filterIds,
|
||||
...(enableSorting ? ["order"] : []),
|
||||
...(enableSearch ? ["q"] : []),
|
||||
...(enablePagination ? ["offset"] : []),
|
||||
],
|
||||
prefix
|
||||
)
|
||||
const [_, setSearchParams] = useSearchParams()
|
||||
|
||||
const [search, setSearch] = useState<string>(q ?? "")
|
||||
const handleSearchChange = (value: string) => {
|
||||
setSearch(value)
|
||||
setSearchParams((prev) => {
|
||||
if (value) {
|
||||
prev.set(getQueryParamKey("q", prefix), value)
|
||||
} else {
|
||||
prev.delete(getQueryParamKey("q", prefix))
|
||||
}
|
||||
|
||||
return prev
|
||||
})
|
||||
}
|
||||
|
||||
const [pagination, setPagination] = useState<DataTablePaginationState>(
|
||||
offset ? parsePaginationState(offset, pageSize) : { pageIndex: 0, pageSize }
|
||||
)
|
||||
const handlePaginationChange = (value: DataTablePaginationState) => {
|
||||
setPagination(value)
|
||||
setSearchParams((prev) => {
|
||||
if (value.pageIndex === 0) {
|
||||
prev.delete(getQueryParamKey("offset", prefix))
|
||||
} else {
|
||||
prev.set(
|
||||
getQueryParamKey("offset", prefix),
|
||||
transformPaginationState(value).toString()
|
||||
)
|
||||
}
|
||||
|
||||
return prev
|
||||
})
|
||||
}
|
||||
|
||||
const [filtering, setFiltering] = useState<DataTableFilteringState>(
|
||||
parseFilterState(filterIds, filterParams)
|
||||
)
|
||||
const handleFilteringChange = (value: DataTableFilteringState) => {
|
||||
setFiltering(value)
|
||||
|
||||
setSearchParams((prev) => {
|
||||
Array.from(prev.keys()).forEach((key) => {
|
||||
if (prefixedFilterIds.includes(key) && !(key in value)) {
|
||||
prev.delete(key)
|
||||
}
|
||||
})
|
||||
|
||||
Object.entries(value).forEach(([key, filter]) => {
|
||||
if (
|
||||
prefixedFilterIds.includes(getQueryParamKey(key, prefix)) &&
|
||||
filter
|
||||
) {
|
||||
prev.set(getQueryParamKey(key, prefix), JSON.stringify(filter))
|
||||
}
|
||||
})
|
||||
|
||||
return prev
|
||||
})
|
||||
}
|
||||
|
||||
const [sorting, setSorting] = useState<DataTableSortingState | null>(
|
||||
order ? parseSortingState(order) : null
|
||||
)
|
||||
const handleSortingChange = (value: DataTableSortingState) => {
|
||||
setSorting(value)
|
||||
setSearchParams((prev) => {
|
||||
if (value) {
|
||||
const valueToStore = transformSortingState(value)
|
||||
|
||||
prev.set(getQueryParamKey("order", prefix), valueToStore)
|
||||
} else {
|
||||
prev.delete(getQueryParamKey("order", prefix))
|
||||
}
|
||||
|
||||
return prev
|
||||
})
|
||||
}
|
||||
|
||||
const { pagination: paginationTranslations, toolbar: toolbarTranslations } =
|
||||
useDataTableTranslations()
|
||||
|
||||
const navigate = useNavigate()
|
||||
|
||||
const onRowClick = useCallback(
|
||||
(event: React.MouseEvent<HTMLTableRowElement, MouseEvent>, row: TData) => {
|
||||
if (!rowHref) {
|
||||
return
|
||||
}
|
||||
|
||||
const href = rowHref(row)
|
||||
|
||||
if (event.metaKey || event.ctrlKey || event.button === 1) {
|
||||
window.open(href, "_blank", "noreferrer")
|
||||
return
|
||||
}
|
||||
|
||||
if (event.shiftKey) {
|
||||
window.open(href, undefined, "noreferrer")
|
||||
return
|
||||
}
|
||||
|
||||
navigate(href)
|
||||
},
|
||||
[navigate, rowHref]
|
||||
)
|
||||
|
||||
const instance = useDataTable({
|
||||
data,
|
||||
columns,
|
||||
filters,
|
||||
commands,
|
||||
rowCount,
|
||||
getRowId,
|
||||
onRowClick: rowHref ? onRowClick : undefined,
|
||||
pagination: enablePagination
|
||||
? {
|
||||
state: pagination,
|
||||
onPaginationChange: handlePaginationChange,
|
||||
}
|
||||
: undefined,
|
||||
filtering: enableFiltering
|
||||
? {
|
||||
state: filtering,
|
||||
onFilteringChange: handleFilteringChange,
|
||||
}
|
||||
: undefined,
|
||||
sorting: enableSorting
|
||||
? {
|
||||
state: sorting,
|
||||
onSortingChange: handleSortingChange,
|
||||
}
|
||||
: undefined,
|
||||
search: enableSearch
|
||||
? {
|
||||
state: search,
|
||||
onSearchChange: handleSearchChange,
|
||||
}
|
||||
: undefined,
|
||||
rowSelection,
|
||||
isLoading,
|
||||
})
|
||||
|
||||
return (
|
||||
<Primitive instance={instance}>
|
||||
<Primitive.Toolbar
|
||||
className="flex flex-col items-start justify-between gap-2 md:flex-row md:items-center"
|
||||
translations={toolbarTranslations}
|
||||
>
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<Heading>{heading}</Heading>
|
||||
<div className="flex items-center justify-end gap-x-2 md:hidden">
|
||||
{enableFiltering && (
|
||||
<Primitive.FilterMenu tooltip={t("filters.filterLabel")} />
|
||||
)}
|
||||
<Primitive.SortingMenu tooltip={t("filters.sortLabel")} />
|
||||
{actionMenu && <ActionMenu variant="primary" {...actionMenu} />}
|
||||
{action && <DataTableAction {...action} />}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex w-full items-center gap-2 md:justify-end">
|
||||
{enableSearch && (
|
||||
<div className="w-full md:w-auto">
|
||||
<Primitive.Search
|
||||
placeholder={t("filters.searchLabel")}
|
||||
autoFocus={autoFocusSearch}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="hidden items-center gap-x-2 md:flex">
|
||||
{enableFiltering && (
|
||||
<Primitive.FilterMenu tooltip={t("filters.filterLabel")} />
|
||||
)}
|
||||
<Primitive.SortingMenu tooltip={t("filters.sortLabel")} />
|
||||
{actionMenu && <ActionMenu variant="primary" {...actionMenu} />}
|
||||
{action && <DataTableAction {...action} />}
|
||||
</div>
|
||||
</div>
|
||||
</Primitive.Toolbar>
|
||||
<Primitive.Table emptyState={emptyState} />
|
||||
{enablePagination && (
|
||||
<Primitive.Pagination translations={paginationTranslations} />
|
||||
)}
|
||||
{enableCommands && (
|
||||
<Primitive.CommandBar selectedLabel={(count) => `${count} selected`} />
|
||||
)}
|
||||
</Primitive>
|
||||
)
|
||||
}
|
||||
|
||||
function transformSortingState(value: DataTableSortingState) {
|
||||
return value.desc ? `-${value.id}` : value.id
|
||||
}
|
||||
|
||||
function parseSortingState(value: string) {
|
||||
return value.startsWith("-")
|
||||
? { id: value.slice(1), desc: true }
|
||||
: { id: value, desc: false }
|
||||
}
|
||||
|
||||
function transformPaginationState(value: DataTablePaginationState) {
|
||||
return value.pageIndex * value.pageSize
|
||||
}
|
||||
|
||||
function parsePaginationState(value: string, pageSize: number) {
|
||||
const offset = parseInt(value)
|
||||
|
||||
return {
|
||||
pageIndex: Math.floor(offset / pageSize),
|
||||
pageSize,
|
||||
}
|
||||
}
|
||||
|
||||
function parseFilterState(
|
||||
filterIds: string[],
|
||||
value: Record<string, string | undefined>
|
||||
) {
|
||||
if (!value) {
|
||||
return {}
|
||||
}
|
||||
|
||||
const filters: DataTableFilteringState = {}
|
||||
|
||||
for (const id of filterIds) {
|
||||
const filterValue = value[id]
|
||||
|
||||
if (filterValue) {
|
||||
filters[id] = {
|
||||
id,
|
||||
value: JSON.parse(filterValue),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return filters
|
||||
}
|
||||
|
||||
function getQueryParamKey(key: string, prefix?: string) {
|
||||
return prefix ? `${prefix}_${key}` : key
|
||||
}
|
||||
|
||||
const useDataTableTranslations = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const paginationTranslations = {
|
||||
of: t("general.of"),
|
||||
results: t("general.results"),
|
||||
pages: t("general.pages"),
|
||||
prev: t("general.prev"),
|
||||
next: t("general.next"),
|
||||
}
|
||||
|
||||
const toolbarTranslations = {
|
||||
clearAll: t("actions.clearAll"),
|
||||
}
|
||||
|
||||
return {
|
||||
pagination: paginationTranslations,
|
||||
toolbar: toolbarTranslations,
|
||||
}
|
||||
}
|
||||
|
||||
const DataTableAction = ({
|
||||
label,
|
||||
disabled,
|
||||
...props
|
||||
}: DataTableActionProps) => {
|
||||
const buttonProps = {
|
||||
size: "small" as const,
|
||||
disabled: disabled ?? false,
|
||||
type: "button" as const,
|
||||
variant: "secondary" as const,
|
||||
}
|
||||
|
||||
if ("to" in props) {
|
||||
return (
|
||||
<Button {...buttonProps} asChild>
|
||||
<Link to={props.to}>{label}</Link>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Button {...buttonProps} onClick={props.onClick}>
|
||||
{label}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./data-table"
|
||||
@@ -7,8 +7,8 @@ import { useTranslation } from "react-i18next"
|
||||
|
||||
import { useSelectedParams } from "../hooks"
|
||||
import { useDataTableFilterContext } from "./context"
|
||||
import { IFilter } from "./types"
|
||||
import FilterChip from "./filter-chip"
|
||||
import { IFilter } from "./types"
|
||||
|
||||
interface SelectFilterProps extends IFilter {
|
||||
options: { label: string; value: unknown }[]
|
||||
@@ -41,7 +41,9 @@ export const SelectFilter = ({
|
||||
.map((v) => options.find((o) => o.value === v)?.label)
|
||||
.filter(Boolean) as string[]
|
||||
|
||||
const [previousValue, setPreviousValue] = useState<string | string[] | undefined>(labelValues)
|
||||
const [previousValue, setPreviousValue] = useState<
|
||||
string | string[] | undefined
|
||||
>(labelValues)
|
||||
|
||||
const handleRemove = () => {
|
||||
selectedParams.delete()
|
||||
@@ -84,8 +86,16 @@ export const SelectFilter = ({
|
||||
}
|
||||
}
|
||||
|
||||
const normalizedValues = labelValues ? (Array.isArray(labelValues) ? labelValues : [labelValues]) : null
|
||||
const normalizedPrev = previousValue ? (Array.isArray(previousValue) ? previousValue : [previousValue]) : null
|
||||
const normalizedValues = labelValues
|
||||
? Array.isArray(labelValues)
|
||||
? labelValues
|
||||
: [labelValues]
|
||||
: null
|
||||
const normalizedPrev = previousValue
|
||||
? Array.isArray(previousValue)
|
||||
? previousValue
|
||||
: [previousValue]
|
||||
: null
|
||||
|
||||
return (
|
||||
<Popover.Root modal open={open} onOpenChange={handleOpenChange}>
|
||||
|
||||
@@ -176,7 +176,7 @@ export const DataTableRoot = <TData,>({
|
||||
: undefined,
|
||||
}}
|
||||
className={clx({
|
||||
"bg-ui-bg-base sticky left-0 after:absolute after:inset-y-0 after:right-0 after:h-full after:w-px after:bg-transparent after:content-['']":
|
||||
"bg-ui-bg-subtle sticky left-0 after:absolute after:inset-y-0 after:right-0 after:h-full after:w-px after:bg-transparent after:content-['']":
|
||||
isStickyHeader,
|
||||
"left-[68px]":
|
||||
isStickyHeader && hasSelect && !isSelectHeader,
|
||||
|
||||
@@ -18,7 +18,10 @@ interface DataTableProps<TData>
|
||||
// const MemoizedDataTableRoot = memo(DataTableRoot) as typeof DataTableRoot
|
||||
const MemoizedDataTableQuery = memo(DataTableQuery) as typeof DataTableQuery
|
||||
|
||||
export const DataTable = <TData,>({
|
||||
/**
|
||||
* @deprecated Use the DataTable component from "/components/data-table" instead
|
||||
*/
|
||||
export const _DataTable = <TData,>({
|
||||
table,
|
||||
columns,
|
||||
pagination,
|
||||
|
||||
@@ -40,7 +40,7 @@ export const useCustomerGroup = (
|
||||
}
|
||||
|
||||
export const useCustomerGroups = (
|
||||
query?: Record<string, any>,
|
||||
query?: HttpTypes.AdminGetCustomerGroupsParams,
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
HttpTypes.AdminGetCustomerGroupsParams,
|
||||
@@ -127,6 +127,29 @@ export const useDeleteCustomerGroup = (
|
||||
})
|
||||
}
|
||||
|
||||
export const useDeleteCustomerGroupLazy = (
|
||||
options?: UseMutationOptions<
|
||||
HttpTypes.AdminCustomerGroupDeleteResponse,
|
||||
FetchError,
|
||||
{ id: string }
|
||||
>
|
||||
) => {
|
||||
return useMutation({
|
||||
mutationFn: ({ id }) => sdk.admin.customerGroup.delete(id),
|
||||
onSuccess: (data, variables, context) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: customerGroupsQueryKeys.lists(),
|
||||
})
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: customerGroupsQueryKeys.detail(variables.id),
|
||||
})
|
||||
|
||||
options?.onSuccess?.(data, variables, context)
|
||||
},
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
export const useAddCustomersToGroup = (
|
||||
id: string,
|
||||
options?: UseMutationOptions<
|
||||
|
||||
@@ -241,6 +241,32 @@ export const useDeleteVariant = (
|
||||
})
|
||||
}
|
||||
|
||||
export const useDeleteVariantLazy = (
|
||||
productId: string,
|
||||
options?: UseMutationOptions<
|
||||
HttpTypes.AdminProductVariantDeleteResponse,
|
||||
FetchError,
|
||||
{ variantId: string }
|
||||
>
|
||||
) => {
|
||||
return useMutation({
|
||||
mutationFn: ({ variantId }) =>
|
||||
sdk.admin.product.deleteVariant(productId, variantId),
|
||||
onSuccess: (data, variables, context) => {
|
||||
queryClient.invalidateQueries({ queryKey: variantsQueryKeys.lists() })
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: variantsQueryKeys.detail(variables.variantId),
|
||||
})
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: productsQueryKeys.detail(productId),
|
||||
})
|
||||
|
||||
options?.onSuccess?.(data, variables, context)
|
||||
},
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
export const useProduct = (
|
||||
id: string,
|
||||
query?: Record<string, any>,
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import { useMemo } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
export const useDateFilterOptions = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const today = useMemo(() => {
|
||||
const date = new Date()
|
||||
date.setHours(0, 0, 0, 0)
|
||||
return date
|
||||
}, [])
|
||||
|
||||
return useMemo(() => {
|
||||
return [
|
||||
{
|
||||
label: t("filters.date.today"),
|
||||
value: {
|
||||
$gte: today.toISOString(),
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t("filters.date.lastSevenDays"),
|
||||
value: {
|
||||
$gte: new Date(
|
||||
today.getTime() - 7 * 24 * 60 * 60 * 1000
|
||||
).toISOString(), // 7 days ago
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t("filters.date.lastThirtyDays"),
|
||||
value: {
|
||||
$gte: new Date(
|
||||
today.getTime() - 30 * 24 * 60 * 60 * 1000
|
||||
).toISOString(), // 30 days ago
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t("filters.date.lastNinetyDays"),
|
||||
value: {
|
||||
$gte: new Date(
|
||||
today.getTime() - 90 * 24 * 60 * 60 * 1000
|
||||
).toISOString(), // 90 days ago
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t("filters.date.lastTwelveMonths"),
|
||||
value: {
|
||||
$gte: new Date(
|
||||
today.getTime() - 365 * 24 * 60 * 60 * 1000
|
||||
).toISOString(), // 365 days ago
|
||||
},
|
||||
},
|
||||
]
|
||||
}, [today, t])
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -316,6 +316,12 @@
|
||||
"greaterThanLabel": "größer als {{value}}",
|
||||
"andLabel": "Und"
|
||||
},
|
||||
"radio": {
|
||||
"yes": "Ja",
|
||||
"no": "Nein",
|
||||
"true": "Wahr",
|
||||
"false": "Falsch"
|
||||
},
|
||||
"addFilter": "Filter hinzufügen"
|
||||
},
|
||||
"errorBoundary": {
|
||||
@@ -467,7 +473,17 @@
|
||||
}
|
||||
},
|
||||
"deleteWarning": "Sie sind dabei, das Produkt {{title}} zu löschen. Diese Aktion kann nicht rückgängig gemacht werden.",
|
||||
"variants": "Varianten",
|
||||
"variants": {
|
||||
"header": "Varianten",
|
||||
"empty": {
|
||||
"heading": "Keine Varianten",
|
||||
"description": "Es gibt keine Varianten, um angezeigt zu werden."
|
||||
},
|
||||
"filtered": {
|
||||
"heading": "Keine Ergebnisse",
|
||||
"description": "Keine Varianten stimmen mit den aktuellen Filterkriterien überein."
|
||||
}
|
||||
},
|
||||
"attributes": "Attribute",
|
||||
"editAttributes": "Attribute bearbeiten",
|
||||
"editOptions": "Optionen bearbeiten",
|
||||
|
||||
@@ -298,6 +298,9 @@
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
"sortLabel": "Sort",
|
||||
"filterLabel": "Filter",
|
||||
"searchLabel": "Search",
|
||||
"date": {
|
||||
"today": "Today",
|
||||
"lastSevenDays": "Last 7 days",
|
||||
@@ -306,7 +309,9 @@
|
||||
"lastTwelveMonths": "Last 12 months",
|
||||
"custom": "Custom",
|
||||
"from": "From",
|
||||
"to": "To"
|
||||
"to": "To",
|
||||
"starting": "Starting",
|
||||
"ending": "Ending"
|
||||
},
|
||||
"compare": {
|
||||
"lessThan": "Less than",
|
||||
@@ -317,6 +322,18 @@
|
||||
"greaterThanLabel": "greater than {{value}}",
|
||||
"andLabel": "and"
|
||||
},
|
||||
"sorting": {
|
||||
"alphabeticallyAsc": "A to Z",
|
||||
"alphabeticallyDesc": "Z to A",
|
||||
"dateAsc": "Newest first",
|
||||
"dateDesc": "Oldest first"
|
||||
},
|
||||
"radio": {
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"true": "True",
|
||||
"false": "False"
|
||||
},
|
||||
"addFilter": "Add filter"
|
||||
},
|
||||
"errorBoundary": {
|
||||
@@ -468,7 +485,17 @@
|
||||
}
|
||||
},
|
||||
"deleteWarning": "You are about to delete the product {{title}}. This action cannot be undone.",
|
||||
"variants": "Variants",
|
||||
"variants": {
|
||||
"header": "Variants",
|
||||
"empty": {
|
||||
"heading": "No variants",
|
||||
"description": "There are no variants to display."
|
||||
},
|
||||
"filtered": {
|
||||
"heading": "No results",
|
||||
"description": "No variants match the current filter criteria."
|
||||
}
|
||||
},
|
||||
"attributes": "Attributes",
|
||||
"editAttributes": "Edit Attributes",
|
||||
"editOptions": "Edit Options",
|
||||
@@ -891,6 +918,16 @@
|
||||
"customerGroups": {
|
||||
"domain": "Customer Groups",
|
||||
"subtitle": "Organize customers into groups. Groups can have different promotions and prices.",
|
||||
"list": {
|
||||
"empty": {
|
||||
"heading": "No customer groups",
|
||||
"description": "There are no customer groups to display."
|
||||
},
|
||||
"filtered": {
|
||||
"heading": "No results",
|
||||
"description": "No customer groups match the current filter criteria."
|
||||
}
|
||||
},
|
||||
"create": {
|
||||
"header": "Create Customer Group",
|
||||
"hint": "Create a new customer group to segment your customers.",
|
||||
|
||||
@@ -316,6 +316,12 @@
|
||||
"greaterThanLabel": "mayor que {{value}}",
|
||||
"andLabel": "y"
|
||||
},
|
||||
"radio": {
|
||||
"yes": "Sí",
|
||||
"no": "No",
|
||||
"true": "Verdadero",
|
||||
"false": "Falso"
|
||||
},
|
||||
"addFilter": "Agregar filtro"
|
||||
},
|
||||
"errorBoundary": {
|
||||
@@ -467,7 +473,17 @@
|
||||
}
|
||||
},
|
||||
"deleteWarning": "Estás a punto de eliminar el producto {{title}}. Esta acción no puede deshacerse.",
|
||||
"variants": "Variantes",
|
||||
"variants": {
|
||||
"header": "Variantes",
|
||||
"empty": {
|
||||
"heading": "No hay variantes",
|
||||
"description": "No hay variantes para mostrar."
|
||||
},
|
||||
"filtered": {
|
||||
"heading": "No hay resultados",
|
||||
"description": "No hay variantes que coincidan con los criterios de filtro actuales."
|
||||
}
|
||||
},
|
||||
"attributes": "Atributos",
|
||||
"editAttributes": "Editar Atributos",
|
||||
"editOptions": "Editar Opciones",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -317,6 +317,12 @@
|
||||
"greaterThanLabel": "maggiore di {{value}}",
|
||||
"andLabel": "e"
|
||||
},
|
||||
"radio": {
|
||||
"yes": "Sì",
|
||||
"no": "No",
|
||||
"true": "Vero",
|
||||
"false": "Falso"
|
||||
},
|
||||
"addFilter": "Aggiungi filtro"
|
||||
},
|
||||
"errorBoundary": {
|
||||
@@ -468,7 +474,17 @@
|
||||
}
|
||||
},
|
||||
"deleteWarning": "Stai per eliminare il prodotto {{title}}. Questa azione non può essere annullata.",
|
||||
"variants": "Varianti",
|
||||
"variants": {
|
||||
"header": "Varianti",
|
||||
"empty": {
|
||||
"heading": "Nessuna variante",
|
||||
"description": "Non ci sono varianti da visualizzare."
|
||||
},
|
||||
"filtered": {
|
||||
"heading": "Nessun risultato",
|
||||
"description": "Nessuna variante corrisponde ai criteri di filtro correnti."
|
||||
}
|
||||
},
|
||||
"attributes": "Attributi",
|
||||
"editAttributes": "Modifica Attributi",
|
||||
"editOptions": "Modifica Opzioni",
|
||||
@@ -2092,14 +2108,14 @@
|
||||
"label": "La lista prezzi ha una data di scadenza?",
|
||||
"hint": "Pianifica la lista prezzi per disattivarsi in futuro."
|
||||
},
|
||||
"customerAvailability": {
|
||||
"header": "Scegli gruppi di clienti",
|
||||
"label": "Disponibilità cliente",
|
||||
"hint": "Scegli quali gruppi di clienti la lista prezzi dovrebbe essere applicata.",
|
||||
"placeholder": "Cerca gruppi di clienti",
|
||||
"attribute": "Gruppi di clienti"
|
||||
}
|
||||
"customerAvailability": {
|
||||
"header": "Scegli gruppi di clienti",
|
||||
"label": "Disponibilità cliente",
|
||||
"hint": "Scegli quali gruppi di clienti la lista prezzi dovrebbe essere applicata.",
|
||||
"placeholder": "Cerca gruppi di clienti",
|
||||
"attribute": "Gruppi di clienti"
|
||||
}
|
||||
}
|
||||
},
|
||||
"profile": {
|
||||
"domain": "Profilo",
|
||||
@@ -2771,4 +2787,4 @@
|
||||
"seconds_one": "Secondo",
|
||||
"seconds_other": "Secondi"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,6 +317,12 @@
|
||||
"greaterThanLabel": "{{value}}以上",
|
||||
"andLabel": "かつ"
|
||||
},
|
||||
"radio": {
|
||||
"yes": "はい",
|
||||
"no": "いいえ",
|
||||
"true": "真",
|
||||
"false": "偽"
|
||||
},
|
||||
"addFilter": "フィルター追加"
|
||||
},
|
||||
"errorBoundary": {
|
||||
@@ -468,7 +474,17 @@
|
||||
}
|
||||
},
|
||||
"deleteWarning": "商品「{{title}}」を削除しようとしています。この操作は元に戻せません。",
|
||||
"variants": "バリエーション",
|
||||
"variants": {
|
||||
"header": "バリエーション",
|
||||
"empty": {
|
||||
"heading": "バリエーションはありません",
|
||||
"description": "表示するバリエーションはありません。"
|
||||
},
|
||||
"filtered": {
|
||||
"heading": "結果はありません",
|
||||
"description": "現在のフィルター条件に一致するバリエーションはありません。"
|
||||
}
|
||||
},
|
||||
"attributes": "属性",
|
||||
"editAttributes": "属性を編集",
|
||||
"editOptions": "オプションを編集",
|
||||
|
||||
@@ -316,6 +316,12 @@
|
||||
"greaterThanLabel": "więcej niż {{value}}",
|
||||
"andLabel": "i"
|
||||
},
|
||||
"radio": {
|
||||
"yes": "Tak",
|
||||
"no": "Nie",
|
||||
"true": "Prawda",
|
||||
"false": "Fałsz"
|
||||
},
|
||||
"addFilter": "Dodaj filtr"
|
||||
},
|
||||
"errorBoundary": {
|
||||
@@ -467,7 +473,17 @@
|
||||
}
|
||||
},
|
||||
"deleteWarning": "Zamierzasz usunąć produkt {{title}}. Ta akcja nie może zostać cofnięta.",
|
||||
"variants": "Warianty",
|
||||
"variants": {
|
||||
"header": "Warianty",
|
||||
"empty": {
|
||||
"heading": "Brak wariantów",
|
||||
"description": "Nie ma wariantów do wyświetlenia."
|
||||
},
|
||||
"filtered": {
|
||||
"heading": "Brak wariantów",
|
||||
"description": "Nie ma wariantów, które pasują do aktualnych kryteriów filtrów."
|
||||
}
|
||||
},
|
||||
"attributes": "Atrybuty",
|
||||
"editAttributes": "Edytuj atrybuty",
|
||||
"editOptions": "Edytuj opcje",
|
||||
|
||||
@@ -316,6 +316,12 @@
|
||||
"greaterThanLabel": "maior que {{value}}",
|
||||
"andLabel": "e"
|
||||
},
|
||||
"radio": {
|
||||
"yes": "Sim",
|
||||
"no": "Não",
|
||||
"true": "Verdadeiro",
|
||||
"false": "Falso"
|
||||
},
|
||||
"addFilter": "Adicionar filtro"
|
||||
},
|
||||
"errorBoundary": {
|
||||
@@ -467,7 +473,17 @@
|
||||
}
|
||||
},
|
||||
"deleteWarning": "Você está prestes a excluir o produto {{title}}. Esta ação não pode ser desfeita.",
|
||||
"variants": "Variantes",
|
||||
"variants": {
|
||||
"header": "Variantes",
|
||||
"empty": {
|
||||
"heading": "Nenhuma variante",
|
||||
"description": "Não há variantes para exibir."
|
||||
},
|
||||
"filtered": {
|
||||
"heading": "Nenhuma variante",
|
||||
"description": "Nenhuma variante corresponde aos critérios de filtro atuais."
|
||||
}
|
||||
},
|
||||
"attributes": "Atributos",
|
||||
"editAttributes": "Editar atributos",
|
||||
"editOptions": "Editar opções",
|
||||
|
||||
@@ -316,6 +316,12 @@
|
||||
"greaterThanLabel": "มากกว่า {{value}}",
|
||||
"andLabel": "และ"
|
||||
},
|
||||
"radio": {
|
||||
"yes": "ใช่",
|
||||
"no": "ไม่",
|
||||
"true": "จริง",
|
||||
"false": "เท็จ"
|
||||
},
|
||||
"addFilter": "เพิ่มตัวกรอง"
|
||||
},
|
||||
"errorBoundary": {
|
||||
@@ -467,7 +473,17 @@
|
||||
}
|
||||
},
|
||||
"deleteWarning": "คุณกำลังจะลบสินค้า {{title}} การดำเนินการนี้ไม่สามารถยกเลิกได้",
|
||||
"variants": "ตัวเลือก",
|
||||
"variants": {
|
||||
"header": "ตัวเลือก",
|
||||
"empty": {
|
||||
"heading": "ไม่มีตัวเลือก",
|
||||
"description": "ไม่มีตัวเลือกที่จะแสดง"
|
||||
},
|
||||
"filtered": {
|
||||
"heading": "ไม่มีผลลัพธ์",
|
||||
"description": "ไม่มีตัวเลือกที่ตรงกับเกณฑ์การกรองปัจจุบัน"
|
||||
}
|
||||
},
|
||||
"attributes": "แอตทริบิวต์",
|
||||
"editAttributes": "แก้ไขแอตทริบิวต์",
|
||||
"editOptions": "แก้ไขตัวเลือก",
|
||||
|
||||
@@ -316,6 +316,12 @@
|
||||
"greaterThanLabel": "{{value}}'den büyük",
|
||||
"andLabel": "ve"
|
||||
},
|
||||
"radio": {
|
||||
"yes": "Evet",
|
||||
"no": "Hayır",
|
||||
"true": "Doğru",
|
||||
"false": "Yanlış"
|
||||
},
|
||||
"addFilter": "Filtre Ekle"
|
||||
},
|
||||
"errorBoundary": {
|
||||
@@ -467,7 +473,17 @@
|
||||
}
|
||||
},
|
||||
"deleteWarning": "Ürün {{title}}'i silmek üzeresiniz. Bu işlem geri alınamaz.",
|
||||
"variants": "Varyantlar",
|
||||
"variants": {
|
||||
"header": "Varyantlar",
|
||||
"empty": {
|
||||
"heading": "Varyant yok",
|
||||
"description": "Görüntülenecek varyant yok."
|
||||
},
|
||||
"filtered": {
|
||||
"heading": "Sonuç yok",
|
||||
"description": "Varyantlar mevcut filtrelerle eşleşmiyor."
|
||||
}
|
||||
},
|
||||
"attributes": "Öznitelikler",
|
||||
"editAttributes": "Öznitelikleri Düzenle",
|
||||
"editOptions": "Seçenekleri Düzenle",
|
||||
|
||||
@@ -317,6 +317,12 @@
|
||||
"greaterThanLabel": "більше ніж {{value}}",
|
||||
"andLabel": "і"
|
||||
},
|
||||
"radio": {
|
||||
"yes": "Так",
|
||||
"no": "немає",
|
||||
"true": "правда",
|
||||
"false": "помилковий"
|
||||
},
|
||||
"addFilter": "Додати фільтр"
|
||||
},
|
||||
"errorBoundary": {
|
||||
@@ -468,7 +474,17 @@
|
||||
}
|
||||
},
|
||||
"deleteWarning": "Ви збираєтеся видалити продукт {{title}}. Цю дію не можна скасувати.",
|
||||
"variants": "Варіанти",
|
||||
"variants": {
|
||||
"header": "Варіанти",
|
||||
"empty": {
|
||||
"heading": "Немає варіантів",
|
||||
"description": "Немає варіантів для відображення."
|
||||
},
|
||||
"filtered": {
|
||||
"heading": "Немає результатів",
|
||||
"description": "Немає варіантів, які відповідають поточному критерію фільтрації."
|
||||
}
|
||||
},
|
||||
"attributes": "Атрибути",
|
||||
"editAttributes": "Редагувати атрибути",
|
||||
"editOptions": "Редагувати опції",
|
||||
|
||||
@@ -6,7 +6,7 @@ import { RowSelectionState, createColumnHelper } from "@tanstack/react-table"
|
||||
import { useMemo, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
import { useBatchRemoveSalesChannelsFromApiKey } from "../../../../../hooks/api/api-keys"
|
||||
import { useSalesChannels } from "../../../../../hooks/api/sales-channels"
|
||||
import { useSalesChannelTableColumns } from "../../../../../hooks/table/columns/use-sales-channel-table-columns"
|
||||
@@ -109,7 +109,7 @@ export const ApiKeySalesChannelSection = ({
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
filters={filters}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Button, Container, Heading, Text } from "@medusajs/ui"
|
||||
import { keepPreviousData } from "@tanstack/react-query"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Link } from "react-router-dom"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
import { useApiKeys } from "../../../../../hooks/api/api-keys"
|
||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
import { useApiKeyManagementTableColumns } from "./use-api-key-management-table-columns"
|
||||
@@ -70,7 +70,7 @@ export const ApiKeyManagementListTable = ({
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
filters={filters}
|
||||
columns={columns}
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
RouteFocusModal,
|
||||
useRouteModal,
|
||||
} from "../../../../../components/modals"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
import { KeyboundForm } from "../../../../../components/utilities/keybound-form"
|
||||
import { VisuallyHidden } from "../../../../../components/utilities/visually-hidden"
|
||||
import { useBatchAddSalesChannelsToApiKey } from "../../../../../hooks/api/api-keys"
|
||||
@@ -139,7 +139,7 @@ export const ApiKeySalesChannelsForm = ({
|
||||
</div>
|
||||
</RouteFocusModal.Header>
|
||||
<RouteFocusModal.Body className="flex flex-1 flex-col overflow-auto">
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
count={count}
|
||||
|
||||
@@ -12,7 +12,7 @@ import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import * as zod from "zod"
|
||||
import { RouteFocusModal, useRouteModal } from "../../../../components/modals"
|
||||
import { DataTable } from "../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../components/table/data-table"
|
||||
import { KeyboundForm } from "../../../../components/utilities/keybound-form"
|
||||
import { useAddOrRemoveCampaignPromotions } from "../../../../hooks/api/campaigns"
|
||||
import { usePromotions } from "../../../../hooks/api/promotions"
|
||||
@@ -124,7 +124,7 @@ export const AddCampaignPromotionsForm = ({
|
||||
</div>
|
||||
</RouteFocusModal.Header>
|
||||
<RouteFocusModal.Body className="flex size-full flex-col overflow-y-auto">
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
count={count}
|
||||
columns={columns}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useTranslation } from "react-i18next"
|
||||
import { Link } from "react-router-dom"
|
||||
|
||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
import { useAddOrRemoveCampaignPromotions } from "../../../../../hooks/api/campaigns"
|
||||
import { usePromotions } from "../../../../../hooks/api/promotions"
|
||||
import { usePromotionTableColumns } from "../../../../../hooks/table/columns/use-promotion-table-columns"
|
||||
@@ -89,7 +89,7 @@ export const CampaignPromotionSection = ({
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
pageSize={PAGE_SIZE}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useMemo } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Link } from "react-router-dom"
|
||||
import { ActionMenu } from "../../../../components/common/action-menu"
|
||||
import { DataTable } from "../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../components/table/data-table"
|
||||
import {
|
||||
useCampaigns,
|
||||
useDeleteCampaign,
|
||||
@@ -58,7 +58,7 @@ export const CampaignListTable = () => {
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
count={count}
|
||||
|
||||
@@ -14,7 +14,7 @@ import { useMemo, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
import { useUpdateProductCategoryProducts } from "../../../../../hooks/api/categories"
|
||||
import { useProducts } from "../../../../../hooks/api/products"
|
||||
import { useProductTableColumns } from "../../../../../hooks/table/columns/use-product-table-columns"
|
||||
@@ -125,7 +125,7 @@ export const CategoryProductSection = ({
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
filters={filters}
|
||||
columns={columns}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { useTranslation } from "react-i18next"
|
||||
|
||||
import { Link } from "react-router-dom"
|
||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
import { useProductCategories } from "../../../../../hooks/api/categories"
|
||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
import { useDeleteProductCategoryAction } from "../../../common/hooks/use-delete-product-category-action"
|
||||
@@ -84,7 +84,7 @@ export const CategoryListTable = () => {
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
count={count}
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
RouteFocusModal,
|
||||
useRouteModal,
|
||||
} from "../../../../../components/modals"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
import { KeyboundForm } from "../../../../../components/utilities/keybound-form"
|
||||
import { useUpdateProductCategoryProducts } from "../../../../../hooks/api/categories"
|
||||
import { useProducts } from "../../../../../hooks/api/products"
|
||||
@@ -159,7 +159,7 @@ export const EditCategoryProductsForm = ({
|
||||
</div>
|
||||
</RouteFocusModal.Header>
|
||||
<RouteFocusModal.Body className="size-full overflow-hidden">
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
pageSize={PAGE_SIZE}
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
RouteFocusModal,
|
||||
useRouteModal,
|
||||
} from "../../../../../components/modals/index.ts"
|
||||
import { DataTable } from "../../../../../components/table/data-table/data-table.tsx"
|
||||
import { _DataTable } from "../../../../../components/table/data-table/data-table.tsx"
|
||||
import { KeyboundForm } from "../../../../../components/utilities/keybound-form/keybound-form.tsx"
|
||||
import { useUpdateCollectionProducts } from "../../../../../hooks/api/collections.tsx"
|
||||
import { useProducts } from "../../../../../hooks/api/products.tsx"
|
||||
@@ -168,7 +168,7 @@ export const AddProductsToCollectionForm = ({
|
||||
</div>
|
||||
</RouteFocusModal.Header>
|
||||
<RouteFocusModal.Body className="size-full overflow-hidden">
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
pageSize={PAGE_SIZE}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { createColumnHelper } from "@tanstack/react-table"
|
||||
import { useMemo } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
import { useUpdateCollectionProducts } from "../../../../../hooks/api/collections"
|
||||
import { useProducts } from "../../../../../hooks/api/products"
|
||||
import { useProductTableColumns } from "../../../../../hooks/table/columns/use-product-table-columns"
|
||||
@@ -114,7 +114,7 @@ export const CollectionProductSection = ({
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
search
|
||||
|
||||
@@ -6,7 +6,7 @@ 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 { _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"
|
||||
@@ -60,7 +60,7 @@ export const CollectionListTable = () => {
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
pageSize={PAGE_SIZE}
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
RouteFocusModal,
|
||||
useRouteModal,
|
||||
} from "../../../../../components/modals"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
import { KeyboundForm } from "../../../../../components/utilities/keybound-form"
|
||||
import { useAddCustomersToGroup } from "../../../../../hooks/api/customer-groups"
|
||||
import { useCustomers } from "../../../../../hooks/api/customers"
|
||||
@@ -153,7 +153,7 @@ export const AddCustomersForm = ({
|
||||
</div>
|
||||
</RouteFocusModal.Header>
|
||||
<RouteFocusModal.Body className="size-full overflow-hidden">
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
pageSize={PAGE_SIZE}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useTranslation } from "react-i18next"
|
||||
import { Link } from "react-router-dom"
|
||||
|
||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
import { useRemoveCustomersFromGroup } from "../../../../../hooks/api/customer-groups"
|
||||
import { useCustomers } from "../../../../../hooks/api/customers"
|
||||
import { useCustomerTableColumns } from "../../../../../hooks/table/columns/use-customer-table-columns"
|
||||
@@ -95,7 +95,7 @@ export const CustomerGroupCustomerSection = ({
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
pageSize={PAGE_SIZE}
|
||||
|
||||
@@ -1,172 +1,249 @@
|
||||
import { PencilSquare, Trash } from "@medusajs/icons"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import {
|
||||
Button,
|
||||
Container,
|
||||
Heading,
|
||||
Text,
|
||||
createDataTableColumnHelper,
|
||||
createDataTableFilterHelper,
|
||||
toast,
|
||||
usePrompt,
|
||||
} from "@medusajs/ui"
|
||||
import { createColumnHelper } from "@tanstack/react-table"
|
||||
import { useMemo } from "react"
|
||||
import { keepPreviousData } from "@tanstack/react-query"
|
||||
import { useCallback, useMemo } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Link } from "react-router-dom"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { DataTable } from "../../../../../components/data-table"
|
||||
import { SingleColumnPage } from "../../../../../components/layout/pages"
|
||||
import { useDashboardExtension } from "../../../../../extensions"
|
||||
import {
|
||||
useCustomerGroups,
|
||||
useDeleteCustomerGroup,
|
||||
} from "../../../../../hooks/api/customer-groups"
|
||||
import { useCustomerGroupTableColumns } from "../../../../../hooks/table/columns/use-customer-group-table-columns"
|
||||
import { useCustomerGroupTableFilters } from "../../../../../hooks/table/filters/use-customer-group-table-filters"
|
||||
import { useCustomerGroupTableQuery } from "../../../../../hooks/table/query/use-customer-group-table-query"
|
||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
useDeleteCustomerGroupLazy,
|
||||
} from "../../../../../hooks/api"
|
||||
import { useDateFilterOptions } from "../../../../../hooks/filters/use-date-filter-options"
|
||||
import { useDate } from "../../../../../hooks/use-date"
|
||||
import { useQueryParams } from "../../../../../hooks/use-query-params"
|
||||
|
||||
const PAGE_SIZE = 20
|
||||
const PAGE_SIZE = 10
|
||||
|
||||
export const CustomerGroupListTable = () => {
|
||||
const { t } = useTranslation()
|
||||
const { getWidgets } = useDashboardExtension()
|
||||
|
||||
const { searchParams, raw } = useCustomerGroupTableQuery({
|
||||
pageSize: PAGE_SIZE,
|
||||
})
|
||||
const { customer_groups, count, isLoading, isError, error } =
|
||||
useCustomerGroups({
|
||||
...searchParams,
|
||||
fields: "id,name,customers.id",
|
||||
})
|
||||
const { q, order, offset, created_at, updated_at } = useQueryParams([
|
||||
"q",
|
||||
"order",
|
||||
"offset",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
])
|
||||
|
||||
const filters = useCustomerGroupTableFilters()
|
||||
const columns = useColumns()
|
||||
const filters = useFilters()
|
||||
|
||||
const { table } = useDataTable({
|
||||
data: customer_groups ?? [],
|
||||
columns,
|
||||
enablePagination: true,
|
||||
count,
|
||||
getRowId: (row) => row.id,
|
||||
pageSize: PAGE_SIZE,
|
||||
})
|
||||
const { customer_groups, count, isPending, isError, error } =
|
||||
useCustomerGroups(
|
||||
{
|
||||
q,
|
||||
order,
|
||||
offset: offset ? parseInt(offset) : undefined,
|
||||
limit: PAGE_SIZE,
|
||||
created_at: created_at ? JSON.parse(created_at) : undefined,
|
||||
updated_at: updated_at ? JSON.parse(updated_at) : undefined,
|
||||
fields: "id,name,created_at,updated_at,customers.id",
|
||||
},
|
||||
{
|
||||
placeholderData: keepPreviousData,
|
||||
}
|
||||
)
|
||||
|
||||
if (isError) {
|
||||
throw error
|
||||
}
|
||||
|
||||
return (
|
||||
<Container className="divide-y p-0">
|
||||
<div className="flex items-center justify-between px-6 py-4">
|
||||
<div>
|
||||
<Heading level="h2">{t("customerGroups.domain")}</Heading>
|
||||
<Text className="text-ui-fg-subtle" size="small">
|
||||
{t("customerGroups.subtitle")}
|
||||
</Text>
|
||||
</div>
|
||||
<Link to="/customer-groups/create">
|
||||
<Button size="small" variant="secondary">
|
||||
{t("actions.create")}
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
<DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
pageSize={PAGE_SIZE}
|
||||
count={count}
|
||||
filters={filters}
|
||||
search
|
||||
pagination
|
||||
navigateTo={(row) => `/customer-groups/${row.original.id}`}
|
||||
orderBy={[
|
||||
{ key: "name", label: t("fields.name") },
|
||||
{ key: "created_at", label: t("fields.createdAt") },
|
||||
{ key: "updated_at", label: t("fields.updatedAt") },
|
||||
]}
|
||||
queryObject={raw}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
</Container>
|
||||
<SingleColumnPage
|
||||
widgets={{
|
||||
before: getWidgets("customer_group.list.before"),
|
||||
after: getWidgets("customer_group.list.after"),
|
||||
}}
|
||||
>
|
||||
<Container className="overflow-hidden p-0">
|
||||
<DataTable
|
||||
data={customer_groups}
|
||||
columns={columns}
|
||||
filters={filters}
|
||||
heading={t("customerGroups.domain")}
|
||||
rowCount={count}
|
||||
getRowId={(row) => row.id}
|
||||
rowHref={(row) => `/customer-groups/${row.id}`}
|
||||
action={{
|
||||
label: t("actions.create"),
|
||||
to: "/customer-groups/create",
|
||||
}}
|
||||
emptyState={{
|
||||
empty: {
|
||||
heading: t("customerGroups.list.empty.heading"),
|
||||
description: t("customerGroups.list.empty.description"),
|
||||
},
|
||||
filtered: {
|
||||
heading: t("customerGroups.list.filtered.heading"),
|
||||
description: t("customerGroups.list.filtered.description"),
|
||||
},
|
||||
}}
|
||||
pageSize={PAGE_SIZE}
|
||||
isLoading={isPending}
|
||||
/>
|
||||
</Container>
|
||||
</SingleColumnPage>
|
||||
)
|
||||
}
|
||||
|
||||
const CustomerGroupRowActions = ({
|
||||
group,
|
||||
}: {
|
||||
group: HttpTypes.AdminCustomerGroup
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const prompt = usePrompt()
|
||||
|
||||
const { mutateAsync } = useDeleteCustomerGroup(group.id)
|
||||
|
||||
const handleDelete = async () => {
|
||||
const res = await prompt({
|
||||
title: t("customerGroups.delete.title"),
|
||||
description: t("customerGroups.delete.description", {
|
||||
name: group.name,
|
||||
}),
|
||||
confirmText: t("actions.delete"),
|
||||
cancelText: t("actions.cancel"),
|
||||
})
|
||||
|
||||
if (!res) {
|
||||
return
|
||||
}
|
||||
|
||||
await mutateAsync(undefined, {
|
||||
onSuccess: () => {
|
||||
toast.success(
|
||||
t("customerGroups.delete.successToast", {
|
||||
name: group.name,
|
||||
})
|
||||
)
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(error.message)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<ActionMenu
|
||||
groups={[
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
label: t("actions.edit"),
|
||||
to: `/customer-groups/${group.id}/edit`,
|
||||
icon: <PencilSquare />,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
label: t("actions.delete"),
|
||||
onClick: handleDelete,
|
||||
icon: <Trash />,
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const columnHelper = createColumnHelper<HttpTypes.AdminCustomerGroup>()
|
||||
const columnHelper = createDataTableColumnHelper<HttpTypes.AdminCustomerGroup>()
|
||||
|
||||
const useColumns = () => {
|
||||
const columns = useCustomerGroupTableColumns()
|
||||
const { t } = useTranslation()
|
||||
const { getFullDate } = useDate()
|
||||
const navigate = useNavigate()
|
||||
const prompt = usePrompt()
|
||||
|
||||
return useMemo(
|
||||
() => [
|
||||
...columns,
|
||||
columnHelper.display({
|
||||
id: "actions",
|
||||
cell: ({ row }) => <CustomerGroupRowActions group={row.original} />,
|
||||
}),
|
||||
],
|
||||
[columns]
|
||||
const { mutateAsync: deleteCustomerGroup } = useDeleteCustomerGroupLazy()
|
||||
|
||||
const handleDeleteCustomerGroup = useCallback(
|
||||
async ({ id, name }: { id: string; name: string }) => {
|
||||
const res = await prompt({
|
||||
title: t("customerGroups.delete.title"),
|
||||
description: t("customerGroups.delete.description", {
|
||||
name,
|
||||
}),
|
||||
verificationText: name,
|
||||
verificationInstruction: t("general.typeToConfirm"),
|
||||
confirmText: t("actions.delete"),
|
||||
cancelText: t("actions.cancel"),
|
||||
})
|
||||
|
||||
if (!res) {
|
||||
return
|
||||
}
|
||||
|
||||
await deleteCustomerGroup(
|
||||
{ id },
|
||||
{
|
||||
onSuccess: () => {
|
||||
toast.success(t("customerGroups.delete.successToast", { name }))
|
||||
},
|
||||
onError: (e) => {
|
||||
toast.error(e.message)
|
||||
},
|
||||
}
|
||||
)
|
||||
},
|
||||
[t, prompt, deleteCustomerGroup]
|
||||
)
|
||||
|
||||
return useMemo(() => {
|
||||
return [
|
||||
columnHelper.accessor("name", {
|
||||
header: t("fields.name"),
|
||||
enableSorting: true,
|
||||
sortAscLabel: t("filters.sorting.alphabeticallyAsc"),
|
||||
sortDescLabel: t("filters.sorting.alphabeticallyDesc"),
|
||||
}),
|
||||
columnHelper.accessor("customers", {
|
||||
header: t("customers.domain"),
|
||||
cell: ({ row }) => {
|
||||
return <span>{row.original.customers?.length ?? 0}</span>
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor("created_at", {
|
||||
header: t("fields.createdAt"),
|
||||
cell: ({ row }) => {
|
||||
return (
|
||||
<span>
|
||||
{getFullDate({
|
||||
date: row.original.created_at,
|
||||
includeTime: true,
|
||||
})}
|
||||
</span>
|
||||
)
|
||||
},
|
||||
enableSorting: true,
|
||||
sortAscLabel: t("filters.sorting.dateAsc"),
|
||||
sortDescLabel: t("filters.sorting.dateDesc"),
|
||||
}),
|
||||
columnHelper.accessor("updated_at", {
|
||||
header: t("fields.updatedAt"),
|
||||
cell: ({ row }) => {
|
||||
return (
|
||||
<span>
|
||||
{getFullDate({
|
||||
date: row.original.updated_at,
|
||||
includeTime: true,
|
||||
})}
|
||||
</span>
|
||||
)
|
||||
},
|
||||
enableSorting: true,
|
||||
sortAscLabel: t("filters.sorting.dateAsc"),
|
||||
sortDescLabel: t("filters.sorting.dateDesc"),
|
||||
}),
|
||||
columnHelper.action({
|
||||
actions: [
|
||||
[
|
||||
{
|
||||
icon: <PencilSquare />,
|
||||
label: t("actions.edit"),
|
||||
onClick: (row) => {
|
||||
navigate(`/customer-groups/${row.row.original.id}/edit`)
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
icon: <Trash />,
|
||||
label: t("actions.delete"),
|
||||
onClick: (row) => {
|
||||
handleDeleteCustomerGroup({
|
||||
id: row.row.original.id,
|
||||
name: row.row.original.name ?? "",
|
||||
})
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
}),
|
||||
]
|
||||
}, [t, navigate, getFullDate, handleDeleteCustomerGroup])
|
||||
}
|
||||
|
||||
const filterHelper = createDataTableFilterHelper<HttpTypes.AdminCustomerGroup>()
|
||||
|
||||
const useFilters = () => {
|
||||
const { t } = useTranslation()
|
||||
const { getFullDate } = useDate()
|
||||
const dateFilterOptions = useDateFilterOptions()
|
||||
|
||||
return useMemo(() => {
|
||||
return [
|
||||
filterHelper.accessor("created_at", {
|
||||
type: "date",
|
||||
label: t("fields.createdAt"),
|
||||
format: "date",
|
||||
formatDateValue: (date) => getFullDate({ date }),
|
||||
rangeOptionStartLabel: t("filters.date.starting"),
|
||||
rangeOptionEndLabel: t("filters.date.ending"),
|
||||
rangeOptionLabel: t("filters.date.custom"),
|
||||
options: dateFilterOptions,
|
||||
}),
|
||||
filterHelper.accessor("updated_at", {
|
||||
type: "date",
|
||||
label: t("fields.updatedAt"),
|
||||
format: "date",
|
||||
rangeOptionStartLabel: t("filters.date.starting"),
|
||||
rangeOptionEndLabel: t("filters.date.ending"),
|
||||
rangeOptionLabel: t("filters.date.custom"),
|
||||
formatDateValue: (date) => getFullDate({ date }),
|
||||
options: dateFilterOptions,
|
||||
}),
|
||||
]
|
||||
}, [t, dateFilterOptions, getFullDate])
|
||||
}
|
||||
|
||||
@@ -15,17 +15,18 @@ import { PencilSquare, Trash } from "@medusajs/icons"
|
||||
import { keepPreviousData } from "@tanstack/react-query"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Link } from "react-router-dom"
|
||||
import { ActionMenu } from "../../../../../components/common/action-menu/index.ts"
|
||||
import { DataTable } from "../../../../../components/table/data-table/index.ts"
|
||||
|
||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
import { useBatchCustomerCustomerGroups } from "../../../../../hooks/api"
|
||||
import {
|
||||
useCustomerGroups,
|
||||
useRemoveCustomersFromGroup,
|
||||
} from "../../../../../hooks/api/customer-groups.tsx"
|
||||
import { useCustomerGroupTableColumns } from "../../../../../hooks/table/columns/use-customer-group-table-columns.tsx"
|
||||
import { useCustomerGroupTableFilters } from "../../../../../hooks/table/filters/use-customer-group-table-filters.tsx"
|
||||
import { useCustomerGroupTableQuery } from "../../../../../hooks/table/query/use-customer-group-table-query.tsx"
|
||||
import { useDataTable } from "../../../../../hooks/use-data-table.tsx"
|
||||
import { useBatchCustomerCustomerGroups } from "../../../../../hooks/api"
|
||||
} from "../../../../../hooks/api/customer-groups"
|
||||
import { useCustomerGroupTableColumns } from "../../../../../hooks/table/columns/use-customer-group-table-columns"
|
||||
import { useCustomerGroupTableFilters } from "../../../../../hooks/table/filters/use-customer-group-table-filters"
|
||||
import { useCustomerGroupTableQuery } from "../../../../../hooks/table/query/use-customer-group-table-query"
|
||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
|
||||
type CustomerGroupSectionProps = {
|
||||
customer: HttpTypes.AdminCustomer
|
||||
@@ -97,19 +98,23 @@ export const CustomerGroupSection = ({
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await batchCustomerCustomerGroups({ remove: customerGroupIds })
|
||||
|
||||
toast.success(
|
||||
t("customers.groups.removed.success", {
|
||||
groups: customer_groups!
|
||||
.filter((cg) => customerGroupIds.includes(cg.id))
|
||||
.map((cg) => cg?.name),
|
||||
})
|
||||
)
|
||||
} catch (e) {
|
||||
toast.error(e.message)
|
||||
}
|
||||
await batchCustomerCustomerGroups(
|
||||
{ remove: customerGroupIds },
|
||||
{
|
||||
onSuccess: () => {
|
||||
toast.success(
|
||||
t("customers.groups.removed.success", {
|
||||
groups: customer_groups!
|
||||
.filter((cg) => customerGroupIds.includes(cg.id))
|
||||
.map((cg) => cg?.name),
|
||||
})
|
||||
)
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(error.message)
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
@@ -126,7 +131,7 @@ export const CustomerGroupSection = ({
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
pageSize={PAGE_SIZE}
|
||||
@@ -259,6 +264,6 @@ const useColumns = (customerId: string) => {
|
||||
),
|
||||
}),
|
||||
],
|
||||
[columns]
|
||||
[columns, customerId]
|
||||
)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { createColumnHelper } from "@tanstack/react-table"
|
||||
import { useMemo } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
import { useOrders } from "../../../../../hooks/api/orders"
|
||||
import { useOrderTableColumns } from "../../../../../hooks/table/columns/use-order-table-columns"
|
||||
import { useOrderTableFilters } from "../../../../../hooks/table/filters/use-order-table-filters"
|
||||
@@ -70,7 +70,7 @@ export const CustomerOrderSection = ({
|
||||
{/* </Button>*/}
|
||||
{/*</div>*/}
|
||||
</div>
|
||||
<DataTable
|
||||
<_DataTable
|
||||
columns={columns}
|
||||
table={table}
|
||||
pagination
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Link } from "react-router-dom"
|
||||
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
import { useCustomers } from "../../../../../hooks/api/customers"
|
||||
import { useCustomerTableColumns } from "../../../../../hooks/table/columns/use-customer-table-columns"
|
||||
import { useCustomerTableFilters } from "../../../../../hooks/table/filters/use-customer-table-filters"
|
||||
@@ -56,7 +56,7 @@ export const CustomerListTable = () => {
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
pageSize={PAGE_SIZE}
|
||||
|
||||
@@ -15,14 +15,14 @@ import {
|
||||
RouteFocusModal,
|
||||
useRouteModal,
|
||||
} from "../../../../../components/modals"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
import { KeyboundForm } from "../../../../../components/utilities/keybound-form"
|
||||
import { useBatchCustomerCustomerGroups } from "../../../../../hooks/api"
|
||||
import { useCustomerGroups } from "../../../../../hooks/api/customer-groups"
|
||||
import { useCustomerGroupTableColumns } from "../../../../../hooks/table/columns/use-customer-group-table-columns"
|
||||
import { useCustomerGroupTableFilters } from "../../../../../hooks/table/filters/use-customer-group-table-filters"
|
||||
import { useCustomerGroupTableQuery } from "../../../../../hooks/table/query/use-customer-group-table-query"
|
||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
import { useBatchCustomerCustomerGroups } from "../../../../../hooks/api"
|
||||
|
||||
type AddCustomerGroupsFormProps = {
|
||||
customerId: string
|
||||
@@ -155,7 +155,7 @@ export const AddCustomerGroupsForm = ({
|
||||
</div>
|
||||
</RouteFocusModal.Header>
|
||||
<RouteFocusModal.Body className="size-full overflow-hidden">
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
pageSize={PAGE_SIZE}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
import { useInventoryItemLevels } from "../../../../../hooks/api/inventory"
|
||||
import { useLocationLevelTableQuery } from "./use-location-list-table-query"
|
||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
import { useLocationListTableColumns } from "./use-location-list-table-columns"
|
||||
import { useLocationLevelTableQuery } from "./use-location-list-table-query"
|
||||
|
||||
const PAGE_SIZE = 20
|
||||
|
||||
@@ -42,7 +42,7 @@ export const ItemLocationListTable = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
pageSize={PAGE_SIZE}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { useMemo } from "react"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { useMemo } from "react"
|
||||
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
import { useStockLocations } from "../../../../../hooks/api"
|
||||
import { useReservationItems } from "../../../../../hooks/api/reservations"
|
||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
import {
|
||||
ExtendedReservationItem,
|
||||
useReservationTableColumn,
|
||||
} from "./use-reservation-list-table-columns"
|
||||
import { useReservationsTableQuery } from "./use-reservation-list-table-query"
|
||||
import { useStockLocations } from "../../../../../hooks/api"
|
||||
|
||||
const PAGE_SIZE = 20
|
||||
|
||||
@@ -57,7 +57,7 @@ export const ReservationItemTable = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
pageSize={PAGE_SIZE}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { RowSelectionState } from "@tanstack/react-table"
|
||||
import { useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Link, useNavigate } from "react-router-dom"
|
||||
import { DataTable } from "../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../components/table/data-table"
|
||||
import { useInventoryItems } from "../../../../hooks/api/inventory"
|
||||
import { useDataTable } from "../../../../hooks/use-data-table"
|
||||
import { INVENTORY_ITEM_IDS_KEY } from "../../common/constants"
|
||||
@@ -69,7 +69,7 @@ export const InventoryListTable = () => {
|
||||
<Link to="create">{t("actions.create")}</Link>
|
||||
</Button>
|
||||
</div>
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
pageSize={PAGE_SIZE}
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
StackedFocusModal,
|
||||
useStackedModal,
|
||||
} from "../../../../../components/modals"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
import {
|
||||
StaticCountry,
|
||||
@@ -238,7 +238,7 @@ const AreaStackedModal = <TForm extends UseFormReturn<any>>({
|
||||
</StackedFocusModal.Description>
|
||||
</StackedFocusModal.Header>
|
||||
<StackedFocusModal.Body className="flex-1 overflow-hidden">
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
pageSize={PAGE_SIZE}
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
RouteFocusModal,
|
||||
useRouteModal,
|
||||
} from "../../../../../components/modals"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
import { KeyboundForm } from "../../../../../components/utilities/keybound-form"
|
||||
import { useFulfillmentProviders } from "../../../../../hooks/api/fulfillment-providers"
|
||||
import { useUpdateStockLocationFulfillmentProviders } from "../../../../../hooks/api/stock-locations"
|
||||
@@ -132,7 +132,7 @@ export const LocationEditFulfillmentProvidersForm = ({
|
||||
<KeyboundForm onSubmit={handleSubmit} className="flex size-full flex-col">
|
||||
<RouteFocusModal.Header />
|
||||
<RouteFocusModal.Body className="flex flex-1 flex-col overflow-auto">
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
pageSize={PAGE_SIZE}
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
RouteFocusModal,
|
||||
useRouteModal,
|
||||
} from "../../../../../components/modals"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
import { useSalesChannels } from "../../../../../hooks/api/sales-channels"
|
||||
import { useUpdateStockLocationSalesChannels } from "../../../../../hooks/api/stock-locations"
|
||||
import { useSalesChannelTableColumns } from "../../../../../hooks/table/columns/use-sales-channel-table-columns"
|
||||
@@ -137,7 +137,7 @@ export const LocationEditSalesChannelsForm = ({
|
||||
</div>
|
||||
</RouteFocusModal.Header>
|
||||
<RouteFocusModal.Body>
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
pageSize={PAGE_SIZE}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { OnChangeFn, RowSelectionState } from "@tanstack/react-table"
|
||||
import { useMemo, useState } from "react"
|
||||
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
import { getStylizedAmount } from "../../../../../lib/money-amount-helpers"
|
||||
import { getReturnableQuantity } from "../../../../../lib/rma"
|
||||
@@ -134,7 +134,7 @@ export const AddClaimItemsTable = ({
|
||||
|
||||
return (
|
||||
<div className="flex size-full flex-col overflow-hidden">
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
pageSize={PAGE_SIZE}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { OnChangeFn, RowSelectionState } from "@tanstack/react-table"
|
||||
import { useState } from "react"
|
||||
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
import { useVariants } from "../../../../../hooks/api"
|
||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
import { useClaimOutboundItemTableColumns } from "./use-claim-outbound-item-table-columns"
|
||||
@@ -72,7 +72,7 @@ export const AddClaimOutboundItemsTable = ({
|
||||
|
||||
return (
|
||||
<div className="flex size-full flex-col overflow-hidden">
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
pageSize={PAGE_SIZE}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { OnChangeFn, RowSelectionState } from "@tanstack/react-table"
|
||||
import { useState } from "react"
|
||||
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
import { useVariants } from "../../../../../hooks/api"
|
||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
import { useOrderEditItemsTableColumns } from "./use-order-edit-item-table-columns"
|
||||
@@ -65,7 +65,7 @@ export const AddOrderEditItemsTable = ({
|
||||
|
||||
return (
|
||||
<div className="flex size-full flex-col overflow-hidden">
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
pageSize={PAGE_SIZE}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { OnChangeFn, RowSelectionState } from "@tanstack/react-table"
|
||||
import { useMemo, useState } from "react"
|
||||
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
import { getReturnableQuantity } from "../../../../../lib/rma"
|
||||
import { useExchangeItemTableColumns } from "./use-exchange-item-table-columns"
|
||||
@@ -102,7 +102,7 @@ export const AddExchangeInboundItemsTable = ({
|
||||
|
||||
return (
|
||||
<div className="flex size-full flex-col overflow-hidden">
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
pageSize={PAGE_SIZE}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { OnChangeFn, RowSelectionState } from "@tanstack/react-table"
|
||||
import { useState } from "react"
|
||||
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
import { useVariants } from "../../../../../hooks/api"
|
||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
import { useExchangeOutboundItemTableColumns } from "./use-exchange-outbound-item-table-columns"
|
||||
@@ -72,7 +72,7 @@ export const AddExchangeOutboundItemsTable = ({
|
||||
|
||||
return (
|
||||
<div className="flex size-full flex-col overflow-hidden">
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
pageSize={PAGE_SIZE}
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
} from "@medusajs/types"
|
||||
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
import { getStylizedAmount } from "../../../../../lib/money-amount-helpers"
|
||||
import { getReturnableQuantity } from "../../../../../lib/rma"
|
||||
@@ -135,7 +135,7 @@ export const AddReturnItemsTable = ({
|
||||
|
||||
return (
|
||||
<div className="flex size-full flex-col overflow-hidden">
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
pageSize={PAGE_SIZE}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Container, Heading } from "@medusajs/ui"
|
||||
import { keepPreviousData } from "@tanstack/react-query"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import { DataTable } from "../../../../../components/table/data-table/data-table"
|
||||
import { _DataTable } from "../../../../../components/table/data-table/data-table"
|
||||
import { useOrders } from "../../../../../hooks/api/orders"
|
||||
import { useOrderTableColumns } from "../../../../../hooks/table/columns/use-order-table-columns"
|
||||
import { useOrderTableFilters } from "../../../../../hooks/table/filters/use-order-table-filters"
|
||||
@@ -49,7 +49,7 @@ export const OrderListTable = () => {
|
||||
<div className="flex items-center justify-between px-6 py-4">
|
||||
<Heading>{t("orders.domain")}</Heading>
|
||||
</div>
|
||||
<DataTable
|
||||
<_DataTable
|
||||
columns={columns}
|
||||
table={table}
|
||||
pagination
|
||||
|
||||
@@ -10,7 +10,7 @@ import { useEffect, useMemo, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { StackedDrawer } from "../../../../../components/modals/stacked-drawer"
|
||||
import { StackedFocusModal } from "../../../../../components/modals/stacked-focus-modal"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
import { useCustomerGroups } from "../../../../../hooks/api/customer-groups"
|
||||
import { useCustomerGroupTableColumns } from "../../../../../hooks/table/columns/use-customer-group-table-columns"
|
||||
import { useCustomerGroupTableFilters } from "../../../../../hooks/table/filters/use-customer-group-table-filters"
|
||||
@@ -119,7 +119,7 @@ export const PriceListCustomerGroupRuleForm = ({
|
||||
return (
|
||||
<div className="flex size-full flex-col overflow-hidden">
|
||||
<Component.Body className="min-h-0 p-0">
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
pageSize={PAGE_SIZE}
|
||||
|
||||
@@ -10,7 +10,7 @@ import { useMemo, useState } from "react"
|
||||
import { UseFormReturn, useWatch } from "react-hook-form"
|
||||
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
import { useProducts } from "../../../../../hooks/api/products"
|
||||
import { useProductTableColumns } from "../../../../../hooks/table/columns/use-product-table-columns"
|
||||
import { useProductTableFilters } from "../../../../../hooks/table/filters/use-product-table-filters"
|
||||
@@ -117,7 +117,7 @@ export const PriceListProductsForm = ({ form }: PriceListProductsFormProps) => {
|
||||
|
||||
return (
|
||||
<div className="flex size-full flex-col">
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
filters={filters}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { useTranslation } from "react-i18next"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
|
||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
import { usePriceListLinkProducts } from "../../../../../hooks/api/price-lists"
|
||||
import { useProducts } from "../../../../../hooks/api/products"
|
||||
import { useProductTableColumns } from "../../../../../hooks/table/columns/use-product-table-columns"
|
||||
@@ -133,7 +133,7 @@ export const PriceListProductSection = ({
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
filters={filters}
|
||||
columns={columns}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Button, Container, Heading, Text } from "@medusajs/ui"
|
||||
import { keepPreviousData } from "@tanstack/react-query"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Link } from "react-router-dom"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
import { usePriceLists } from "../../../../../hooks/api/price-lists"
|
||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
import { usePricingTableColumns } from "./use-pricing-table-columns"
|
||||
@@ -53,7 +53,7 @@ export const PriceListListTable = () => {
|
||||
<Link to="create">{t("actions.create")}</Link>
|
||||
</Button>
|
||||
</div>
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
count={count}
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
import { useMemo, useState } from "react"
|
||||
import { UseFormReturn, useWatch } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
import { useProducts } from "../../../../../hooks/api/products"
|
||||
import { useProductTableColumns } from "../../../../../hooks/table/columns/use-product-table-columns"
|
||||
import { useProductTableFilters } from "../../../../../hooks/table/filters/use-product-table-filters"
|
||||
@@ -136,7 +136,7 @@ export const PriceListPricesAddProductIdsForm = ({
|
||||
|
||||
return (
|
||||
<div className="flex size-full flex-col">
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
filters={filters}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { Container, Heading } from "@medusajs/ui"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
import { useProducts } from "../../../../../hooks/api"
|
||||
import { useProductTableColumns } from "../../../../../hooks/table/columns"
|
||||
import { useProductTableFilters } from "../../../../../hooks/table/filters"
|
||||
@@ -51,7 +51,7 @@ export const ProductTagProductSection = ({
|
||||
<div className="px-6 py-4">
|
||||
<Heading level="h2">{t("products.domain")}</Heading>
|
||||
</div>
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
filters={filters}
|
||||
queryObject={raw}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { useTranslation } from "react-i18next"
|
||||
import { Link, useLoaderData } from "react-router-dom"
|
||||
|
||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
import { useProductTags } from "../../../../../hooks/api"
|
||||
import { useProductTagTableColumns } from "../../../../../hooks/table/columns"
|
||||
import { useProductTagTableFilters } from "../../../../../hooks/table/filters"
|
||||
@@ -60,7 +60,7 @@ export const ProductTagListTable = () => {
|
||||
<Link to="create">{t("actions.create")}</Link>
|
||||
</Button>
|
||||
</div>
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
filters={filters}
|
||||
queryObject={raw}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { HttpTypes } from "@medusajs/types"
|
||||
import { Container, Heading } from "@medusajs/ui"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
import { useProducts } from "../../../../../hooks/api/products"
|
||||
import { useProductTableColumns } from "../../../../../hooks/table/columns/use-product-table-columns"
|
||||
import { useProductTableFilters } from "../../../../../hooks/table/filters/use-product-table-filters"
|
||||
@@ -48,7 +48,7 @@ export const ProductTypeProductSection = ({
|
||||
<div className="px-6 py-4">
|
||||
<Heading level="h2">{t("products.domain")}</Heading>
|
||||
</div>
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
filters={filters}
|
||||
isLoading={isPending}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useMemo } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Link } from "react-router-dom"
|
||||
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
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"
|
||||
@@ -57,7 +57,7 @@ export const ProductTypeListTable = () => {
|
||||
<Link to="create">{t("actions.create")}</Link>
|
||||
</Button>
|
||||
</div>
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
filters={filters}
|
||||
isLoading={isLoading}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import { Buildings, Component } from "@medusajs/icons"
|
||||
import { Container, Heading } from "@medusajs/ui"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { Container, Heading } from "@medusajs/ui"
|
||||
|
||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
|
||||
import { LinkButton } from "../../../../../components/common/link-button"
|
||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
import { useInventoryTableColumns } from "./use-inventory-table-columns"
|
||||
import { LinkButton } from "../../../../../components/common/link-button"
|
||||
|
||||
const PAGE_SIZE = 20
|
||||
|
||||
@@ -62,7 +62,7 @@ export function VariantInventorySection({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
pageSize={PAGE_SIZE}
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
StackedFocusModal,
|
||||
useStackedModal,
|
||||
} from "../../../../../../../components/modals"
|
||||
import { DataTable } from "../../../../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../../../../components/table/data-table"
|
||||
import { useSalesChannels } from "../../../../../../../hooks/api/sales-channels"
|
||||
import { useSalesChannelTableColumns } from "../../../../../../../hooks/table/columns/use-sales-channel-table-columns"
|
||||
import { useSalesChannelTableFilters } from "../../../../../../../hooks/table/filters/use-sales-channel-table-filters"
|
||||
@@ -131,7 +131,7 @@ export const ProductCreateSalesChannelStackedModal = ({
|
||||
<StackedFocusModal.Content className="flex flex-col overflow-hidden">
|
||||
<StackedFocusModal.Header />
|
||||
<StackedFocusModal.Body className="flex-1 overflow-hidden">
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
pageSize={PAGE_SIZE}
|
||||
|
||||
@@ -1,20 +1,31 @@
|
||||
import { Buildings, PencilSquare, Plus } from "@medusajs/icons"
|
||||
import { Buildings, Component, PencilSquare, Trash } from "@medusajs/icons"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { Container, Heading } from "@medusajs/ui"
|
||||
import {
|
||||
Badge,
|
||||
clx,
|
||||
Container,
|
||||
createDataTableColumnHelper,
|
||||
createDataTableCommandHelper,
|
||||
createDataTableFilterHelper,
|
||||
DataTableAction,
|
||||
Tooltip,
|
||||
usePrompt,
|
||||
} from "@medusajs/ui"
|
||||
import { keepPreviousData } from "@tanstack/react-query"
|
||||
import { useCallback, useMemo } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import { RowSelectionState } from "@tanstack/react-table"
|
||||
import { useState } from "react"
|
||||
import { CellContext } from "@tanstack/react-table"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { useProductVariants } from "../../../../../hooks/api/products"
|
||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
import { DataTable } from "../../../../../components/data-table"
|
||||
import {
|
||||
useDeleteVariantLazy,
|
||||
useProductVariants,
|
||||
} from "../../../../../hooks/api/products"
|
||||
import { useDateFilterOptions } from "../../../../../hooks/filters/use-date-filter-options"
|
||||
import { useDate } from "../../../../../hooks/use-date"
|
||||
import { useQueryParams } from "../../../../../hooks/use-query-params"
|
||||
import { PRODUCT_VARIANT_IDS_KEY } from "../../../common/constants"
|
||||
import { useProductVariantTableColumns } from "./use-variant-table-columns"
|
||||
import { useProductVariantTableFilters } from "./use-variant-table-filters"
|
||||
import { useProductVariantTableQuery } from "./use-variant-table-query"
|
||||
|
||||
type ProductVariantSectionProps = {
|
||||
product: HttpTypes.AdminProduct
|
||||
@@ -26,15 +37,44 @@ export const ProductVariantSection = ({
|
||||
product,
|
||||
}: ProductVariantSectionProps) => {
|
||||
const { t } = useTranslation()
|
||||
const navigate = useNavigate()
|
||||
|
||||
const { searchParams, raw } = useProductVariantTableQuery({
|
||||
pageSize: PAGE_SIZE,
|
||||
})
|
||||
const { variants, count, isLoading, isError, error } = useProductVariants(
|
||||
const {
|
||||
q,
|
||||
order,
|
||||
offset,
|
||||
allow_backorder,
|
||||
manage_inventory,
|
||||
created_at,
|
||||
updated_at,
|
||||
} = useQueryParams([
|
||||
"q",
|
||||
"order",
|
||||
"offset",
|
||||
"manage_inventory",
|
||||
"allow_backorder",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
])
|
||||
|
||||
const columns = useColumns(product)
|
||||
const filters = useFilters()
|
||||
const commands = useCommands()
|
||||
|
||||
const { variants, count, isPending, isError, error } = useProductVariants(
|
||||
product.id,
|
||||
{
|
||||
...searchParams,
|
||||
q,
|
||||
order,
|
||||
offset: offset ? parseInt(offset) : undefined,
|
||||
limit: PAGE_SIZE,
|
||||
allow_backorder: allow_backorder
|
||||
? JSON.parse(allow_backorder)
|
||||
: undefined,
|
||||
manage_inventory: manage_inventory
|
||||
? JSON.parse(manage_inventory)
|
||||
: undefined,
|
||||
created_at: created_at ? JSON.parse(created_at) : undefined,
|
||||
updated_at: updated_at ? JSON.parse(updated_at) : undefined,
|
||||
fields: "*inventory_items.inventory.location_levels,+inventory_quantity",
|
||||
},
|
||||
{
|
||||
@@ -42,45 +82,40 @@ export const ProductVariantSection = ({
|
||||
}
|
||||
)
|
||||
|
||||
const [selection, setSelection] = useState<RowSelectionState>({})
|
||||
|
||||
const filters = useProductVariantTableFilters()
|
||||
const columns = useProductVariantTableColumns(product)
|
||||
|
||||
const { table } = useDataTable({
|
||||
data: variants ?? [],
|
||||
columns,
|
||||
count,
|
||||
enablePagination: true,
|
||||
getRowId: (row) => row.id,
|
||||
pageSize: PAGE_SIZE,
|
||||
enableRowSelection: true,
|
||||
rowSelection: {
|
||||
state: selection,
|
||||
updater: setSelection,
|
||||
},
|
||||
meta: {
|
||||
product,
|
||||
},
|
||||
})
|
||||
|
||||
if (isError) {
|
||||
throw error
|
||||
}
|
||||
|
||||
return (
|
||||
<Container className="divide-y p-0">
|
||||
<div className="flex items-center justify-between px-6 py-4">
|
||||
<Heading level="h2">{t("products.variants")}</Heading>
|
||||
<ActionMenu
|
||||
groups={[
|
||||
<DataTable
|
||||
data={variants}
|
||||
columns={columns}
|
||||
filters={filters}
|
||||
rowCount={count}
|
||||
getRowId={(row) => row.id}
|
||||
rowHref={(row) => `/products/${product.id}/variants/${row.id}`}
|
||||
pageSize={PAGE_SIZE}
|
||||
isLoading={isPending}
|
||||
heading={t("products.variants.header")}
|
||||
emptyState={{
|
||||
empty: {
|
||||
heading: t("products.variants.empty.heading"),
|
||||
description: t("products.variants.empty.description"),
|
||||
},
|
||||
filtered: {
|
||||
heading: t("products.variants.filtered.heading"),
|
||||
description: t("products.variants.filtered.description"),
|
||||
},
|
||||
}}
|
||||
action={{
|
||||
label: t("actions.create"),
|
||||
to: `variants/create`,
|
||||
}}
|
||||
actionMenu={{
|
||||
groups: [
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
label: t("actions.create"),
|
||||
to: `variants/create`,
|
||||
icon: <Plus />,
|
||||
},
|
||||
{
|
||||
label: t("products.editPrices"),
|
||||
to: `prices`,
|
||||
@@ -93,41 +128,295 @@ export const ProductVariantSection = ({
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
filters={filters}
|
||||
count={count}
|
||||
pageSize={PAGE_SIZE}
|
||||
isLoading={isLoading}
|
||||
orderBy={[
|
||||
{ key: "title", label: t("fields.title") },
|
||||
{ key: "created_at", label: t("fields.createdAt") },
|
||||
{ key: "updated_at", label: t("fields.updatedAt") },
|
||||
]}
|
||||
navigateTo={(row) =>
|
||||
`/products/${row.original.product_id}/variants/${row.id}`
|
||||
}
|
||||
pagination
|
||||
search
|
||||
queryObject={raw}
|
||||
commands={[
|
||||
{
|
||||
action: async (selection) => {
|
||||
navigate(
|
||||
`stock?${PRODUCT_VARIANT_IDS_KEY}=${Object.keys(selection).join(
|
||||
","
|
||||
)}`
|
||||
)
|
||||
},
|
||||
label: t("inventory.stock.action"),
|
||||
shortcut: "i",
|
||||
},
|
||||
]}
|
||||
],
|
||||
}}
|
||||
commands={commands}
|
||||
/>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
const columnHelper =
|
||||
createDataTableColumnHelper<HttpTypes.AdminProductVariant>()
|
||||
|
||||
const useColumns = (product: HttpTypes.AdminProduct) => {
|
||||
const { t } = useTranslation()
|
||||
const navigate = useNavigate()
|
||||
const { mutateAsync } = useDeleteVariantLazy(product.id)
|
||||
const prompt = usePrompt()
|
||||
|
||||
const handleDelete = useCallback(
|
||||
async (id: string, title: string) => {
|
||||
const res = await prompt({
|
||||
title: t("general.areYouSure"),
|
||||
description: t("products.deleteVariantWarning", {
|
||||
title,
|
||||
}),
|
||||
confirmText: t("actions.delete"),
|
||||
cancelText: t("actions.cancel"),
|
||||
})
|
||||
|
||||
if (!res) {
|
||||
return
|
||||
}
|
||||
|
||||
await mutateAsync({ variantId: id })
|
||||
},
|
||||
[mutateAsync, prompt, t]
|
||||
)
|
||||
|
||||
const optionColumns = useMemo(() => {
|
||||
if (!product?.options) {
|
||||
return []
|
||||
}
|
||||
|
||||
return product.options.map((option) => {
|
||||
return columnHelper.display({
|
||||
id: option.id,
|
||||
header: option.title,
|
||||
cell: ({ row }) => {
|
||||
const variantOpt = row.original.options?.find(
|
||||
(opt) => opt.option_id === option.id
|
||||
)
|
||||
|
||||
if (!variantOpt) {
|
||||
return <span className="text-ui-fg-muted">-</span>
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
<Tooltip content={variantOpt.value}>
|
||||
<Badge
|
||||
size="2xsmall"
|
||||
title={variantOpt.value}
|
||||
className="inline-flex min-w-[20px] max-w-[140px] items-center justify-center overflow-hidden truncate"
|
||||
>
|
||||
{variantOpt.value}
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
})
|
||||
})
|
||||
}, [product])
|
||||
|
||||
const getActions = useCallback(
|
||||
(ctx: CellContext<HttpTypes.AdminProductVariant, unknown>) => {
|
||||
const variant = ctx.row.original as HttpTypes.AdminProductVariant & {
|
||||
inventory_items: { inventory: HttpTypes.AdminInventoryItem }[]
|
||||
}
|
||||
|
||||
const mainActions: DataTableAction<HttpTypes.AdminProductVariant>[] = [
|
||||
{
|
||||
icon: <PencilSquare />,
|
||||
label: t("actions.edit"),
|
||||
onClick: (row) => {
|
||||
navigate(`edit-variant?variant_id=${row.row.original.id}`)
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
const secondaryActions: DataTableAction<HttpTypes.AdminProductVariant>[] =
|
||||
[
|
||||
{
|
||||
icon: <Trash />,
|
||||
label: t("actions.delete"),
|
||||
onClick: () => handleDelete(variant.id, variant.title!),
|
||||
},
|
||||
]
|
||||
|
||||
const inventoryItemsCount = variant.inventory_items?.length || 0
|
||||
|
||||
switch (inventoryItemsCount) {
|
||||
case 0:
|
||||
break
|
||||
case 1: {
|
||||
const inventoryItemLink = `/inventory/${
|
||||
variant.inventory_items![0].inventory.id
|
||||
}`
|
||||
|
||||
mainActions.push({
|
||||
label: t("products.variant.inventory.actions.inventoryItems"),
|
||||
onClick: () => {
|
||||
navigate(inventoryItemLink)
|
||||
},
|
||||
icon: <Buildings />,
|
||||
})
|
||||
break
|
||||
}
|
||||
default: {
|
||||
const ids = variant.inventory_items?.map((i) => i.inventory?.id)
|
||||
|
||||
if (!ids || ids.length === 0) {
|
||||
break
|
||||
}
|
||||
|
||||
const inventoryKitLink = `/inventory?${new URLSearchParams({
|
||||
id: ids.join(","),
|
||||
}).toString()}`
|
||||
|
||||
mainActions.push({
|
||||
label: t("products.variant.inventory.actions.inventoryKit"),
|
||||
onClick: () => {
|
||||
navigate(inventoryKitLink)
|
||||
},
|
||||
icon: <Component />,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return [mainActions, secondaryActions]
|
||||
},
|
||||
[handleDelete, navigate, t]
|
||||
)
|
||||
|
||||
const getInventory = useCallback(
|
||||
(variant: HttpTypes.AdminProductVariant) => {
|
||||
const castVariant = variant as HttpTypes.AdminProductVariant & {
|
||||
inventory_items: { inventory: HttpTypes.AdminInventoryItem }[]
|
||||
}
|
||||
const quantity = variant.inventory_quantity
|
||||
|
||||
const inventoryItems = castVariant.inventory_items
|
||||
?.map((i) => i.inventory)
|
||||
.filter(Boolean) as HttpTypes.AdminInventoryItem[]
|
||||
|
||||
const hasInventoryKit = inventoryItems.length > 1
|
||||
|
||||
const locations: Record<string, boolean> = {}
|
||||
|
||||
inventoryItems.forEach((i) => {
|
||||
i.location_levels?.forEach((l) => {
|
||||
locations[l.id] = true
|
||||
})
|
||||
})
|
||||
|
||||
const locationCount = Object.keys(locations).length
|
||||
|
||||
const text = hasInventoryKit
|
||||
? t("products.variant.tableItemAvailable", {
|
||||
availableCount: quantity,
|
||||
})
|
||||
: t("products.variant.tableItem", {
|
||||
availableCount: quantity,
|
||||
locationCount,
|
||||
count: locationCount,
|
||||
})
|
||||
|
||||
return { text, hasInventoryKit, quantity }
|
||||
},
|
||||
[t]
|
||||
)
|
||||
|
||||
return useMemo(() => {
|
||||
return [
|
||||
columnHelper.accessor("title", {
|
||||
header: t("fields.title"),
|
||||
enableSorting: true,
|
||||
sortAscLabel: t("filters.sorting.alphabeticallyAsc"),
|
||||
sortDescLabel: t("filters.sorting.alphabeticallyDesc"),
|
||||
}),
|
||||
columnHelper.accessor("sku", {
|
||||
header: t("fields.sku"),
|
||||
enableSorting: true,
|
||||
sortAscLabel: t("filters.sorting.alphabeticallyAsc"),
|
||||
sortDescLabel: t("filters.sorting.alphabeticallyDesc"),
|
||||
}),
|
||||
...optionColumns,
|
||||
columnHelper.display({
|
||||
id: "inventory",
|
||||
header: t("fields.inventory"),
|
||||
cell: ({ row }) => {
|
||||
const { text, hasInventoryKit, quantity } = getInventory(row.original)
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full items-center gap-2 overflow-hidden">
|
||||
{hasInventoryKit && <Component />}
|
||||
<span
|
||||
className={clx("truncate", {
|
||||
"text-ui-fg-error": !quantity,
|
||||
})}
|
||||
title={text}
|
||||
>
|
||||
{text}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}),
|
||||
columnHelper.action({
|
||||
actions: getActions,
|
||||
}),
|
||||
]
|
||||
}, [t, optionColumns, getActions, getInventory])
|
||||
}
|
||||
|
||||
const filterHelper =
|
||||
createDataTableFilterHelper<HttpTypes.AdminProductVariant>()
|
||||
|
||||
const useFilters = () => {
|
||||
const { t } = useTranslation()
|
||||
const { getFullDate } = useDate()
|
||||
const dateFilterOptions = useDateFilterOptions()
|
||||
|
||||
return useMemo(() => {
|
||||
return [
|
||||
filterHelper.accessor("allow_backorder", {
|
||||
type: "radio",
|
||||
label: t("fields.allowBackorder"),
|
||||
options: [
|
||||
{ label: t("filters.radio.yes"), value: "true" },
|
||||
{ label: t("filters.radio.no"), value: "false" },
|
||||
],
|
||||
}),
|
||||
filterHelper.accessor("manage_inventory", {
|
||||
type: "radio",
|
||||
label: t("fields.manageInventory"),
|
||||
options: [
|
||||
{ label: t("filters.radio.yes"), value: "true" },
|
||||
{ label: t("filters.radio.no"), value: "false" },
|
||||
],
|
||||
}),
|
||||
filterHelper.accessor("created_at", {
|
||||
type: "date",
|
||||
label: t("fields.createdAt"),
|
||||
format: "date",
|
||||
formatDateValue: (date) => getFullDate({ date }),
|
||||
rangeOptionStartLabel: t("filters.date.starting"),
|
||||
rangeOptionEndLabel: t("filters.date.ending"),
|
||||
rangeOptionLabel: t("filters.date.custom"),
|
||||
options: dateFilterOptions,
|
||||
}),
|
||||
filterHelper.accessor("updated_at", {
|
||||
type: "date",
|
||||
label: t("fields.updatedAt"),
|
||||
format: "date",
|
||||
rangeOptionStartLabel: t("filters.date.starting"),
|
||||
rangeOptionEndLabel: t("filters.date.ending"),
|
||||
rangeOptionLabel: t("filters.date.custom"),
|
||||
formatDateValue: (date) => getFullDate({ date }),
|
||||
options: dateFilterOptions,
|
||||
}),
|
||||
]
|
||||
}, [t, dateFilterOptions, getFullDate])
|
||||
}
|
||||
|
||||
const commandHelper = createDataTableCommandHelper()
|
||||
|
||||
const useCommands = () => {
|
||||
const { t } = useTranslation()
|
||||
const navigate = useNavigate()
|
||||
|
||||
return [
|
||||
commandHelper.command({
|
||||
label: t("inventory.stock.action"),
|
||||
shortcut: "i",
|
||||
action: async (selection) => {
|
||||
navigate(
|
||||
`stock?${PRODUCT_VARIANT_IDS_KEY}=${Object.keys(selection).join(",")}`
|
||||
)
|
||||
},
|
||||
}),
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,280 +0,0 @@
|
||||
import { Buildings, Component, PencilSquare, Trash } from "@medusajs/icons"
|
||||
import { HttpTypes, InventoryItemDTO } from "@medusajs/types"
|
||||
import { Badge, Checkbox, clx, usePrompt } from "@medusajs/ui"
|
||||
import { createColumnHelper } from "@tanstack/react-table"
|
||||
import { useMemo } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import {
|
||||
Action,
|
||||
ActionMenu,
|
||||
} from "../../../../../components/common/action-menu"
|
||||
import { PlaceholderCell } from "../../../../../components/table/table-cells/common/placeholder-cell"
|
||||
import { useDeleteVariant } from "../../../../../hooks/api/products"
|
||||
|
||||
const VariantActions = ({
|
||||
variant,
|
||||
product,
|
||||
}: {
|
||||
variant: HttpTypes.AdminProductVariant & {
|
||||
inventory_items: { inventory: InventoryItemDTO }[]
|
||||
}
|
||||
product: HttpTypes.AdminProduct
|
||||
}) => {
|
||||
const { mutateAsync } = useDeleteVariant(product.id, variant.id)
|
||||
const { t } = useTranslation()
|
||||
const prompt = usePrompt()
|
||||
|
||||
const inventoryItemsCount = variant.inventory_items?.length || 0
|
||||
const hasInventoryItem = inventoryItemsCount === 1
|
||||
const hasInventoryKit = inventoryItemsCount > 1
|
||||
|
||||
const handleDelete = async () => {
|
||||
const res = await prompt({
|
||||
title: t("general.areYouSure"),
|
||||
description: t("products.deleteVariantWarning", {
|
||||
title: variant.title,
|
||||
}),
|
||||
confirmText: t("actions.delete"),
|
||||
cancelText: t("actions.cancel"),
|
||||
})
|
||||
|
||||
if (!res) {
|
||||
return
|
||||
}
|
||||
|
||||
await mutateAsync()
|
||||
}
|
||||
|
||||
const [inventoryItemLink, inventoryKitLink] = useMemo(() => {
|
||||
if (!variant.inventory_items?.length) {
|
||||
return ["", ""]
|
||||
}
|
||||
|
||||
const itemId = variant.inventory_items![0].inventory.id
|
||||
const itemLink = `/inventory/${itemId}`
|
||||
|
||||
const itemIds = variant.inventory_items!.map((i) => i.inventory.id)
|
||||
const params = { id: itemIds }
|
||||
const query = new URLSearchParams(params).toString()
|
||||
|
||||
const kitLink = `/inventory?${query}`
|
||||
|
||||
return [itemLink, kitLink]
|
||||
}, [variant.inventory_items])
|
||||
|
||||
return (
|
||||
<ActionMenu
|
||||
groups={[
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
label: t("actions.edit"),
|
||||
to: `edit-variant?variant_id=${variant.id}`,
|
||||
icon: <PencilSquare />,
|
||||
},
|
||||
hasInventoryItem
|
||||
? {
|
||||
label: t("products.variant.inventory.actions.inventoryItems"),
|
||||
to: inventoryItemLink,
|
||||
icon: <Buildings />,
|
||||
}
|
||||
: false,
|
||||
hasInventoryKit
|
||||
? {
|
||||
label: t("products.variant.inventory.actions.inventoryKit"),
|
||||
to: inventoryKitLink,
|
||||
icon: <Component />,
|
||||
}
|
||||
: false,
|
||||
].filter(Boolean) as Action[],
|
||||
},
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
label: t("actions.delete"),
|
||||
onClick: handleDelete,
|
||||
icon: <Trash />,
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const columnHelper = createColumnHelper<HttpTypes.AdminProductVariant>()
|
||||
|
||||
export const useProductVariantTableColumns = (
|
||||
product?: HttpTypes.AdminProduct
|
||||
) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const optionColumns = useMemo(() => {
|
||||
if (!product?.options) {
|
||||
return []
|
||||
}
|
||||
|
||||
return product.options.map((option) => {
|
||||
return columnHelper.display({
|
||||
id: option.id,
|
||||
header: () => (
|
||||
<div className="flex h-full w-full items-center">
|
||||
<span className="truncate">{option.title}</span>
|
||||
</div>
|
||||
),
|
||||
cell: ({ row }) => {
|
||||
const variantOpt = row.original.options?.find(
|
||||
(opt) => opt.option_id === option.id
|
||||
)
|
||||
if (!variantOpt) {
|
||||
return <PlaceholderCell />
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
<Badge
|
||||
size="2xsmall"
|
||||
title={variantOpt.value}
|
||||
className="inline-flex min-w-[20px] max-w-[140px] items-center justify-center overflow-hidden truncate"
|
||||
>
|
||||
{variantOpt.value}
|
||||
</Badge>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
})
|
||||
})
|
||||
}, [product])
|
||||
|
||||
return useMemo(
|
||||
() => [
|
||||
columnHelper.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()
|
||||
}}
|
||||
/>
|
||||
)
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor("title", {
|
||||
header: () => (
|
||||
<div className="flex h-full w-full items-center">
|
||||
<span className="truncate">{t("fields.title")}</span>
|
||||
</div>
|
||||
),
|
||||
cell: ({ getValue }) => (
|
||||
<div className="flex h-full w-full items-center overflow-hidden">
|
||||
<span className="truncate">{getValue()}</span>
|
||||
</div>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("sku", {
|
||||
header: () => (
|
||||
<div className="flex h-full w-full items-center">
|
||||
<span className="truncate">{t("fields.sku")}</span>
|
||||
</div>
|
||||
),
|
||||
cell: ({ getValue }) => {
|
||||
const value = getValue()
|
||||
|
||||
if (!value) {
|
||||
return <PlaceholderCell />
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full items-center overflow-hidden">
|
||||
<span className="truncate">{value}</span>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}),
|
||||
...optionColumns,
|
||||
columnHelper.accessor("inventory_items", {
|
||||
header: () => (
|
||||
<div className="flex h-full w-full items-center">
|
||||
<span className="truncate">{t("fields.inventory")}</span>
|
||||
</div>
|
||||
),
|
||||
cell: ({ getValue, row }) => {
|
||||
const variant = row.original
|
||||
|
||||
if (!variant.manage_inventory) {
|
||||
return t("products.variant.inventory.notManaged")
|
||||
}
|
||||
|
||||
const inventory: InventoryItemDTO[] = getValue().map(
|
||||
(i) => i.inventory
|
||||
)
|
||||
|
||||
const hasInventoryKit = inventory.length > 1
|
||||
|
||||
const locations = {}
|
||||
|
||||
inventory.forEach((i) => {
|
||||
i.location_levels.forEach((l) => {
|
||||
locations[l.id] = true
|
||||
})
|
||||
})
|
||||
|
||||
const locationCount = Object.keys(locations).length
|
||||
|
||||
const text = hasInventoryKit
|
||||
? t("products.variant.tableItemAvailable", {
|
||||
availableCount: variant.inventory_quantity,
|
||||
})
|
||||
: t("products.variant.tableItem", {
|
||||
availableCount: variant.inventory_quantity,
|
||||
locationCount,
|
||||
count: locationCount,
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full items-center gap-2 overflow-hidden">
|
||||
{hasInventoryKit && <Component style={{ marginTop: 1 }} />}
|
||||
<span
|
||||
className={clx("truncate", {
|
||||
"text-ui-fg-error": !variant.inventory_quantity,
|
||||
})}
|
||||
title={text}
|
||||
>
|
||||
{text}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}),
|
||||
columnHelper.display({
|
||||
id: "actions",
|
||||
cell: ({ row, table }) => {
|
||||
const { product } = table.options.meta as {
|
||||
product: HttpTypes.AdminProduct
|
||||
}
|
||||
|
||||
return <VariantActions variant={row.original} product={product} />
|
||||
},
|
||||
}),
|
||||
],
|
||||
[t, optionColumns]
|
||||
)
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Filter } from "../../../../../components/table/data-table"
|
||||
|
||||
export const useProductVariantTableFilters = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
let filters: Filter[] = []
|
||||
|
||||
const manageInventoryFilter: Filter = {
|
||||
key: "manage_inventory",
|
||||
label: t("fields.managedInventory"),
|
||||
type: "select",
|
||||
options: [
|
||||
{
|
||||
label: t("fields.true"),
|
||||
value: "true",
|
||||
},
|
||||
{
|
||||
label: t("fields.false"),
|
||||
value: "false",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const allowBackorderFilter: Filter = {
|
||||
key: "allow_backorder",
|
||||
label: t("fields.allowBackorder"),
|
||||
type: "select",
|
||||
options: [
|
||||
{
|
||||
label: t("fields.true"),
|
||||
value: "true",
|
||||
},
|
||||
{
|
||||
label: t("fields.false"),
|
||||
value: "false",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
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,
|
||||
manageInventoryFilter,
|
||||
allowBackorderFilter,
|
||||
...dateFilters,
|
||||
]
|
||||
|
||||
return filters
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { useQueryParams } from "../../../../../hooks/use-query-params"
|
||||
|
||||
export const useProductVariantTableQuery = ({
|
||||
pageSize,
|
||||
prefix,
|
||||
}: {
|
||||
pageSize: number
|
||||
prefix?: string
|
||||
}) => {
|
||||
const queryObject = useQueryParams(
|
||||
[
|
||||
"offset",
|
||||
"q",
|
||||
"manage_inventory",
|
||||
"allow_backorder",
|
||||
"order",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
],
|
||||
prefix
|
||||
)
|
||||
|
||||
const {
|
||||
offset,
|
||||
manage_inventory,
|
||||
allow_backorder,
|
||||
created_at,
|
||||
updated_at,
|
||||
q,
|
||||
order,
|
||||
} = queryObject
|
||||
|
||||
const searchParams: HttpTypes.AdminProductVariantParams = {
|
||||
limit: pageSize,
|
||||
offset: offset ? Number(offset) : 0,
|
||||
manage_inventory: manage_inventory
|
||||
? manage_inventory === "true"
|
||||
: undefined,
|
||||
allow_backorder: allow_backorder ? allow_backorder === "true" : undefined,
|
||||
order,
|
||||
created_at: created_at ? JSON.parse(created_at) : undefined,
|
||||
updated_at: updated_at ? JSON.parse(updated_at) : undefined,
|
||||
q,
|
||||
}
|
||||
|
||||
return {
|
||||
searchParams,
|
||||
raw: queryObject,
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import { Link, Outlet, useLoaderData, useLocation } from "react-router-dom"
|
||||
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
import {
|
||||
useDeleteProduct,
|
||||
useProducts,
|
||||
@@ -72,7 +72,7 @@ export const ProductListTable = () => {
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
count={count}
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
RouteFocusModal,
|
||||
useRouteModal,
|
||||
} from "../../../../../components/modals"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
import { useUpdateProduct } from "../../../../../hooks/api/products"
|
||||
import { useSalesChannels } from "../../../../../hooks/api/sales-channels"
|
||||
import { useSalesChannelTableColumns } from "../../../../../hooks/table/columns/use-sales-channel-table-columns"
|
||||
@@ -123,7 +123,7 @@ export const EditSalesChannelsForm = ({
|
||||
<div className="flex h-full flex-col overflow-hidden">
|
||||
<RouteFocusModal.Header />
|
||||
<RouteFocusModal.Body className="flex-1 overflow-hidden">
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
pageSize={PAGE_SIZE}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Link, Outlet, useLoaderData, useNavigate } from "react-router-dom"
|
||||
|
||||
import { keepPreviousData } from "@tanstack/react-query"
|
||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
import {
|
||||
useDeletePromotion,
|
||||
usePromotions,
|
||||
@@ -62,7 +62,7 @@ export const PromotionListTable = () => {
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
count={count}
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
RouteFocusModal,
|
||||
useRouteModal,
|
||||
} from "../../../../../components/modals"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
import { KeyboundForm } from "../../../../../components/utilities/keybound-form"
|
||||
import { useUpdateRegion } from "../../../../../hooks/api/regions"
|
||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
@@ -142,7 +142,7 @@ export const AddCountriesForm = ({ region }: AddCountriesFormProps) => {
|
||||
</div>
|
||||
</RouteFocusModal.Header>
|
||||
<RouteFocusModal.Body className="overflow-hidden">
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
pageSize={PAGE_SIZE}
|
||||
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
useRouteModal,
|
||||
useStackedModal,
|
||||
} from "../../../../../components/modals"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
import { KeyboundForm } from "../../../../../components/utilities/keybound-form"
|
||||
import { useCreateRegion } from "../../../../../hooks/api/regions"
|
||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
@@ -359,7 +359,7 @@ export const CreateRegionForm = ({
|
||||
</StackedFocusModal.Title>
|
||||
</StackedFocusModal.Header>
|
||||
<StackedFocusModal.Body className="overflow-hidden">
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
count={count}
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
import { useMemo, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
import { useUpdateRegion } from "../../../../../hooks/api/regions"
|
||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
import { useCountries } from "../../../common/hooks/use-countries"
|
||||
@@ -115,7 +115,7 @@ export const RegionCountrySection = ({ region }: RegionCountrySectionProps) => {
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
pageSize={PAGE_SIZE}
|
||||
|
||||
@@ -15,7 +15,7 @@ import { useTranslation } from "react-i18next"
|
||||
import { Link } from "react-router-dom"
|
||||
|
||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
import { useDeleteRegion, useRegions } from "../../../../../hooks/api/regions"
|
||||
import { useRegionTableColumns } from "../../../../../hooks/table/columns/use-region-table-columns"
|
||||
import { useRegionTableFilters } from "../../../../../hooks/table/filters/use-region-table-filters"
|
||||
@@ -76,7 +76,7 @@ export const RegionListTable = () => {
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
count={count}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { Button, Container, Heading, Text } from "@medusajs/ui"
|
||||
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Link } from "react-router-dom"
|
||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
import { useReservationItems } from "../../../../../hooks/api/reservations"
|
||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
import { useReservationTableColumns } from "./use-reservation-table-columns"
|
||||
import { useReservationTableFilters } from "./use-reservation-table-filters"
|
||||
import { useReservationTableQuery } from "./use-reservation-table-query"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
const PAGE_SIZE = 20
|
||||
|
||||
@@ -51,7 +51,7 @@ export const ReservationListTable = () => {
|
||||
<Link to="create">{t("actions.create")}</Link>
|
||||
</Button>
|
||||
</div>
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
pageSize={PAGE_SIZE}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { useTranslation } from "react-i18next"
|
||||
import { Link } from "react-router-dom"
|
||||
|
||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
import { useReturnReasons } from "../../../../../hooks/api/return-reasons"
|
||||
import { useReturnReasonTableColumns } from "../../../../../hooks/table/columns"
|
||||
import { useReturnReasonTableQuery } from "../../../../../hooks/table/query"
|
||||
@@ -57,7 +57,7 @@ export const ReturnReasonListTable = () => {
|
||||
<Link to="create">{t("actions.create")}</Link>
|
||||
</Button>
|
||||
</div>
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
queryObject={raw}
|
||||
count={count}
|
||||
|
||||
@@ -12,7 +12,7 @@ import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import * as zod from "zod"
|
||||
import { RouteFocusModal, useRouteModal } from "../../../../components/modals"
|
||||
import { DataTable } from "../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../components/table/data-table"
|
||||
import { KeyboundForm } from "../../../../components/utilities/keybound-form"
|
||||
import { useProducts } from "../../../../hooks/api/products"
|
||||
import { useSalesChannelAddProducts } from "../../../../hooks/api/sales-channels"
|
||||
@@ -134,7 +134,7 @@ export const AddProductsToSalesChannelForm = ({
|
||||
</div>
|
||||
</RouteFocusModal.Header>
|
||||
<RouteFocusModal.Body className="flex size-full flex-col overflow-y-auto">
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
count={count}
|
||||
columns={columns}
|
||||
|
||||
@@ -15,7 +15,7 @@ import { Link } from "react-router-dom"
|
||||
import { HttpTypes, SalesChannelDTO } from "@medusajs/types"
|
||||
import { keepPreviousData } from "@tanstack/react-query"
|
||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
import { useProducts } from "../../../../../hooks/api/products"
|
||||
import { useSalesChannelRemoveProducts } from "../../../../../hooks/api/sales-channels"
|
||||
import { useProductTableColumns } from "../../../../../hooks/table/columns/use-product-table-columns"
|
||||
@@ -118,7 +118,7 @@ export const SalesChannelProductSection = ({
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
pageSize={PAGE_SIZE}
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
ActionGroup,
|
||||
ActionMenu,
|
||||
} from "../../../../components/common/action-menu"
|
||||
import { DataTable } from "../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../components/table/data-table"
|
||||
import { useStore } from "../../../../hooks/api"
|
||||
import {
|
||||
useDeleteSalesChannel,
|
||||
@@ -89,7 +89,7 @@ export const SalesChannelListTable = () => {
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
count={count}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Link } from "react-router-dom"
|
||||
|
||||
import { keepPreviousData } from "@tanstack/react-query"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
import { useShippingProfiles } from "../../../../../hooks/api/shipping-profiles"
|
||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
import { useShippingProfileTableColumns } from "./use-shipping-profile-table-columns"
|
||||
@@ -55,7 +55,7 @@ export const ShippingProfileListTable = () => {
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
pageSize={PAGE_SIZE}
|
||||
count={count}
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
RouteFocusModal,
|
||||
useRouteModal,
|
||||
} from "../../../../../components/modals"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
import { KeyboundForm } from "../../../../../components/utilities/keybound-form"
|
||||
import { useCurrencies } from "../../../../../hooks/api/currencies"
|
||||
import { pricePreferencesQueryKeys } from "../../../../../hooks/api/price-preferences"
|
||||
@@ -186,7 +186,7 @@ export const AddCurrenciesForm = ({
|
||||
</div>
|
||||
</RouteFocusModal.Header>
|
||||
<RouteFocusModal.Body className="flex flex-1 flex-col overflow-hidden">
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
pageSize={PAGE_SIZE}
|
||||
count={count}
|
||||
|
||||
@@ -14,7 +14,7 @@ import { useMemo, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import { ActionMenu } from "../../../../../../components/common/action-menu"
|
||||
import { DataTable } from "../../../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../../../components/table/data-table"
|
||||
import { StatusCell } from "../../../../../../components/table/table-cells/common/status-cell"
|
||||
import { useCurrencies } from "../../../../../../hooks/api/currencies"
|
||||
import { usePricePreferences } from "../../../../../../hooks/api/price-preferences"
|
||||
@@ -164,7 +164,7 @@ export const StoreCurrencySection = ({ store }: StoreCurrencySectionProps) => {
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<DataTable
|
||||
<_DataTable
|
||||
orderBy={[
|
||||
{ key: "name", label: t("fields.name") },
|
||||
{ key: "code", label: t("fields.code") },
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
StackedDrawer,
|
||||
StackedFocusModal,
|
||||
} from "../../../../../components/modals"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
import {
|
||||
useCollections,
|
||||
useCustomerGroups,
|
||||
@@ -201,7 +201,7 @@ const CustomerGroupTable = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
pageSize={PAGE_SIZE}
|
||||
@@ -337,7 +337,7 @@ const ProductTable = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
pageSize={PAGE_SIZE}
|
||||
@@ -473,7 +473,7 @@ const ProductCollectionTable = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
pageSize={PAGE_SIZE}
|
||||
@@ -609,7 +609,7 @@ const ProductTypeTable = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
pageSize={PAGE_SIZE}
|
||||
@@ -745,7 +745,7 @@ const ProductTagTable = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
pageSize={PAGE_SIZE}
|
||||
|
||||
@@ -22,7 +22,7 @@ import * as zod from "zod"
|
||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||
import { Form } from "../../../../../components/common/form"
|
||||
import { RouteFocusModal } from "../../../../../components/modals/index.ts"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
import { KeyboundForm } from "../../../../../components/utilities/keybound-form/keybound-form.tsx"
|
||||
import {
|
||||
useCreateInvite,
|
||||
@@ -159,7 +159,7 @@ export const InviteUserForm = () => {
|
||||
<div className="flex flex-col gap-y-4">
|
||||
<Heading level="h2">{t("users.pendingInvites")}</Heading>
|
||||
<Container className="overflow-hidden p-0">
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
count={count}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Button, Container, Heading } from "@medusajs/ui"
|
||||
import { keepPreviousData } from "@tanstack/react-query"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Link } from "react-router-dom"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
import { useUsers } from "../../../../../hooks/api/users"
|
||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
import { useUserTableColumns } from "./use-user-table-columns"
|
||||
@@ -49,7 +49,7 @@ export const UserListTable = () => {
|
||||
<Link to="invite">{t("users.invite")}</Link>
|
||||
</Button>
|
||||
</div>
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
count={count}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Container, Heading, Text } from "@medusajs/ui"
|
||||
import { keepPreviousData } from "@tanstack/react-query"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
import { useWorkflowExecutions } from "../../../../../hooks/api/workflow-executions"
|
||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
import { useWorkflowExecutionTableColumns } from "./use-workflow-execution-table-columns"
|
||||
@@ -50,7 +50,7 @@ export const WorkflowExecutionListTable = () => {
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
<DataTable
|
||||
<_DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
count={count}
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@faker-js/faker": "^9.2.0",
|
||||
"@medusajs/ui-preset": "^2.2.0",
|
||||
"@storybook/addon-essentials": "^8.3.5",
|
||||
"@storybook/addon-interactions": "^8.3.5",
|
||||
@@ -96,6 +97,7 @@
|
||||
"@radix-ui/react-switch": "1.1.0",
|
||||
"@radix-ui/react-tabs": "1.1.0",
|
||||
"@radix-ui/react-tooltip": "1.1.2",
|
||||
"@tanstack/react-table": "8.20.5",
|
||||
"clsx": "^1.2.1",
|
||||
"copy-to-clipboard": "^3.3.3",
|
||||
"cva": "1.0.0-beta.1",
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
|
||||
import { EllipsisHorizontal } from "@medusajs/icons"
|
||||
import { CellContext } from "@tanstack/react-table"
|
||||
import { DropdownMenu } from "../../../components/dropdown-menu"
|
||||
import { IconButton } from "../../../components/icon-button"
|
||||
import { DataTableActionColumnDefMeta } from "../types"
|
||||
|
||||
interface DataTableActionCellProps<TData> {
|
||||
ctx: CellContext<TData, unknown>
|
||||
}
|
||||
|
||||
const DataTableActionCell = <TData,>({
|
||||
ctx,
|
||||
}: DataTableActionCellProps<TData>) => {
|
||||
const meta = ctx.column.columnDef.meta as
|
||||
| DataTableActionColumnDefMeta<TData>
|
||||
| undefined
|
||||
const actions = meta?.___actions
|
||||
|
||||
if (!actions) {
|
||||
return null
|
||||
}
|
||||
|
||||
const resolvedActions = typeof actions === "function" ? actions(ctx) : actions
|
||||
|
||||
if (!Array.isArray(resolvedActions)) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenu.Trigger asChild className="ml-1">
|
||||
<IconButton size="small" variant="transparent">
|
||||
<EllipsisHorizontal />
|
||||
</IconButton>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content side="bottom">
|
||||
{resolvedActions.map((actionOrGroup, idx) => {
|
||||
const isArray = Array.isArray(actionOrGroup)
|
||||
const isLast = idx === resolvedActions.length - 1
|
||||
|
||||
return isArray ? (
|
||||
<React.Fragment key={idx}>
|
||||
{actionOrGroup.map((action) => (
|
||||
<DropdownMenu.Item
|
||||
key={action.label}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
action.onClick(ctx)
|
||||
}}
|
||||
className="[&>svg]:text-ui-fg-subtle flex items-center gap-2"
|
||||
>
|
||||
{action.icon}
|
||||
{action.label}
|
||||
</DropdownMenu.Item>
|
||||
))}
|
||||
{!isLast && <DropdownMenu.Separator />}
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<DropdownMenu.Item
|
||||
key={actionOrGroup.label}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
actionOrGroup.onClick(ctx)
|
||||
}}
|
||||
className="[&>svg]:text-ui-fg-subtle flex items-center gap-2"
|
||||
>
|
||||
{actionOrGroup.icon}
|
||||
{actionOrGroup.label}
|
||||
</DropdownMenu.Item>
|
||||
)
|
||||
})}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu>
|
||||
)
|
||||
}
|
||||
|
||||
export { DataTableActionCell }
|
||||
export type { DataTableActionCellProps }
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
|
||||
import { useDataTableContext } from "@/blocks/data-table/context/use-data-table-context"
|
||||
import { CommandBar } from "@/components/command-bar"
|
||||
|
||||
interface DataTableCommandBarProps {
|
||||
selectedLabel?: ((count: number) => string) | string
|
||||
}
|
||||
|
||||
const DataTableCommandBar = (props: DataTableCommandBarProps) => {
|
||||
const { instance } = useDataTableContext()
|
||||
|
||||
const commands = instance.getCommands()
|
||||
const rowSelection = instance.getRowSelection()
|
||||
|
||||
const count = Object.keys(rowSelection || []).length
|
||||
|
||||
const open = commands && commands.length > 0 && count > 0
|
||||
|
||||
function getSelectedLabel(count: number) {
|
||||
if (typeof props.selectedLabel === "function") {
|
||||
return props.selectedLabel(count)
|
||||
}
|
||||
|
||||
return props.selectedLabel
|
||||
}
|
||||
|
||||
if (!commands || commands.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<CommandBar open={open}>
|
||||
<CommandBar.Bar>
|
||||
{props.selectedLabel && (
|
||||
<React.Fragment>
|
||||
<CommandBar.Value>{getSelectedLabel(count)}</CommandBar.Value>
|
||||
<CommandBar.Seperator />
|
||||
</React.Fragment>
|
||||
)}
|
||||
{commands.map((command, idx) => (
|
||||
<React.Fragment key={idx}>
|
||||
<CommandBar.Command
|
||||
key={command.label}
|
||||
action={() => command.action(rowSelection)}
|
||||
label={command.label}
|
||||
shortcut={command.shortcut}
|
||||
/>
|
||||
{idx < commands.length - 1 && <CommandBar.Seperator />}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</CommandBar.Bar>
|
||||
</CommandBar>
|
||||
)
|
||||
}
|
||||
|
||||
export { DataTableCommandBar }
|
||||
export type { DataTableCommandBarProps }
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
|
||||
import { DataTableFilter } from "@/blocks/data-table/components/data-table-filter"
|
||||
import { useDataTableContext } from "@/blocks/data-table/context/use-data-table-context"
|
||||
import { Button } from "@/components/button"
|
||||
import { Skeleton } from "@/components/skeleton"
|
||||
|
||||
interface DataTableFilterBarProps {
|
||||
clearAllFiltersLabel?: string
|
||||
}
|
||||
|
||||
const DataTableFilterBar = ({
|
||||
clearAllFiltersLabel = "Clear all",
|
||||
}: DataTableFilterBarProps) => {
|
||||
const { instance } = useDataTableContext()
|
||||
|
||||
const filterState = instance.getFiltering()
|
||||
|
||||
const clearFilters = React.useCallback(() => {
|
||||
instance.clearFilters()
|
||||
}, [instance])
|
||||
|
||||
const filterCount = Object.keys(filterState).length
|
||||
|
||||
if (filterCount === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (instance.showSkeleton) {
|
||||
return <DataTableFilterBarSkeleton filterCount={filterCount} />
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-ui-bg-subtle flex w-full flex-nowrap items-center gap-2 overflow-x-auto border-t px-6 py-2 md:flex-wrap">
|
||||
{Object.entries(filterState).map(([id, filter]) => (
|
||||
<DataTableFilter key={id} id={id} filter={filter} />
|
||||
))}
|
||||
{filterCount > 0 ? (
|
||||
<Button
|
||||
variant="transparent"
|
||||
size="small"
|
||||
className="text-ui-fg-muted hover:text-ui-fg-subtle flex-shrink-0 whitespace-nowrap"
|
||||
type="button"
|
||||
onClick={clearFilters}
|
||||
>
|
||||
{clearAllFiltersLabel}
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const DataTableFilterBarSkeleton = ({
|
||||
filterCount,
|
||||
}: {
|
||||
filterCount: number
|
||||
}) => {
|
||||
return (
|
||||
<div className="bg-ui-bg-subtle flex w-full flex-nowrap items-center gap-2 overflow-x-auto border-t px-6 py-2 md:flex-wrap">
|
||||
{Array.from({ length: filterCount }).map((_, index) => (
|
||||
<Skeleton key={index} className="h-7 w-[180px]" />
|
||||
))}
|
||||
{filterCount > 0 ? <Skeleton className="h-7 w-[66px]" /> : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export { DataTableFilterBar }
|
||||
export type { DataTableFilterBarProps }
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import * as React from "react"
|
||||
|
||||
import { useDataTableContext } from "@/blocks/data-table/context/use-data-table-context"
|
||||
import { DropdownMenu } from "@/components/dropdown-menu"
|
||||
import { IconButton } from "@/components/icon-button"
|
||||
import { Skeleton } from "@/components/skeleton"
|
||||
import { Tooltip } from "@/components/tooltip"
|
||||
import { Funnel } from "@medusajs/icons"
|
||||
|
||||
interface DataTableFilterMenuProps {
|
||||
tooltip?: string
|
||||
}
|
||||
|
||||
const DataTableFilterMenu = (props: DataTableFilterMenuProps) => {
|
||||
const { instance } = useDataTableContext()
|
||||
|
||||
const enabledFilters = Object.keys(instance.getFiltering())
|
||||
|
||||
const filterOptions = instance
|
||||
.getFilters()
|
||||
.filter((filter) => !enabledFilters.includes(filter.id))
|
||||
|
||||
if (!enabledFilters.length && !filterOptions.length) {
|
||||
throw new Error(
|
||||
"DataTable.FilterMenu was rendered but there are no filters to apply. Make sure to pass filters to 'useDataTable'"
|
||||
)
|
||||
}
|
||||
|
||||
const Wrapper = props.tooltip ? Tooltip : React.Fragment
|
||||
|
||||
if (instance.showSkeleton) {
|
||||
return <DataTableFilterMenuSkeleton />
|
||||
}
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<Wrapper content={props.tooltip} hidden={filterOptions.length === 0}>
|
||||
<DropdownMenu.Trigger asChild disabled={filterOptions.length === 0}>
|
||||
<IconButton size="small">
|
||||
<Funnel />
|
||||
</IconButton>
|
||||
</DropdownMenu.Trigger>
|
||||
</Wrapper>
|
||||
<DropdownMenu.Content side="bottom">
|
||||
{filterOptions.map((filter) => (
|
||||
<DropdownMenu.Item
|
||||
key={filter.id}
|
||||
onClick={() => {
|
||||
instance.addFilter({ id: filter.id, value: undefined })
|
||||
}}
|
||||
>
|
||||
{filter.label}
|
||||
</DropdownMenu.Item>
|
||||
))}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu>
|
||||
)
|
||||
}
|
||||
|
||||
const DataTableFilterMenuSkeleton = () => {
|
||||
return <Skeleton className="size-7" />
|
||||
}
|
||||
|
||||
export { DataTableFilterMenu }
|
||||
export type { DataTableFilterMenuProps }
|
||||
|
||||
@@ -0,0 +1,616 @@
|
||||
"use client"
|
||||
|
||||
import { CheckMini, EllipseMiniSolid, XMark } from "@medusajs/icons"
|
||||
import * as React from "react"
|
||||
|
||||
import { useDataTableContext } from "@/blocks/data-table/context/use-data-table-context"
|
||||
import type {
|
||||
DataTableDateComparisonOperator,
|
||||
DataTableDateFilterProps,
|
||||
DataTableFilterOption,
|
||||
} from "@/blocks/data-table/types"
|
||||
import { isDateComparisonOperator } from "@/blocks/data-table/utils/is-date-comparison-operator"
|
||||
import { DatePicker } from "@/components/date-picker"
|
||||
import { Label } from "@/components/label"
|
||||
import { Popover } from "@/components/popover"
|
||||
import { clx } from "@/utils/clx"
|
||||
|
||||
interface DataTableFilterProps {
|
||||
id: string
|
||||
filter: unknown
|
||||
}
|
||||
|
||||
const DEFAULT_FORMAT_DATE_VALUE = (d: Date) =>
|
||||
d.toLocaleDateString(undefined, {
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
})
|
||||
const DEFAULT_RANGE_OPTION_LABEL = "Custom"
|
||||
const DEFAULT_RANGE_OPTION_START_LABEL = "Starting"
|
||||
const DEFAULT_RANGE_OPTION_END_LABEL = "Ending"
|
||||
|
||||
const DataTableFilter = ({ id, filter }: DataTableFilterProps) => {
|
||||
const { instance } = useDataTableContext()
|
||||
const [open, setOpen] = React.useState(filter === undefined)
|
||||
const [isCustom, setIsCustom] = React.useState(false)
|
||||
|
||||
const onOpenChange = React.useCallback(
|
||||
(open: boolean) => {
|
||||
if (
|
||||
!open &&
|
||||
(!filter || (Array.isArray(filter) && filter.length === 0))
|
||||
) {
|
||||
instance.removeFilter(id)
|
||||
}
|
||||
|
||||
setOpen(open)
|
||||
},
|
||||
[instance, id, filter]
|
||||
)
|
||||
|
||||
const removeFilter = React.useCallback(() => {
|
||||
instance.removeFilter(id)
|
||||
}, [instance, id])
|
||||
|
||||
const meta = instance.getFilterMeta(id)
|
||||
const { type, options, label, ...rest } = meta ?? {}
|
||||
|
||||
const { displayValue, isCustomRange } = React.useMemo(() => {
|
||||
let displayValue: string | null = null
|
||||
let isCustomRange = false
|
||||
|
||||
if (typeof filter === "string") {
|
||||
displayValue = options?.find((o) => o.value === filter)?.label ?? null
|
||||
}
|
||||
|
||||
if (Array.isArray(filter)) {
|
||||
displayValue =
|
||||
filter
|
||||
.map((v) => options?.find((o) => o.value === v)?.label)
|
||||
.join(", ") ?? null
|
||||
}
|
||||
|
||||
if (isDateComparisonOperator(filter)) {
|
||||
displayValue =
|
||||
options?.find((o) => {
|
||||
if (!isDateComparisonOperator(o.value)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return (
|
||||
!isCustom &&
|
||||
(filter.$gte === o.value.$gte || (!filter.$gte && !o.value.$gte)) &&
|
||||
(filter.$lte === o.value.$lte || (!filter.$lte && !o.value.$lte)) &&
|
||||
(filter.$gt === o.value.$gt || (!filter.$gt && !o.value.$gt)) &&
|
||||
(filter.$lt === o.value.$lt || (!filter.$lt && !o.value.$lt))
|
||||
)
|
||||
})?.label ?? null
|
||||
|
||||
if (!displayValue && isDateFilterProps(meta)) {
|
||||
const formatDateValue = meta.formatDateValue
|
||||
? meta.formatDateValue
|
||||
: DEFAULT_FORMAT_DATE_VALUE
|
||||
|
||||
if (filter.$gte && !filter.$lte) {
|
||||
isCustomRange = true
|
||||
displayValue = `${
|
||||
meta.rangeOptionStartLabel || DEFAULT_RANGE_OPTION_START_LABEL
|
||||
} ${formatDateValue(new Date(filter.$gte))}`
|
||||
}
|
||||
|
||||
if (filter.$lte && !filter.$gte) {
|
||||
isCustomRange = true
|
||||
displayValue = `${
|
||||
meta.rangeOptionEndLabel || DEFAULT_RANGE_OPTION_END_LABEL
|
||||
} ${formatDateValue(new Date(filter.$lte))}`
|
||||
}
|
||||
|
||||
if (filter.$gte && filter.$lte) {
|
||||
isCustomRange = true
|
||||
displayValue = `${formatDateValue(
|
||||
new Date(filter.$gte)
|
||||
)} - ${formatDateValue(new Date(filter.$lte))}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { displayValue, isCustomRange }
|
||||
}, [filter, options])
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isCustomRange && !isCustom) {
|
||||
setIsCustom(true)
|
||||
}
|
||||
}, [isCustomRange, isCustom])
|
||||
|
||||
if (!meta) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={onOpenChange} modal>
|
||||
<Popover.Anchor asChild>
|
||||
<div
|
||||
className={clx(
|
||||
"bg-ui-bg-component flex flex-shrink-0 items-center overflow-hidden rounded-md",
|
||||
"[&>*]:txt-compact-small-plus [&>*]:flex [&>*]:items-center [&>*]:justify-center",
|
||||
{
|
||||
"shadow-borders-base divide-x": displayValue,
|
||||
"border border-dashed": !displayValue,
|
||||
}
|
||||
)}
|
||||
>
|
||||
{displayValue && (
|
||||
<div className="text-ui-fg-muted whitespace-nowrap px-2 py-1">
|
||||
{label || id}
|
||||
</div>
|
||||
)}
|
||||
<Popover.Trigger
|
||||
className={clx(
|
||||
"text-ui-fg-subtle hover:bg-ui-bg-base-hover active:bg-ui-bg-base-pressed transition-fg whitespace-nowrap px-2 py-1 outline-none",
|
||||
{
|
||||
"text-ui-fg-muted": !displayValue,
|
||||
}
|
||||
)}
|
||||
>
|
||||
{displayValue || label || id}
|
||||
</Popover.Trigger>
|
||||
|
||||
{displayValue && (
|
||||
<button
|
||||
type="button"
|
||||
className="text-ui-fg-muted hover:bg-ui-bg-base-hover active:bg-ui-bg-base-pressed transition-fg size-7 outline-none"
|
||||
onClick={removeFilter}
|
||||
>
|
||||
<XMark />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</Popover.Anchor>
|
||||
<Popover.Content
|
||||
align="start"
|
||||
className="bg-ui-bg-component p-0 outline-none"
|
||||
>
|
||||
{(() => {
|
||||
switch (type) {
|
||||
case "select":
|
||||
return (
|
||||
<DataTableFilterSelectContent
|
||||
id={id}
|
||||
filter={filter as string[] | undefined}
|
||||
options={options as DataTableFilterOption<string>[]}
|
||||
/>
|
||||
)
|
||||
case "radio":
|
||||
return (
|
||||
<DataTableFilterRadioContent
|
||||
id={id}
|
||||
filter={filter}
|
||||
options={options as DataTableFilterOption<string>[]}
|
||||
/>
|
||||
)
|
||||
case "date":
|
||||
return (
|
||||
<DataTableFilterDateContent
|
||||
id={id}
|
||||
filter={filter}
|
||||
options={
|
||||
options as DataTableFilterOption<DataTableDateComparisonOperator>[]
|
||||
}
|
||||
isCustom={isCustom}
|
||||
setIsCustom={setIsCustom}
|
||||
{...rest}
|
||||
/>
|
||||
)
|
||||
default:
|
||||
return null
|
||||
}
|
||||
})()}
|
||||
</Popover.Content>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
|
||||
type DataTableFilterDateContentProps = {
|
||||
id: string
|
||||
filter: unknown
|
||||
options: DataTableFilterOption<DataTableDateComparisonOperator>[]
|
||||
isCustom: boolean
|
||||
setIsCustom: (isCustom: boolean) => void
|
||||
} & Pick<
|
||||
DataTableDateFilterProps,
|
||||
| "format"
|
||||
| "rangeOptionLabel"
|
||||
| "disableRangeOption"
|
||||
| "rangeOptionStartLabel"
|
||||
| "rangeOptionEndLabel"
|
||||
>
|
||||
|
||||
const DataTableFilterDateContent = ({
|
||||
id,
|
||||
filter,
|
||||
options,
|
||||
format = "date",
|
||||
rangeOptionLabel = DEFAULT_RANGE_OPTION_LABEL,
|
||||
rangeOptionStartLabel = DEFAULT_RANGE_OPTION_START_LABEL,
|
||||
rangeOptionEndLabel = DEFAULT_RANGE_OPTION_END_LABEL,
|
||||
disableRangeOption = false,
|
||||
isCustom,
|
||||
setIsCustom,
|
||||
}: DataTableFilterDateContentProps) => {
|
||||
const currentValue = filter as DataTableDateComparisonOperator | undefined
|
||||
const { instance } = useDataTableContext()
|
||||
|
||||
const selectedValue = React.useMemo(() => {
|
||||
if (!currentValue || isCustom) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return JSON.stringify(currentValue)
|
||||
}, [currentValue, isCustom])
|
||||
|
||||
const onValueChange = React.useCallback(
|
||||
(valueStr: string) => {
|
||||
setIsCustom(false)
|
||||
|
||||
const value = JSON.parse(valueStr) as DataTableDateComparisonOperator
|
||||
instance.updateFilter({ id, value })
|
||||
},
|
||||
[instance, id]
|
||||
)
|
||||
|
||||
const onSelectCustom = React.useCallback(() => {
|
||||
setIsCustom(true)
|
||||
instance.updateFilter({ id, value: undefined })
|
||||
}, [instance, id])
|
||||
|
||||
const onCustomValueChange = React.useCallback(
|
||||
(input: "$gte" | "$lte", value: Date | null) => {
|
||||
const newCurrentValue = { ...currentValue }
|
||||
newCurrentValue[input] = value ? value.toISOString() : undefined
|
||||
instance.updateFilter({ id, value: newCurrentValue })
|
||||
},
|
||||
[instance, id]
|
||||
)
|
||||
|
||||
const { focusedIndex, setFocusedIndex } = useKeyboardNavigation(
|
||||
options,
|
||||
(index) => {
|
||||
if (index === options.length && !disableRangeOption) {
|
||||
onSelectCustom()
|
||||
} else {
|
||||
onValueChange(JSON.stringify(options[index].value))
|
||||
}
|
||||
},
|
||||
disableRangeOption ? 0 : 1
|
||||
)
|
||||
|
||||
const granularity = format === "date-time" ? "minute" : "day"
|
||||
|
||||
const maxDate = currentValue?.$lte
|
||||
? granularity === "minute"
|
||||
? new Date(currentValue.$lte)
|
||||
: new Date(new Date(currentValue.$lte).setHours(23, 59, 59, 999))
|
||||
: undefined
|
||||
|
||||
const minDate = currentValue?.$gte
|
||||
? granularity === "minute"
|
||||
? new Date(currentValue.$gte)
|
||||
: new Date(new Date(currentValue.$gte).setHours(0, 0, 0, 0))
|
||||
: undefined
|
||||
|
||||
const initialFocusedIndex = isCustom ? options.length : 0
|
||||
|
||||
const onListFocus = React.useCallback(() => {
|
||||
if (focusedIndex === -1) {
|
||||
setFocusedIndex(initialFocusedIndex)
|
||||
}
|
||||
}, [focusedIndex, initialFocusedIndex])
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div
|
||||
className="flex flex-col p-1 outline-none"
|
||||
tabIndex={0}
|
||||
role="list"
|
||||
onFocus={onListFocus}
|
||||
autoFocus
|
||||
>
|
||||
{options.map((option, idx) => {
|
||||
const value = JSON.stringify(option.value)
|
||||
const isSelected = selectedValue === value
|
||||
|
||||
return (
|
||||
<OptionButton
|
||||
key={idx}
|
||||
index={idx}
|
||||
option={option}
|
||||
isSelected={isSelected}
|
||||
isFocused={focusedIndex === idx}
|
||||
onClick={() => onValueChange(value)}
|
||||
onMouseEvent={setFocusedIndex}
|
||||
icon={EllipseMiniSolid}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
{!disableRangeOption && (
|
||||
<OptionButton
|
||||
index={options.length}
|
||||
option={{
|
||||
label: rangeOptionLabel,
|
||||
value: "__custom",
|
||||
}}
|
||||
icon={EllipseMiniSolid}
|
||||
isSelected={isCustom}
|
||||
isFocused={focusedIndex === options.length}
|
||||
onClick={onSelectCustom}
|
||||
onMouseEvent={setFocusedIndex}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{!disableRangeOption && isCustom && (
|
||||
<React.Fragment>
|
||||
<div className="flex flex-col py-[3px]">
|
||||
<div className="bg-ui-border-menu-top h-px w-full" />
|
||||
<div className="bg-ui-border-menu-bot h-px w-full" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 px-2 pb-3 pt-1">
|
||||
<div className="flex flex-col gap-1">
|
||||
<Label id="custom-start-date-label" size="xsmall" weight="plus">
|
||||
{rangeOptionStartLabel}
|
||||
</Label>
|
||||
<DatePicker
|
||||
aria-labelledby="custom-start-date-label"
|
||||
granularity={granularity}
|
||||
maxValue={maxDate}
|
||||
value={currentValue?.$gte ? new Date(currentValue.$gte) : null}
|
||||
onChange={(value) => onCustomValueChange("$gte", value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Label id="custom-end-date-label" size="xsmall" weight="plus">
|
||||
{rangeOptionEndLabel}
|
||||
</Label>
|
||||
<DatePicker
|
||||
aria-labelledby="custom-end-date-label"
|
||||
granularity={granularity}
|
||||
minValue={minDate}
|
||||
value={currentValue?.$lte ? new Date(currentValue.$lte) : null}
|
||||
onChange={(value) => onCustomValueChange("$lte", value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
type DataTableFilterSelectContentProps = {
|
||||
id: string
|
||||
filter?: string[]
|
||||
options: DataTableFilterOption<string>[]
|
||||
}
|
||||
|
||||
const DataTableFilterSelectContent = ({
|
||||
id,
|
||||
filter = [],
|
||||
options,
|
||||
}: DataTableFilterSelectContentProps) => {
|
||||
const { instance } = useDataTableContext()
|
||||
|
||||
const onValueChange = React.useCallback(
|
||||
(value: string) => {
|
||||
if (filter?.includes(value)) {
|
||||
const newValues = filter?.filter((v) => v !== value)
|
||||
instance.updateFilter({
|
||||
id,
|
||||
value: newValues,
|
||||
})
|
||||
} else {
|
||||
instance.updateFilter({
|
||||
id,
|
||||
value: [...(filter ?? []), value],
|
||||
})
|
||||
}
|
||||
},
|
||||
[instance, id, filter]
|
||||
)
|
||||
|
||||
const { focusedIndex, setFocusedIndex } = useKeyboardNavigation(
|
||||
options,
|
||||
(index) => onValueChange(options[index].value)
|
||||
)
|
||||
|
||||
const onListFocus = React.useCallback(() => {
|
||||
if (focusedIndex === -1) {
|
||||
setFocusedIndex(0)
|
||||
}
|
||||
}, [focusedIndex])
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex flex-col p-1 outline-none"
|
||||
role="list"
|
||||
tabIndex={0}
|
||||
onFocus={onListFocus}
|
||||
autoFocus
|
||||
>
|
||||
{options.map((option, idx) => {
|
||||
const isSelected = !!filter?.includes(option.value)
|
||||
|
||||
return (
|
||||
<OptionButton
|
||||
key={idx}
|
||||
index={idx}
|
||||
option={option}
|
||||
isSelected={isSelected}
|
||||
isFocused={focusedIndex === idx}
|
||||
onClick={() => onValueChange(option.value)}
|
||||
onMouseEvent={setFocusedIndex}
|
||||
icon={CheckMini}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
type DataTableFilterRadioContentProps = {
|
||||
id: string
|
||||
filter: unknown
|
||||
options: DataTableFilterOption<string>[]
|
||||
}
|
||||
|
||||
const DataTableFilterRadioContent = ({
|
||||
id,
|
||||
filter,
|
||||
options,
|
||||
}: DataTableFilterRadioContentProps) => {
|
||||
const { instance } = useDataTableContext()
|
||||
|
||||
const onValueChange = React.useCallback(
|
||||
(value: string) => {
|
||||
instance.updateFilter({ id, value })
|
||||
},
|
||||
[instance, id]
|
||||
)
|
||||
|
||||
const { focusedIndex, setFocusedIndex } = useKeyboardNavigation(
|
||||
options,
|
||||
(index) => onValueChange(options[index].value)
|
||||
)
|
||||
|
||||
const onListFocus = React.useCallback(() => {
|
||||
if (focusedIndex === -1) {
|
||||
setFocusedIndex(0)
|
||||
}
|
||||
}, [focusedIndex])
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex flex-col p-1 outline-none"
|
||||
role="list"
|
||||
tabIndex={0}
|
||||
onFocus={onListFocus}
|
||||
autoFocus
|
||||
>
|
||||
{options.map((option, idx) => {
|
||||
const isSelected = filter === option.value
|
||||
|
||||
return (
|
||||
<OptionButton
|
||||
key={idx}
|
||||
index={idx}
|
||||
option={option}
|
||||
isSelected={isSelected}
|
||||
isFocused={focusedIndex === idx}
|
||||
onClick={() => onValueChange(option.value)}
|
||||
onMouseEvent={setFocusedIndex}
|
||||
icon={EllipseMiniSolid}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function isDateFilterProps(props?: unknown | null): props is DataTableDateFilterProps {
|
||||
if (!props) {
|
||||
return false
|
||||
}
|
||||
|
||||
return (props as DataTableDateFilterProps).type === "date"
|
||||
}
|
||||
|
||||
type OptionButtonProps = {
|
||||
index: number
|
||||
option: DataTableFilterOption<string | DataTableDateComparisonOperator>
|
||||
isSelected: boolean
|
||||
isFocused: boolean
|
||||
onClick: () => void
|
||||
onMouseEvent: (idx: number) => void
|
||||
icon: React.ElementType
|
||||
}
|
||||
|
||||
const OptionButton = React.memo(
|
||||
({
|
||||
index,
|
||||
option,
|
||||
isSelected,
|
||||
isFocused,
|
||||
onClick,
|
||||
onMouseEvent,
|
||||
icon: Icon,
|
||||
}: OptionButtonProps) => (
|
||||
<button
|
||||
type="button"
|
||||
role="listitem"
|
||||
className={clx(
|
||||
"bg-ui-bg-component txt-compact-small transition-fg flex items-center gap-2 rounded px-2 py-1 outline-none",
|
||||
{ "bg-ui-bg-component-hover": isFocused }
|
||||
)}
|
||||
onClick={onClick}
|
||||
onMouseEnter={() => onMouseEvent(index)}
|
||||
onMouseLeave={() => onMouseEvent(-1)}
|
||||
tabIndex={-1}
|
||||
>
|
||||
<div className="flex size-[15px] items-center justify-center">
|
||||
{isSelected && <Icon />}
|
||||
</div>
|
||||
<span>{option.label}</span>
|
||||
</button>
|
||||
)
|
||||
)
|
||||
|
||||
function useKeyboardNavigation(
|
||||
options: unknown[],
|
||||
onSelect: (index: number) => void,
|
||||
extraItems: number = 0
|
||||
) {
|
||||
const [focusedIndex, setFocusedIndex] = React.useState(-1)
|
||||
|
||||
const onKeyDown = React.useCallback(
|
||||
(e: KeyboardEvent) => {
|
||||
const totalLength = options.length + extraItems
|
||||
|
||||
if ((document.activeElement as HTMLElement).contentEditable === "true") {
|
||||
return
|
||||
}
|
||||
|
||||
switch (e.key) {
|
||||
case "ArrowDown":
|
||||
e.preventDefault()
|
||||
setFocusedIndex((prev) => (prev < totalLength - 1 ? prev + 1 : prev))
|
||||
break
|
||||
case "ArrowUp":
|
||||
e.preventDefault()
|
||||
setFocusedIndex((prev) => (prev > 0 ? prev - 1 : prev))
|
||||
break
|
||||
case " ":
|
||||
case "Enter":
|
||||
e.preventDefault()
|
||||
if (focusedIndex >= 0) {
|
||||
onSelect(focusedIndex)
|
||||
}
|
||||
break
|
||||
}
|
||||
},
|
||||
[options.length, extraItems, focusedIndex, onSelect]
|
||||
)
|
||||
|
||||
React.useEffect(() => {
|
||||
window.addEventListener("keydown", onKeyDown)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("keydown", onKeyDown)
|
||||
}
|
||||
}, [onKeyDown])
|
||||
|
||||
return { focusedIndex, setFocusedIndex }
|
||||
}
|
||||
|
||||
export { DataTableFilter }
|
||||
export type { DataTableFilterProps }
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
|
||||
import { useDataTableContext } from "@/blocks/data-table/context/use-data-table-context"
|
||||
import { Skeleton } from "@/components/skeleton"
|
||||
import { Table } from "@/components/table"
|
||||
|
||||
interface DataTablePaginationProps {
|
||||
translations?: React.ComponentProps<typeof Table.Pagination>["translations"]
|
||||
}
|
||||
|
||||
const DataTablePagination = (props: DataTablePaginationProps) => {
|
||||
const { instance } = useDataTableContext()
|
||||
|
||||
if (!instance.enablePagination) {
|
||||
throw new Error(
|
||||
"DataTable.Pagination was rendered but pagination is not enabled. Make sure to pass pagination to 'useDataTable'"
|
||||
)
|
||||
}
|
||||
|
||||
if (instance.showSkeleton) {
|
||||
return <DataTablePaginationSkeleton />
|
||||
}
|
||||
|
||||
return (
|
||||
<Table.Pagination
|
||||
translations={props.translations}
|
||||
className="flex-shrink-0"
|
||||
canNextPage={instance.getCanNextPage()}
|
||||
canPreviousPage={instance.getCanPreviousPage()}
|
||||
pageCount={instance.getPageCount()}
|
||||
count={instance.rowCount}
|
||||
nextPage={instance.nextPage}
|
||||
previousPage={instance.previousPage}
|
||||
pageIndex={instance.pageIndex}
|
||||
pageSize={instance.pageSize}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const DataTablePaginationSkeleton = () => {
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center justify-between p-4">
|
||||
<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>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export { DataTablePagination }
|
||||
export type { DataTablePaginationProps }
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
"use client"
|
||||
|
||||
import { Input } from "@/components/input"
|
||||
import { Skeleton } from "@/components/skeleton"
|
||||
import { clx } from "@/utils/clx"
|
||||
import * as React from "react"
|
||||
|
||||
import { useDataTableContext } from "@/blocks/data-table/context/use-data-table-context"
|
||||
|
||||
interface DataTableSearchProps {
|
||||
autoFocus?: boolean
|
||||
className?: string
|
||||
placeholder?: string
|
||||
}
|
||||
|
||||
const DataTableSearch = (props: DataTableSearchProps) => {
|
||||
const { className, ...rest } = props
|
||||
const { instance } = useDataTableContext()
|
||||
|
||||
if (!instance.enableSearch) {
|
||||
throw new Error(
|
||||
"DataTable.Search was rendered but search is not enabled. Make sure to pass search to 'useDataTable'"
|
||||
)
|
||||
}
|
||||
|
||||
if (instance.showSkeleton) {
|
||||
return <DataTableSearchSkeleton />
|
||||
}
|
||||
|
||||
return (
|
||||
<Input
|
||||
size="small"
|
||||
type="search"
|
||||
value={instance.getSearch()}
|
||||
onChange={(e) => instance.onSearchChange(e.target.value)}
|
||||
className={clx(
|
||||
{
|
||||
"pr-[calc(15px+2px+8px)]": instance.isLoading,
|
||||
},
|
||||
className
|
||||
)}
|
||||
{...rest}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const DataTableSearchSkeleton = () => {
|
||||
return <Skeleton className="h-7 w-[128px]" />
|
||||
}
|
||||
|
||||
export { DataTableSearch }
|
||||
export type { DataTableSearchProps }
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
"use client"
|
||||
|
||||
import type { DataTableCellContext, DataTableHeaderContext } from "@/blocks/data-table/types"
|
||||
import { Checkbox } from "@/components/checkbox"
|
||||
import { CheckedState } from "@radix-ui/react-checkbox"
|
||||
import * as React from "react"
|
||||
|
||||
interface DataTableSelectCellProps<TData> {
|
||||
ctx: DataTableCellContext<TData, unknown>
|
||||
}
|
||||
|
||||
const DataTableSelectCell = <TData,>(props: DataTableSelectCellProps<TData>) => {
|
||||
const checked = props.ctx.row.getIsSelected()
|
||||
const onChange = props.ctx.row.getToggleSelectedHandler()
|
||||
|
||||
return (
|
||||
<Checkbox
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
checked={checked}
|
||||
onCheckedChange={onChange}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
interface DataTableSelectHeaderProps<TData> {
|
||||
ctx: DataTableHeaderContext<TData, unknown>
|
||||
}
|
||||
|
||||
const DataTableSelectHeader = <TData,>(props: DataTableSelectHeaderProps<TData>) => {
|
||||
const checked = props.ctx.table.getIsSomePageRowsSelected()
|
||||
? "indeterminate"
|
||||
: props.ctx.table.getIsAllPageRowsSelected()
|
||||
|
||||
const onChange = (checked: CheckedState) => {
|
||||
props.ctx.table.toggleAllPageRowsSelected(!!checked)
|
||||
}
|
||||
|
||||
return (
|
||||
<Checkbox
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
checked={checked}
|
||||
onCheckedChange={onChange}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { DataTableSelectCell, DataTableSelectHeader }
|
||||
export type { DataTableSelectCellProps, DataTableSelectHeaderProps }
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
"use client"
|
||||
|
||||
import { DataTableSortDirection } from "@/blocks/data-table/types"
|
||||
import { clx } from "@/utils/clx"
|
||||
import * as React from "react"
|
||||
|
||||
interface SortingIconProps {
|
||||
direction: DataTableSortDirection | false
|
||||
}
|
||||
|
||||
const DataTableSortingIcon = (props: SortingIconProps) => {
|
||||
const isAscending = props.direction === "asc"
|
||||
const isDescending = props.direction === "desc"
|
||||
|
||||
const isSorted = isAscending || isDescending
|
||||
|
||||
return (
|
||||
<svg
|
||||
width="16"
|
||||
height="15"
|
||||
viewBox="0 0 16 15"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={clx("opacity-0 transition-opacity group-hover:opacity-100", {
|
||||
"opacity-100": isSorted,
|
||||
})}
|
||||
>
|
||||
<path
|
||||
d="M5.82651 5.75C5.66344 5.74994 5.50339 5.71269 5.36308 5.64216C5.22277 5.57162 5.10736 5.47039 5.02891 5.34904C4.95045 5.22769 4.91184 5.09067 4.9171 4.95232C4.92236 4.81397 4.97131 4.67936 5.05882 4.56255L7.64833 1.10788C7.73055 0.998207 7.84403 0.907911 7.97827 0.845354C8.11252 0.782797 8.26318 0.75 8.41632 0.75C8.56946 0.75 8.72013 0.782797 8.85437 0.845354C8.98862 0.907911 9.1021 0.998207 9.18432 1.10788L11.7744 4.56255C11.862 4.67939 11.9109 4.81405 11.9162 4.95245C11.9214 5.09085 11.8827 5.2279 11.8042 5.34926C11.7257 5.47063 11.6102 5.57185 11.4698 5.64235C11.3294 5.71285 11.1693 5.75003 11.0061 5.75H5.82651Z"
|
||||
className={clx("fill-ui-fg-muted", {
|
||||
"fill-ui-fg-subtle": isAscending,
|
||||
})}
|
||||
/>
|
||||
<path
|
||||
d="M11.0067 9.25C11.1698 9.25006 11.3299 9.28731 11.4702 9.35784C11.6105 9.42838 11.7259 9.52961 11.8043 9.65096C11.8828 9.77231 11.9214 9.90933 11.9162 10.0477C11.9109 10.186 11.8619 10.3206 11.7744 10.4374L9.18492 13.8921C9.10271 14.0018 8.98922 14.0921 8.85498 14.1546C8.72074 14.2172 8.57007 14.25 8.41693 14.25C8.26379 14.25 8.11312 14.2172 7.97888 14.1546C7.84464 14.0921 7.73115 14.0018 7.64894 13.8921L5.05882 10.4374C4.97128 10.3206 4.92233 10.1859 4.9171 10.0476C4.91186 9.90915 4.95053 9.7721 5.02905 9.65074C5.10758 9.52937 5.22308 9.42815 5.36347 9.35765C5.50387 9.28715 5.664 9.24997 5.82712 9.25H11.0067Z"
|
||||
className={clx("fill-ui-fg-muted", {
|
||||
"fill-ui-fg-subtle": isDescending,
|
||||
})}
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export { DataTableSortingIcon }
|
||||
export type { SortingIconProps }
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user