feat(ui,dashboard): Migrate SC tables to DataTable (#11106)
This commit is contained in:
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/ui": patch
|
||||||
|
"@medusajs/dashboard": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
feat(ui,dashboard): Migrate SC tables to DataTable
|
||||||
@@ -45,10 +45,10 @@
|
|||||||
"@dnd-kit/sortable": "^8.0.0",
|
"@dnd-kit/sortable": "^8.0.0",
|
||||||
"@hookform/error-message": "^2.0.1",
|
"@hookform/error-message": "^2.0.1",
|
||||||
"@hookform/resolvers": "3.4.2",
|
"@hookform/resolvers": "3.4.2",
|
||||||
"@medusajs/admin-shared": "2.4.0",
|
"@medusajs/admin-shared": "~2.4.0",
|
||||||
"@medusajs/icons": "2.4.0",
|
"@medusajs/icons": "~2.4.0",
|
||||||
"@medusajs/js-sdk": "2.4.0",
|
"@medusajs/js-sdk": "~2.4.0",
|
||||||
"@medusajs/ui": "4.0.4",
|
"@medusajs/ui": "~4.0.4",
|
||||||
"@tanstack/react-query": "5.64.2",
|
"@tanstack/react-query": "5.64.2",
|
||||||
"@tanstack/react-table": "8.20.5",
|
"@tanstack/react-table": "8.20.5",
|
||||||
"@tanstack/react-virtual": "^3.8.3",
|
"@tanstack/react-virtual": "^3.8.3",
|
||||||
@@ -76,9 +76,9 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@medusajs/admin-shared": "~2.4.0",
|
"@medusajs/admin-shared": "~2.4.0",
|
||||||
"@medusajs/admin-vite-plugin": "2.4.0",
|
"@medusajs/admin-vite-plugin": "~2.4.0",
|
||||||
"@medusajs/types": "2.4.0",
|
"@medusajs/types": "~2.4.0",
|
||||||
"@medusajs/ui-preset": "2.4.0",
|
"@medusajs/ui-preset": "~2.4.0",
|
||||||
"@types/node": "^20.11.15",
|
"@types/node": "^20.11.15",
|
||||||
"@types/react": "^18.2.79",
|
"@types/react": "^18.2.79",
|
||||||
"@types/react-dom": "^18.2.25",
|
"@types/react-dom": "^18.2.25",
|
||||||
|
|||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
import { clx } from "@medusajs/ui"
|
||||||
|
import { PropsWithChildren } from "react"
|
||||||
|
|
||||||
|
type DataTableStatusCellProps = PropsWithChildren<{
|
||||||
|
color?: "green" | "red" | "blue" | "orange" | "grey" | "purple"
|
||||||
|
}>
|
||||||
|
|
||||||
|
export const DataTableStatusCell = ({
|
||||||
|
color,
|
||||||
|
children,
|
||||||
|
}: DataTableStatusCellProps) => {
|
||||||
|
return (
|
||||||
|
<div className="txt-compact-small text-ui-fg-subtle flex h-full w-full items-center gap-x-2 overflow-hidden">
|
||||||
|
<div
|
||||||
|
role="presentation"
|
||||||
|
className="flex h-5 w-2 items-center justify-center"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={clx(
|
||||||
|
"h-2 w-2 rounded-sm shadow-[0px_0px_0px_1px_rgba(0,0,0,0.12)_inset]",
|
||||||
|
{
|
||||||
|
"bg-ui-tag-neutral-icon": color === "grey",
|
||||||
|
"bg-ui-tag-green-icon": color === "green",
|
||||||
|
"bg-ui-tag-red-icon": color === "red",
|
||||||
|
"bg-ui-tag-blue-icon": color === "blue",
|
||||||
|
"bg-ui-tag-orange-icon": color === "orange",
|
||||||
|
"bg-ui-tag-purple-icon": color === "purple",
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span className="truncate">{children}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,15 +1,18 @@
|
|||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
|
clx,
|
||||||
DataTableColumnDef,
|
DataTableColumnDef,
|
||||||
DataTableCommand,
|
DataTableCommand,
|
||||||
DataTableEmptyStateProps,
|
DataTableEmptyStateProps,
|
||||||
DataTableFilter,
|
DataTableFilter,
|
||||||
DataTableFilteringState,
|
DataTableFilteringState,
|
||||||
DataTablePaginationState,
|
DataTablePaginationState,
|
||||||
|
DataTableRow,
|
||||||
DataTableRowSelectionState,
|
DataTableRowSelectionState,
|
||||||
DataTableSortingState,
|
DataTableSortingState,
|
||||||
Heading,
|
Heading,
|
||||||
DataTable as Primitive,
|
DataTable as Primitive,
|
||||||
|
Text,
|
||||||
useDataTable,
|
useDataTable,
|
||||||
} from "@medusajs/ui"
|
} from "@medusajs/ui"
|
||||||
import React, { ReactNode, useCallback, useState } from "react"
|
import React, { ReactNode, useCallback, useState } from "react"
|
||||||
@@ -66,14 +69,17 @@ interface DataTableProps<TData> {
|
|||||||
autoFocusSearch?: boolean
|
autoFocusSearch?: boolean
|
||||||
rowHref?: (row: TData) => string
|
rowHref?: (row: TData) => string
|
||||||
emptyState?: DataTableEmptyStateProps
|
emptyState?: DataTableEmptyStateProps
|
||||||
heading: string
|
heading?: string
|
||||||
|
subHeading?: string
|
||||||
prefix?: string
|
prefix?: string
|
||||||
pageSize?: number
|
pageSize?: number
|
||||||
isLoading?: boolean
|
isLoading?: boolean
|
||||||
rowSelection?: {
|
rowSelection?: {
|
||||||
state: DataTableRowSelectionState
|
state: DataTableRowSelectionState
|
||||||
onRowSelectionChange: (value: DataTableRowSelectionState) => void
|
onRowSelectionChange: (value: DataTableRowSelectionState) => void
|
||||||
|
enableRowSelection?: boolean | ((row: DataTableRow<TData>) => boolean)
|
||||||
}
|
}
|
||||||
|
layout?: "fill" | "auto"
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DataTable = <TData,>({
|
export const DataTable = <TData,>({
|
||||||
@@ -90,11 +96,13 @@ export const DataTable = <TData,>({
|
|||||||
autoFocusSearch = false,
|
autoFocusSearch = false,
|
||||||
rowHref,
|
rowHref,
|
||||||
heading,
|
heading,
|
||||||
|
subHeading,
|
||||||
prefix,
|
prefix,
|
||||||
pageSize = 10,
|
pageSize = 10,
|
||||||
emptyState,
|
emptyState,
|
||||||
rowSelection,
|
rowSelection,
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
|
layout = "auto",
|
||||||
}: DataTableProps<TData>) => {
|
}: DataTableProps<TData>) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
@@ -258,14 +266,30 @@ export const DataTable = <TData,>({
|
|||||||
isLoading,
|
isLoading,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const shouldRenderHeading = heading || subHeading
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Primitive instance={instance}>
|
<Primitive
|
||||||
|
instance={instance}
|
||||||
|
className={clx({
|
||||||
|
"h-full [&_tr]:last-of-type:!border-b": layout === "fill",
|
||||||
|
})}
|
||||||
|
>
|
||||||
<Primitive.Toolbar
|
<Primitive.Toolbar
|
||||||
className="flex flex-col items-start justify-between gap-2 md:flex-row md:items-center"
|
className="flex flex-col items-start justify-between gap-2 md:flex-row md:items-center"
|
||||||
translations={toolbarTranslations}
|
translations={toolbarTranslations}
|
||||||
>
|
>
|
||||||
<div className="flex w-full items-center justify-between">
|
<div className="flex w-full items-center justify-between gap-2">
|
||||||
<Heading>{heading}</Heading>
|
{shouldRenderHeading && (
|
||||||
|
<div>
|
||||||
|
{heading && <Heading>{heading}</Heading>}
|
||||||
|
{subHeading && (
|
||||||
|
<Text size="small" className="text-ui-fg-subtle">
|
||||||
|
{subHeading}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="flex items-center justify-end gap-x-2 md:hidden">
|
<div className="flex items-center justify-end gap-x-2 md:hidden">
|
||||||
{enableFiltering && (
|
{enableFiltering && (
|
||||||
<Primitive.FilterMenu tooltip={t("filters.filterLabel")} />
|
<Primitive.FilterMenu tooltip={t("filters.filterLabel")} />
|
||||||
|
|||||||
+61
@@ -0,0 +1,61 @@
|
|||||||
|
import {
|
||||||
|
createDataTableColumnHelper,
|
||||||
|
DataTableColumnDef,
|
||||||
|
Tooltip,
|
||||||
|
} from "@medusajs/ui"
|
||||||
|
import { useMemo } from "react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
import { useDate } from "../../../../hooks/use-date"
|
||||||
|
|
||||||
|
type EntityWithDates = {
|
||||||
|
created_at: string
|
||||||
|
updated_at: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const columnHelper = createDataTableColumnHelper<EntityWithDates>()
|
||||||
|
|
||||||
|
export const useDataTableDateColumns = <TData extends EntityWithDates>() => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { getFullDate } = useDate()
|
||||||
|
|
||||||
|
return useMemo(() => {
|
||||||
|
return [
|
||||||
|
columnHelper.accessor("created_at", {
|
||||||
|
header: t("fields.createdAt"),
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return (
|
||||||
|
<Tooltip
|
||||||
|
content={getFullDate({
|
||||||
|
date: row.original.created_at,
|
||||||
|
includeTime: true,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<span>{getFullDate({ date: row.original.created_at })}</span>
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
enableSorting: true,
|
||||||
|
sortAscLabel: t("filters.sorting.dateAsc"),
|
||||||
|
sortDescLabel: t("filters.sorting.dateDesc"),
|
||||||
|
}),
|
||||||
|
columnHelper.accessor("updated_at", {
|
||||||
|
header: t("fields.updatedAt"),
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return (
|
||||||
|
<Tooltip
|
||||||
|
content={getFullDate({
|
||||||
|
date: row.original.updated_at,
|
||||||
|
includeTime: true,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<span>{getFullDate({ date: row.original.updated_at })}</span>
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
enableSorting: true,
|
||||||
|
sortAscLabel: t("filters.sorting.dateAsc"),
|
||||||
|
sortDescLabel: t("filters.sorting.dateDesc"),
|
||||||
|
}),
|
||||||
|
] as DataTableColumnDef<TData>[]
|
||||||
|
}, [t, getFullDate])
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export * from "./use-sales-channel-table-columns"
|
||||||
|
export * from "./use-sales-channel-table-empty-state"
|
||||||
|
export * from "./use-sales-channel-table-filters"
|
||||||
|
export * from "./use-sales-channel-table-query"
|
||||||
+61
@@ -0,0 +1,61 @@
|
|||||||
|
import { HttpTypes } from "@medusajs/types"
|
||||||
|
import { useMemo } from "react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
|
||||||
|
import { createDataTableColumnHelper, Tooltip } from "@medusajs/ui"
|
||||||
|
import { DataTableStatusCell } from "../../components/data-table-status-cell/data-table-status-cell"
|
||||||
|
import { useDataTableDateColumns } from "../general/use-data-table-date-columns"
|
||||||
|
|
||||||
|
const columnHelper = createDataTableColumnHelper<HttpTypes.AdminSalesChannel>()
|
||||||
|
|
||||||
|
export const useSalesChannelTableColumns = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const dateColumns = useDataTableDateColumns<HttpTypes.AdminSalesChannel>()
|
||||||
|
|
||||||
|
return useMemo(
|
||||||
|
() => [
|
||||||
|
columnHelper.accessor("name", {
|
||||||
|
header: () => t("fields.name"),
|
||||||
|
enableSorting: true,
|
||||||
|
sortLabel: t("fields.name"),
|
||||||
|
sortAscLabel: t("filters.sorting.alphabeticallyAsc"),
|
||||||
|
sortDescLabel: t("filters.sorting.alphabeticallyDesc"),
|
||||||
|
}),
|
||||||
|
columnHelper.accessor("description", {
|
||||||
|
header: () => t("fields.description"),
|
||||||
|
cell: ({ getValue }) => {
|
||||||
|
return (
|
||||||
|
<Tooltip content={getValue()}>
|
||||||
|
<div className="flex h-full w-full items-center overflow-hidden">
|
||||||
|
<span className="truncate">{getValue()}</span>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
enableSorting: true,
|
||||||
|
sortLabel: t("fields.description"),
|
||||||
|
sortAscLabel: t("filters.sorting.alphabeticallyAsc"),
|
||||||
|
sortDescLabel: t("filters.sorting.alphabeticallyDesc"),
|
||||||
|
maxSize: 250,
|
||||||
|
minSize: 100,
|
||||||
|
}),
|
||||||
|
columnHelper.accessor("is_disabled", {
|
||||||
|
header: () => t("fields.status"),
|
||||||
|
enableSorting: true,
|
||||||
|
sortLabel: t("fields.status"),
|
||||||
|
sortAscLabel: t("filters.sorting.alphabeticallyAsc"),
|
||||||
|
sortDescLabel: t("filters.sorting.alphabeticallyDesc"),
|
||||||
|
cell: ({ getValue }) => {
|
||||||
|
const value = getValue()
|
||||||
|
return (
|
||||||
|
<DataTableStatusCell color={value ? "grey" : "green"}>
|
||||||
|
{value ? t("general.disabled") : t("general.enabled")}
|
||||||
|
</DataTableStatusCell>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
...dateColumns,
|
||||||
|
],
|
||||||
|
[t, dateColumns]
|
||||||
|
)
|
||||||
|
}
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
import { DataTableEmptyStateProps } from "@medusajs/ui"
|
||||||
|
import { useMemo } from "react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
|
||||||
|
export const useSalesChannelTableEmptyState = (): DataTableEmptyStateProps => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return useMemo(() => {
|
||||||
|
const content: DataTableEmptyStateProps = {
|
||||||
|
empty: {
|
||||||
|
heading: t("salesChannels.list.empty.heading"),
|
||||||
|
description: t("salesChannels.list.empty.description"),
|
||||||
|
},
|
||||||
|
filtered: {
|
||||||
|
heading: t("salesChannels.list.filtered.heading"),
|
||||||
|
description: t("salesChannels.list.filtered.description"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return content
|
||||||
|
}, [t])
|
||||||
|
}
|
||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
import { HttpTypes } from "@medusajs/types"
|
||||||
|
import { createDataTableFilterHelper } from "@medusajs/ui"
|
||||||
|
import { useMemo } from "react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
import { useDataTableDateFilters } from "../general/use-data-table-date-filters"
|
||||||
|
|
||||||
|
const filterHelper = createDataTableFilterHelper<HttpTypes.AdminSalesChannel>()
|
||||||
|
|
||||||
|
export const useSalesChannelTableFilters = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const dateFilters = useDataTableDateFilters()
|
||||||
|
|
||||||
|
return useMemo(
|
||||||
|
() => [
|
||||||
|
filterHelper.accessor("is_disabled", {
|
||||||
|
label: t("fields.status"),
|
||||||
|
type: "radio",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: t("general.enabled"),
|
||||||
|
value: "false",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t("general.disabled"),
|
||||||
|
value: "true",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
...dateFilters,
|
||||||
|
],
|
||||||
|
[dateFilters, t]
|
||||||
|
)
|
||||||
|
}
|
||||||
+3
-11
@@ -1,5 +1,5 @@
|
|||||||
import { HttpTypes } from "@medusajs/types"
|
import { HttpTypes } from "@medusajs/types"
|
||||||
import { useQueryParams } from "../../use-query-params"
|
import { useQueryParams } from "../../../../hooks/use-query-params"
|
||||||
|
|
||||||
type UseSalesChannelTableQueryProps = {
|
type UseSalesChannelTableQueryProps = {
|
||||||
prefix?: string
|
prefix?: string
|
||||||
@@ -22,17 +22,9 @@ export const useSalesChannelTableQuery = ({
|
|||||||
offset: offset ? Number(offset) : 0,
|
offset: offset ? Number(offset) : 0,
|
||||||
created_at: created_at ? JSON.parse(created_at) : undefined,
|
created_at: created_at ? JSON.parse(created_at) : undefined,
|
||||||
updated_at: updated_at ? JSON.parse(updated_at) : undefined,
|
updated_at: updated_at ? JSON.parse(updated_at) : undefined,
|
||||||
is_disabled:
|
is_disabled: is_disabled ? JSON.parse(is_disabled) : undefined,
|
||||||
is_disabled === "true"
|
|
||||||
? true
|
|
||||||
: is_disabled === "false"
|
|
||||||
? false
|
|
||||||
: undefined,
|
|
||||||
...rest,
|
...rest,
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return searchParams
|
||||||
searchParams,
|
|
||||||
raw: queryObject,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
+3
@@ -5,6 +5,9 @@ type StatusCellProps = PropsWithChildren<{
|
|||||||
color?: "green" | "red" | "blue" | "orange" | "grey" | "purple"
|
color?: "green" | "red" | "blue" | "orange" | "grey" | "purple"
|
||||||
}>
|
}>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use the new DataTable and DataTableStatusCell instead
|
||||||
|
*/
|
||||||
export const StatusCell = ({ color, children }: StatusCellProps) => {
|
export const StatusCell = ({ color, children }: StatusCellProps) => {
|
||||||
return (
|
return (
|
||||||
<div className="txt-compact-small text-ui-fg-subtle flex h-full w-full items-center gap-x-2 overflow-hidden">
|
<div className="txt-compact-small text-ui-fg-subtle flex h-full w-full items-center gap-x-2 overflow-hidden">
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export const useSalesChannel = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const useSalesChannels = (
|
export const useSalesChannels = (
|
||||||
query?: Record<string, any>,
|
query?: HttpTypes.AdminSalesChannelListParams,
|
||||||
options?: Omit<
|
options?: Omit<
|
||||||
UseQueryOptions<
|
UseQueryOptions<
|
||||||
AdminSalesChannelListResponse,
|
AdminSalesChannelListResponse,
|
||||||
@@ -133,6 +133,34 @@ export const useDeleteSalesChannel = (
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const useDeleteSalesChannelLazy = (
|
||||||
|
options?: UseMutationOptions<
|
||||||
|
HttpTypes.AdminSalesChannelDeleteResponse,
|
||||||
|
FetchError,
|
||||||
|
string
|
||||||
|
>
|
||||||
|
) => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: (id: string) => sdk.admin.salesChannel.delete(id),
|
||||||
|
onSuccess: (data, variables, context) => {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: salesChannelsQueryKeys.lists(),
|
||||||
|
})
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: salesChannelsQueryKeys.detail(variables),
|
||||||
|
})
|
||||||
|
|
||||||
|
// Invalidate all products to ensure they are updated if they were linked to the sales channel
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: productsQueryKeys.all,
|
||||||
|
})
|
||||||
|
|
||||||
|
options?.onSuccess?.(data, variables, context)
|
||||||
|
},
|
||||||
|
...options,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export const useSalesChannelRemoveProducts = (
|
export const useSalesChannelRemoveProducts = (
|
||||||
id: string,
|
id: string,
|
||||||
options?: UseMutationOptions<
|
options?: UseMutationOptions<
|
||||||
|
|||||||
@@ -8,6 +8,4 @@ export * from "./use-product-tag-table-columns"
|
|||||||
export * from "./use-product-type-table-columns"
|
export * from "./use-product-type-table-columns"
|
||||||
export * from "./use-region-table-columns"
|
export * from "./use-region-table-columns"
|
||||||
export * from "./use-return-reason-table-columns"
|
export * from "./use-return-reason-table-columns"
|
||||||
export * from "./use-sales-channel-table-columns"
|
|
||||||
export * from "./use-tax-rates-table-columns"
|
export * from "./use-tax-rates-table-columns"
|
||||||
|
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
import { createColumnHelper } from "@tanstack/react-table"
|
|
||||||
|
|
||||||
import { HttpTypes } from "@medusajs/types"
|
|
||||||
import { useMemo } from "react"
|
|
||||||
import { useTranslation } from "react-i18next"
|
|
||||||
|
|
||||||
import { StatusCell } from "../../../components/table/table-cells/common/status-cell"
|
|
||||||
import { TextHeader } from "../../../components/table/table-cells/common/text-cell"
|
|
||||||
import {
|
|
||||||
DescriptionCell,
|
|
||||||
DescriptionHeader,
|
|
||||||
} from "../../../components/table/table-cells/sales-channel/description-cell"
|
|
||||||
import {
|
|
||||||
NameCell,
|
|
||||||
NameHeader,
|
|
||||||
} from "../../../components/table/table-cells/sales-channel/name-cell"
|
|
||||||
|
|
||||||
const columnHelper = createColumnHelper<HttpTypes.AdminSalesChannel>()
|
|
||||||
|
|
||||||
export const useSalesChannelTableColumns = () => {
|
|
||||||
const { t } = useTranslation()
|
|
||||||
|
|
||||||
return useMemo(
|
|
||||||
() => [
|
|
||||||
columnHelper.accessor("name", {
|
|
||||||
header: () => <NameHeader />,
|
|
||||||
cell: ({ getValue }) => <NameCell name={getValue()} />,
|
|
||||||
}),
|
|
||||||
columnHelper.accessor("description", {
|
|
||||||
header: () => <DescriptionHeader />,
|
|
||||||
cell: ({ getValue }) => <DescriptionCell description={getValue()} />,
|
|
||||||
}),
|
|
||||||
columnHelper.accessor("is_disabled", {
|
|
||||||
header: () => <TextHeader text={t("fields.status")} />,
|
|
||||||
cell: ({ getValue }) => {
|
|
||||||
const value = getValue()
|
|
||||||
return (
|
|
||||||
<StatusCell color={value ? "grey" : "green"}>
|
|
||||||
{value ? t("general.disabled") : t("general.enabled")}
|
|
||||||
</StatusCell>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
[t]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -8,6 +8,5 @@ export * from "./use-product-tag-table-filters"
|
|||||||
export * from "./use-product-type-table-filters"
|
export * from "./use-product-type-table-filters"
|
||||||
export * from "./use-promotion-table-filters"
|
export * from "./use-promotion-table-filters"
|
||||||
export * from "./use-region-table-filters"
|
export * from "./use-region-table-filters"
|
||||||
export * from "./use-sales-channel-table-filters"
|
|
||||||
export * from "./use-shipping-option-table-filters"
|
export * from "./use-shipping-option-table-filters"
|
||||||
export * from "./use-tax-rate-table-filters"
|
export * from "./use-tax-rate-table-filters"
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
import { useTranslation } from "react-i18next"
|
|
||||||
import { Filter } from "../../../components/table/data-table"
|
|
||||||
|
|
||||||
export const useSalesChannelTableFilters = () => {
|
|
||||||
const { t } = useTranslation()
|
|
||||||
|
|
||||||
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",
|
|
||||||
}))
|
|
||||||
|
|
||||||
return dateFilters
|
|
||||||
}
|
|
||||||
@@ -8,7 +8,6 @@ export * from "./use-product-tag-table-query"
|
|||||||
export * from "./use-product-type-table-query"
|
export * from "./use-product-type-table-query"
|
||||||
export * from "./use-region-table-query"
|
export * from "./use-region-table-query"
|
||||||
export * from "./use-return-reason-table-query"
|
export * from "./use-return-reason-table-query"
|
||||||
export * from "./use-sales-channel-table-query"
|
|
||||||
export * from "./use-shipping-option-table-query"
|
export * from "./use-shipping-option-table-query"
|
||||||
export * from "./use-tax-rate-table-query"
|
export * from "./use-tax-rate-table-query"
|
||||||
export * from "./use-tax-region-table-query"
|
export * from "./use-tax-region-table-query"
|
||||||
|
|||||||
@@ -5724,6 +5724,9 @@
|
|||||||
"header": {
|
"header": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"hint": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"label": {
|
"label": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@@ -5742,6 +5745,7 @@
|
|||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"header",
|
"header",
|
||||||
|
"hint",
|
||||||
"label",
|
"label",
|
||||||
"connectedTo",
|
"connectedTo",
|
||||||
"noChannels",
|
"noChannels",
|
||||||
@@ -9202,6 +9206,39 @@
|
|||||||
"subtitle": {
|
"subtitle": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"list": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"empty": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"heading": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["heading", "description"],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
|
"filtered": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"heading": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["heading", "description"],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["empty", "filtered"],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
"createSalesChannel": {
|
"createSalesChannel": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@@ -9293,6 +9330,7 @@
|
|||||||
"required": [
|
"required": [
|
||||||
"domain",
|
"domain",
|
||||||
"subtitle",
|
"subtitle",
|
||||||
|
"list",
|
||||||
"createSalesChannel",
|
"createSalesChannel",
|
||||||
"createSalesChannelHint",
|
"createSalesChannelHint",
|
||||||
"enabledHint",
|
"enabledHint",
|
||||||
|
|||||||
@@ -1513,6 +1513,7 @@
|
|||||||
},
|
},
|
||||||
"salesChannels": {
|
"salesChannels": {
|
||||||
"header": "Sales Channels",
|
"header": "Sales Channels",
|
||||||
|
"hint": "Manage the sales channels that are connected to this location.",
|
||||||
"label": "Connected sales channels",
|
"label": "Connected sales channels",
|
||||||
"connectedTo": "Connected to {{count}} of {{total}} sales channels",
|
"connectedTo": "Connected to {{count}} of {{total}} sales channels",
|
||||||
"noChannels": "The location is not connected to any sales channels.",
|
"noChannels": "The location is not connected to any sales channels.",
|
||||||
@@ -2469,6 +2470,16 @@
|
|||||||
"salesChannels": {
|
"salesChannels": {
|
||||||
"domain": "Sales Channels",
|
"domain": "Sales Channels",
|
||||||
"subtitle": "Manage the online and offline channels you sell products on.",
|
"subtitle": "Manage the online and offline channels you sell products on.",
|
||||||
|
"list": {
|
||||||
|
"empty": {
|
||||||
|
"heading": "No sales channels found",
|
||||||
|
"description": "Once a sales channel has been created, it will appear here."
|
||||||
|
},
|
||||||
|
"filtered": {
|
||||||
|
"heading": "No results",
|
||||||
|
"description": "No sales channels match the current filter criteria."
|
||||||
|
}
|
||||||
|
},
|
||||||
"createSalesChannel": "Create Sales Channel",
|
"createSalesChannel": "Create Sales Channel",
|
||||||
"createSalesChannelHint": "Create a new sales channel to sell your products on.",
|
"createSalesChannelHint": "Create a new sales channel to sell your products on.",
|
||||||
"enabledHint": "Specify whether the sales channel is enabled.",
|
"enabledHint": "Specify whether the sales channel is enabled.",
|
||||||
|
|||||||
+150
-215
@@ -1,270 +1,205 @@
|
|||||||
import { PencilSquare, Plus, Trash } from "@medusajs/icons"
|
import { PencilSquare, Trash } from "@medusajs/icons"
|
||||||
import { AdminApiKeyResponse, AdminSalesChannelResponse } from "@medusajs/types"
|
import { AdminApiKeyResponse, HttpTypes } from "@medusajs/types"
|
||||||
import { Checkbox, Container, Heading, toast, usePrompt } from "@medusajs/ui"
|
import {
|
||||||
|
Container,
|
||||||
|
createDataTableColumnHelper,
|
||||||
|
createDataTableCommandHelper,
|
||||||
|
DataTableRowSelectionState,
|
||||||
|
toast,
|
||||||
|
usePrompt,
|
||||||
|
} from "@medusajs/ui"
|
||||||
import { keepPreviousData } from "@tanstack/react-query"
|
import { keepPreviousData } from "@tanstack/react-query"
|
||||||
import { RowSelectionState, createColumnHelper } from "@tanstack/react-table"
|
import { RowSelectionState } from "@tanstack/react-table"
|
||||||
import { useMemo, useState } from "react"
|
import { useCallback, useMemo, useState } from "react"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
import { useNavigate } from "react-router-dom"
|
||||||
import { _DataTable } from "../../../../../components/table/data-table"
|
import { DataTable } from "../../../../../components/data-table"
|
||||||
|
import * as hooks from "../../../../../components/data-table/helpers/sales-channels"
|
||||||
import { useBatchRemoveSalesChannelsFromApiKey } from "../../../../../hooks/api/api-keys"
|
import { useBatchRemoveSalesChannelsFromApiKey } from "../../../../../hooks/api/api-keys"
|
||||||
import { useSalesChannels } from "../../../../../hooks/api/sales-channels"
|
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"
|
|
||||||
import { useSalesChannelTableQuery } from "../../../../../hooks/table/query/use-sales-channel-table-query"
|
|
||||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
|
||||||
|
|
||||||
type ApiKeySalesChannelSectionProps = {
|
type ApiKeySalesChannelSectionProps = {
|
||||||
apiKey: AdminApiKeyResponse["api_key"]
|
apiKey: AdminApiKeyResponse["api_key"]
|
||||||
}
|
}
|
||||||
|
|
||||||
const PAGE_SIZE = 10
|
const PAGE_SIZE = 10
|
||||||
|
const PREFIX = "sc"
|
||||||
|
|
||||||
export const ApiKeySalesChannelSection = ({
|
export const ApiKeySalesChannelSection = ({
|
||||||
apiKey,
|
apiKey,
|
||||||
}: ApiKeySalesChannelSectionProps) => {
|
}: ApiKeySalesChannelSectionProps) => {
|
||||||
const [rowSelection, setRowSelection] = useState<RowSelectionState>({})
|
const [rowSelection, setRowSelection] = useState<RowSelectionState>({})
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const prompt = usePrompt()
|
|
||||||
|
|
||||||
const { raw, searchParams } = useSalesChannelTableQuery({
|
const searchParams = hooks.useSalesChannelTableQuery({
|
||||||
pageSize: PAGE_SIZE,
|
pageSize: PAGE_SIZE,
|
||||||
|
prefix: PREFIX,
|
||||||
})
|
})
|
||||||
|
|
||||||
const { sales_channels, count, isLoading } = useSalesChannels(
|
const { sales_channels, count, isPending } = useSalesChannels(
|
||||||
{ ...searchParams, publishable_key_id: apiKey.id },
|
{ ...searchParams, publishable_key_id: apiKey.id },
|
||||||
{
|
{
|
||||||
placeholderData: keepPreviousData,
|
placeholderData: keepPreviousData,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const columns = useColumns()
|
const columns = useColumns(apiKey.id)
|
||||||
const filters = useSalesChannelTableFilters()
|
const filters = hooks.useSalesChannelTableFilters()
|
||||||
|
const commands = useCommands(apiKey.id, setRowSelection)
|
||||||
const { table } = useDataTable({
|
const emptyState = hooks.useSalesChannelTableEmptyState()
|
||||||
data: sales_channels ?? [],
|
|
||||||
columns,
|
|
||||||
count,
|
|
||||||
enablePagination: true,
|
|
||||||
enableRowSelection: true,
|
|
||||||
getRowId: (row) => row.id,
|
|
||||||
pageSize: PAGE_SIZE,
|
|
||||||
rowSelection: {
|
|
||||||
state: rowSelection,
|
|
||||||
updater: setRowSelection,
|
|
||||||
},
|
|
||||||
meta: {
|
|
||||||
apiKey: apiKey.id,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const { mutateAsync } = useBatchRemoveSalesChannelsFromApiKey(apiKey.id)
|
|
||||||
|
|
||||||
const handleRemove = async () => {
|
|
||||||
const keys = Object.keys(rowSelection)
|
|
||||||
|
|
||||||
const res = await prompt({
|
|
||||||
title: t("general.areYouSure"),
|
|
||||||
description: t("apiKeyManagement.removeSalesChannel.warningBatch", {
|
|
||||||
count: keys.length,
|
|
||||||
}),
|
|
||||||
confirmText: t("actions.continue"),
|
|
||||||
cancelText: t("actions.cancel"),
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!res) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
await mutateAsync(keys, {
|
|
||||||
onSuccess: () => {
|
|
||||||
toast.success(
|
|
||||||
t("apiKeyManagement.removeSalesChannel.successToastBatch", {
|
|
||||||
count: keys.length,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
setRowSelection({})
|
|
||||||
},
|
|
||||||
onError: (err) => {
|
|
||||||
toast.error(err.message)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container className="divide-y p-0">
|
<Container className="divide-y p-0">
|
||||||
<div className="flex items-center justify-between px-6 py-4">
|
<DataTable
|
||||||
<Heading level="h2">{t("salesChannels.domain")}</Heading>
|
data={sales_channels}
|
||||||
<ActionMenu
|
|
||||||
groups={[
|
|
||||||
{
|
|
||||||
actions: [
|
|
||||||
{
|
|
||||||
icon: <Plus />,
|
|
||||||
label: t("actions.add"),
|
|
||||||
to: "sales-channels",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<_DataTable
|
|
||||||
table={table}
|
|
||||||
columns={columns}
|
columns={columns}
|
||||||
filters={filters}
|
filters={filters}
|
||||||
count={count}
|
commands={commands}
|
||||||
isLoading={isLoading}
|
heading={t("salesChannels.domain")}
|
||||||
queryObject={raw}
|
getRowId={(row) => row.id}
|
||||||
navigateTo={(row) => `/settings/sales-channels/${row.id}`}
|
rowCount={count}
|
||||||
orderBy={[
|
isLoading={isPending}
|
||||||
{
|
emptyState={emptyState}
|
||||||
key: "name",
|
rowSelection={{
|
||||||
label: t("fields.name"),
|
state: rowSelection,
|
||||||
},
|
onRowSelectionChange: setRowSelection,
|
||||||
{
|
|
||||||
key: "created_at",
|
|
||||||
label: t("fields.createdAt"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "updated_at",
|
|
||||||
label: t("fields.updatedAt"),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
commands={[
|
|
||||||
{
|
|
||||||
action: handleRemove,
|
|
||||||
label: t("actions.remove"),
|
|
||||||
shortcut: "r",
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
pageSize={PAGE_SIZE}
|
|
||||||
pagination
|
|
||||||
search
|
|
||||||
noRecords={{
|
|
||||||
message: t("apiKeyManagement.salesChannels.list.noRecordsMessage"),
|
|
||||||
}}
|
}}
|
||||||
|
rowHref={(row) => `/settings/sales-channels/${row.id}`}
|
||||||
|
action={{
|
||||||
|
label: t("actions.add"),
|
||||||
|
to: "sales-channels",
|
||||||
|
}}
|
||||||
|
prefix={PREFIX}
|
||||||
|
pageSize={PAGE_SIZE}
|
||||||
/>
|
/>
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const SalesChannelActions = ({
|
const columnHelper = createDataTableColumnHelper<HttpTypes.AdminSalesChannel>()
|
||||||
salesChannel,
|
|
||||||
apiKey,
|
const useColumns = (id: string) => {
|
||||||
}: {
|
|
||||||
salesChannel: AdminSalesChannelResponse["sales_channel"]
|
|
||||||
apiKey: string
|
|
||||||
}) => {
|
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const navigate = useNavigate()
|
||||||
const prompt = usePrompt()
|
const prompt = usePrompt()
|
||||||
|
|
||||||
const { mutateAsync } = useBatchRemoveSalesChannelsFromApiKey(apiKey)
|
const base = hooks.useSalesChannelTableColumns()
|
||||||
|
|
||||||
const handleDelete = async () => {
|
const { mutateAsync } = useBatchRemoveSalesChannelsFromApiKey(id)
|
||||||
const res = await prompt({
|
|
||||||
title: t("general.areYouSure"),
|
|
||||||
description: t("apiKeyManagement.removeSalesChannel.warning", {
|
|
||||||
name: salesChannel.name,
|
|
||||||
}),
|
|
||||||
confirmText: t("actions.delete"),
|
|
||||||
cancelText: t("actions.cancel"),
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!res) {
|
const handleDelete = useCallback(
|
||||||
return
|
async (salesChannel: HttpTypes.AdminSalesChannel) => {
|
||||||
}
|
const res = await prompt({
|
||||||
|
title: t("general.areYouSure"),
|
||||||
|
description: t("apiKeyManagement.removeSalesChannel.warning", {
|
||||||
|
name: salesChannel.name,
|
||||||
|
}),
|
||||||
|
confirmText: t("actions.delete"),
|
||||||
|
cancelText: t("actions.cancel"),
|
||||||
|
})
|
||||||
|
|
||||||
await mutateAsync([salesChannel.id], {
|
if (!res) {
|
||||||
onSuccess: () => {
|
return
|
||||||
toast.success(
|
}
|
||||||
t("apiKeyManagement.removeSalesChannel.successToast", {
|
|
||||||
count: 1,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
},
|
|
||||||
onError: (err) => {
|
|
||||||
toast.error(err.message)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
await mutateAsync([salesChannel.id], {
|
||||||
<ActionMenu
|
onSuccess: () => {
|
||||||
groups={[
|
toast.success(
|
||||||
{
|
t("apiKeyManagement.removeSalesChannel.successToast", {
|
||||||
actions: [
|
count: 1,
|
||||||
{
|
})
|
||||||
icon: <PencilSquare />,
|
)
|
||||||
label: t("actions.edit"),
|
|
||||||
to: `/settings/sales-channels/${salesChannel.id}/edit`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
onError: (err) => {
|
||||||
actions: [
|
toast.error(err.message)
|
||||||
{
|
|
||||||
icon: <Trash />,
|
|
||||||
label: t("actions.delete"),
|
|
||||||
onClick: handleDelete,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
]}
|
})
|
||||||
/>
|
},
|
||||||
|
[mutateAsync, prompt, t]
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
const columnHelper =
|
|
||||||
createColumnHelper<AdminSalesChannelResponse["sales_channel"]>()
|
|
||||||
|
|
||||||
const useColumns = () => {
|
|
||||||
const base = useSalesChannelTableColumns()
|
|
||||||
|
|
||||||
return useMemo(
|
return useMemo(
|
||||||
() => [
|
() => [
|
||||||
columnHelper.display({
|
columnHelper.select(),
|
||||||
id: "select",
|
|
||||||
header: ({ table }) => {
|
|
||||||
return (
|
|
||||||
<Checkbox
|
|
||||||
checked={
|
|
||||||
table.getIsSomePageRowsSelected()
|
|
||||||
? "indeterminate"
|
|
||||||
: table.getIsAllPageRowsSelected()
|
|
||||||
}
|
|
||||||
onCheckedChange={(value) =>
|
|
||||||
table.toggleAllPageRowsSelected(!!value)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
cell: ({ row }) => {
|
|
||||||
return (
|
|
||||||
<Checkbox
|
|
||||||
checked={row.getIsSelected()}
|
|
||||||
onCheckedChange={(value) => row.toggleSelected(!!value)}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation()
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
...base,
|
...base,
|
||||||
columnHelper.display({
|
columnHelper.action({
|
||||||
id: "actions",
|
actions: (ctx) => [
|
||||||
cell: ({ row, table }) => {
|
[
|
||||||
const { apiKey } = table.options.meta as {
|
{
|
||||||
apiKey: string
|
label: t("actions.edit"),
|
||||||
}
|
icon: <PencilSquare />,
|
||||||
|
onClick: () => {
|
||||||
return (
|
navigate(`/settings/sales-channels/${ctx.row.original.id}/edit`)
|
||||||
<SalesChannelActions salesChannel={row.original} apiKey={apiKey} />
|
},
|
||||||
)
|
},
|
||||||
},
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
icon: <Trash />,
|
||||||
|
label: t("actions.delete"),
|
||||||
|
onClick: () => handleDelete(ctx.row.original),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
[base]
|
[base, handleDelete, navigate, t]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const commandHelper = createDataTableCommandHelper()
|
||||||
|
|
||||||
|
const useCommands = (
|
||||||
|
id: string,
|
||||||
|
setRowSelection: (state: DataTableRowSelectionState) => void
|
||||||
|
) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const prompt = usePrompt()
|
||||||
|
|
||||||
|
const { mutateAsync } = useBatchRemoveSalesChannelsFromApiKey(id)
|
||||||
|
|
||||||
|
const handleRemove = useCallback(
|
||||||
|
async (rowSelection: DataTableRowSelectionState) => {
|
||||||
|
const keys = Object.keys(rowSelection)
|
||||||
|
|
||||||
|
const res = await prompt({
|
||||||
|
title: t("general.areYouSure"),
|
||||||
|
description: t("apiKeyManagement.removeSalesChannel.warningBatch", {
|
||||||
|
count: keys.length,
|
||||||
|
}),
|
||||||
|
confirmText: t("actions.continue"),
|
||||||
|
cancelText: t("actions.cancel"),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!res) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await mutateAsync(keys, {
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success(
|
||||||
|
t("apiKeyManagement.removeSalesChannel.successToastBatch", {
|
||||||
|
count: keys.length,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
setRowSelection({})
|
||||||
|
},
|
||||||
|
onError: (err) => {
|
||||||
|
toast.error(err.message)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
[mutateAsync, prompt, t, setRowSelection]
|
||||||
|
)
|
||||||
|
|
||||||
|
return useMemo(
|
||||||
|
() => [
|
||||||
|
commandHelper.command({
|
||||||
|
action: handleRemove,
|
||||||
|
label: t("actions.remove"),
|
||||||
|
shortcut: "r",
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
[handleRemove, t]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+59
-99
@@ -1,29 +1,31 @@
|
|||||||
import { zodResolver } from "@hookform/resolvers/zod"
|
import { zodResolver } from "@hookform/resolvers/zod"
|
||||||
import { AdminSalesChannelResponse } from "@medusajs/types"
|
import { HttpTypes } from "@medusajs/types"
|
||||||
import { Button, Checkbox, Hint, Tooltip, toast } from "@medusajs/ui"
|
|
||||||
import { keepPreviousData } from "@tanstack/react-query"
|
|
||||||
import {
|
import {
|
||||||
OnChangeFn,
|
Button,
|
||||||
RowSelectionState,
|
Checkbox,
|
||||||
createColumnHelper,
|
DataTableRowSelectionState,
|
||||||
} from "@tanstack/react-table"
|
Hint,
|
||||||
|
createDataTableColumnHelper,
|
||||||
|
toast,
|
||||||
|
} from "@medusajs/ui"
|
||||||
|
import { keepPreviousData } from "@tanstack/react-query"
|
||||||
|
import { RowSelectionState } from "@tanstack/react-table"
|
||||||
import { useMemo, useState } from "react"
|
import { useMemo, useState } from "react"
|
||||||
import { useForm } from "react-hook-form"
|
import { useForm } from "react-hook-form"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import * as zod from "zod"
|
import * as zod from "zod"
|
||||||
|
|
||||||
|
import { ConditionalTooltip } from "../../../../../components/common/conditional-tooltip"
|
||||||
|
import { DataTable } from "../../../../../components/data-table"
|
||||||
|
import * as hooks from "../../../../../components/data-table/helpers/sales-channels"
|
||||||
import {
|
import {
|
||||||
RouteFocusModal,
|
RouteFocusModal,
|
||||||
useRouteModal,
|
useRouteModal,
|
||||||
} from "../../../../../components/modals"
|
} from "../../../../../components/modals"
|
||||||
import { _DataTable } from "../../../../../components/table/data-table"
|
|
||||||
import { KeyboundForm } from "../../../../../components/utilities/keybound-form"
|
import { KeyboundForm } from "../../../../../components/utilities/keybound-form"
|
||||||
import { VisuallyHidden } from "../../../../../components/utilities/visually-hidden"
|
import { VisuallyHidden } from "../../../../../components/utilities/visually-hidden"
|
||||||
import { useBatchAddSalesChannelsToApiKey } from "../../../../../hooks/api/api-keys"
|
import { useBatchAddSalesChannelsToApiKey } from "../../../../../hooks/api/api-keys"
|
||||||
import { useSalesChannels } from "../../../../../hooks/api/sales-channels"
|
import { useSalesChannels } from "../../../../../hooks/api/sales-channels"
|
||||||
import { useSalesChannelTableColumns } from "../../../../../hooks/table/columns/use-sales-channel-table-columns"
|
|
||||||
import { useSalesChannelTableFilters } from "../../../../../hooks/table/filters"
|
|
||||||
import { useSalesChannelTableQuery } from "../../../../../hooks/table/query/use-sales-channel-table-query"
|
|
||||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
|
||||||
|
|
||||||
type ApiKeySalesChannelFormProps = {
|
type ApiKeySalesChannelFormProps = {
|
||||||
apiKey: string
|
apiKey: string
|
||||||
@@ -31,10 +33,11 @@ type ApiKeySalesChannelFormProps = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const AddSalesChannelsToApiKeySchema = zod.object({
|
const AddSalesChannelsToApiKeySchema = zod.object({
|
||||||
sales_channel_ids: zod.array(zod.string()),
|
sales_channel_ids: zod.array(zod.string()).min(1),
|
||||||
})
|
})
|
||||||
|
|
||||||
const PAGE_SIZE = 50
|
const PAGE_SIZE = 50
|
||||||
|
const PREFIX = "sc_add"
|
||||||
|
|
||||||
export const ApiKeySalesChannelsForm = ({
|
export const ApiKeySalesChannelsForm = ({
|
||||||
apiKey,
|
apiKey,
|
||||||
@@ -54,51 +57,36 @@ export const ApiKeySalesChannelsForm = ({
|
|||||||
|
|
||||||
const [rowSelection, setRowSelection] = useState<RowSelectionState>({})
|
const [rowSelection, setRowSelection] = useState<RowSelectionState>({})
|
||||||
|
|
||||||
const { mutateAsync, isPending } = useBatchAddSalesChannelsToApiKey(apiKey)
|
const { mutateAsync, isPending: isMutating } =
|
||||||
|
useBatchAddSalesChannelsToApiKey(apiKey)
|
||||||
|
|
||||||
const { raw, searchParams } = useSalesChannelTableQuery({
|
const searchParams = hooks.useSalesChannelTableQuery({
|
||||||
pageSize: PAGE_SIZE,
|
pageSize: PAGE_SIZE,
|
||||||
|
prefix: PREFIX,
|
||||||
})
|
})
|
||||||
|
|
||||||
const columns = useColumns()
|
const columns = useColumns()
|
||||||
const filters = useSalesChannelTableFilters()
|
const filters = hooks.useSalesChannelTableFilters()
|
||||||
|
const emptyState = hooks.useSalesChannelTableEmptyState()
|
||||||
|
|
||||||
const { sales_channels, count, isLoading } = useSalesChannels(
|
const { sales_channels, count, isPending } = useSalesChannels(
|
||||||
{ ...searchParams },
|
{ ...searchParams },
|
||||||
{
|
{
|
||||||
placeholderData: keepPreviousData,
|
placeholderData: keepPreviousData,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const updater: OnChangeFn<RowSelectionState> = (fn) => {
|
const updater = (selection: DataTableRowSelectionState) => {
|
||||||
const state = typeof fn === "function" ? fn(rowSelection) : fn
|
const ids = Object.keys(selection)
|
||||||
|
|
||||||
const ids = Object.keys(state)
|
|
||||||
|
|
||||||
setValue("sales_channel_ids", ids, {
|
setValue("sales_channel_ids", ids, {
|
||||||
shouldDirty: true,
|
shouldDirty: true,
|
||||||
shouldTouch: true,
|
shouldTouch: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
setRowSelection(state)
|
setRowSelection(selection)
|
||||||
}
|
}
|
||||||
|
|
||||||
const { table } = useDataTable({
|
|
||||||
data: sales_channels ?? [],
|
|
||||||
columns,
|
|
||||||
count,
|
|
||||||
enablePagination: true,
|
|
||||||
enableRowSelection: (row) => {
|
|
||||||
return !preSelected.includes(row.original.id!)
|
|
||||||
},
|
|
||||||
getRowId: (row) => row.id,
|
|
||||||
pageSize: PAGE_SIZE,
|
|
||||||
rowSelection: {
|
|
||||||
state: rowSelection,
|
|
||||||
updater,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const handleSubmit = form.handleSubmit(async (values) => {
|
const handleSubmit = form.handleSubmit(async (values) => {
|
||||||
await mutateAsync(values.sales_channel_ids, {
|
await mutateAsync(values.sales_channel_ids, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
@@ -139,27 +127,23 @@ export const ApiKeySalesChannelsForm = ({
|
|||||||
</div>
|
</div>
|
||||||
</RouteFocusModal.Header>
|
</RouteFocusModal.Header>
|
||||||
<RouteFocusModal.Body className="flex flex-1 flex-col overflow-auto">
|
<RouteFocusModal.Body className="flex flex-1 flex-col overflow-auto">
|
||||||
<_DataTable
|
<DataTable
|
||||||
table={table}
|
data={sales_channels}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
count={count}
|
|
||||||
pageSize={PAGE_SIZE}
|
|
||||||
filters={filters}
|
filters={filters}
|
||||||
pagination
|
getRowId={(row) => row.id}
|
||||||
search="autofocus"
|
rowCount={count}
|
||||||
isLoading={isLoading}
|
|
||||||
queryObject={raw}
|
|
||||||
orderBy={[
|
|
||||||
{ key: "name", label: t("fields.name") },
|
|
||||||
{ key: "created_at", label: t("fields.createdAt") },
|
|
||||||
{ key: "updated_at", label: t("fields.updatedAt") },
|
|
||||||
]}
|
|
||||||
layout="fill"
|
layout="fill"
|
||||||
noRecords={{
|
emptyState={emptyState}
|
||||||
message: t(
|
isLoading={isPending}
|
||||||
"apiKeyManagement.addSalesChannels.list.noRecordsMessage"
|
rowSelection={{
|
||||||
),
|
state: rowSelection,
|
||||||
|
onRowSelectionChange: updater,
|
||||||
|
enableRowSelection: (row) => !preSelected.includes(row.id),
|
||||||
}}
|
}}
|
||||||
|
prefix={PREFIX}
|
||||||
|
pageSize={PAGE_SIZE}
|
||||||
|
autoFocusSearch
|
||||||
/>
|
/>
|
||||||
</RouteFocusModal.Body>
|
</RouteFocusModal.Body>
|
||||||
<RouteFocusModal.Footer>
|
<RouteFocusModal.Footer>
|
||||||
@@ -169,7 +153,7 @@ export const ApiKeySalesChannelsForm = ({
|
|||||||
{t("actions.cancel")}
|
{t("actions.cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
</RouteFocusModal.Close>
|
</RouteFocusModal.Close>
|
||||||
<Button size="small" type="submit" isLoading={isPending}>
|
<Button size="small" type="submit" isLoading={isMutating}>
|
||||||
{t("actions.save")}
|
{t("actions.save")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -179,60 +163,36 @@ export const ApiKeySalesChannelsForm = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const columnHelper =
|
const columnHelper = createDataTableColumnHelper<HttpTypes.AdminSalesChannel>()
|
||||||
createColumnHelper<AdminSalesChannelResponse["sales_channel"]>()
|
|
||||||
|
|
||||||
const useColumns = () => {
|
const useColumns = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const base = useSalesChannelTableColumns()
|
const base = hooks.useSalesChannelTableColumns()
|
||||||
|
|
||||||
return useMemo(
|
return useMemo(
|
||||||
() => [
|
() => [
|
||||||
columnHelper.display({
|
columnHelper.select({
|
||||||
id: "select",
|
|
||||||
header: ({ table }) => {
|
|
||||||
return (
|
|
||||||
<Checkbox
|
|
||||||
checked={
|
|
||||||
table.getIsSomePageRowsSelected()
|
|
||||||
? "indeterminate"
|
|
||||||
: table.getIsAllPageRowsSelected()
|
|
||||||
}
|
|
||||||
onCheckedChange={(value) =>
|
|
||||||
table.toggleAllPageRowsSelected(!!value)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const isPreSelected = !row.getCanSelect()
|
const isPreSelected = !row.getCanSelect()
|
||||||
const isSelected = row.getIsSelected() || isPreSelected
|
const isSelected = row.getIsSelected() || isPreSelected
|
||||||
|
|
||||||
const Component = (
|
return (
|
||||||
<Checkbox
|
<ConditionalTooltip
|
||||||
checked={isSelected}
|
content={t("apiKeyManagement.salesChannels.alreadyAddedTooltip")}
|
||||||
disabled={isPreSelected}
|
showTooltip={isPreSelected}
|
||||||
onCheckedChange={(value) => row.toggleSelected(!!value)}
|
>
|
||||||
onClick={(e) => {
|
<div>
|
||||||
e.stopPropagation()
|
<Checkbox
|
||||||
}}
|
checked={isSelected}
|
||||||
/>
|
disabled={isPreSelected}
|
||||||
|
onCheckedChange={(value) => row.toggleSelected(!!value)}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</ConditionalTooltip>
|
||||||
)
|
)
|
||||||
|
|
||||||
if (isPreSelected) {
|
|
||||||
return (
|
|
||||||
<Tooltip
|
|
||||||
content={t(
|
|
||||||
"apiKeyManagement.salesChannels.alreadyAddedTooltip"
|
|
||||||
)}
|
|
||||||
side="right"
|
|
||||||
>
|
|
||||||
{Component}
|
|
||||||
</Tooltip>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return Component
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
...base,
|
...base,
|
||||||
|
|||||||
+7
-7
@@ -1,10 +1,10 @@
|
|||||||
import { PencilSquare, Trash } from "@medusajs/icons"
|
import { PencilSquare, Trash } from "@medusajs/icons"
|
||||||
import { HttpTypes } from "@medusajs/types"
|
import { HttpTypes } from "@medusajs/types"
|
||||||
import {
|
import {
|
||||||
Container,
|
Container,
|
||||||
createDataTableColumnHelper,
|
createDataTableColumnHelper,
|
||||||
toast,
|
toast,
|
||||||
usePrompt,
|
usePrompt,
|
||||||
} from "@medusajs/ui"
|
} from "@medusajs/ui"
|
||||||
import { keepPreviousData } from "@tanstack/react-query"
|
import { keepPreviousData } from "@tanstack/react-query"
|
||||||
import { useCallback, useMemo } from "react"
|
import { useCallback, useMemo } from "react"
|
||||||
@@ -12,12 +12,12 @@ import { useTranslation } from "react-i18next"
|
|||||||
import { useNavigate } from "react-router-dom"
|
import { useNavigate } from "react-router-dom"
|
||||||
|
|
||||||
import { DataTable } from "../../../../../components/data-table"
|
import { DataTable } from "../../../../../components/data-table"
|
||||||
import { useDataTableDateFilters } from "../../../../../components/data-table/hooks/general/use-data-table-date-filters"
|
import { useDataTableDateFilters } from "../../../../../components/data-table/helpers/general/use-data-table-date-filters"
|
||||||
import { SingleColumnPage } from "../../../../../components/layout/pages"
|
import { SingleColumnPage } from "../../../../../components/layout/pages"
|
||||||
import { useDashboardExtension } from "../../../../../extensions"
|
import { useDashboardExtension } from "../../../../../extensions"
|
||||||
import {
|
import {
|
||||||
useCustomerGroups,
|
useCustomerGroups,
|
||||||
useDeleteCustomerGroupLazy,
|
useDeleteCustomerGroupLazy,
|
||||||
} from "../../../../../hooks/api"
|
} from "../../../../../hooks/api"
|
||||||
import { useDate } from "../../../../../hooks/use-date"
|
import { useDate } from "../../../../../hooks/use-date"
|
||||||
import { useQueryParams } from "../../../../../hooks/use-query-params"
|
import { useQueryParams } from "../../../../../hooks/use-query-params"
|
||||||
|
|||||||
+72
-97
@@ -1,24 +1,27 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod"
|
||||||
import { HttpTypes } from "@medusajs/types"
|
import { HttpTypes } from "@medusajs/types"
|
||||||
import { Button, Checkbox, toast } from "@medusajs/ui"
|
import {
|
||||||
|
Button,
|
||||||
|
createDataTableColumnHelper,
|
||||||
|
DataTableRowSelectionState,
|
||||||
|
toast,
|
||||||
|
} from "@medusajs/ui"
|
||||||
import { keepPreviousData } from "@tanstack/react-query"
|
import { keepPreviousData } from "@tanstack/react-query"
|
||||||
import { RowSelectionState, createColumnHelper } from "@tanstack/react-table"
|
import { useMemo, useState } from "react"
|
||||||
import { useEffect, useMemo, useState } from "react"
|
import { useForm } from "react-hook-form"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import * as zod from "zod"
|
import * as zod from "zod"
|
||||||
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod"
|
import { DataTable } from "../../../../../components/data-table"
|
||||||
import { useForm } from "react-hook-form"
|
import * as hooks from "../../../../../components/data-table/helpers/sales-channels"
|
||||||
import {
|
import {
|
||||||
RouteFocusModal,
|
RouteFocusModal,
|
||||||
useRouteModal,
|
useRouteModal,
|
||||||
} from "../../../../../components/modals"
|
} from "../../../../../components/modals"
|
||||||
import { _DataTable } from "../../../../../components/table/data-table"
|
import { KeyboundForm } from "../../../../../components/utilities/keybound-form"
|
||||||
|
import { VisuallyHidden } from "../../../../../components/utilities/visually-hidden"
|
||||||
import { useSalesChannels } from "../../../../../hooks/api/sales-channels"
|
import { useSalesChannels } from "../../../../../hooks/api/sales-channels"
|
||||||
import { useUpdateStockLocationSalesChannels } from "../../../../../hooks/api/stock-locations"
|
import { useUpdateStockLocationSalesChannels } from "../../../../../hooks/api/stock-locations"
|
||||||
import { useSalesChannelTableColumns } from "../../../../../hooks/table/columns/use-sales-channel-table-columns"
|
|
||||||
import { useSalesChannelTableFilters } from "../../../../../hooks/table/filters/use-sales-channel-table-filters"
|
|
||||||
import { useSalesChannelTableQuery } from "../../../../../hooks/table/query/use-sales-channel-table-query"
|
|
||||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
|
||||||
|
|
||||||
type EditSalesChannelsFormProps = {
|
type EditSalesChannelsFormProps = {
|
||||||
location: HttpTypes.AdminStockLocation
|
location: HttpTypes.AdminStockLocation
|
||||||
@@ -29,6 +32,7 @@ const EditSalesChannelsSchema = zod.object({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const PAGE_SIZE = 50
|
const PAGE_SIZE = 50
|
||||||
|
const PREFIX = "sc"
|
||||||
|
|
||||||
export const LocationEditSalesChannelsForm = ({
|
export const LocationEditSalesChannelsForm = ({
|
||||||
location,
|
location,
|
||||||
@@ -45,28 +49,25 @@ export const LocationEditSalesChannelsForm = ({
|
|||||||
|
|
||||||
const { setValue } = form
|
const { setValue } = form
|
||||||
|
|
||||||
const initialState =
|
const [rowSelection, setRowSelection] = useState<DataTableRowSelectionState>(
|
||||||
location.sales_channels?.reduce((acc, curr) => {
|
getInitialState(location)
|
||||||
acc[curr.id] = true
|
)
|
||||||
return acc
|
|
||||||
}, {} as RowSelectionState) ?? {}
|
|
||||||
|
|
||||||
const [rowSelection, setRowSelection] =
|
const onRowSelectionChange = (selection: DataTableRowSelectionState) => {
|
||||||
useState<RowSelectionState>(initialState)
|
const ids = Object.keys(selection)
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const ids = Object.keys(rowSelection)
|
|
||||||
setValue("sales_channels", ids, {
|
setValue("sales_channels", ids, {
|
||||||
shouldDirty: true,
|
shouldDirty: true,
|
||||||
shouldTouch: true,
|
shouldTouch: true,
|
||||||
})
|
})
|
||||||
}, [rowSelection, setValue])
|
setRowSelection(selection)
|
||||||
|
}
|
||||||
|
|
||||||
const { searchParams, raw } = useSalesChannelTableQuery({
|
const searchParams = hooks.useSalesChannelTableQuery({
|
||||||
pageSize: PAGE_SIZE,
|
pageSize: PAGE_SIZE,
|
||||||
|
prefix: PREFIX,
|
||||||
})
|
})
|
||||||
|
|
||||||
const { sales_channels, count, isLoading, isError, error } = useSalesChannels(
|
const { sales_channels, count, isPending, isError, error } = useSalesChannels(
|
||||||
{
|
{
|
||||||
...searchParams,
|
...searchParams,
|
||||||
},
|
},
|
||||||
@@ -75,22 +76,9 @@ export const LocationEditSalesChannelsForm = ({
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const filters = useSalesChannelTableFilters()
|
const filters = hooks.useSalesChannelTableFilters()
|
||||||
const columns = useColumns()
|
const columns = useColumns()
|
||||||
|
const emptyState = hooks.useSalesChannelTableEmptyState()
|
||||||
const { table } = useDataTable({
|
|
||||||
data: sales_channels ?? [],
|
|
||||||
columns,
|
|
||||||
count,
|
|
||||||
enablePagination: true,
|
|
||||||
enableRowSelection: true,
|
|
||||||
rowSelection: {
|
|
||||||
state: rowSelection,
|
|
||||||
updater: setRowSelection,
|
|
||||||
},
|
|
||||||
getRowId: (row) => row.id,
|
|
||||||
pageSize: PAGE_SIZE,
|
|
||||||
})
|
|
||||||
|
|
||||||
const { mutateAsync, isPending: isMutating } =
|
const { mutateAsync, isPending: isMutating } =
|
||||||
useUpdateStockLocationSalesChannels(location.id)
|
useUpdateStockLocationSalesChannels(location.id)
|
||||||
@@ -123,80 +111,67 @@ export const LocationEditSalesChannelsForm = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<RouteFocusModal.Form form={form}>
|
<RouteFocusModal.Form form={form}>
|
||||||
<div className="flex h-full flex-col overflow-hidden">
|
<KeyboundForm onSubmit={handleSubmit} className="flex h-full flex-col">
|
||||||
<RouteFocusModal.Header>
|
<RouteFocusModal.Header>
|
||||||
|
<RouteFocusModal.Title asChild>
|
||||||
|
<VisuallyHidden>
|
||||||
|
{t("stockLocations.salesChannels.header")}
|
||||||
|
</VisuallyHidden>
|
||||||
|
</RouteFocusModal.Title>
|
||||||
|
<RouteFocusModal.Description asChild>
|
||||||
|
<VisuallyHidden>
|
||||||
|
{t("stockLocations.salesChannels.hint")}
|
||||||
|
</VisuallyHidden>
|
||||||
|
</RouteFocusModal.Description>
|
||||||
|
</RouteFocusModal.Header>
|
||||||
|
<RouteFocusModal.Body className="flex flex-1 flex-col overflow-auto">
|
||||||
|
<DataTable
|
||||||
|
data={sales_channels}
|
||||||
|
columns={columns}
|
||||||
|
filters={filters}
|
||||||
|
emptyState={emptyState}
|
||||||
|
prefix={PREFIX}
|
||||||
|
rowSelection={{
|
||||||
|
state: rowSelection,
|
||||||
|
onRowSelectionChange,
|
||||||
|
}}
|
||||||
|
pageSize={PAGE_SIZE}
|
||||||
|
isLoading={isPending}
|
||||||
|
rowCount={count}
|
||||||
|
layout="fill"
|
||||||
|
getRowId={(row) => row.id}
|
||||||
|
/>
|
||||||
|
</RouteFocusModal.Body>
|
||||||
|
<RouteFocusModal.Footer>
|
||||||
<div className="flex items-center justify-end gap-x-2">
|
<div className="flex items-center justify-end gap-x-2">
|
||||||
<RouteFocusModal.Close asChild>
|
<RouteFocusModal.Close asChild>
|
||||||
<Button size="small" variant="secondary">
|
<Button size="small" variant="secondary" type="button">
|
||||||
{t("actions.cancel")}
|
{t("actions.cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
</RouteFocusModal.Close>
|
</RouteFocusModal.Close>
|
||||||
<Button size="small" isLoading={isMutating} onClick={handleSubmit}>
|
<Button size="small" isLoading={isMutating} type="submit">
|
||||||
{t("actions.save")}
|
{t("actions.save")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</RouteFocusModal.Header>
|
</RouteFocusModal.Footer>
|
||||||
<RouteFocusModal.Body>
|
</KeyboundForm>
|
||||||
<_DataTable
|
|
||||||
table={table}
|
|
||||||
columns={columns}
|
|
||||||
pageSize={PAGE_SIZE}
|
|
||||||
isLoading={isLoading}
|
|
||||||
count={count}
|
|
||||||
filters={filters}
|
|
||||||
search="autofocus"
|
|
||||||
pagination
|
|
||||||
orderBy={[
|
|
||||||
{ key: "name", label: t("fields.name") },
|
|
||||||
{ key: "created_at", label: t("fields.createdAt") },
|
|
||||||
{ key: "updated_at", label: t("fields.updatedAt") },
|
|
||||||
]}
|
|
||||||
queryObject={raw}
|
|
||||||
layout="fill"
|
|
||||||
/>
|
|
||||||
</RouteFocusModal.Body>
|
|
||||||
</div>
|
|
||||||
</RouteFocusModal.Form>
|
</RouteFocusModal.Form>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const columnHelper = createColumnHelper<HttpTypes.AdminSalesChannel>()
|
const columnHelper = createDataTableColumnHelper<HttpTypes.AdminSalesChannel>()
|
||||||
|
|
||||||
const useColumns = () => {
|
const useColumns = () => {
|
||||||
const columns = useSalesChannelTableColumns()
|
const base = hooks.useSalesChannelTableColumns()
|
||||||
|
|
||||||
return useMemo(
|
return useMemo(() => [columnHelper.select(), ...base], [base])
|
||||||
() => [
|
}
|
||||||
columnHelper.display({
|
|
||||||
id: "select",
|
function getInitialState(location: HttpTypes.AdminStockLocation) {
|
||||||
header: ({ table }) => {
|
return (
|
||||||
return (
|
location.sales_channels?.reduce((acc, curr) => {
|
||||||
<Checkbox
|
acc[curr.id] = true
|
||||||
checked={
|
return acc
|
||||||
table.getIsSomePageRowsSelected()
|
}, {} as DataTableRowSelectionState) ?? {}
|
||||||
? "indeterminate"
|
|
||||||
: table.getIsAllPageRowsSelected()
|
|
||||||
}
|
|
||||||
onCheckedChange={(value) =>
|
|
||||||
table.toggleAllPageRowsSelected(!!value)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
cell: ({ row }) => {
|
|
||||||
return (
|
|
||||||
<Checkbox
|
|
||||||
checked={row.getIsSelected()}
|
|
||||||
onCheckedChange={(value) => row.toggleSelected(!!value)}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation()
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
...columns,
|
|
||||||
],
|
|
||||||
[columns]
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+37
-86
@@ -1,24 +1,21 @@
|
|||||||
import { AdminSalesChannelResponse } from "@medusajs/types"
|
import { HttpTypes } from "@medusajs/types"
|
||||||
import { Button, Checkbox } from "@medusajs/ui"
|
|
||||||
import {
|
import {
|
||||||
OnChangeFn,
|
Button,
|
||||||
RowSelectionState,
|
createDataTableColumnHelper,
|
||||||
createColumnHelper,
|
DataTableRowSelectionState,
|
||||||
} from "@tanstack/react-table"
|
} from "@medusajs/ui"
|
||||||
import { useEffect, useMemo, useState } from "react"
|
import { useEffect, useMemo, useState } from "react"
|
||||||
import { UseFormReturn } from "react-hook-form"
|
import { UseFormReturn } from "react-hook-form"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
|
|
||||||
|
import { keepPreviousData } from "@tanstack/react-query"
|
||||||
|
import { DataTable } from "../../../../../../../components/data-table"
|
||||||
|
import * as hooks from "../../../../../../../components/data-table/helpers/sales-channels"
|
||||||
import {
|
import {
|
||||||
StackedFocusModal,
|
StackedFocusModal,
|
||||||
useStackedModal,
|
useStackedModal,
|
||||||
} from "../../../../../../../components/modals"
|
} from "../../../../../../../components/modals"
|
||||||
import { _DataTable } from "../../../../../../../components/table/data-table"
|
|
||||||
import { useSalesChannels } from "../../../../../../../hooks/api/sales-channels"
|
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"
|
|
||||||
import { useSalesChannelTableQuery } from "../../../../../../../hooks/table/query/use-sales-channel-table-query"
|
|
||||||
import { useDataTable } from "../../../../../../../hooks/use-data-table"
|
|
||||||
import { ProductCreateSchemaType } from "../../../../types"
|
import { ProductCreateSchemaType } from "../../../../types"
|
||||||
import { SC_STACKED_MODAL_ID } from "../../constants"
|
import { SC_STACKED_MODAL_ID } from "../../constants"
|
||||||
|
|
||||||
@@ -35,16 +32,19 @@ export const ProductCreateSalesChannelStackedModal = ({
|
|||||||
const { getValues, setValue } = form
|
const { getValues, setValue } = form
|
||||||
const { setIsOpen, getIsOpen } = useStackedModal()
|
const { setIsOpen, getIsOpen } = useStackedModal()
|
||||||
|
|
||||||
const [selection, setSelection] = useState<RowSelectionState>({})
|
const [rowSelection, setRowSelection] = useState<DataTableRowSelectionState>(
|
||||||
|
{}
|
||||||
|
)
|
||||||
const [state, setState] = useState<{ id: string; name: string }[]>([])
|
const [state, setState] = useState<{ id: string; name: string }[]>([])
|
||||||
|
|
||||||
const { searchParams, raw } = useSalesChannelTableQuery({
|
const searchParams = hooks.useSalesChannelTableQuery({
|
||||||
pageSize: PAGE_SIZE,
|
pageSize: PAGE_SIZE,
|
||||||
prefix: SC_STACKED_MODAL_ID,
|
prefix: SC_STACKED_MODAL_ID,
|
||||||
})
|
})
|
||||||
const { sales_channels, count, isLoading, isError, error } = useSalesChannels(
|
const { sales_channels, count, isLoading, isError, error } = useSalesChannels(
|
||||||
|
searchParams,
|
||||||
{
|
{
|
||||||
...searchParams,
|
placeholderData: keepPreviousData,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ export const ProductCreateSalesChannelStackedModal = ({
|
|||||||
}))
|
}))
|
||||||
)
|
)
|
||||||
|
|
||||||
setSelection(
|
setRowSelection(
|
||||||
salesChannels.reduce(
|
salesChannels.reduce(
|
||||||
(acc, channel) => ({
|
(acc, channel) => ({
|
||||||
...acc,
|
...acc,
|
||||||
@@ -77,11 +77,12 @@ export const ProductCreateSalesChannelStackedModal = ({
|
|||||||
}
|
}
|
||||||
}, [open, getValues])
|
}, [open, getValues])
|
||||||
|
|
||||||
const updater: OnChangeFn<RowSelectionState> = (fn) => {
|
const onRowSelectionChange = (state: DataTableRowSelectionState) => {
|
||||||
const value = typeof fn === "function" ? fn(selection) : fn
|
const ids = Object.keys(state)
|
||||||
const ids = Object.keys(value)
|
|
||||||
|
|
||||||
const addedIdsSet = new Set(ids.filter((id) => value[id] && !selection[id]))
|
const addedIdsSet = new Set(
|
||||||
|
ids.filter((id) => state[id] && !rowSelection[id])
|
||||||
|
)
|
||||||
|
|
||||||
let addedSalesChannels: { id: string; name: string }[] = []
|
let addedSalesChannels: { id: string; name: string }[] = []
|
||||||
|
|
||||||
@@ -91,10 +92,10 @@ export const ProductCreateSalesChannelStackedModal = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
setState((prev) => {
|
setState((prev) => {
|
||||||
const filteredPrev = prev.filter((channel) => value[channel.id])
|
const filteredPrev = prev.filter((channel) => state[channel.id])
|
||||||
return Array.from(new Set([...filteredPrev, ...addedSalesChannels]))
|
return Array.from(new Set([...filteredPrev, ...addedSalesChannels]))
|
||||||
})
|
})
|
||||||
setSelection(value)
|
setRowSelection(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleAdd = () => {
|
const handleAdd = () => {
|
||||||
@@ -105,23 +106,9 @@ export const ProductCreateSalesChannelStackedModal = ({
|
|||||||
setIsOpen(SC_STACKED_MODAL_ID, false)
|
setIsOpen(SC_STACKED_MODAL_ID, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
const filters = useSalesChannelTableFilters()
|
const filters = hooks.useSalesChannelTableFilters()
|
||||||
const columns = useColumns()
|
const columns = useColumns()
|
||||||
|
const emptyState = hooks.useSalesChannelTableEmptyState()
|
||||||
const { table } = useDataTable({
|
|
||||||
data: sales_channels ?? [],
|
|
||||||
columns,
|
|
||||||
count: sales_channels?.length ?? 0,
|
|
||||||
enablePagination: true,
|
|
||||||
enableRowSelection: true,
|
|
||||||
getRowId: (row) => row.id,
|
|
||||||
pageSize: PAGE_SIZE,
|
|
||||||
rowSelection: {
|
|
||||||
state: selection,
|
|
||||||
updater,
|
|
||||||
},
|
|
||||||
prefix: SC_STACKED_MODAL_ID,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (isError) {
|
if (isError) {
|
||||||
throw error
|
throw error
|
||||||
@@ -131,22 +118,20 @@ export const ProductCreateSalesChannelStackedModal = ({
|
|||||||
<StackedFocusModal.Content className="flex flex-col overflow-hidden">
|
<StackedFocusModal.Content className="flex flex-col overflow-hidden">
|
||||||
<StackedFocusModal.Header />
|
<StackedFocusModal.Header />
|
||||||
<StackedFocusModal.Body className="flex-1 overflow-hidden">
|
<StackedFocusModal.Body className="flex-1 overflow-hidden">
|
||||||
<_DataTable
|
<DataTable
|
||||||
table={table}
|
data={sales_channels}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
pageSize={PAGE_SIZE}
|
|
||||||
filters={filters}
|
filters={filters}
|
||||||
|
emptyState={emptyState}
|
||||||
|
rowCount={count}
|
||||||
|
pageSize={PAGE_SIZE}
|
||||||
|
getRowId={(row) => row.id}
|
||||||
|
rowSelection={{
|
||||||
|
state: rowSelection,
|
||||||
|
onRowSelectionChange,
|
||||||
|
}}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
layout="fill"
|
layout="fill"
|
||||||
orderBy={[
|
|
||||||
{ key: "name", label: t("fields.name") },
|
|
||||||
{ key: "created_at", label: t("fields.createdAt") },
|
|
||||||
{ key: "updated_at", label: t("fields.updatedAt") },
|
|
||||||
]}
|
|
||||||
queryObject={raw}
|
|
||||||
search
|
|
||||||
pagination
|
|
||||||
count={count}
|
|
||||||
prefix={SC_STACKED_MODAL_ID}
|
prefix={SC_STACKED_MODAL_ID}
|
||||||
/>
|
/>
|
||||||
</StackedFocusModal.Body>
|
</StackedFocusModal.Body>
|
||||||
@@ -166,44 +151,10 @@ export const ProductCreateSalesChannelStackedModal = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const columnHelper =
|
const columnHelper = createDataTableColumnHelper<HttpTypes.AdminSalesChannel>()
|
||||||
createColumnHelper<AdminSalesChannelResponse["sales_channel"]>()
|
|
||||||
|
|
||||||
const useColumns = () => {
|
const useColumns = () => {
|
||||||
const base = useSalesChannelTableColumns()
|
const base = hooks.useSalesChannelTableColumns()
|
||||||
|
|
||||||
return useMemo(
|
return useMemo(() => [columnHelper.select(), ...base], [base])
|
||||||
() => [
|
|
||||||
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()
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
...base,
|
|
||||||
],
|
|
||||||
[base]
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-13
@@ -18,7 +18,8 @@ import { useTranslation } from "react-i18next"
|
|||||||
import { CellContext } from "@tanstack/react-table"
|
import { CellContext } from "@tanstack/react-table"
|
||||||
import { useNavigate } from "react-router-dom"
|
import { useNavigate } from "react-router-dom"
|
||||||
import { DataTable } from "../../../../../components/data-table"
|
import { DataTable } from "../../../../../components/data-table"
|
||||||
import { useDataTableDateFilters } from "../../../../../components/data-table/hooks/general/use-data-table-date-filters"
|
import { useDataTableDateColumns } from "../../../../../components/data-table/helpers/general/use-data-table-date-columns"
|
||||||
|
import { useDataTableDateFilters } from "../../../../../components/data-table/helpers/general/use-data-table-date-filters"
|
||||||
import {
|
import {
|
||||||
useDeleteVariantLazy,
|
useDeleteVariantLazy,
|
||||||
useProductVariants,
|
useProductVariants,
|
||||||
@@ -144,6 +145,8 @@ const useColumns = (product: HttpTypes.AdminProduct) => {
|
|||||||
const { mutateAsync } = useDeleteVariantLazy(product.id)
|
const { mutateAsync } = useDeleteVariantLazy(product.id)
|
||||||
const prompt = usePrompt()
|
const prompt = usePrompt()
|
||||||
|
|
||||||
|
const dateColumns = useDataTableDateColumns<HttpTypes.AdminProductVariant>()
|
||||||
|
|
||||||
const handleDelete = useCallback(
|
const handleDelete = useCallback(
|
||||||
async (id: string, title: string) => {
|
async (id: string, title: string) => {
|
||||||
const res = await prompt({
|
const res = await prompt({
|
||||||
@@ -330,25 +333,28 @@ const useColumns = (product: HttpTypes.AdminProduct) => {
|
|||||||
const { text, hasInventoryKit, quantity } = getInventory(row.original)
|
const { text, hasInventoryKit, quantity } = getInventory(row.original)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full w-full items-center gap-2 overflow-hidden">
|
<Tooltip content={text}>
|
||||||
{hasInventoryKit && <Component />}
|
<div className="flex h-full w-full items-center gap-2 overflow-hidden">
|
||||||
<span
|
{hasInventoryKit && <Component />}
|
||||||
className={clx("truncate", {
|
<span
|
||||||
"text-ui-fg-error": !quantity,
|
className={clx("truncate", {
|
||||||
})}
|
"text-ui-fg-error": !quantity,
|
||||||
title={text}
|
})}
|
||||||
>
|
>
|
||||||
{text}
|
{text}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
</Tooltip>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
maxSize: 250,
|
||||||
}),
|
}),
|
||||||
|
...dateColumns,
|
||||||
columnHelper.action({
|
columnHelper.action({
|
||||||
actions: getActions,
|
actions: getActions,
|
||||||
}),
|
}),
|
||||||
]
|
]
|
||||||
}, [t, optionColumns, getActions, getInventory])
|
}, [t, optionColumns, dateColumns, getActions, getInventory])
|
||||||
}
|
}
|
||||||
|
|
||||||
const filterHelper =
|
const filterHelper =
|
||||||
|
|||||||
+25
-71
@@ -1,5 +1,5 @@
|
|||||||
import { Button, Checkbox } from "@medusajs/ui"
|
import { Button, createDataTableColumnHelper } from "@medusajs/ui"
|
||||||
import { RowSelectionState, createColumnHelper } from "@tanstack/react-table"
|
import { RowSelectionState } from "@tanstack/react-table"
|
||||||
import { useEffect, useMemo, useState } from "react"
|
import { useEffect, useMemo, useState } from "react"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import * as zod from "zod"
|
import * as zod from "zod"
|
||||||
@@ -8,17 +8,14 @@ import { zodResolver } from "@hookform/resolvers/zod"
|
|||||||
import { HttpTypes } from "@medusajs/types"
|
import { HttpTypes } from "@medusajs/types"
|
||||||
import { keepPreviousData } from "@tanstack/react-query"
|
import { keepPreviousData } from "@tanstack/react-query"
|
||||||
import { useForm } from "react-hook-form"
|
import { useForm } from "react-hook-form"
|
||||||
|
import { DataTable } from "../../../../../components/data-table"
|
||||||
|
import * as hooks from "../../../../../components/data-table/helpers/sales-channels"
|
||||||
import {
|
import {
|
||||||
RouteFocusModal,
|
RouteFocusModal,
|
||||||
useRouteModal,
|
useRouteModal,
|
||||||
} from "../../../../../components/modals"
|
} from "../../../../../components/modals"
|
||||||
import { _DataTable } from "../../../../../components/table/data-table"
|
|
||||||
import { useUpdateProduct } from "../../../../../hooks/api/products"
|
import { useUpdateProduct } from "../../../../../hooks/api/products"
|
||||||
import { useSalesChannels } from "../../../../../hooks/api/sales-channels"
|
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"
|
|
||||||
import { useSalesChannelTableQuery } from "../../../../../hooks/table/query/use-sales-channel-table-query"
|
|
||||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
|
||||||
|
|
||||||
type EditSalesChannelsFormProps = {
|
type EditSalesChannelsFormProps = {
|
||||||
product: HttpTypes.AdminProduct
|
product: HttpTypes.AdminProduct
|
||||||
@@ -29,6 +26,7 @@ const EditSalesChannelsSchema = zod.object({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const PAGE_SIZE = 50
|
const PAGE_SIZE = 50
|
||||||
|
const PREFIX = "sc"
|
||||||
|
|
||||||
export const EditSalesChannelsForm = ({
|
export const EditSalesChannelsForm = ({
|
||||||
product,
|
product,
|
||||||
@@ -62,8 +60,9 @@ export const EditSalesChannelsForm = ({
|
|||||||
})
|
})
|
||||||
}, [rowSelection, setValue])
|
}, [rowSelection, setValue])
|
||||||
|
|
||||||
const { searchParams, raw } = useSalesChannelTableQuery({
|
const searchParams = hooks.useSalesChannelTableQuery({
|
||||||
pageSize: PAGE_SIZE,
|
pageSize: PAGE_SIZE,
|
||||||
|
prefix: PREFIX,
|
||||||
})
|
})
|
||||||
const { sales_channels, count, isLoading, isError, error } = useSalesChannels(
|
const { sales_channels, count, isLoading, isError, error } = useSalesChannels(
|
||||||
{
|
{
|
||||||
@@ -74,23 +73,10 @@ export const EditSalesChannelsForm = ({
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const filters = useSalesChannelTableFilters()
|
const filters = hooks.useSalesChannelTableFilters()
|
||||||
|
const emptyState = hooks.useSalesChannelTableEmptyState()
|
||||||
const columns = useColumns()
|
const columns = useColumns()
|
||||||
|
|
||||||
const { table } = useDataTable({
|
|
||||||
data: sales_channels ?? [],
|
|
||||||
columns,
|
|
||||||
count,
|
|
||||||
enablePagination: true,
|
|
||||||
enableRowSelection: true,
|
|
||||||
rowSelection: {
|
|
||||||
state: rowSelection,
|
|
||||||
updater: setRowSelection,
|
|
||||||
},
|
|
||||||
getRowId: (row) => row.id,
|
|
||||||
pageSize: PAGE_SIZE,
|
|
||||||
})
|
|
||||||
|
|
||||||
const { mutateAsync, isPending: isMutating } = useUpdateProduct(product.id)
|
const { mutateAsync, isPending: isMutating } = useUpdateProduct(product.id)
|
||||||
|
|
||||||
const handleSubmit = form.handleSubmit(async (data) => {
|
const handleSubmit = form.handleSubmit(async (data) => {
|
||||||
@@ -123,22 +109,21 @@ export const EditSalesChannelsForm = ({
|
|||||||
<div className="flex h-full flex-col overflow-hidden">
|
<div className="flex h-full flex-col overflow-hidden">
|
||||||
<RouteFocusModal.Header />
|
<RouteFocusModal.Header />
|
||||||
<RouteFocusModal.Body className="flex-1 overflow-hidden">
|
<RouteFocusModal.Body className="flex-1 overflow-hidden">
|
||||||
<_DataTable
|
<DataTable
|
||||||
table={table}
|
data={sales_channels}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
pageSize={PAGE_SIZE}
|
getRowId={(row) => row.id}
|
||||||
|
rowCount={count}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
count={count}
|
|
||||||
filters={filters}
|
filters={filters}
|
||||||
search="autofocus"
|
rowSelection={{
|
||||||
pagination
|
state: rowSelection,
|
||||||
orderBy={[
|
onRowSelectionChange: setRowSelection,
|
||||||
{ key: "name", label: t("fields.name") },
|
}}
|
||||||
{ key: "created_at", label: t("fields.createdAt") },
|
autoFocusSearch
|
||||||
{ key: "updated_at", label: t("fields.updatedAt") },
|
|
||||||
]}
|
|
||||||
queryObject={raw}
|
|
||||||
layout="fill"
|
layout="fill"
|
||||||
|
emptyState={emptyState}
|
||||||
|
prefix={PREFIX}
|
||||||
/>
|
/>
|
||||||
</RouteFocusModal.Body>
|
</RouteFocusModal.Body>
|
||||||
<RouteFocusModal.Footer>
|
<RouteFocusModal.Footer>
|
||||||
@@ -159,43 +144,12 @@ export const EditSalesChannelsForm = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const columnHelper =
|
const columnHelper =
|
||||||
createColumnHelper<HttpTypes.AdminSalesChannelResponse["sales_channel"]>()
|
createDataTableColumnHelper<
|
||||||
|
HttpTypes.AdminSalesChannelResponse["sales_channel"]
|
||||||
|
>()
|
||||||
|
|
||||||
const useColumns = () => {
|
const useColumns = () => {
|
||||||
const columns = useSalesChannelTableColumns()
|
const columns = hooks.useSalesChannelTableColumns()
|
||||||
|
|
||||||
return useMemo(
|
return useMemo(() => [columnHelper.select(), ...columns], [columns])
|
||||||
() => [
|
|
||||||
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()
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
...columns,
|
|
||||||
],
|
|
||||||
[columns]
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
+104
-137
@@ -1,32 +1,26 @@
|
|||||||
import { PencilSquare, Trash } from "@medusajs/icons"
|
import { PencilSquare, Trash } from "@medusajs/icons"
|
||||||
import { HttpTypes } from "@medusajs/types"
|
import { HttpTypes } from "@medusajs/types"
|
||||||
import {
|
import {
|
||||||
Button,
|
|
||||||
Container,
|
Container,
|
||||||
Heading,
|
createDataTableColumnHelper,
|
||||||
Text,
|
|
||||||
toast,
|
toast,
|
||||||
usePrompt,
|
usePrompt,
|
||||||
} from "@medusajs/ui"
|
} from "@medusajs/ui"
|
||||||
import { keepPreviousData } from "@tanstack/react-query"
|
import { keepPreviousData } from "@tanstack/react-query"
|
||||||
import { createColumnHelper } from "@tanstack/react-table"
|
import { useCallback, useMemo } from "react"
|
||||||
import { useMemo } from "react"
|
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import { Link } from "react-router-dom"
|
import { useNavigate } from "react-router-dom"
|
||||||
import {
|
import { DataTable } from "../../../../components/data-table"
|
||||||
ActionGroup,
|
import * as hooks from "../../../../components/data-table/helpers/sales-channels"
|
||||||
ActionMenu,
|
|
||||||
} from "../../../../components/common/action-menu"
|
|
||||||
import { _DataTable } from "../../../../components/table/data-table"
|
|
||||||
import { useStore } from "../../../../hooks/api"
|
import { useStore } from "../../../../hooks/api"
|
||||||
import {
|
import {
|
||||||
useDeleteSalesChannel,
|
useDeleteSalesChannelLazy,
|
||||||
useSalesChannels,
|
useSalesChannels,
|
||||||
} from "../../../../hooks/api/sales-channels"
|
} from "../../../../hooks/api/sales-channels"
|
||||||
import { useSalesChannelTableColumns } from "../../../../hooks/table/columns/use-sales-channel-table-columns"
|
|
||||||
import { useSalesChannelTableFilters } from "../../../../hooks/table/filters"
|
type SalesChannelWithIsDefault = HttpTypes.AdminSalesChannel & {
|
||||||
import { useSalesChannelTableQuery } from "../../../../hooks/table/query/use-sales-channel-table-query"
|
is_default?: boolean
|
||||||
import { useDataTable } from "../../../../hooks/use-data-table"
|
}
|
||||||
|
|
||||||
const PAGE_SIZE = 20
|
const PAGE_SIZE = 20
|
||||||
|
|
||||||
@@ -35,157 +29,130 @@ export const SalesChannelListTable = () => {
|
|||||||
|
|
||||||
const { store } = useStore()
|
const { store } = useStore()
|
||||||
|
|
||||||
const { raw, searchParams } = useSalesChannelTableQuery({
|
const searchParams = hooks.useSalesChannelTableQuery({
|
||||||
pageSize: PAGE_SIZE,
|
pageSize: PAGE_SIZE,
|
||||||
})
|
})
|
||||||
|
|
||||||
const {
|
const { sales_channels, count, isPending, isError, error } = useSalesChannels(
|
||||||
sales_channels,
|
searchParams,
|
||||||
count,
|
{
|
||||||
isPending: isLoading,
|
placeholderData: keepPreviousData,
|
||||||
isError,
|
}
|
||||||
error,
|
)
|
||||||
} = useSalesChannels(searchParams, {
|
|
||||||
placeholderData: keepPreviousData,
|
|
||||||
}) as Omit<ReturnType<typeof useSalesChannels>, "sales_channels"> & {
|
|
||||||
sales_channels: (HttpTypes.AdminSalesChannel & { is_default?: boolean })[]
|
|
||||||
}
|
|
||||||
|
|
||||||
const columns = useColumns()
|
const columns = useColumns()
|
||||||
const filters = useSalesChannelTableFilters()
|
const filters = hooks.useSalesChannelTableFilters()
|
||||||
|
const emptyState = hooks.useSalesChannelTableEmptyState()
|
||||||
|
|
||||||
const sales_channels_data =
|
const sales_channels_data: SalesChannelWithIsDefault[] =
|
||||||
sales_channels?.map((sales_channel) => {
|
sales_channels?.map((sales_channel) => {
|
||||||
sales_channel.is_default =
|
return {
|
||||||
store?.default_sales_channel_id === sales_channel.id
|
...sales_channel,
|
||||||
return sales_channel
|
is_default: store?.default_sales_channel_id === sales_channel.id,
|
||||||
|
}
|
||||||
}) ?? []
|
}) ?? []
|
||||||
|
|
||||||
const { table } = useDataTable({
|
|
||||||
data: sales_channels_data,
|
|
||||||
columns,
|
|
||||||
count,
|
|
||||||
enablePagination: true,
|
|
||||||
getRowId: (row) => row.id,
|
|
||||||
pageSize: PAGE_SIZE,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (isError) {
|
if (isError) {
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container className="divide-y p-0">
|
<Container className="p-0">
|
||||||
<div className="flex items-center justify-between px-6 py-4">
|
<DataTable
|
||||||
<div>
|
data={sales_channels_data}
|
||||||
<Heading>{t("salesChannels.domain")}</Heading>
|
|
||||||
<Text className="text-ui-fg-subtle" size="small">
|
|
||||||
{t("salesChannels.subtitle")}
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
<Link to="/settings/sales-channels/create">
|
|
||||||
<Button size="small" variant="secondary">
|
|
||||||
{t("actions.create")}
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
<_DataTable
|
|
||||||
table={table}
|
|
||||||
columns={columns}
|
columns={columns}
|
||||||
count={count}
|
rowCount={count}
|
||||||
|
getRowId={(row) => row.id}
|
||||||
pageSize={PAGE_SIZE}
|
pageSize={PAGE_SIZE}
|
||||||
filters={filters}
|
filters={filters}
|
||||||
pagination
|
isLoading={isPending}
|
||||||
search
|
emptyState={emptyState}
|
||||||
navigateTo={(row) => row.id}
|
heading={t("salesChannels.domain")}
|
||||||
isLoading={isLoading}
|
subHeading={t("salesChannels.subtitle")}
|
||||||
queryObject={raw}
|
action={{
|
||||||
orderBy={[
|
label: t("actions.create"),
|
||||||
{ key: "name", label: t("fields.name") },
|
to: "/settings/sales-channels/create",
|
||||||
{ key: "created_at", label: t("fields.createdAt") },
|
}}
|
||||||
{ key: "updated_at", label: t("fields.updatedAt") },
|
rowHref={(row) => `/settings/sales-channels/${row.id}`}
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const SalesChannelActions = ({
|
const columnHelper = createDataTableColumnHelper<
|
||||||
salesChannel,
|
HttpTypes.AdminSalesChannel & { is_default?: boolean }
|
||||||
}: {
|
>()
|
||||||
salesChannel: HttpTypes.AdminSalesChannel & { is_default?: boolean }
|
|
||||||
}) => {
|
|
||||||
const { t } = useTranslation()
|
|
||||||
const prompt = usePrompt()
|
|
||||||
const { mutateAsync } = useDeleteSalesChannel(salesChannel.id)
|
|
||||||
|
|
||||||
const handleDelete = async () => {
|
|
||||||
const confirm = await prompt({
|
|
||||||
title: t("general.areYouSure"),
|
|
||||||
description: t("salesChannels.deleteSalesChannelWarning", {
|
|
||||||
name: salesChannel.name,
|
|
||||||
}),
|
|
||||||
verificationInstruction: t("general.typeToConfirm"),
|
|
||||||
verificationText: salesChannel.name,
|
|
||||||
confirmText: t("actions.delete"),
|
|
||||||
cancelText: t("actions.cancel"),
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!confirm) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
await mutateAsync(undefined, {
|
|
||||||
onSuccess: () => {
|
|
||||||
toast.success(t("salesChannels.toast.delete"))
|
|
||||||
},
|
|
||||||
onError: (e) => {
|
|
||||||
toast.error(e.message)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const disabledTooltip = salesChannel.is_default
|
|
||||||
? t("salesChannels.tooltip.cannotDeleteDefault")
|
|
||||||
: undefined
|
|
||||||
|
|
||||||
const groups: ActionGroup[] = [
|
|
||||||
{
|
|
||||||
actions: [
|
|
||||||
{
|
|
||||||
icon: <PencilSquare />,
|
|
||||||
label: t("actions.edit"),
|
|
||||||
to: `/settings/sales-channels/${salesChannel.id}/edit`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: <Trash />,
|
|
||||||
label: t("actions.delete"),
|
|
||||||
onClick: handleDelete,
|
|
||||||
disabled: salesChannel.is_default,
|
|
||||||
disabledTooltip,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
return <ActionMenu groups={groups} />
|
|
||||||
}
|
|
||||||
|
|
||||||
const columnHelper = createColumnHelper<HttpTypes.AdminSalesChannel>()
|
|
||||||
|
|
||||||
const useColumns = () => {
|
const useColumns = () => {
|
||||||
const base = useSalesChannelTableColumns()
|
const { t } = useTranslation()
|
||||||
|
const prompt = usePrompt()
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const base = hooks.useSalesChannelTableColumns()
|
||||||
|
|
||||||
|
const { mutateAsync } = useDeleteSalesChannelLazy()
|
||||||
|
|
||||||
|
const handleDelete = useCallback(
|
||||||
|
async (salesChannel: HttpTypes.AdminSalesChannel) => {
|
||||||
|
const confirm = await prompt({
|
||||||
|
title: t("general.areYouSure"),
|
||||||
|
description: t("salesChannels.deleteSalesChannelWarning", {
|
||||||
|
name: salesChannel.name,
|
||||||
|
}),
|
||||||
|
verificationInstruction: t("general.typeToConfirm"),
|
||||||
|
verificationText: salesChannel.name,
|
||||||
|
confirmText: t("actions.delete"),
|
||||||
|
cancelText: t("actions.cancel"),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!confirm) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await mutateAsync(salesChannel.id, {
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success(t("salesChannels.toast.delete"))
|
||||||
|
},
|
||||||
|
onError: (e) => {
|
||||||
|
toast.error(e.message)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
[t, prompt, mutateAsync]
|
||||||
|
)
|
||||||
|
|
||||||
return useMemo(
|
return useMemo(
|
||||||
() => [
|
() => [
|
||||||
...base,
|
...base,
|
||||||
columnHelper.display({
|
columnHelper.action({
|
||||||
id: "actions",
|
actions: (ctx) => {
|
||||||
cell: ({ row }) => {
|
const disabledTooltip = ctx.row.original.is_default
|
||||||
return <SalesChannelActions salesChannel={row.original} />
|
? t("salesChannels.tooltip.cannotDeleteDefault")
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
icon: <PencilSquare />,
|
||||||
|
label: t("actions.edit"),
|
||||||
|
onClick: () =>
|
||||||
|
navigate(
|
||||||
|
`/settings/sales-channels/${ctx.row.original.id}/edit`
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
icon: <Trash />,
|
||||||
|
label: t("actions.delete"),
|
||||||
|
onClick: () => handleDelete(ctx.row.original),
|
||||||
|
disabled: ctx.row.original.is_default,
|
||||||
|
disabledTooltip,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
[base]
|
[base, handleDelete, navigate, t]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-22
@@ -7,9 +7,9 @@ import { useNavigate } from "react-router-dom"
|
|||||||
|
|
||||||
import { PencilSquare } from "@medusajs/icons"
|
import { PencilSquare } from "@medusajs/icons"
|
||||||
import { DataTable } from "../../../../../components/data-table"
|
import { DataTable } from "../../../../../components/data-table"
|
||||||
import { useDataTableDateFilters } from "../../../../../components/data-table/hooks/general/use-data-table-date-filters"
|
import { useDataTableDateColumns } from "../../../../../components/data-table/helpers/general/use-data-table-date-columns"
|
||||||
|
import { useDataTableDateFilters } from "../../../../../components/data-table/helpers/general/use-data-table-date-filters"
|
||||||
import { useUsers } from "../../../../../hooks/api/users"
|
import { useUsers } from "../../../../../hooks/api/users"
|
||||||
import { useDate } from "../../../../../hooks/use-date"
|
|
||||||
import { useQueryParams } from "../../../../../hooks/use-query-params"
|
import { useQueryParams } from "../../../../../hooks/use-query-params"
|
||||||
|
|
||||||
const PAGE_SIZE = 20
|
const PAGE_SIZE = 20
|
||||||
@@ -73,7 +73,8 @@ const columnHelper = createDataTableColumnHelper<HttpTypes.AdminUser>()
|
|||||||
const useColumns = () => {
|
const useColumns = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const { getFullDate } = useDate()
|
|
||||||
|
const dateColumns = useDataTableDateColumns<HttpTypes.AdminUser>()
|
||||||
|
|
||||||
return useMemo(
|
return useMemo(
|
||||||
() => [
|
() => [
|
||||||
@@ -104,24 +105,7 @@ const useColumns = () => {
|
|||||||
sortAscLabel: t("filters.sorting.alphabeticallyAsc"),
|
sortAscLabel: t("filters.sorting.alphabeticallyAsc"),
|
||||||
sortDescLabel: t("filters.sorting.alphabeticallyDesc"),
|
sortDescLabel: t("filters.sorting.alphabeticallyDesc"),
|
||||||
}),
|
}),
|
||||||
columnHelper.accessor("created_at", {
|
...dateColumns,
|
||||||
header: t("fields.createdAt"),
|
|
||||||
cell: ({ row }) => {
|
|
||||||
return getFullDate({ date: row.original.created_at })
|
|
||||||
},
|
|
||||||
enableSorting: true,
|
|
||||||
sortAscLabel: t("filters.sorting.dateAsc"),
|
|
||||||
sortDescLabel: t("filters.sorting.dateDesc"),
|
|
||||||
}),
|
|
||||||
columnHelper.accessor("updated_at", {
|
|
||||||
header: t("fields.updatedAt"),
|
|
||||||
cell: ({ row }) => {
|
|
||||||
return getFullDate({ date: row.original.updated_at })
|
|
||||||
},
|
|
||||||
enableSorting: true,
|
|
||||||
sortAscLabel: t("filters.sorting.dateAsc"),
|
|
||||||
sortDescLabel: t("filters.sorting.dateDesc"),
|
|
||||||
}),
|
|
||||||
columnHelper.action({
|
columnHelper.action({
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
@@ -134,7 +118,7 @@ const useColumns = () => {
|
|||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
[t, getFullDate, navigate]
|
[t, navigate, dateColumns]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ interface DataTableTableProps {
|
|||||||
* This component renders the table in a data table, supporting advanced features.
|
* This component renders the table in a data table, supporting advanced features.
|
||||||
*/
|
*/
|
||||||
const DataTableTable = (props: DataTableTableProps) => {
|
const DataTableTable = (props: DataTableTableProps) => {
|
||||||
const [hoveredRowId, setHoveredRowId] = React.useState<string | null>(null)
|
const hoveredRowId = React.useRef<string | null>(null)
|
||||||
const isKeyDown = React.useRef(false)
|
const isKeyDown = React.useRef(false)
|
||||||
|
|
||||||
const [showStickyBorder, setShowStickyBorder] = React.useState(false)
|
const [showStickyBorder, setShowStickyBorder] = React.useState(false)
|
||||||
@@ -57,7 +57,7 @@ const DataTableTable = (props: DataTableTableProps) => {
|
|||||||
|
|
||||||
const row = instance
|
const row = instance
|
||||||
.getRowModel()
|
.getRowModel()
|
||||||
.rows.find((r) => r.id === hoveredRowId)
|
.rows.find((r) => r.id === hoveredRowId.current)
|
||||||
|
|
||||||
if (row && row.getCanSelect()) {
|
if (row && row.getCanSelect()) {
|
||||||
row.toggleSelected()
|
row.toggleSelected()
|
||||||
@@ -190,10 +190,10 @@ const DataTableTable = (props: DataTableTableProps) => {
|
|||||||
return (
|
return (
|
||||||
<Table.Row
|
<Table.Row
|
||||||
key={row.id}
|
key={row.id}
|
||||||
onMouseEnter={() => setHoveredRowId(row.id)}
|
onMouseEnter={() => (hoveredRowId.current = row.id)}
|
||||||
onMouseLeave={() => setHoveredRowId(null)}
|
onMouseLeave={() => (hoveredRowId.current = null)}
|
||||||
onClick={(e) => instance.onRowClick?.(e, row)}
|
onClick={(e) => instance.onRowClick?.(e, row)}
|
||||||
className={clx("group/row last:border-b-0", {
|
className={clx("group/row last-of-type:border-b-0", {
|
||||||
"cursor-pointer": !!instance.onRowClick,
|
"cursor-pointer": !!instance.onRowClick,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
@@ -288,7 +288,7 @@ const DataTableEmptyStateDisplay = ({
|
|||||||
state === DataTableEmptyState.EMPTY ? props?.empty : props?.filtered
|
state === DataTableEmptyState.EMPTY ? props?.empty : props?.filtered
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-[250px] w-full flex-col items-center justify-center border-y px-6 py-4">
|
<div className="flex min-h-[250px] w-full flex-1 flex-col items-center justify-center border-y px-6 py-4">
|
||||||
{content?.custom ?? (
|
{content?.custom ?? (
|
||||||
<DefaultEmptyStateContent
|
<DefaultEmptyStateContent
|
||||||
heading={content?.heading}
|
heading={content?.heading}
|
||||||
@@ -332,4 +332,3 @@ function getIsEditableElementFocused() {
|
|||||||
|
|
||||||
export { DataTableTable }
|
export { DataTableTable }
|
||||||
export type { DataTableEmptyStateProps, DataTableTableProps }
|
export type { DataTableEmptyStateProps, DataTableTableProps }
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ export type {
|
|||||||
DataTableFilteringState,
|
DataTableFilteringState,
|
||||||
DataTableHeaderContext,
|
DataTableHeaderContext,
|
||||||
DataTablePaginationState,
|
DataTablePaginationState,
|
||||||
|
DataTableRow,
|
||||||
|
DataTableRowData,
|
||||||
DataTableRowSelectionState,
|
DataTableRowSelectionState,
|
||||||
DataTableSortDirection,
|
DataTableSortDirection,
|
||||||
DataTableSortingState,
|
DataTableSortingState,
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import type {
|
|||||||
IdentifiedColumnDef,
|
IdentifiedColumnDef,
|
||||||
IdIdentifier,
|
IdIdentifier,
|
||||||
PaginationState,
|
PaginationState,
|
||||||
|
Row,
|
||||||
RowData,
|
RowData,
|
||||||
RowSelectionState,
|
RowSelectionState,
|
||||||
SortDirection,
|
SortDirection,
|
||||||
@@ -23,6 +24,8 @@ import type {
|
|||||||
|
|
||||||
export type DataTableRowData = RowData
|
export type DataTableRowData = RowData
|
||||||
|
|
||||||
|
export type DataTableRow<TData extends DataTableRowData> = Row<TData>
|
||||||
|
|
||||||
export type DataTableAction<TData> = {
|
export type DataTableAction<TData> = {
|
||||||
label: string
|
label: string
|
||||||
onClick: (ctx: CellContext<TData, unknown>) => void
|
onClick: (ctx: CellContext<TData, unknown>) => void
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
DataTableFilteringState,
|
DataTableFilteringState,
|
||||||
DataTableFilterOption,
|
DataTableFilterOption,
|
||||||
DataTablePaginationState,
|
DataTablePaginationState,
|
||||||
|
DataTableRow,
|
||||||
DataTableRowSelectionState,
|
DataTableRowSelectionState,
|
||||||
DataTableSortingState,
|
DataTableSortingState,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
@@ -56,6 +57,7 @@ interface DataTableOptions<TData>
|
|||||||
rowSelection?: {
|
rowSelection?: {
|
||||||
state: DataTableRowSelectionState
|
state: DataTableRowSelectionState
|
||||||
onRowSelectionChange: (state: DataTableRowSelectionState) => void
|
onRowSelectionChange: (state: DataTableRowSelectionState) => void
|
||||||
|
enableRowSelection?: boolean | ((row: DataTableRow<TData>) => boolean) | undefined
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* The state and callback for the sorting.
|
* The state and callback for the sorting.
|
||||||
@@ -170,7 +172,7 @@ const useDataTable = <TData,>({
|
|||||||
const { state: sortingState, onSortingChange } = sorting ?? {}
|
const { state: sortingState, onSortingChange } = sorting ?? {}
|
||||||
const { state: filteringState, onFilteringChange } = filtering ?? {}
|
const { state: filteringState, onFilteringChange } = filtering ?? {}
|
||||||
const { state: paginationState, onPaginationChange } = pagination ?? {}
|
const { state: paginationState, onPaginationChange } = pagination ?? {}
|
||||||
const { state: rowSelectionState, onRowSelectionChange } = rowSelection ?? {}
|
const { state: rowSelectionState, onRowSelectionChange, enableRowSelection } = rowSelection ?? {}
|
||||||
|
|
||||||
const autoResetPageIndexHandler = React.useCallback(() => {
|
const autoResetPageIndexHandler = React.useCallback(() => {
|
||||||
return autoResetPageIndex
|
return autoResetPageIndex
|
||||||
@@ -236,6 +238,7 @@ const useDataTable = <TData,>({
|
|||||||
),
|
),
|
||||||
pagination: paginationState,
|
pagination: paginationState,
|
||||||
},
|
},
|
||||||
|
enableRowSelection,
|
||||||
rowCount,
|
rowCount,
|
||||||
onColumnFiltersChange: filteringStateHandler(),
|
onColumnFiltersChange: filteringStateHandler(),
|
||||||
onRowSelectionChange: rowSelectionStateHandler(),
|
onRowSelectionChange: rowSelectionStateHandler(),
|
||||||
|
|||||||
@@ -5294,7 +5294,7 @@ __metadata:
|
|||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
||||||
"@medusajs/admin-vite-plugin@2.4.0, @medusajs/admin-vite-plugin@workspace:packages/admin/admin-vite-plugin":
|
"@medusajs/admin-vite-plugin@2.4.0, @medusajs/admin-vite-plugin@workspace:packages/admin/admin-vite-plugin, @medusajs/admin-vite-plugin@~2.4.0":
|
||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "@medusajs/admin-vite-plugin@workspace:packages/admin/admin-vite-plugin"
|
resolution: "@medusajs/admin-vite-plugin@workspace:packages/admin/admin-vite-plugin"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -5605,12 +5605,12 @@ __metadata:
|
|||||||
"@hookform/error-message": ^2.0.1
|
"@hookform/error-message": ^2.0.1
|
||||||
"@hookform/resolvers": 3.4.2
|
"@hookform/resolvers": 3.4.2
|
||||||
"@medusajs/admin-shared": ~2.4.0
|
"@medusajs/admin-shared": ~2.4.0
|
||||||
"@medusajs/admin-vite-plugin": 2.4.0
|
"@medusajs/admin-vite-plugin": ~2.4.0
|
||||||
"@medusajs/icons": 2.4.0
|
"@medusajs/icons": ~2.4.0
|
||||||
"@medusajs/js-sdk": 2.4.0
|
"@medusajs/js-sdk": ~2.4.0
|
||||||
"@medusajs/types": 2.4.0
|
"@medusajs/types": ~2.4.0
|
||||||
"@medusajs/ui": 4.0.4
|
"@medusajs/ui": ~4.0.4
|
||||||
"@medusajs/ui-preset": 2.4.0
|
"@medusajs/ui-preset": ~2.4.0
|
||||||
"@tanstack/react-query": 5.64.2
|
"@tanstack/react-query": 5.64.2
|
||||||
"@tanstack/react-table": 8.20.5
|
"@tanstack/react-table": 8.20.5
|
||||||
"@tanstack/react-virtual": ^3.8.3
|
"@tanstack/react-virtual": ^3.8.3
|
||||||
@@ -5848,7 +5848,7 @@ __metadata:
|
|||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
||||||
"@medusajs/icons@2.4.0, @medusajs/icons@workspace:packages/design-system/icons, @medusajs/icons@~2.4.0":
|
"@medusajs/icons@workspace:packages/design-system/icons, @medusajs/icons@~2.4.0":
|
||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "@medusajs/icons@workspace:packages/design-system/icons"
|
resolution: "@medusajs/icons@workspace:packages/design-system/icons"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -5934,7 +5934,7 @@ __metadata:
|
|||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
||||||
"@medusajs/js-sdk@2.4.0, @medusajs/js-sdk@workspace:packages/core/js-sdk":
|
"@medusajs/js-sdk@workspace:packages/core/js-sdk, @medusajs/js-sdk@~2.4.0":
|
||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "@medusajs/js-sdk@workspace:packages/core/js-sdk"
|
resolution: "@medusajs/js-sdk@workspace:packages/core/js-sdk"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -6631,7 +6631,7 @@ __metadata:
|
|||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
||||||
"@medusajs/types@2.4.0, @medusajs/types@workspace:^, @medusajs/types@workspace:packages/core/types, @medusajs/types@~2.4.0":
|
"@medusajs/types@workspace:^, @medusajs/types@workspace:packages/core/types, @medusajs/types@~2.4.0":
|
||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "@medusajs/types@workspace:packages/core/types"
|
resolution: "@medusajs/types@workspace:packages/core/types"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -6654,7 +6654,7 @@ __metadata:
|
|||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
||||||
"@medusajs/ui-preset@2.4.0, @medusajs/ui-preset@workspace:packages/design-system/ui-preset, @medusajs/ui-preset@~2.4.0":
|
"@medusajs/ui-preset@workspace:packages/design-system/ui-preset, @medusajs/ui-preset@~2.4.0":
|
||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "@medusajs/ui-preset@workspace:packages/design-system/ui-preset"
|
resolution: "@medusajs/ui-preset@workspace:packages/design-system/ui-preset"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -6669,7 +6669,7 @@ __metadata:
|
|||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
||||||
"@medusajs/ui@4.0.4, @medusajs/ui@workspace:packages/design-system/ui":
|
"@medusajs/ui@workspace:packages/design-system/ui, @medusajs/ui@~4.0.4":
|
||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "@medusajs/ui@workspace:packages/design-system/ui"
|
resolution: "@medusajs/ui@workspace:packages/design-system/ui"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|||||||
Reference in New Issue
Block a user