feat(dashboard): Refactor Users table to use the new DataTable block (#11058)
**What** - Updates the Users table to use the new DataTable - Fixes an issue where initial parsing of URL filters weren't formatted correctly.
This commit is contained in:
committed by
GitHub
parent
0bef3202f3
commit
6346836c2d
5
.changeset/loud-geese-report.md
Normal file
5
.changeset/loud-geese-report.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@medusajs/dashboard": patch
|
||||
---
|
||||
|
||||
feat(dashboard): Refactor Users table to use the new DataTable block
|
||||
@@ -152,6 +152,7 @@ export const DataTable = <TData,>({
|
||||
const [filtering, setFiltering] = useState<DataTableFilteringState>(
|
||||
parseFilterState(filterIds, filterParams)
|
||||
)
|
||||
|
||||
const handleFilteringChange = (value: DataTableFilteringState) => {
|
||||
setFiltering(value)
|
||||
|
||||
@@ -341,10 +342,7 @@ function parseFilterState(
|
||||
const filterValue = value[id]
|
||||
|
||||
if (filterValue) {
|
||||
filters[id] = {
|
||||
id,
|
||||
value: JSON.parse(filterValue),
|
||||
}
|
||||
filters[id] = JSON.parse(filterValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
import { createDataTableFilterHelper } from "@medusajs/ui"
|
||||
import { subDays, subMonths } from "date-fns"
|
||||
import { useMemo } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import { useDate } from "../../../../hooks/use-date"
|
||||
|
||||
const filterHelper = createDataTableFilterHelper<any>()
|
||||
|
||||
const useDateFilterOptions = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const today = useMemo(() => {
|
||||
const date = new Date()
|
||||
date.setHours(0, 0, 0, 0)
|
||||
return date
|
||||
}, [])
|
||||
|
||||
return useMemo(() => {
|
||||
return [
|
||||
{
|
||||
label: t("filters.date.today"),
|
||||
value: {
|
||||
$gte: today.toISOString(),
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t("filters.date.lastSevenDays"),
|
||||
value: {
|
||||
$gte: subDays(today, 7).toISOString(), // 7 days ago
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t("filters.date.lastThirtyDays"),
|
||||
value: {
|
||||
$gte: subDays(today, 30).toISOString(), // 30 days ago
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t("filters.date.lastNinetyDays"),
|
||||
value: {
|
||||
$gte: subDays(today, 90).toISOString(), // 90 days ago
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t("filters.date.lastTwelveMonths"),
|
||||
value: {
|
||||
$gte: subMonths(today, 12).toISOString(), // 12 months ago
|
||||
},
|
||||
},
|
||||
]
|
||||
}, [today, t])
|
||||
}
|
||||
|
||||
export const useDataTableDateFilters = (disableRangeOption?: boolean) => {
|
||||
const { t } = useTranslation()
|
||||
const { getFullDate } = useDate()
|
||||
const dateFilterOptions = useDateFilterOptions()
|
||||
|
||||
const rangeOptions = useMemo(() => {
|
||||
if (disableRangeOption) {
|
||||
return {
|
||||
disableRangeOption: true,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
rangeOptionStartLabel: t("filters.date.starting"),
|
||||
rangeOptionEndLabel: t("filters.date.ending"),
|
||||
rangeOptionLabel: t("filters.date.custom"),
|
||||
options: dateFilterOptions,
|
||||
}
|
||||
}, [disableRangeOption, t, dateFilterOptions])
|
||||
|
||||
return useMemo(() => {
|
||||
return [
|
||||
filterHelper.accessor("created_at", {
|
||||
type: "date",
|
||||
label: t("fields.createdAt"),
|
||||
format: "date",
|
||||
formatDateValue: (date) => getFullDate({ date }),
|
||||
options: dateFilterOptions,
|
||||
...rangeOptions,
|
||||
}),
|
||||
filterHelper.accessor("updated_at", {
|
||||
type: "date",
|
||||
label: t("fields.updatedAt"),
|
||||
format: "date",
|
||||
formatDateValue: (date) => getFullDate({ date }),
|
||||
options: dateFilterOptions,
|
||||
...rangeOptions,
|
||||
}),
|
||||
]
|
||||
}, [t, dateFilterOptions, getFullDate, rangeOptions])
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
import { useMemo } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
export const useDateFilterOptions = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const today = useMemo(() => {
|
||||
const date = new Date()
|
||||
date.setHours(0, 0, 0, 0)
|
||||
return date
|
||||
}, [])
|
||||
|
||||
return useMemo(() => {
|
||||
return [
|
||||
{
|
||||
label: t("filters.date.today"),
|
||||
value: {
|
||||
$gte: today.toISOString(),
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t("filters.date.lastSevenDays"),
|
||||
value: {
|
||||
$gte: new Date(
|
||||
today.getTime() - 7 * 24 * 60 * 60 * 1000
|
||||
).toISOString(), // 7 days ago
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t("filters.date.lastThirtyDays"),
|
||||
value: {
|
||||
$gte: new Date(
|
||||
today.getTime() - 30 * 24 * 60 * 60 * 1000
|
||||
).toISOString(), // 30 days ago
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t("filters.date.lastNinetyDays"),
|
||||
value: {
|
||||
$gte: new Date(
|
||||
today.getTime() - 90 * 24 * 60 * 60 * 1000
|
||||
).toISOString(), // 90 days ago
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t("filters.date.lastTwelveMonths"),
|
||||
value: {
|
||||
$gte: new Date(
|
||||
today.getTime() - 365 * 24 * 60 * 60 * 1000
|
||||
).toISOString(), // 365 days ago
|
||||
},
|
||||
},
|
||||
]
|
||||
}, [today, t])
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2279,6 +2279,16 @@
|
||||
"developer": "Developer",
|
||||
"member": "Member"
|
||||
},
|
||||
"list": {
|
||||
"empty": {
|
||||
"heading": "No users found",
|
||||
"description": "Once a user has been invited, they will appear here."
|
||||
},
|
||||
"filtered": {
|
||||
"heading": "No results",
|
||||
"description": "No users match the current filter criteria."
|
||||
}
|
||||
},
|
||||
"deleteUserWarning": "You are about to delete the user {{name}}. This action cannot be undone.",
|
||||
"deleteUserSuccess": "User {{name}} deleted successfully",
|
||||
"invite": "Invite"
|
||||
|
||||
@@ -3,7 +3,6 @@ import { HttpTypes } from "@medusajs/types"
|
||||
import {
|
||||
Container,
|
||||
createDataTableColumnHelper,
|
||||
createDataTableFilterHelper,
|
||||
toast,
|
||||
usePrompt,
|
||||
} from "@medusajs/ui"
|
||||
@@ -13,13 +12,13 @@ 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 { SingleColumnPage } from "../../../../../components/layout/pages"
|
||||
import { useDashboardExtension } from "../../../../../extensions"
|
||||
import {
|
||||
useCustomerGroups,
|
||||
useDeleteCustomerGroupLazy,
|
||||
} from "../../../../../hooks/api"
|
||||
import { useDateFilterOptions } from "../../../../../hooks/filters/use-date-filter-options"
|
||||
import { useDate } from "../../../../../hooks/use-date"
|
||||
import { useQueryParams } from "../../../../../hooks/use-query-params"
|
||||
|
||||
@@ -215,35 +214,10 @@ const useColumns = () => {
|
||||
}, [t, navigate, getFullDate, handleDeleteCustomerGroup])
|
||||
}
|
||||
|
||||
const filterHelper = createDataTableFilterHelper<HttpTypes.AdminCustomerGroup>()
|
||||
|
||||
const useFilters = () => {
|
||||
const { t } = useTranslation()
|
||||
const { getFullDate } = useDate()
|
||||
const dateFilterOptions = useDateFilterOptions()
|
||||
const dateFilters = useDataTableDateFilters()
|
||||
|
||||
return useMemo(() => {
|
||||
return [
|
||||
filterHelper.accessor("created_at", {
|
||||
type: "date",
|
||||
label: t("fields.createdAt"),
|
||||
format: "date",
|
||||
formatDateValue: (date) => getFullDate({ date }),
|
||||
rangeOptionStartLabel: t("filters.date.starting"),
|
||||
rangeOptionEndLabel: t("filters.date.ending"),
|
||||
rangeOptionLabel: t("filters.date.custom"),
|
||||
options: dateFilterOptions,
|
||||
}),
|
||||
filterHelper.accessor("updated_at", {
|
||||
type: "date",
|
||||
label: t("fields.updatedAt"),
|
||||
format: "date",
|
||||
rangeOptionStartLabel: t("filters.date.starting"),
|
||||
rangeOptionEndLabel: t("filters.date.ending"),
|
||||
rangeOptionLabel: t("filters.date.custom"),
|
||||
formatDateValue: (date) => getFullDate({ date }),
|
||||
options: dateFilterOptions,
|
||||
}),
|
||||
]
|
||||
}, [t, dateFilterOptions, getFullDate])
|
||||
return dateFilters
|
||||
}, [dateFilters])
|
||||
}
|
||||
|
||||
@@ -18,12 +18,11 @@ 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 {
|
||||
useDeleteVariantLazy,
|
||||
useProductVariants,
|
||||
} from "../../../../../hooks/api/products"
|
||||
import { useDateFilterOptions } from "../../../../../hooks/filters/use-date-filter-options"
|
||||
import { useDate } from "../../../../../hooks/use-date"
|
||||
import { useQueryParams } from "../../../../../hooks/use-query-params"
|
||||
import { PRODUCT_VARIANT_IDS_KEY } from "../../../common/constants"
|
||||
|
||||
@@ -357,8 +356,7 @@ const filterHelper =
|
||||
|
||||
const useFilters = () => {
|
||||
const { t } = useTranslation()
|
||||
const { getFullDate } = useDate()
|
||||
const dateFilterOptions = useDateFilterOptions()
|
||||
const dateFilters = useDataTableDateFilters()
|
||||
|
||||
return useMemo(() => {
|
||||
return [
|
||||
@@ -378,28 +376,9 @@ const useFilters = () => {
|
||||
{ label: t("filters.radio.no"), value: "false" },
|
||||
],
|
||||
}),
|
||||
filterHelper.accessor("created_at", {
|
||||
type: "date",
|
||||
label: t("fields.createdAt"),
|
||||
format: "date",
|
||||
formatDateValue: (date) => getFullDate({ date }),
|
||||
rangeOptionStartLabel: t("filters.date.starting"),
|
||||
rangeOptionEndLabel: t("filters.date.ending"),
|
||||
rangeOptionLabel: t("filters.date.custom"),
|
||||
options: dateFilterOptions,
|
||||
}),
|
||||
filterHelper.accessor("updated_at", {
|
||||
type: "date",
|
||||
label: t("fields.updatedAt"),
|
||||
format: "date",
|
||||
rangeOptionStartLabel: t("filters.date.starting"),
|
||||
rangeOptionEndLabel: t("filters.date.ending"),
|
||||
rangeOptionLabel: t("filters.date.custom"),
|
||||
formatDateValue: (date) => getFullDate({ date }),
|
||||
options: dateFilterOptions,
|
||||
}),
|
||||
...dateFilters,
|
||||
]
|
||||
}, [t, dateFilterOptions, getFullDate])
|
||||
}, [t, dateFilters])
|
||||
}
|
||||
|
||||
const commandHelper = createDataTableCommandHelper()
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
import { UserDTO } from "@medusajs/types"
|
||||
import { createColumnHelper } from "@tanstack/react-table"
|
||||
import { useMemo } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { UserRowActions } from "./user-row-actions"
|
||||
|
||||
const columnHelper = createColumnHelper<UserDTO>()
|
||||
|
||||
export const useUserTableColumns = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return useMemo(
|
||||
() => [
|
||||
columnHelper.accessor("email", {
|
||||
header: t("fields.email"),
|
||||
cell: ({ row }) => {
|
||||
return row.original.email
|
||||
},
|
||||
}),
|
||||
columnHelper.display({
|
||||
id: "name",
|
||||
header: t("fields.name"),
|
||||
cell: ({ row }) => {
|
||||
const name = [row.original.first_name, row.original.last_name]
|
||||
.filter(Boolean)
|
||||
.join(" ")
|
||||
|
||||
if (!name) {
|
||||
return <span className="text-ui-fg-muted">-</span>
|
||||
}
|
||||
|
||||
return name
|
||||
},
|
||||
}),
|
||||
columnHelper.display({
|
||||
id: "actions",
|
||||
cell: ({ row }) => <UserRowActions user={row.original} />,
|
||||
}),
|
||||
],
|
||||
[t]
|
||||
)
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import { useQueryParams } from "../../../../../hooks/use-query-params"
|
||||
|
||||
export const useUserTableQuery = ({
|
||||
pageSize = 20,
|
||||
prefix,
|
||||
}: {
|
||||
pageSize?: number
|
||||
prefix?: string
|
||||
}) => {
|
||||
const raw = useQueryParams(["q", "order", "offset"], prefix)
|
||||
|
||||
const { offset, ...params } = raw
|
||||
|
||||
const searchParams = {
|
||||
limit: pageSize,
|
||||
offset: offset ? parseInt(offset) : 0,
|
||||
...params,
|
||||
}
|
||||
|
||||
return {
|
||||
searchParams,
|
||||
raw,
|
||||
}
|
||||
}
|
||||
@@ -1,39 +1,35 @@
|
||||
import { Button, Container, Heading } from "@medusajs/ui"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { Container, createDataTableColumnHelper } from "@medusajs/ui"
|
||||
import { keepPreviousData } from "@tanstack/react-query"
|
||||
import { useMemo } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Link } from "react-router-dom"
|
||||
import { _DataTable } from "../../../../../components/table/data-table"
|
||||
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 { useUsers } from "../../../../../hooks/api/users"
|
||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
import { useUserTableColumns } from "./use-user-table-columns"
|
||||
import { useUserTableQuery } from "./use-user-table-query"
|
||||
import { useDate } from "../../../../../hooks/use-date"
|
||||
import { useQueryParams } from "../../../../../hooks/use-query-params"
|
||||
|
||||
const PAGE_SIZE = 20
|
||||
|
||||
export const UserListTable = () => {
|
||||
const { raw, searchParams } = useUserTableQuery({
|
||||
pageSize: PAGE_SIZE,
|
||||
})
|
||||
const {
|
||||
users,
|
||||
count,
|
||||
isPending: isLoading,
|
||||
isError,
|
||||
error,
|
||||
} = useUsers(searchParams, {
|
||||
placeholderData: keepPreviousData,
|
||||
})
|
||||
const { q, order, offset } = useQueryParams(["q", "order", "offset"])
|
||||
const { users, count, isPending, isError, error } = useUsers(
|
||||
{
|
||||
q,
|
||||
order,
|
||||
offset: offset ? parseInt(offset) : 0,
|
||||
limit: PAGE_SIZE,
|
||||
},
|
||||
{
|
||||
placeholderData: keepPreviousData,
|
||||
}
|
||||
)
|
||||
|
||||
const columns = useUserTableColumns()
|
||||
|
||||
const { table } = useDataTable({
|
||||
data: users ?? [],
|
||||
columns,
|
||||
count,
|
||||
enablePagination: true,
|
||||
getRowId: (row) => row.id,
|
||||
pageSize: PAGE_SIZE,
|
||||
})
|
||||
const columns = useColumns()
|
||||
const filters = useFilters()
|
||||
|
||||
const { t } = useTranslation()
|
||||
|
||||
@@ -43,28 +39,109 @@ export const UserListTable = () => {
|
||||
|
||||
return (
|
||||
<Container className="divide-y p-0">
|
||||
<div className="flex items-center justify-between px-6 py-4">
|
||||
<Heading>{t("users.domain")}</Heading>
|
||||
<Button size="small" variant="secondary" asChild>
|
||||
<Link to="invite">{t("users.invite")}</Link>
|
||||
</Button>
|
||||
</div>
|
||||
<_DataTable
|
||||
table={table}
|
||||
<DataTable
|
||||
data={users}
|
||||
columns={columns}
|
||||
count={count}
|
||||
filters={filters}
|
||||
getRowId={(row) => row.id}
|
||||
rowCount={count}
|
||||
pageSize={PAGE_SIZE}
|
||||
isLoading={isLoading}
|
||||
orderBy={[
|
||||
{ key: "email", label: t("fields.email") },
|
||||
{ key: "first_name", label: t("fields.firstName") },
|
||||
{ key: "last_name", label: t("fields.lastName") },
|
||||
]}
|
||||
navigateTo={(row) => `${row.id}`}
|
||||
search
|
||||
pagination
|
||||
queryObject={raw}
|
||||
heading={t("users.domain")}
|
||||
rowHref={(row) => `${row.id}`}
|
||||
isLoading={isPending}
|
||||
action={{
|
||||
label: t("users.invite"),
|
||||
to: "invite",
|
||||
}}
|
||||
emptyState={{
|
||||
empty: {
|
||||
heading: t("users.list.empty.heading"),
|
||||
description: t("users.list.empty.description"),
|
||||
},
|
||||
filtered: {
|
||||
heading: t("users.list.filtered.heading"),
|
||||
description: t("users.list.filtered.description"),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
const columnHelper = createDataTableColumnHelper<HttpTypes.AdminUser>()
|
||||
|
||||
const useColumns = () => {
|
||||
const { t } = useTranslation()
|
||||
const navigate = useNavigate()
|
||||
const { getFullDate } = useDate()
|
||||
|
||||
return useMemo(
|
||||
() => [
|
||||
columnHelper.accessor("email", {
|
||||
header: t("fields.email"),
|
||||
cell: ({ row }) => {
|
||||
return row.original.email
|
||||
},
|
||||
enableSorting: true,
|
||||
sortAscLabel: t("filters.sorting.alphabeticallyAsc"),
|
||||
sortDescLabel: t("filters.sorting.alphabeticallyDesc"),
|
||||
}),
|
||||
columnHelper.accessor("first_name", {
|
||||
header: t("fields.firstName"),
|
||||
cell: ({ row }) => {
|
||||
return row.original.first_name || "-"
|
||||
},
|
||||
enableSorting: true,
|
||||
sortAscLabel: t("filters.sorting.alphabeticallyAsc"),
|
||||
sortDescLabel: t("filters.sorting.alphabeticallyDesc"),
|
||||
}),
|
||||
columnHelper.accessor("last_name", {
|
||||
header: t("fields.lastName"),
|
||||
cell: ({ row }) => {
|
||||
return row.original.last_name || "-"
|
||||
},
|
||||
enableSorting: true,
|
||||
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"),
|
||||
}),
|
||||
columnHelper.action({
|
||||
actions: [
|
||||
{
|
||||
label: t("actions.edit"),
|
||||
icon: <PencilSquare />,
|
||||
onClick: (ctx) => {
|
||||
navigate(`${ctx.row.original.id}/edit`)
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
[t, getFullDate, navigate]
|
||||
)
|
||||
}
|
||||
|
||||
const useFilters = () => {
|
||||
const dateFilters = useDataTableDateFilters()
|
||||
|
||||
return useMemo(() => {
|
||||
return dateFilters
|
||||
}, [dateFilters])
|
||||
}
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import { PencilSquare } from "@medusajs/icons"
|
||||
import { UserDTO } from "@medusajs/types"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||
|
||||
export const UserRowActions = ({ user }: { user: UserDTO }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<ActionMenu
|
||||
groups={[
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
icon: <PencilSquare />,
|
||||
label: t("actions.edit"),
|
||||
to: `${user.id}/edit`,
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user