From fcd3e2226ee389e89cc5b03defda9852cf99f624 Mon Sep 17 00:00:00 2001 From: Kasper Fabricius Kristensen <45367945+kasperkristensen@users.noreply.github.com> Date: Fri, 7 Feb 2025 15:26:49 +0100 Subject: [PATCH] feat(ui,dashboard): Migrate SC tables to DataTable (#11106) --- .changeset/fuzzy-crabs-fry.md | 6 + packages/admin/dashboard/package.json | 14 +- .../data-table-status-cell.tsx | 35 ++ .../src/components/data-table/data-table.tsx | 32 +- .../general/use-data-table-date-columns.tsx | 61 +++ .../general/use-data-table-date-filters.tsx | 0 .../helpers/sales-channels/index.ts | 4 + .../use-sales-channel-table-columns.tsx | 61 +++ .../use-sales-channel-table-empty-state.tsx | 22 ++ .../use-sales-channel-table-filters.tsx | 33 ++ .../use-sales-channel-table-query.tsx | 14 +- .../common/status-cell/status-cell.tsx | 3 + .../src/hooks/api/sales-channels.tsx | 30 +- .../src/hooks/table/columns/index.ts | 2 - .../use-sales-channel-table-columns.tsx | 47 --- .../src/hooks/table/filters/index.ts | 1 - .../use-sales-channel-table-filters.tsx | 17 - .../dashboard/src/hooks/table/query/index.ts | 1 - .../src/i18n/translations/$schema.json | 38 ++ .../dashboard/src/i18n/translations/en.json | 11 + .../api-key-sales-channel-section.tsx | 365 +++++++----------- .../api-key-sales-channels-form.tsx | 158 +++----- .../customer-group-list-table.tsx | 14 +- .../edit-sales-channels-form.tsx | 169 ++++---- .../product-create-sales-channel-drawer.tsx | 123 ++---- .../product-variant-section.tsx | 32 +- .../edit-sales-channels-form.tsx | 96 ++--- .../components/sales-channel-list-table.tsx | 241 +++++------- .../user-list-table/user-list-table.tsx | 28 +- .../components/data-table-table.tsx | 13 +- .../ui/src/blocks/data-table/index.ts | 2 + .../ui/src/blocks/data-table/types.ts | 3 + .../src/blocks/data-table/use-data-table.tsx | 5 +- yarn.lock | 24 +- 34 files changed, 847 insertions(+), 858 deletions(-) create mode 100644 .changeset/fuzzy-crabs-fry.md create mode 100644 packages/admin/dashboard/src/components/data-table/components/data-table-status-cell/data-table-status-cell.tsx create mode 100644 packages/admin/dashboard/src/components/data-table/helpers/general/use-data-table-date-columns.tsx rename packages/admin/dashboard/src/components/data-table/{hooks => helpers}/general/use-data-table-date-filters.tsx (100%) create mode 100644 packages/admin/dashboard/src/components/data-table/helpers/sales-channels/index.ts create mode 100644 packages/admin/dashboard/src/components/data-table/helpers/sales-channels/use-sales-channel-table-columns.tsx create mode 100644 packages/admin/dashboard/src/components/data-table/helpers/sales-channels/use-sales-channel-table-empty-state.tsx create mode 100644 packages/admin/dashboard/src/components/data-table/helpers/sales-channels/use-sales-channel-table-filters.tsx rename packages/admin/dashboard/src/{hooks/table/query => components/data-table/helpers/sales-channels}/use-sales-channel-table-query.tsx (74%) delete mode 100644 packages/admin/dashboard/src/hooks/table/columns/use-sales-channel-table-columns.tsx delete mode 100644 packages/admin/dashboard/src/hooks/table/filters/use-sales-channel-table-filters.tsx diff --git a/.changeset/fuzzy-crabs-fry.md b/.changeset/fuzzy-crabs-fry.md new file mode 100644 index 0000000000..98b91dd67c --- /dev/null +++ b/.changeset/fuzzy-crabs-fry.md @@ -0,0 +1,6 @@ +--- +"@medusajs/ui": patch +"@medusajs/dashboard": patch +--- + +feat(ui,dashboard): Migrate SC tables to DataTable diff --git a/packages/admin/dashboard/package.json b/packages/admin/dashboard/package.json index e9423ebcea..2f8e8ec506 100644 --- a/packages/admin/dashboard/package.json +++ b/packages/admin/dashboard/package.json @@ -45,10 +45,10 @@ "@dnd-kit/sortable": "^8.0.0", "@hookform/error-message": "^2.0.1", "@hookform/resolvers": "3.4.2", - "@medusajs/admin-shared": "2.4.0", - "@medusajs/icons": "2.4.0", - "@medusajs/js-sdk": "2.4.0", - "@medusajs/ui": "4.0.4", + "@medusajs/admin-shared": "~2.4.0", + "@medusajs/icons": "~2.4.0", + "@medusajs/js-sdk": "~2.4.0", + "@medusajs/ui": "~4.0.4", "@tanstack/react-query": "5.64.2", "@tanstack/react-table": "8.20.5", "@tanstack/react-virtual": "^3.8.3", @@ -76,9 +76,9 @@ }, "devDependencies": { "@medusajs/admin-shared": "~2.4.0", - "@medusajs/admin-vite-plugin": "2.4.0", - "@medusajs/types": "2.4.0", - "@medusajs/ui-preset": "2.4.0", + "@medusajs/admin-vite-plugin": "~2.4.0", + "@medusajs/types": "~2.4.0", + "@medusajs/ui-preset": "~2.4.0", "@types/node": "^20.11.15", "@types/react": "^18.2.79", "@types/react-dom": "^18.2.25", diff --git a/packages/admin/dashboard/src/components/data-table/components/data-table-status-cell/data-table-status-cell.tsx b/packages/admin/dashboard/src/components/data-table/components/data-table-status-cell/data-table-status-cell.tsx new file mode 100644 index 0000000000..2bf7a2b8c9 --- /dev/null +++ b/packages/admin/dashboard/src/components/data-table/components/data-table-status-cell/data-table-status-cell.tsx @@ -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 ( +
+
+
+
+ {children} +
+ ) +} diff --git a/packages/admin/dashboard/src/components/data-table/data-table.tsx b/packages/admin/dashboard/src/components/data-table/data-table.tsx index e5d976ea23..13fe49c5f1 100644 --- a/packages/admin/dashboard/src/components/data-table/data-table.tsx +++ b/packages/admin/dashboard/src/components/data-table/data-table.tsx @@ -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 { 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) => boolean) } + layout?: "fill" | "auto" } export const DataTable = ({ @@ -90,11 +96,13 @@ export const DataTable = ({ autoFocusSearch = false, rowHref, heading, + subHeading, prefix, pageSize = 10, emptyState, rowSelection, isLoading = false, + layout = "auto", }: DataTableProps) => { const { t } = useTranslation() @@ -258,14 +266,30 @@ export const DataTable = ({ isLoading, }) + const shouldRenderHeading = heading || subHeading + return ( - + -
- {heading} +
+ {shouldRenderHeading && ( +
+ {heading && {heading}} + {subHeading && ( + + {subHeading} + + )} +
+ )}
{enableFiltering && ( diff --git a/packages/admin/dashboard/src/components/data-table/helpers/general/use-data-table-date-columns.tsx b/packages/admin/dashboard/src/components/data-table/helpers/general/use-data-table-date-columns.tsx new file mode 100644 index 0000000000..5513f2754b --- /dev/null +++ b/packages/admin/dashboard/src/components/data-table/helpers/general/use-data-table-date-columns.tsx @@ -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() + +export const useDataTableDateColumns = () => { + const { t } = useTranslation() + const { getFullDate } = useDate() + + return useMemo(() => { + return [ + 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"), + }), + ] as DataTableColumnDef[] + }, [t, getFullDate]) +} diff --git a/packages/admin/dashboard/src/components/data-table/hooks/general/use-data-table-date-filters.tsx b/packages/admin/dashboard/src/components/data-table/helpers/general/use-data-table-date-filters.tsx similarity index 100% rename from packages/admin/dashboard/src/components/data-table/hooks/general/use-data-table-date-filters.tsx rename to packages/admin/dashboard/src/components/data-table/helpers/general/use-data-table-date-filters.tsx diff --git a/packages/admin/dashboard/src/components/data-table/helpers/sales-channels/index.ts b/packages/admin/dashboard/src/components/data-table/helpers/sales-channels/index.ts new file mode 100644 index 0000000000..197fc601a2 --- /dev/null +++ b/packages/admin/dashboard/src/components/data-table/helpers/sales-channels/index.ts @@ -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" diff --git a/packages/admin/dashboard/src/components/data-table/helpers/sales-channels/use-sales-channel-table-columns.tsx b/packages/admin/dashboard/src/components/data-table/helpers/sales-channels/use-sales-channel-table-columns.tsx new file mode 100644 index 0000000000..255ee68d55 --- /dev/null +++ b/packages/admin/dashboard/src/components/data-table/helpers/sales-channels/use-sales-channel-table-columns.tsx @@ -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() + +export const useSalesChannelTableColumns = () => { + const { t } = useTranslation() + const dateColumns = useDataTableDateColumns() + + 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 ( + +
+ {getValue()} +
+
+ ) + }, + 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 ( + + {value ? t("general.disabled") : t("general.enabled")} + + ) + }, + }), + ...dateColumns, + ], + [t, dateColumns] + ) +} diff --git a/packages/admin/dashboard/src/components/data-table/helpers/sales-channels/use-sales-channel-table-empty-state.tsx b/packages/admin/dashboard/src/components/data-table/helpers/sales-channels/use-sales-channel-table-empty-state.tsx new file mode 100644 index 0000000000..f92daa9eb7 --- /dev/null +++ b/packages/admin/dashboard/src/components/data-table/helpers/sales-channels/use-sales-channel-table-empty-state.tsx @@ -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]) +} diff --git a/packages/admin/dashboard/src/components/data-table/helpers/sales-channels/use-sales-channel-table-filters.tsx b/packages/admin/dashboard/src/components/data-table/helpers/sales-channels/use-sales-channel-table-filters.tsx new file mode 100644 index 0000000000..ff8ad1474a --- /dev/null +++ b/packages/admin/dashboard/src/components/data-table/helpers/sales-channels/use-sales-channel-table-filters.tsx @@ -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() + +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] + ) +} diff --git a/packages/admin/dashboard/src/hooks/table/query/use-sales-channel-table-query.tsx b/packages/admin/dashboard/src/components/data-table/helpers/sales-channels/use-sales-channel-table-query.tsx similarity index 74% rename from packages/admin/dashboard/src/hooks/table/query/use-sales-channel-table-query.tsx rename to packages/admin/dashboard/src/components/data-table/helpers/sales-channels/use-sales-channel-table-query.tsx index 0c35b9a67c..2252691db3 100644 --- a/packages/admin/dashboard/src/hooks/table/query/use-sales-channel-table-query.tsx +++ b/packages/admin/dashboard/src/components/data-table/helpers/sales-channels/use-sales-channel-table-query.tsx @@ -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 } diff --git a/packages/admin/dashboard/src/components/table/table-cells/common/status-cell/status-cell.tsx b/packages/admin/dashboard/src/components/table/table-cells/common/status-cell/status-cell.tsx index 14a5f29a33..97e8e578dc 100644 --- a/packages/admin/dashboard/src/components/table/table-cells/common/status-cell/status-cell.tsx +++ b/packages/admin/dashboard/src/components/table/table-cells/common/status-cell/status-cell.tsx @@ -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 (
diff --git a/packages/admin/dashboard/src/hooks/api/sales-channels.tsx b/packages/admin/dashboard/src/hooks/api/sales-channels.tsx index efb04f45a3..9fcec01503 100644 --- a/packages/admin/dashboard/src/hooks/api/sales-channels.tsx +++ b/packages/admin/dashboard/src/hooks/api/sales-channels.tsx @@ -41,7 +41,7 @@ export const useSalesChannel = ( } export const useSalesChannels = ( - query?: Record, + 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< diff --git a/packages/admin/dashboard/src/hooks/table/columns/index.ts b/packages/admin/dashboard/src/hooks/table/columns/index.ts index 5e367512d1..bbeb3d4d62 100644 --- a/packages/admin/dashboard/src/hooks/table/columns/index.ts +++ b/packages/admin/dashboard/src/hooks/table/columns/index.ts @@ -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" - diff --git a/packages/admin/dashboard/src/hooks/table/columns/use-sales-channel-table-columns.tsx b/packages/admin/dashboard/src/hooks/table/columns/use-sales-channel-table-columns.tsx deleted file mode 100644 index 89662d222a..0000000000 --- a/packages/admin/dashboard/src/hooks/table/columns/use-sales-channel-table-columns.tsx +++ /dev/null @@ -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() - -export const useSalesChannelTableColumns = () => { - const { t } = useTranslation() - - return useMemo( - () => [ - columnHelper.accessor("name", { - header: () => , - cell: ({ getValue }) => , - }), - columnHelper.accessor("description", { - header: () => , - cell: ({ getValue }) => , - }), - columnHelper.accessor("is_disabled", { - header: () => , - cell: ({ getValue }) => { - const value = getValue() - return ( - - {value ? t("general.disabled") : t("general.enabled")} - - ) - }, - }), - ], - [t] - ) -} diff --git a/packages/admin/dashboard/src/hooks/table/filters/index.ts b/packages/admin/dashboard/src/hooks/table/filters/index.ts index 7301d6f347..d98d694931 100644 --- a/packages/admin/dashboard/src/hooks/table/filters/index.ts +++ b/packages/admin/dashboard/src/hooks/table/filters/index.ts @@ -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" diff --git a/packages/admin/dashboard/src/hooks/table/filters/use-sales-channel-table-filters.tsx b/packages/admin/dashboard/src/hooks/table/filters/use-sales-channel-table-filters.tsx deleted file mode 100644 index f1e9719043..0000000000 --- a/packages/admin/dashboard/src/hooks/table/filters/use-sales-channel-table-filters.tsx +++ /dev/null @@ -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 -} diff --git a/packages/admin/dashboard/src/hooks/table/query/index.ts b/packages/admin/dashboard/src/hooks/table/query/index.ts index 2c6bd485e1..fb5f55c912 100644 --- a/packages/admin/dashboard/src/hooks/table/query/index.ts +++ b/packages/admin/dashboard/src/hooks/table/query/index.ts @@ -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" diff --git a/packages/admin/dashboard/src/i18n/translations/$schema.json b/packages/admin/dashboard/src/i18n/translations/$schema.json index 27245c3297..df15ba7f77 100644 --- a/packages/admin/dashboard/src/i18n/translations/$schema.json +++ b/packages/admin/dashboard/src/i18n/translations/$schema.json @@ -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", diff --git a/packages/admin/dashboard/src/i18n/translations/en.json b/packages/admin/dashboard/src/i18n/translations/en.json index 572c61d77c..e0c44e373a 100644 --- a/packages/admin/dashboard/src/i18n/translations/en.json +++ b/packages/admin/dashboard/src/i18n/translations/en.json @@ -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.", diff --git a/packages/admin/dashboard/src/routes/api-key-management/api-key-management-detail/components/api-key-sales-channel-section/api-key-sales-channel-section.tsx b/packages/admin/dashboard/src/routes/api-key-management/api-key-management-detail/components/api-key-sales-channel-section/api-key-sales-channel-section.tsx index cda422b2ba..2d064fad15 100644 --- a/packages/admin/dashboard/src/routes/api-key-management/api-key-management-detail/components/api-key-sales-channel-section/api-key-sales-channel-section.tsx +++ b/packages/admin/dashboard/src/routes/api-key-management/api-key-management-detail/components/api-key-sales-channel-section/api-key-sales-channel-section.tsx @@ -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({}) 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 ( -
- {t("salesChannels.domain")} - , - label: t("actions.add"), - to: "sales-channels", - }, - ], - }, - ]} - /> -
- <_DataTable - table={table} + `/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} />
) } -const SalesChannelActions = ({ - salesChannel, - apiKey, -}: { - salesChannel: AdminSalesChannelResponse["sales_channel"] - apiKey: string -}) => { +const columnHelper = createDataTableColumnHelper() + +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 ( - , - 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: , - label: t("actions.delete"), - onClick: handleDelete, - }, - ], + onError: (err) => { + toast.error(err.message) }, - ]} - /> + }) + }, + [mutateAsync, prompt, t] ) -} - -const columnHelper = - createColumnHelper() - -const useColumns = () => { - const base = useSalesChannelTableColumns() return useMemo( () => [ - columnHelper.display({ - id: "select", - header: ({ table }) => { - return ( - - table.toggleAllPageRowsSelected(!!value) - } - /> - ) - }, - cell: ({ row }) => { - return ( - 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 ( - - ) - }, + columnHelper.action({ + actions: (ctx) => [ + [ + { + label: t("actions.edit"), + icon: , + onClick: () => { + navigate(`/settings/sales-channels/${ctx.row.original.id}/edit`) + }, + }, + ], + [ + { + icon: , + 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] ) } diff --git a/packages/admin/dashboard/src/routes/api-key-management/api-key-management-sales-channels/components/api-key-sales-channels-form/api-key-sales-channels-form.tsx b/packages/admin/dashboard/src/routes/api-key-management/api-key-management-sales-channels/components/api-key-sales-channels-form/api-key-sales-channels-form.tsx index 9ebe4a3c50..f43936c84b 100644 --- a/packages/admin/dashboard/src/routes/api-key-management/api-key-management-sales-channels/components/api-key-sales-channels-form/api-key-sales-channels-form.tsx +++ b/packages/admin/dashboard/src/routes/api-key-management/api-key-management-sales-channels/components/api-key-sales-channels-form/api-key-sales-channels-form.tsx @@ -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({}) - 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 = (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 = ({
- <_DataTable - table={table} + 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 /> @@ -169,7 +153,7 @@ export const ApiKeySalesChannelsForm = ({ {t("actions.cancel")} -
@@ -179,60 +163,36 @@ export const ApiKeySalesChannelsForm = ({ ) } -const columnHelper = - createColumnHelper() +const columnHelper = createDataTableColumnHelper() const useColumns = () => { const { t } = useTranslation() - const base = useSalesChannelTableColumns() + const base = hooks.useSalesChannelTableColumns() return useMemo( () => [ - columnHelper.display({ - id: "select", - header: ({ table }) => { - return ( - - table.toggleAllPageRowsSelected(!!value) - } - /> - ) - }, + columnHelper.select({ cell: ({ row }) => { const isPreSelected = !row.getCanSelect() const isSelected = row.getIsSelected() || isPreSelected - const Component = ( - row.toggleSelected(!!value)} - onClick={(e) => { - e.stopPropagation() - }} - /> + return ( + +
+ row.toggleSelected(!!value)} + onClick={(e) => { + e.stopPropagation() + }} + /> +
+
) - - if (isPreSelected) { - return ( - - {Component} - - ) - } - - return Component }, }), ...base, diff --git a/packages/admin/dashboard/src/routes/customer-groups/customer-group-list/components/customer-group-list-table/customer-group-list-table.tsx b/packages/admin/dashboard/src/routes/customer-groups/customer-group-list/components/customer-group-list-table/customer-group-list-table.tsx index 15852d55fc..ff1ac01aac 100644 --- a/packages/admin/dashboard/src/routes/customer-groups/customer-group-list/components/customer-group-list-table/customer-group-list-table.tsx +++ b/packages/admin/dashboard/src/routes/customer-groups/customer-group-list/components/customer-group-list-table/customer-group-list-table.tsx @@ -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" diff --git a/packages/admin/dashboard/src/routes/locations/location-sales-channels/components/edit-sales-channels-form/edit-sales-channels-form.tsx b/packages/admin/dashboard/src/routes/locations/location-sales-channels/components/edit-sales-channels-form/edit-sales-channels-form.tsx index b999d31acb..6dfad6d1ce 100644 --- a/packages/admin/dashboard/src/routes/locations/location-sales-channels/components/edit-sales-channels-form/edit-sales-channels-form.tsx +++ b/packages/admin/dashboard/src/routes/locations/location-sales-channels/components/edit-sales-channels-form/edit-sales-channels-form.tsx @@ -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( + getInitialState(location) + ) - const [rowSelection, setRowSelection] = - useState(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 ( -
+ + + + {t("stockLocations.salesChannels.header")} + + + + + {t("stockLocations.salesChannels.hint")} + + + + + row.id} + /> + +
- -
- - - <_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" - /> - -
+ +
) } -const columnHelper = createColumnHelper() +const columnHelper = createDataTableColumnHelper() const useColumns = () => { - const columns = useSalesChannelTableColumns() + const base = hooks.useSalesChannelTableColumns() - return useMemo( - () => [ - columnHelper.display({ - id: "select", - header: ({ table }) => { - return ( - - table.toggleAllPageRowsSelected(!!value) - } - /> - ) - }, - cell: ({ row }) => { - return ( - 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) ?? {} ) } diff --git a/packages/admin/dashboard/src/routes/products/product-create/components/product-create-organize-form/components/product-create-sales-channel-stacked-modal/product-create-sales-channel-drawer.tsx b/packages/admin/dashboard/src/routes/products/product-create/components/product-create-organize-form/components/product-create-sales-channel-stacked-modal/product-create-sales-channel-drawer.tsx index 2bac1d7e3b..77d4596d0e 100644 --- a/packages/admin/dashboard/src/routes/products/product-create/components/product-create-organize-form/components/product-create-sales-channel-stacked-modal/product-create-sales-channel-drawer.tsx +++ b/packages/admin/dashboard/src/routes/products/product-create/components/product-create-organize-form/components/product-create-sales-channel-stacked-modal/product-create-sales-channel-drawer.tsx @@ -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({}) + const [rowSelection, setRowSelection] = useState( + {} + ) 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 = (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 = ({ - <_DataTable - table={table} + 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} /> @@ -166,44 +151,10 @@ export const ProductCreateSalesChannelStackedModal = ({ ) } -const columnHelper = - createColumnHelper() +const columnHelper = createDataTableColumnHelper() const useColumns = () => { - const base = useSalesChannelTableColumns() + const base = hooks.useSalesChannelTableColumns() - return useMemo( - () => [ - columnHelper.display({ - id: "select", - header: ({ table }) => { - return ( - - table.toggleAllPageRowsSelected(!!value) - } - /> - ) - }, - cell: ({ row }) => { - return ( - row.toggleSelected(!!value)} - onClick={(e) => { - e.stopPropagation() - }} - /> - ) - }, - }), - ...base, - ], - [base] - ) + return useMemo(() => [columnHelper.select(), ...base], [base]) } diff --git a/packages/admin/dashboard/src/routes/products/product-detail/components/product-variant-section/product-variant-section.tsx b/packages/admin/dashboard/src/routes/products/product-detail/components/product-variant-section/product-variant-section.tsx index 5dfb7e6633..fad49a3bf0 100644 --- a/packages/admin/dashboard/src/routes/products/product-detail/components/product-variant-section/product-variant-section.tsx +++ b/packages/admin/dashboard/src/routes/products/product-detail/components/product-variant-section/product-variant-section.tsx @@ -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() + 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 ( -
- {hasInventoryKit && } - - {text} - -
+ +
+ {hasInventoryKit && } + + {text} + +
+
) }, + maxSize: 250, }), + ...dateColumns, columnHelper.action({ actions: getActions, }), ] - }, [t, optionColumns, getActions, getInventory]) + }, [t, optionColumns, dateColumns, getActions, getInventory]) } const filterHelper = diff --git a/packages/admin/dashboard/src/routes/products/product-sales-channels/components/edit-sales-channels-form/edit-sales-channels-form.tsx b/packages/admin/dashboard/src/routes/products/product-sales-channels/components/edit-sales-channels-form/edit-sales-channels-form.tsx index 1e979bfd81..a49a263fac 100644 --- a/packages/admin/dashboard/src/routes/products/product-sales-channels/components/edit-sales-channels-form/edit-sales-channels-form.tsx +++ b/packages/admin/dashboard/src/routes/products/product-sales-channels/components/edit-sales-channels-form/edit-sales-channels-form.tsx @@ -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 = ({
- <_DataTable - table={table} + 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} /> @@ -159,43 +144,12 @@ export const EditSalesChannelsForm = ({ } const columnHelper = - createColumnHelper() + createDataTableColumnHelper< + HttpTypes.AdminSalesChannelResponse["sales_channel"] + >() const useColumns = () => { - const columns = useSalesChannelTableColumns() + const columns = hooks.useSalesChannelTableColumns() - return useMemo( - () => [ - columnHelper.display({ - id: "select", - header: ({ table }) => { - return ( - - table.toggleAllPageRowsSelected(!!value) - } - /> - ) - }, - cell: ({ row }) => { - return ( - row.toggleSelected(!!value)} - onClick={(e) => { - e.stopPropagation() - }} - /> - ) - }, - }), - ...columns, - ], - [columns] - ) + return useMemo(() => [columnHelper.select(), ...columns], [columns]) } diff --git a/packages/admin/dashboard/src/routes/sales-channels/sales-channel-list/components/sales-channel-list-table.tsx b/packages/admin/dashboard/src/routes/sales-channels/sales-channel-list/components/sales-channel-list-table.tsx index ca38f34672..b2de089908 100644 --- a/packages/admin/dashboard/src/routes/sales-channels/sales-channel-list/components/sales-channel-list-table.tsx +++ b/packages/admin/dashboard/src/routes/sales-channels/sales-channel-list/components/sales-channel-list-table.tsx @@ -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, "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 ( - -
-
- {t("salesChannels.domain")} - - {t("salesChannels.subtitle")} - -
- - - -
- <_DataTable - table={table} + + 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}`} /> ) } -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: , - label: t("actions.edit"), - to: `/settings/sales-channels/${salesChannel.id}/edit`, - }, - { - icon: , - label: t("actions.delete"), - onClick: handleDelete, - disabled: salesChannel.is_default, - disabledTooltip, - }, - ], - }, - ] - - return -} - -const columnHelper = createColumnHelper() +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 + columnHelper.action({ + actions: (ctx) => { + const disabledTooltip = ctx.row.original.is_default + ? t("salesChannels.tooltip.cannotDeleteDefault") + : undefined + + return [ + [ + { + icon: , + label: t("actions.edit"), + onClick: () => + navigate( + `/settings/sales-channels/${ctx.row.original.id}/edit` + ), + }, + ], + [ + { + icon: , + label: t("actions.delete"), + onClick: () => handleDelete(ctx.row.original), + disabled: ctx.row.original.is_default, + disabledTooltip, + }, + ], + ] }, }), ], - [base] + [base, handleDelete, navigate, t] ) } diff --git a/packages/admin/dashboard/src/routes/users/user-list/components/user-list-table/user-list-table.tsx b/packages/admin/dashboard/src/routes/users/user-list/components/user-list-table/user-list-table.tsx index 7a1a10565c..e3b4bcea46 100644 --- a/packages/admin/dashboard/src/routes/users/user-list/components/user-list-table/user-list-table.tsx +++ b/packages/admin/dashboard/src/routes/users/user-list/components/user-list-table/user-list-table.tsx @@ -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() const useColumns = () => { const { t } = useTranslation() const navigate = useNavigate() - const { getFullDate } = useDate() + + const dateColumns = useDataTableDateColumns() 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] ) } diff --git a/packages/design-system/ui/src/blocks/data-table/components/data-table-table.tsx b/packages/design-system/ui/src/blocks/data-table/components/data-table-table.tsx index 1de92fcfb5..66a95f0a04 100644 --- a/packages/design-system/ui/src/blocks/data-table/components/data-table-table.tsx +++ b/packages/design-system/ui/src/blocks/data-table/components/data-table-table.tsx @@ -27,7 +27,7 @@ interface DataTableTableProps { * This component renders the table in a data table, supporting advanced features. */ const DataTableTable = (props: DataTableTableProps) => { - const [hoveredRowId, setHoveredRowId] = React.useState(null) + const hoveredRowId = React.useRef(null) const isKeyDown = React.useRef(false) const [showStickyBorder, setShowStickyBorder] = React.useState(false) @@ -57,7 +57,7 @@ const DataTableTable = (props: DataTableTableProps) => { const row = instance .getRowModel() - .rows.find((r) => r.id === hoveredRowId) + .rows.find((r) => r.id === hoveredRowId.current) if (row && row.getCanSelect()) { row.toggleSelected() @@ -190,10 +190,10 @@ const DataTableTable = (props: DataTableTableProps) => { return ( setHoveredRowId(row.id)} - onMouseLeave={() => setHoveredRowId(null)} + onMouseEnter={() => (hoveredRowId.current = row.id)} + onMouseLeave={() => (hoveredRowId.current = null)} 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, })} > @@ -288,7 +288,7 @@ const DataTableEmptyStateDisplay = ({ state === DataTableEmptyState.EMPTY ? props?.empty : props?.filtered return ( -
+
{content?.custom ?? ( = Row + export type DataTableAction = { label: string onClick: (ctx: CellContext) => void diff --git a/packages/design-system/ui/src/blocks/data-table/use-data-table.tsx b/packages/design-system/ui/src/blocks/data-table/use-data-table.tsx index 35289b50a4..1b481d13fc 100644 --- a/packages/design-system/ui/src/blocks/data-table/use-data-table.tsx +++ b/packages/design-system/ui/src/blocks/data-table/use-data-table.tsx @@ -21,6 +21,7 @@ import { DataTableFilteringState, DataTableFilterOption, DataTablePaginationState, + DataTableRow, DataTableRowSelectionState, DataTableSortingState, } from "./types"; @@ -56,6 +57,7 @@ interface DataTableOptions rowSelection?: { state: DataTableRowSelectionState onRowSelectionChange: (state: DataTableRowSelectionState) => void + enableRowSelection?: boolean | ((row: DataTableRow) => boolean) | undefined } /** * The state and callback for the sorting. @@ -170,7 +172,7 @@ const useDataTable = ({ const { state: sortingState, onSortingChange } = sorting ?? {} const { state: filteringState, onFilteringChange } = filtering ?? {} const { state: paginationState, onPaginationChange } = pagination ?? {} - const { state: rowSelectionState, onRowSelectionChange } = rowSelection ?? {} + const { state: rowSelectionState, onRowSelectionChange, enableRowSelection } = rowSelection ?? {} const autoResetPageIndexHandler = React.useCallback(() => { return autoResetPageIndex @@ -236,6 +238,7 @@ const useDataTable = ({ ), pagination: paginationState, }, + enableRowSelection, rowCount, onColumnFiltersChange: filteringStateHandler(), onRowSelectionChange: rowSelectionStateHandler(), diff --git a/yarn.lock b/yarn.lock index 63dd30c3ab..f07dd0a347 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5294,7 +5294,7 @@ __metadata: languageName: unknown 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 resolution: "@medusajs/admin-vite-plugin@workspace:packages/admin/admin-vite-plugin" dependencies: @@ -5605,12 +5605,12 @@ __metadata: "@hookform/error-message": ^2.0.1 "@hookform/resolvers": 3.4.2 "@medusajs/admin-shared": ~2.4.0 - "@medusajs/admin-vite-plugin": 2.4.0 - "@medusajs/icons": 2.4.0 - "@medusajs/js-sdk": 2.4.0 - "@medusajs/types": 2.4.0 - "@medusajs/ui": 4.0.4 - "@medusajs/ui-preset": 2.4.0 + "@medusajs/admin-vite-plugin": ~2.4.0 + "@medusajs/icons": ~2.4.0 + "@medusajs/js-sdk": ~2.4.0 + "@medusajs/types": ~2.4.0 + "@medusajs/ui": ~4.0.4 + "@medusajs/ui-preset": ~2.4.0 "@tanstack/react-query": 5.64.2 "@tanstack/react-table": 8.20.5 "@tanstack/react-virtual": ^3.8.3 @@ -5848,7 +5848,7 @@ __metadata: languageName: unknown 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 resolution: "@medusajs/icons@workspace:packages/design-system/icons" dependencies: @@ -5934,7 +5934,7 @@ __metadata: languageName: unknown 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 resolution: "@medusajs/js-sdk@workspace:packages/core/js-sdk" dependencies: @@ -6631,7 +6631,7 @@ __metadata: languageName: unknown 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 resolution: "@medusajs/types@workspace:packages/core/types" dependencies: @@ -6654,7 +6654,7 @@ __metadata: languageName: unknown 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 resolution: "@medusajs/ui-preset@workspace:packages/design-system/ui-preset" dependencies: @@ -6669,7 +6669,7 @@ __metadata: languageName: unknown 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 resolution: "@medusajs/ui@workspace:packages/design-system/ui" dependencies: