feat(ui,dashboard): Migrate SC tables to DataTable (#11106)
This commit is contained in:
committed by
GitHub
parent
d588073cea
commit
fcd3e2226e
+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 {
|
||||
Button,
|
||||
clx,
|
||||
DataTableColumnDef,
|
||||
DataTableCommand,
|
||||
DataTableEmptyStateProps,
|
||||
DataTableFilter,
|
||||
DataTableFilteringState,
|
||||
DataTablePaginationState,
|
||||
DataTableRow,
|
||||
DataTableRowSelectionState,
|
||||
DataTableSortingState,
|
||||
Heading,
|
||||
DataTable as Primitive,
|
||||
Text,
|
||||
useDataTable,
|
||||
} from "@medusajs/ui"
|
||||
import React, { ReactNode, useCallback, useState } from "react"
|
||||
@@ -66,14 +69,17 @@ interface DataTableProps<TData> {
|
||||
autoFocusSearch?: boolean
|
||||
rowHref?: (row: TData) => string
|
||||
emptyState?: DataTableEmptyStateProps
|
||||
heading: string
|
||||
heading?: string
|
||||
subHeading?: string
|
||||
prefix?: string
|
||||
pageSize?: number
|
||||
isLoading?: boolean
|
||||
rowSelection?: {
|
||||
state: DataTableRowSelectionState
|
||||
onRowSelectionChange: (value: DataTableRowSelectionState) => void
|
||||
enableRowSelection?: boolean | ((row: DataTableRow<TData>) => boolean)
|
||||
}
|
||||
layout?: "fill" | "auto"
|
||||
}
|
||||
|
||||
export const DataTable = <TData,>({
|
||||
@@ -90,11 +96,13 @@ export const DataTable = <TData,>({
|
||||
autoFocusSearch = false,
|
||||
rowHref,
|
||||
heading,
|
||||
subHeading,
|
||||
prefix,
|
||||
pageSize = 10,
|
||||
emptyState,
|
||||
rowSelection,
|
||||
isLoading = false,
|
||||
layout = "auto",
|
||||
}: DataTableProps<TData>) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
@@ -258,14 +266,30 @@ export const DataTable = <TData,>({
|
||||
isLoading,
|
||||
})
|
||||
|
||||
const shouldRenderHeading = heading || subHeading
|
||||
|
||||
return (
|
||||
<Primitive instance={instance}>
|
||||
<Primitive
|
||||
instance={instance}
|
||||
className={clx({
|
||||
"h-full [&_tr]:last-of-type:!border-b": layout === "fill",
|
||||
})}
|
||||
>
|
||||
<Primitive.Toolbar
|
||||
className="flex flex-col items-start justify-between gap-2 md:flex-row md:items-center"
|
||||
translations={toolbarTranslations}
|
||||
>
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<Heading>{heading}</Heading>
|
||||
<div className="flex w-full items-center justify-between gap-2">
|
||||
{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">
|
||||
{enableFiltering && (
|
||||
<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 { useQueryParams } from "../../use-query-params"
|
||||
import { useQueryParams } from "../../../../hooks/use-query-params"
|
||||
|
||||
type UseSalesChannelTableQueryProps = {
|
||||
prefix?: string
|
||||
@@ -22,17 +22,9 @@ export const useSalesChannelTableQuery = ({
|
||||
offset: offset ? Number(offset) : 0,
|
||||
created_at: created_at ? JSON.parse(created_at) : undefined,
|
||||
updated_at: updated_at ? JSON.parse(updated_at) : undefined,
|
||||
is_disabled:
|
||||
is_disabled === "true"
|
||||
? true
|
||||
: is_disabled === "false"
|
||||
? false
|
||||
: undefined,
|
||||
is_disabled: is_disabled ? JSON.parse(is_disabled) : undefined,
|
||||
...rest,
|
||||
}
|
||||
|
||||
return {
|
||||
searchParams,
|
||||
raw: queryObject,
|
||||
}
|
||||
return searchParams
|
||||
}
|
||||
+3
@@ -5,6 +5,9 @@ type StatusCellProps = PropsWithChildren<{
|
||||
color?: "green" | "red" | "blue" | "orange" | "grey" | "purple"
|
||||
}>
|
||||
|
||||
/**
|
||||
* @deprecated Use the new DataTable and DataTableStatusCell instead
|
||||
*/
|
||||
export const StatusCell = ({ color, children }: StatusCellProps) => {
|
||||
return (
|
||||
<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 = (
|
||||
query?: Record<string, any>,
|
||||
query?: HttpTypes.AdminSalesChannelListParams,
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
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 = (
|
||||
id: string,
|
||||
options?: UseMutationOptions<
|
||||
|
||||
@@ -8,6 +8,4 @@ export * from "./use-product-tag-table-columns"
|
||||
export * from "./use-product-type-table-columns"
|
||||
export * from "./use-region-table-columns"
|
||||
export * from "./use-return-reason-table-columns"
|
||||
export * from "./use-sales-channel-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-promotion-table-filters"
|
||||
export * from "./use-region-table-filters"
|
||||
export * from "./use-sales-channel-table-filters"
|
||||
export * from "./use-shipping-option-table-filters"
|
||||
export * from "./use-tax-rate-table-filters"
|
||||
|
||||
@@ -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-region-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-tax-rate-table-query"
|
||||
export * from "./use-tax-region-table-query"
|
||||
|
||||
@@ -5724,6 +5724,9 @@
|
||||
"header": {
|
||||
"type": "string"
|
||||
},
|
||||
"hint": {
|
||||
"type": "string"
|
||||
},
|
||||
"label": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -5742,6 +5745,7 @@
|
||||
},
|
||||
"required": [
|
||||
"header",
|
||||
"hint",
|
||||
"label",
|
||||
"connectedTo",
|
||||
"noChannels",
|
||||
@@ -9202,6 +9206,39 @@
|
||||
"subtitle": {
|
||||
"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": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -9293,6 +9330,7 @@
|
||||
"required": [
|
||||
"domain",
|
||||
"subtitle",
|
||||
"list",
|
||||
"createSalesChannel",
|
||||
"createSalesChannelHint",
|
||||
"enabledHint",
|
||||
|
||||
@@ -1513,6 +1513,7 @@
|
||||
},
|
||||
"salesChannels": {
|
||||
"header": "Sales Channels",
|
||||
"hint": "Manage the sales channels that are connected to this location.",
|
||||
"label": "Connected sales channels",
|
||||
"connectedTo": "Connected to {{count}} of {{total}} sales channels",
|
||||
"noChannels": "The location is not connected to any sales channels.",
|
||||
@@ -2469,6 +2470,16 @@
|
||||
"salesChannels": {
|
||||
"domain": "Sales Channels",
|
||||
"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",
|
||||
"createSalesChannelHint": "Create a new sales channel to sell your products on.",
|
||||
"enabledHint": "Specify whether the sales channel is enabled.",
|
||||
|
||||
+150
-215
@@ -1,270 +1,205 @@
|
||||
import { PencilSquare, Plus, Trash } from "@medusajs/icons"
|
||||
import { AdminApiKeyResponse, AdminSalesChannelResponse } from "@medusajs/types"
|
||||
import { Checkbox, Container, Heading, toast, usePrompt } from "@medusajs/ui"
|
||||
import { PencilSquare, Trash } from "@medusajs/icons"
|
||||
import { AdminApiKeyResponse, HttpTypes } from "@medusajs/types"
|
||||
import {
|
||||
Container,
|
||||
createDataTableColumnHelper,
|
||||
createDataTableCommandHelper,
|
||||
DataTableRowSelectionState,
|
||||
toast,
|
||||
usePrompt,
|
||||
} from "@medusajs/ui"
|
||||
import { keepPreviousData } from "@tanstack/react-query"
|
||||
import { RowSelectionState, createColumnHelper } from "@tanstack/react-table"
|
||||
import { useMemo, useState } from "react"
|
||||
import { RowSelectionState } from "@tanstack/react-table"
|
||||
import { useCallback, useMemo, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import { DataTable } from "../../../../../components/data-table"
|
||||
import * as hooks from "../../../../../components/data-table/helpers/sales-channels"
|
||||
import { useBatchRemoveSalesChannelsFromApiKey } from "../../../../../hooks/api/api-keys"
|
||||
import { useSalesChannels } from "../../../../../hooks/api/sales-channels"
|
||||
import { useSalesChannelTableColumns } from "../../../../../hooks/table/columns/use-sales-channel-table-columns"
|
||||
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 = {
|
||||
apiKey: AdminApiKeyResponse["api_key"]
|
||||
}
|
||||
|
||||
const PAGE_SIZE = 10
|
||||
const PREFIX = "sc"
|
||||
|
||||
export const ApiKeySalesChannelSection = ({
|
||||
apiKey,
|
||||
}: ApiKeySalesChannelSectionProps) => {
|
||||
const [rowSelection, setRowSelection] = useState<RowSelectionState>({})
|
||||
const { t } = useTranslation()
|
||||
const prompt = usePrompt()
|
||||
|
||||
const { raw, searchParams } = useSalesChannelTableQuery({
|
||||
const searchParams = hooks.useSalesChannelTableQuery({
|
||||
pageSize: PAGE_SIZE,
|
||||
prefix: PREFIX,
|
||||
})
|
||||
|
||||
const { sales_channels, count, isLoading } = useSalesChannels(
|
||||
const { sales_channels, count, isPending } = useSalesChannels(
|
||||
{ ...searchParams, publishable_key_id: apiKey.id },
|
||||
{
|
||||
placeholderData: keepPreviousData,
|
||||
}
|
||||
)
|
||||
|
||||
const columns = useColumns()
|
||||
const filters = useSalesChannelTableFilters()
|
||||
|
||||
const { table } = useDataTable({
|
||||
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)
|
||||
},
|
||||
})
|
||||
}
|
||||
const columns = useColumns(apiKey.id)
|
||||
const filters = hooks.useSalesChannelTableFilters()
|
||||
const commands = useCommands(apiKey.id, setRowSelection)
|
||||
const emptyState = hooks.useSalesChannelTableEmptyState()
|
||||
|
||||
return (
|
||||
<Container className="divide-y p-0">
|
||||
<div className="flex items-center justify-between px-6 py-4">
|
||||
<Heading level="h2">{t("salesChannels.domain")}</Heading>
|
||||
<ActionMenu
|
||||
groups={[
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
icon: <Plus />,
|
||||
label: t("actions.add"),
|
||||
to: "sales-channels",
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<_DataTable
|
||||
table={table}
|
||||
<DataTable
|
||||
data={sales_channels}
|
||||
columns={columns}
|
||||
filters={filters}
|
||||
count={count}
|
||||
isLoading={isLoading}
|
||||
queryObject={raw}
|
||||
navigateTo={(row) => `/settings/sales-channels/${row.id}`}
|
||||
orderBy={[
|
||||
{
|
||||
key: "name",
|
||||
label: t("fields.name"),
|
||||
},
|
||||
{
|
||||
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"),
|
||||
commands={commands}
|
||||
heading={t("salesChannels.domain")}
|
||||
getRowId={(row) => row.id}
|
||||
rowCount={count}
|
||||
isLoading={isPending}
|
||||
emptyState={emptyState}
|
||||
rowSelection={{
|
||||
state: rowSelection,
|
||||
onRowSelectionChange: setRowSelection,
|
||||
}}
|
||||
rowHref={(row) => `/settings/sales-channels/${row.id}`}
|
||||
action={{
|
||||
label: t("actions.add"),
|
||||
to: "sales-channels",
|
||||
}}
|
||||
prefix={PREFIX}
|
||||
pageSize={PAGE_SIZE}
|
||||
/>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
const SalesChannelActions = ({
|
||||
salesChannel,
|
||||
apiKey,
|
||||
}: {
|
||||
salesChannel: AdminSalesChannelResponse["sales_channel"]
|
||||
apiKey: string
|
||||
}) => {
|
||||
const columnHelper = createDataTableColumnHelper<HttpTypes.AdminSalesChannel>()
|
||||
|
||||
const useColumns = (id: string) => {
|
||||
const { t } = useTranslation()
|
||||
const navigate = useNavigate()
|
||||
const prompt = usePrompt()
|
||||
|
||||
const { mutateAsync } = useBatchRemoveSalesChannelsFromApiKey(apiKey)
|
||||
const base = hooks.useSalesChannelTableColumns()
|
||||
|
||||
const handleDelete = async () => {
|
||||
const res = await prompt({
|
||||
title: t("general.areYouSure"),
|
||||
description: t("apiKeyManagement.removeSalesChannel.warning", {
|
||||
name: salesChannel.name,
|
||||
}),
|
||||
confirmText: t("actions.delete"),
|
||||
cancelText: t("actions.cancel"),
|
||||
})
|
||||
const { mutateAsync } = useBatchRemoveSalesChannelsFromApiKey(id)
|
||||
|
||||
if (!res) {
|
||||
return
|
||||
}
|
||||
const handleDelete = useCallback(
|
||||
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], {
|
||||
onSuccess: () => {
|
||||
toast.success(
|
||||
t("apiKeyManagement.removeSalesChannel.successToast", {
|
||||
count: 1,
|
||||
})
|
||||
)
|
||||
},
|
||||
onError: (err) => {
|
||||
toast.error(err.message)
|
||||
},
|
||||
})
|
||||
}
|
||||
if (!res) {
|
||||
return
|
||||
}
|
||||
|
||||
return (
|
||||
<ActionMenu
|
||||
groups={[
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
icon: <PencilSquare />,
|
||||
label: t("actions.edit"),
|
||||
to: `/settings/sales-channels/${salesChannel.id}/edit`,
|
||||
},
|
||||
],
|
||||
await mutateAsync([salesChannel.id], {
|
||||
onSuccess: () => {
|
||||
toast.success(
|
||||
t("apiKeyManagement.removeSalesChannel.successToast", {
|
||||
count: 1,
|
||||
})
|
||||
)
|
||||
},
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
icon: <Trash />,
|
||||
label: t("actions.delete"),
|
||||
onClick: handleDelete,
|
||||
},
|
||||
],
|
||||
onError: (err) => {
|
||||
toast.error(err.message)
|
||||
},
|
||||
]}
|
||||
/>
|
||||
})
|
||||
},
|
||||
[mutateAsync, prompt, t]
|
||||
)
|
||||
}
|
||||
|
||||
const columnHelper =
|
||||
createColumnHelper<AdminSalesChannelResponse["sales_channel"]>()
|
||||
|
||||
const useColumns = () => {
|
||||
const base = useSalesChannelTableColumns()
|
||||
|
||||
return useMemo(
|
||||
() => [
|
||||
columnHelper.display({
|
||||
id: "select",
|
||||
header: ({ table }) => {
|
||||
return (
|
||||
<Checkbox
|
||||
checked={
|
||||
table.getIsSomePageRowsSelected()
|
||||
? "indeterminate"
|
||||
: table.getIsAllPageRowsSelected()
|
||||
}
|
||||
onCheckedChange={(value) =>
|
||||
table.toggleAllPageRowsSelected(!!value)
|
||||
}
|
||||
/>
|
||||
)
|
||||
},
|
||||
cell: ({ row }) => {
|
||||
return (
|
||||
<Checkbox
|
||||
checked={row.getIsSelected()}
|
||||
onCheckedChange={(value) => row.toggleSelected(!!value)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
}}
|
||||
/>
|
||||
)
|
||||
},
|
||||
}),
|
||||
columnHelper.select(),
|
||||
...base,
|
||||
columnHelper.display({
|
||||
id: "actions",
|
||||
cell: ({ row, table }) => {
|
||||
const { apiKey } = table.options.meta as {
|
||||
apiKey: string
|
||||
}
|
||||
|
||||
return (
|
||||
<SalesChannelActions salesChannel={row.original} apiKey={apiKey} />
|
||||
)
|
||||
},
|
||||
columnHelper.action({
|
||||
actions: (ctx) => [
|
||||
[
|
||||
{
|
||||
label: t("actions.edit"),
|
||||
icon: <PencilSquare />,
|
||||
onClick: () => {
|
||||
navigate(`/settings/sales-channels/${ctx.row.original.id}/edit`)
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
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 { AdminSalesChannelResponse } from "@medusajs/types"
|
||||
import { Button, Checkbox, Hint, Tooltip, toast } from "@medusajs/ui"
|
||||
import { keepPreviousData } from "@tanstack/react-query"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import {
|
||||
OnChangeFn,
|
||||
RowSelectionState,
|
||||
createColumnHelper,
|
||||
} from "@tanstack/react-table"
|
||||
Button,
|
||||
Checkbox,
|
||||
DataTableRowSelectionState,
|
||||
Hint,
|
||||
createDataTableColumnHelper,
|
||||
toast,
|
||||
} from "@medusajs/ui"
|
||||
import { keepPreviousData } from "@tanstack/react-query"
|
||||
import { RowSelectionState } from "@tanstack/react-table"
|
||||
import { useMemo, useState } from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
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 {
|
||||
RouteFocusModal,
|
||||
useRouteModal,
|
||||
} 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 { useBatchAddSalesChannelsToApiKey } from "../../../../../hooks/api/api-keys"
|
||||
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 = {
|
||||
apiKey: string
|
||||
@@ -31,10 +33,11 @@ type ApiKeySalesChannelFormProps = {
|
||||
}
|
||||
|
||||
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 PREFIX = "sc_add"
|
||||
|
||||
export const ApiKeySalesChannelsForm = ({
|
||||
apiKey,
|
||||
@@ -54,51 +57,36 @@ export const ApiKeySalesChannelsForm = ({
|
||||
|
||||
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,
|
||||
prefix: PREFIX,
|
||||
})
|
||||
|
||||
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 },
|
||||
{
|
||||
placeholderData: keepPreviousData,
|
||||
}
|
||||
)
|
||||
|
||||
const updater: OnChangeFn<RowSelectionState> = (fn) => {
|
||||
const state = typeof fn === "function" ? fn(rowSelection) : fn
|
||||
|
||||
const ids = Object.keys(state)
|
||||
const updater = (selection: DataTableRowSelectionState) => {
|
||||
const ids = Object.keys(selection)
|
||||
|
||||
setValue("sales_channel_ids", ids, {
|
||||
shouldDirty: 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) => {
|
||||
await mutateAsync(values.sales_channel_ids, {
|
||||
onSuccess: () => {
|
||||
@@ -139,27 +127,23 @@ export const ApiKeySalesChannelsForm = ({
|
||||
</div>
|
||||
</RouteFocusModal.Header>
|
||||
<RouteFocusModal.Body className="flex flex-1 flex-col overflow-auto">
|
||||
<_DataTable
|
||||
table={table}
|
||||
<DataTable
|
||||
data={sales_channels}
|
||||
columns={columns}
|
||||
count={count}
|
||||
pageSize={PAGE_SIZE}
|
||||
filters={filters}
|
||||
pagination
|
||||
search="autofocus"
|
||||
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") },
|
||||
]}
|
||||
getRowId={(row) => row.id}
|
||||
rowCount={count}
|
||||
layout="fill"
|
||||
noRecords={{
|
||||
message: t(
|
||||
"apiKeyManagement.addSalesChannels.list.noRecordsMessage"
|
||||
),
|
||||
emptyState={emptyState}
|
||||
isLoading={isPending}
|
||||
rowSelection={{
|
||||
state: rowSelection,
|
||||
onRowSelectionChange: updater,
|
||||
enableRowSelection: (row) => !preSelected.includes(row.id),
|
||||
}}
|
||||
prefix={PREFIX}
|
||||
pageSize={PAGE_SIZE}
|
||||
autoFocusSearch
|
||||
/>
|
||||
</RouteFocusModal.Body>
|
||||
<RouteFocusModal.Footer>
|
||||
@@ -169,7 +153,7 @@ export const ApiKeySalesChannelsForm = ({
|
||||
{t("actions.cancel")}
|
||||
</Button>
|
||||
</RouteFocusModal.Close>
|
||||
<Button size="small" type="submit" isLoading={isPending}>
|
||||
<Button size="small" type="submit" isLoading={isMutating}>
|
||||
{t("actions.save")}
|
||||
</Button>
|
||||
</div>
|
||||
@@ -179,60 +163,36 @@ export const ApiKeySalesChannelsForm = ({
|
||||
)
|
||||
}
|
||||
|
||||
const columnHelper =
|
||||
createColumnHelper<AdminSalesChannelResponse["sales_channel"]>()
|
||||
const columnHelper = createDataTableColumnHelper<HttpTypes.AdminSalesChannel>()
|
||||
|
||||
const useColumns = () => {
|
||||
const { t } = useTranslation()
|
||||
const base = useSalesChannelTableColumns()
|
||||
const base = hooks.useSalesChannelTableColumns()
|
||||
|
||||
return useMemo(
|
||||
() => [
|
||||
columnHelper.display({
|
||||
id: "select",
|
||||
header: ({ table }) => {
|
||||
return (
|
||||
<Checkbox
|
||||
checked={
|
||||
table.getIsSomePageRowsSelected()
|
||||
? "indeterminate"
|
||||
: table.getIsAllPageRowsSelected()
|
||||
}
|
||||
onCheckedChange={(value) =>
|
||||
table.toggleAllPageRowsSelected(!!value)
|
||||
}
|
||||
/>
|
||||
)
|
||||
},
|
||||
columnHelper.select({
|
||||
cell: ({ row }) => {
|
||||
const isPreSelected = !row.getCanSelect()
|
||||
const isSelected = row.getIsSelected() || isPreSelected
|
||||
|
||||
const Component = (
|
||||
<Checkbox
|
||||
checked={isSelected}
|
||||
disabled={isPreSelected}
|
||||
onCheckedChange={(value) => row.toggleSelected(!!value)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
}}
|
||||
/>
|
||||
return (
|
||||
<ConditionalTooltip
|
||||
content={t("apiKeyManagement.salesChannels.alreadyAddedTooltip")}
|
||||
showTooltip={isPreSelected}
|
||||
>
|
||||
<div>
|
||||
<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,
|
||||
|
||||
+7
-7
@@ -1,10 +1,10 @@
|
||||
import { PencilSquare, Trash } from "@medusajs/icons"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import {
|
||||
Container,
|
||||
createDataTableColumnHelper,
|
||||
toast,
|
||||
usePrompt,
|
||||
Container,
|
||||
createDataTableColumnHelper,
|
||||
toast,
|
||||
usePrompt,
|
||||
} from "@medusajs/ui"
|
||||
import { keepPreviousData } from "@tanstack/react-query"
|
||||
import { useCallback, useMemo } from "react"
|
||||
@@ -12,12 +12,12 @@ import { useTranslation } from "react-i18next"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
|
||||
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 { useDashboardExtension } from "../../../../../extensions"
|
||||
import {
|
||||
useCustomerGroups,
|
||||
useDeleteCustomerGroupLazy,
|
||||
useCustomerGroups,
|
||||
useDeleteCustomerGroupLazy,
|
||||
} from "../../../../../hooks/api"
|
||||
import { useDate } from "../../../../../hooks/use-date"
|
||||
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 { Button, Checkbox, toast } from "@medusajs/ui"
|
||||
import {
|
||||
Button,
|
||||
createDataTableColumnHelper,
|
||||
DataTableRowSelectionState,
|
||||
toast,
|
||||
} from "@medusajs/ui"
|
||||
import { keepPreviousData } from "@tanstack/react-query"
|
||||
import { RowSelectionState, createColumnHelper } from "@tanstack/react-table"
|
||||
import { useEffect, useMemo, useState } from "react"
|
||||
import { useMemo, useState } from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import * as zod from "zod"
|
||||
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { DataTable } from "../../../../../components/data-table"
|
||||
import * as hooks from "../../../../../components/data-table/helpers/sales-channels"
|
||||
import {
|
||||
RouteFocusModal,
|
||||
useRouteModal,
|
||||
} 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 { 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 = {
|
||||
location: HttpTypes.AdminStockLocation
|
||||
@@ -29,6 +32,7 @@ const EditSalesChannelsSchema = zod.object({
|
||||
})
|
||||
|
||||
const PAGE_SIZE = 50
|
||||
const PREFIX = "sc"
|
||||
|
||||
export const LocationEditSalesChannelsForm = ({
|
||||
location,
|
||||
@@ -45,28 +49,25 @@ export const LocationEditSalesChannelsForm = ({
|
||||
|
||||
const { setValue } = form
|
||||
|
||||
const initialState =
|
||||
location.sales_channels?.reduce((acc, curr) => {
|
||||
acc[curr.id] = true
|
||||
return acc
|
||||
}, {} as RowSelectionState) ?? {}
|
||||
const [rowSelection, setRowSelection] = useState<DataTableRowSelectionState>(
|
||||
getInitialState(location)
|
||||
)
|
||||
|
||||
const [rowSelection, setRowSelection] =
|
||||
useState<RowSelectionState>(initialState)
|
||||
|
||||
useEffect(() => {
|
||||
const ids = Object.keys(rowSelection)
|
||||
const onRowSelectionChange = (selection: DataTableRowSelectionState) => {
|
||||
const ids = Object.keys(selection)
|
||||
setValue("sales_channels", ids, {
|
||||
shouldDirty: true,
|
||||
shouldTouch: true,
|
||||
})
|
||||
}, [rowSelection, setValue])
|
||||
setRowSelection(selection)
|
||||
}
|
||||
|
||||
const { searchParams, raw } = useSalesChannelTableQuery({
|
||||
const searchParams = hooks.useSalesChannelTableQuery({
|
||||
pageSize: PAGE_SIZE,
|
||||
prefix: PREFIX,
|
||||
})
|
||||
|
||||
const { sales_channels, count, isLoading, isError, error } = useSalesChannels(
|
||||
const { sales_channels, count, isPending, isError, error } = useSalesChannels(
|
||||
{
|
||||
...searchParams,
|
||||
},
|
||||
@@ -75,22 +76,9 @@ export const LocationEditSalesChannelsForm = ({
|
||||
}
|
||||
)
|
||||
|
||||
const filters = useSalesChannelTableFilters()
|
||||
const filters = hooks.useSalesChannelTableFilters()
|
||||
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 emptyState = hooks.useSalesChannelTableEmptyState()
|
||||
|
||||
const { mutateAsync, isPending: isMutating } =
|
||||
useUpdateStockLocationSalesChannels(location.id)
|
||||
@@ -123,80 +111,67 @@ export const LocationEditSalesChannelsForm = ({
|
||||
|
||||
return (
|
||||
<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.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">
|
||||
<RouteFocusModal.Close asChild>
|
||||
<Button size="small" variant="secondary">
|
||||
<Button size="small" variant="secondary" type="button">
|
||||
{t("actions.cancel")}
|
||||
</Button>
|
||||
</RouteFocusModal.Close>
|
||||
<Button size="small" isLoading={isMutating} onClick={handleSubmit}>
|
||||
<Button size="small" isLoading={isMutating} type="submit">
|
||||
{t("actions.save")}
|
||||
</Button>
|
||||
</div>
|
||||
</RouteFocusModal.Header>
|
||||
<RouteFocusModal.Body>
|
||||
<_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.Footer>
|
||||
</KeyboundForm>
|
||||
</RouteFocusModal.Form>
|
||||
)
|
||||
}
|
||||
|
||||
const columnHelper = createColumnHelper<HttpTypes.AdminSalesChannel>()
|
||||
const columnHelper = createDataTableColumnHelper<HttpTypes.AdminSalesChannel>()
|
||||
|
||||
const useColumns = () => {
|
||||
const columns = useSalesChannelTableColumns()
|
||||
const base = hooks.useSalesChannelTableColumns()
|
||||
|
||||
return useMemo(
|
||||
() => [
|
||||
columnHelper.display({
|
||||
id: "select",
|
||||
header: ({ table }) => {
|
||||
return (
|
||||
<Checkbox
|
||||
checked={
|
||||
table.getIsSomePageRowsSelected()
|
||||
? "indeterminate"
|
||||
: table.getIsAllPageRowsSelected()
|
||||
}
|
||||
onCheckedChange={(value) =>
|
||||
table.toggleAllPageRowsSelected(!!value)
|
||||
}
|
||||
/>
|
||||
)
|
||||
},
|
||||
cell: ({ row }) => {
|
||||
return (
|
||||
<Checkbox
|
||||
checked={row.getIsSelected()}
|
||||
onCheckedChange={(value) => row.toggleSelected(!!value)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
}}
|
||||
/>
|
||||
)
|
||||
},
|
||||
}),
|
||||
...columns,
|
||||
],
|
||||
[columns]
|
||||
return useMemo(() => [columnHelper.select(), ...base], [base])
|
||||
}
|
||||
|
||||
function getInitialState(location: HttpTypes.AdminStockLocation) {
|
||||
return (
|
||||
location.sales_channels?.reduce((acc, curr) => {
|
||||
acc[curr.id] = true
|
||||
return acc
|
||||
}, {} as DataTableRowSelectionState) ?? {}
|
||||
)
|
||||
}
|
||||
|
||||
+37
-86
@@ -1,24 +1,21 @@
|
||||
import { AdminSalesChannelResponse } from "@medusajs/types"
|
||||
import { Button, Checkbox } from "@medusajs/ui"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import {
|
||||
OnChangeFn,
|
||||
RowSelectionState,
|
||||
createColumnHelper,
|
||||
} from "@tanstack/react-table"
|
||||
Button,
|
||||
createDataTableColumnHelper,
|
||||
DataTableRowSelectionState,
|
||||
} from "@medusajs/ui"
|
||||
import { useEffect, useMemo, useState } from "react"
|
||||
import { UseFormReturn } from "react-hook-form"
|
||||
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 {
|
||||
StackedFocusModal,
|
||||
useStackedModal,
|
||||
} from "../../../../../../../components/modals"
|
||||
import { _DataTable } from "../../../../../../../components/table/data-table"
|
||||
import { useSalesChannels } from "../../../../../../../hooks/api/sales-channels"
|
||||
import { useSalesChannelTableColumns } from "../../../../../../../hooks/table/columns/use-sales-channel-table-columns"
|
||||
import { useSalesChannelTableFilters } from "../../../../../../../hooks/table/filters/use-sales-channel-table-filters"
|
||||
import { useSalesChannelTableQuery } from "../../../../../../../hooks/table/query/use-sales-channel-table-query"
|
||||
import { useDataTable } from "../../../../../../../hooks/use-data-table"
|
||||
import { ProductCreateSchemaType } from "../../../../types"
|
||||
import { SC_STACKED_MODAL_ID } from "../../constants"
|
||||
|
||||
@@ -35,16 +32,19 @@ export const ProductCreateSalesChannelStackedModal = ({
|
||||
const { getValues, setValue } = form
|
||||
const { setIsOpen, getIsOpen } = useStackedModal()
|
||||
|
||||
const [selection, setSelection] = useState<RowSelectionState>({})
|
||||
const [rowSelection, setRowSelection] = useState<DataTableRowSelectionState>(
|
||||
{}
|
||||
)
|
||||
const [state, setState] = useState<{ id: string; name: string }[]>([])
|
||||
|
||||
const { searchParams, raw } = useSalesChannelTableQuery({
|
||||
const searchParams = hooks.useSalesChannelTableQuery({
|
||||
pageSize: PAGE_SIZE,
|
||||
prefix: SC_STACKED_MODAL_ID,
|
||||
})
|
||||
const { sales_channels, count, isLoading, isError, error } = useSalesChannels(
|
||||
searchParams,
|
||||
{
|
||||
...searchParams,
|
||||
placeholderData: keepPreviousData,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -65,7 +65,7 @@ export const ProductCreateSalesChannelStackedModal = ({
|
||||
}))
|
||||
)
|
||||
|
||||
setSelection(
|
||||
setRowSelection(
|
||||
salesChannels.reduce(
|
||||
(acc, channel) => ({
|
||||
...acc,
|
||||
@@ -77,11 +77,12 @@ export const ProductCreateSalesChannelStackedModal = ({
|
||||
}
|
||||
}, [open, getValues])
|
||||
|
||||
const updater: OnChangeFn<RowSelectionState> = (fn) => {
|
||||
const value = typeof fn === "function" ? fn(selection) : fn
|
||||
const ids = Object.keys(value)
|
||||
const onRowSelectionChange = (state: DataTableRowSelectionState) => {
|
||||
const ids = Object.keys(state)
|
||||
|
||||
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 }[] = []
|
||||
|
||||
@@ -91,10 +92,10 @@ export const ProductCreateSalesChannelStackedModal = ({
|
||||
}
|
||||
|
||||
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]))
|
||||
})
|
||||
setSelection(value)
|
||||
setRowSelection(state)
|
||||
}
|
||||
|
||||
const handleAdd = () => {
|
||||
@@ -105,23 +106,9 @@ export const ProductCreateSalesChannelStackedModal = ({
|
||||
setIsOpen(SC_STACKED_MODAL_ID, false)
|
||||
}
|
||||
|
||||
const filters = useSalesChannelTableFilters()
|
||||
const filters = hooks.useSalesChannelTableFilters()
|
||||
const columns = useColumns()
|
||||
|
||||
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,
|
||||
})
|
||||
const emptyState = hooks.useSalesChannelTableEmptyState()
|
||||
|
||||
if (isError) {
|
||||
throw error
|
||||
@@ -131,22 +118,20 @@ export const ProductCreateSalesChannelStackedModal = ({
|
||||
<StackedFocusModal.Content className="flex flex-col overflow-hidden">
|
||||
<StackedFocusModal.Header />
|
||||
<StackedFocusModal.Body className="flex-1 overflow-hidden">
|
||||
<_DataTable
|
||||
table={table}
|
||||
<DataTable
|
||||
data={sales_channels}
|
||||
columns={columns}
|
||||
pageSize={PAGE_SIZE}
|
||||
filters={filters}
|
||||
emptyState={emptyState}
|
||||
rowCount={count}
|
||||
pageSize={PAGE_SIZE}
|
||||
getRowId={(row) => row.id}
|
||||
rowSelection={{
|
||||
state: rowSelection,
|
||||
onRowSelectionChange,
|
||||
}}
|
||||
isLoading={isLoading}
|
||||
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}
|
||||
/>
|
||||
</StackedFocusModal.Body>
|
||||
@@ -166,44 +151,10 @@ export const ProductCreateSalesChannelStackedModal = ({
|
||||
)
|
||||
}
|
||||
|
||||
const columnHelper =
|
||||
createColumnHelper<AdminSalesChannelResponse["sales_channel"]>()
|
||||
const columnHelper = createDataTableColumnHelper<HttpTypes.AdminSalesChannel>()
|
||||
|
||||
const useColumns = () => {
|
||||
const base = useSalesChannelTableColumns()
|
||||
const base = hooks.useSalesChannelTableColumns()
|
||||
|
||||
return useMemo(
|
||||
() => [
|
||||
columnHelper.display({
|
||||
id: "select",
|
||||
header: ({ table }) => {
|
||||
return (
|
||||
<Checkbox
|
||||
checked={
|
||||
table.getIsSomePageRowsSelected()
|
||||
? "indeterminate"
|
||||
: table.getIsAllPageRowsSelected()
|
||||
}
|
||||
onCheckedChange={(value) =>
|
||||
table.toggleAllPageRowsSelected(!!value)
|
||||
}
|
||||
/>
|
||||
)
|
||||
},
|
||||
cell: ({ row }) => {
|
||||
return (
|
||||
<Checkbox
|
||||
checked={row.getIsSelected()}
|
||||
onCheckedChange={(value) => row.toggleSelected(!!value)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
}}
|
||||
/>
|
||||
)
|
||||
},
|
||||
}),
|
||||
...base,
|
||||
],
|
||||
[base]
|
||||
)
|
||||
return useMemo(() => [columnHelper.select(), ...base], [base])
|
||||
}
|
||||
|
||||
+19
-13
@@ -18,7 +18,8 @@ import { useTranslation } from "react-i18next"
|
||||
import { CellContext } from "@tanstack/react-table"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
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 {
|
||||
useDeleteVariantLazy,
|
||||
useProductVariants,
|
||||
@@ -144,6 +145,8 @@ const useColumns = (product: HttpTypes.AdminProduct) => {
|
||||
const { mutateAsync } = useDeleteVariantLazy(product.id)
|
||||
const prompt = usePrompt()
|
||||
|
||||
const dateColumns = useDataTableDateColumns<HttpTypes.AdminProductVariant>()
|
||||
|
||||
const handleDelete = useCallback(
|
||||
async (id: string, title: string) => {
|
||||
const res = await prompt({
|
||||
@@ -330,25 +333,28 @@ const useColumns = (product: HttpTypes.AdminProduct) => {
|
||||
const { text, hasInventoryKit, quantity } = getInventory(row.original)
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full items-center gap-2 overflow-hidden">
|
||||
{hasInventoryKit && <Component />}
|
||||
<span
|
||||
className={clx("truncate", {
|
||||
"text-ui-fg-error": !quantity,
|
||||
})}
|
||||
title={text}
|
||||
>
|
||||
{text}
|
||||
</span>
|
||||
</div>
|
||||
<Tooltip content={text}>
|
||||
<div className="flex h-full w-full items-center gap-2 overflow-hidden">
|
||||
{hasInventoryKit && <Component />}
|
||||
<span
|
||||
className={clx("truncate", {
|
||||
"text-ui-fg-error": !quantity,
|
||||
})}
|
||||
>
|
||||
{text}
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
)
|
||||
},
|
||||
maxSize: 250,
|
||||
}),
|
||||
...dateColumns,
|
||||
columnHelper.action({
|
||||
actions: getActions,
|
||||
}),
|
||||
]
|
||||
}, [t, optionColumns, getActions, getInventory])
|
||||
}, [t, optionColumns, dateColumns, getActions, getInventory])
|
||||
}
|
||||
|
||||
const filterHelper =
|
||||
|
||||
+25
-71
@@ -1,5 +1,5 @@
|
||||
import { Button, Checkbox } from "@medusajs/ui"
|
||||
import { RowSelectionState, createColumnHelper } from "@tanstack/react-table"
|
||||
import { Button, createDataTableColumnHelper } from "@medusajs/ui"
|
||||
import { RowSelectionState } from "@tanstack/react-table"
|
||||
import { useEffect, useMemo, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import * as zod from "zod"
|
||||
@@ -8,17 +8,14 @@ import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { keepPreviousData } from "@tanstack/react-query"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { DataTable } from "../../../../../components/data-table"
|
||||
import * as hooks from "../../../../../components/data-table/helpers/sales-channels"
|
||||
import {
|
||||
RouteFocusModal,
|
||||
useRouteModal,
|
||||
} from "../../../../../components/modals"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
import { useUpdateProduct } from "../../../../../hooks/api/products"
|
||||
import { useSalesChannels } from "../../../../../hooks/api/sales-channels"
|
||||
import { useSalesChannelTableColumns } from "../../../../../hooks/table/columns/use-sales-channel-table-columns"
|
||||
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 = {
|
||||
product: HttpTypes.AdminProduct
|
||||
@@ -29,6 +26,7 @@ const EditSalesChannelsSchema = zod.object({
|
||||
})
|
||||
|
||||
const PAGE_SIZE = 50
|
||||
const PREFIX = "sc"
|
||||
|
||||
export const EditSalesChannelsForm = ({
|
||||
product,
|
||||
@@ -62,8 +60,9 @@ export const EditSalesChannelsForm = ({
|
||||
})
|
||||
}, [rowSelection, setValue])
|
||||
|
||||
const { searchParams, raw } = useSalesChannelTableQuery({
|
||||
const searchParams = hooks.useSalesChannelTableQuery({
|
||||
pageSize: PAGE_SIZE,
|
||||
prefix: PREFIX,
|
||||
})
|
||||
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 { 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 handleSubmit = form.handleSubmit(async (data) => {
|
||||
@@ -123,22 +109,21 @@ export const EditSalesChannelsForm = ({
|
||||
<div className="flex h-full flex-col overflow-hidden">
|
||||
<RouteFocusModal.Header />
|
||||
<RouteFocusModal.Body className="flex-1 overflow-hidden">
|
||||
<_DataTable
|
||||
table={table}
|
||||
<DataTable
|
||||
data={sales_channels}
|
||||
columns={columns}
|
||||
pageSize={PAGE_SIZE}
|
||||
getRowId={(row) => row.id}
|
||||
rowCount={count}
|
||||
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}
|
||||
rowSelection={{
|
||||
state: rowSelection,
|
||||
onRowSelectionChange: setRowSelection,
|
||||
}}
|
||||
autoFocusSearch
|
||||
layout="fill"
|
||||
emptyState={emptyState}
|
||||
prefix={PREFIX}
|
||||
/>
|
||||
</RouteFocusModal.Body>
|
||||
<RouteFocusModal.Footer>
|
||||
@@ -159,43 +144,12 @@ export const EditSalesChannelsForm = ({
|
||||
}
|
||||
|
||||
const columnHelper =
|
||||
createColumnHelper<HttpTypes.AdminSalesChannelResponse["sales_channel"]>()
|
||||
createDataTableColumnHelper<
|
||||
HttpTypes.AdminSalesChannelResponse["sales_channel"]
|
||||
>()
|
||||
|
||||
const useColumns = () => {
|
||||
const columns = useSalesChannelTableColumns()
|
||||
const columns = hooks.useSalesChannelTableColumns()
|
||||
|
||||
return useMemo(
|
||||
() => [
|
||||
columnHelper.display({
|
||||
id: "select",
|
||||
header: ({ table }) => {
|
||||
return (
|
||||
<Checkbox
|
||||
checked={
|
||||
table.getIsSomePageRowsSelected()
|
||||
? "indeterminate"
|
||||
: table.getIsAllPageRowsSelected()
|
||||
}
|
||||
onCheckedChange={(value) =>
|
||||
table.toggleAllPageRowsSelected(!!value)
|
||||
}
|
||||
/>
|
||||
)
|
||||
},
|
||||
cell: ({ row }) => {
|
||||
return (
|
||||
<Checkbox
|
||||
checked={row.getIsSelected()}
|
||||
onCheckedChange={(value) => row.toggleSelected(!!value)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
}}
|
||||
/>
|
||||
)
|
||||
},
|
||||
}),
|
||||
...columns,
|
||||
],
|
||||
[columns]
|
||||
)
|
||||
return useMemo(() => [columnHelper.select(), ...columns], [columns])
|
||||
}
|
||||
|
||||
+104
-137
@@ -1,32 +1,26 @@
|
||||
import { PencilSquare, Trash } from "@medusajs/icons"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import {
|
||||
Button,
|
||||
Container,
|
||||
Heading,
|
||||
Text,
|
||||
createDataTableColumnHelper,
|
||||
toast,
|
||||
usePrompt,
|
||||
} from "@medusajs/ui"
|
||||
import { keepPreviousData } from "@tanstack/react-query"
|
||||
import { createColumnHelper } from "@tanstack/react-table"
|
||||
import { useMemo } from "react"
|
||||
import { useCallback, useMemo } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Link } from "react-router-dom"
|
||||
import {
|
||||
ActionGroup,
|
||||
ActionMenu,
|
||||
} from "../../../../components/common/action-menu"
|
||||
import { _DataTable } from "../../../../components/table/data-table"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import { DataTable } from "../../../../components/data-table"
|
||||
import * as hooks from "../../../../components/data-table/helpers/sales-channels"
|
||||
import { useStore } from "../../../../hooks/api"
|
||||
import {
|
||||
useDeleteSalesChannel,
|
||||
useDeleteSalesChannelLazy,
|
||||
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 SalesChannelWithIsDefault = HttpTypes.AdminSalesChannel & {
|
||||
is_default?: boolean
|
||||
}
|
||||
|
||||
const PAGE_SIZE = 20
|
||||
|
||||
@@ -35,157 +29,130 @@ export const SalesChannelListTable = () => {
|
||||
|
||||
const { store } = useStore()
|
||||
|
||||
const { raw, searchParams } = useSalesChannelTableQuery({
|
||||
const searchParams = hooks.useSalesChannelTableQuery({
|
||||
pageSize: PAGE_SIZE,
|
||||
})
|
||||
|
||||
const {
|
||||
sales_channels,
|
||||
count,
|
||||
isPending: isLoading,
|
||||
isError,
|
||||
error,
|
||||
} = useSalesChannels(searchParams, {
|
||||
placeholderData: keepPreviousData,
|
||||
}) as Omit<ReturnType<typeof useSalesChannels>, "sales_channels"> & {
|
||||
sales_channels: (HttpTypes.AdminSalesChannel & { is_default?: boolean })[]
|
||||
}
|
||||
const { sales_channels, count, isPending, isError, error } = useSalesChannels(
|
||||
searchParams,
|
||||
{
|
||||
placeholderData: keepPreviousData,
|
||||
}
|
||||
)
|
||||
|
||||
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_channel.is_default =
|
||||
store?.default_sales_channel_id === sales_channel.id
|
||||
return 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) {
|
||||
throw error
|
||||
}
|
||||
|
||||
return (
|
||||
<Container className="divide-y p-0">
|
||||
<div className="flex items-center justify-between px-6 py-4">
|
||||
<div>
|
||||
<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}
|
||||
<Container className="p-0">
|
||||
<DataTable
|
||||
data={sales_channels_data}
|
||||
columns={columns}
|
||||
count={count}
|
||||
rowCount={count}
|
||||
getRowId={(row) => row.id}
|
||||
pageSize={PAGE_SIZE}
|
||||
filters={filters}
|
||||
pagination
|
||||
search
|
||||
navigateTo={(row) => row.id}
|
||||
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") },
|
||||
]}
|
||||
isLoading={isPending}
|
||||
emptyState={emptyState}
|
||||
heading={t("salesChannels.domain")}
|
||||
subHeading={t("salesChannels.subtitle")}
|
||||
action={{
|
||||
label: t("actions.create"),
|
||||
to: "/settings/sales-channels/create",
|
||||
}}
|
||||
rowHref={(row) => `/settings/sales-channels/${row.id}`}
|
||||
/>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
const SalesChannelActions = ({
|
||||
salesChannel,
|
||||
}: {
|
||||
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 columnHelper = createDataTableColumnHelper<
|
||||
HttpTypes.AdminSalesChannel & { is_default?: boolean }
|
||||
>()
|
||||
|
||||
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(
|
||||
() => [
|
||||
...base,
|
||||
columnHelper.display({
|
||||
id: "actions",
|
||||
cell: ({ row }) => {
|
||||
return <SalesChannelActions salesChannel={row.original} />
|
||||
columnHelper.action({
|
||||
actions: (ctx) => {
|
||||
const disabledTooltip = ctx.row.original.is_default
|
||||
? 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 { 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 { useDate } from "../../../../../hooks/use-date"
|
||||
import { useQueryParams } from "../../../../../hooks/use-query-params"
|
||||
|
||||
const PAGE_SIZE = 20
|
||||
@@ -73,7 +73,8 @@ const columnHelper = createDataTableColumnHelper<HttpTypes.AdminUser>()
|
||||
const useColumns = () => {
|
||||
const { t } = useTranslation()
|
||||
const navigate = useNavigate()
|
||||
const { getFullDate } = useDate()
|
||||
|
||||
const dateColumns = useDataTableDateColumns<HttpTypes.AdminUser>()
|
||||
|
||||
return useMemo(
|
||||
() => [
|
||||
@@ -104,24 +105,7 @@ const useColumns = () => {
|
||||
sortAscLabel: t("filters.sorting.alphabeticallyAsc"),
|
||||
sortDescLabel: t("filters.sorting.alphabeticallyDesc"),
|
||||
}),
|
||||
columnHelper.accessor("created_at", {
|
||||
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"),
|
||||
}),
|
||||
...dateColumns,
|
||||
columnHelper.action({
|
||||
actions: [
|
||||
{
|
||||
@@ -134,7 +118,7 @@ const useColumns = () => {
|
||||
],
|
||||
}),
|
||||
],
|
||||
[t, getFullDate, navigate]
|
||||
[t, navigate, dateColumns]
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user