refactor: users domain (#7070)

**What**
- refactor users domain to use `DataTable`
- add search params to `User` module models
This commit is contained in:
Frane Polić authored and GitHub committed 2024-04-16 16:09:07 +00:00
1 parent a7df6235f9
commit 122b3ea76b
10 files changed
+94 -85

No files matched your search

@@ -1,7 +1,7 @@
import { AdminGetSalesChannelsParams } from "@medusajs/medusa" import { AdminGetSalesChannelsParams } from "@medusajs/medusa"
import { useQueryParams } from "../../use-query-params" import { useQueryParams } from "../../use-query-params"
type UseCustomerTableQueryProps = { type UseSalesChannelTableQueryProps = {
prefix?: string prefix?: string
pageSize?: number pageSize?: number
} }
@@ -9,7 +9,7 @@ type UseCustomerTableQueryProps = {
export const useSalesChannelTableQuery = ({ export const useSalesChannelTableQuery = ({
prefix, prefix,
pageSize = 20, pageSize = 20,
}: UseCustomerTableQueryProps) => { }: UseSalesChannelTableQueryProps) => {
const queryObject = useQueryParams( const queryObject = useQueryParams(
["offset", "q", "order", "created_at", "updated_at"], ["offset", "q", "order", "created_at", "updated_at"],
prefix prefix
@@ -0,0 +1,32 @@
import { useQueryParams } from "../../use-query-params"
type UseUserInviteTableQueryProps = {
prefix?: string
pageSize?: number
}
export const useUserInviteTableQuery = ({
prefix,
pageSize = 20,
}: UseUserInviteTableQueryProps) => {
const queryObject = useQueryParams(
["offset", "q", "order", "created_at", "updated_at"],
prefix
)
const { offset, created_at, updated_at, q, order } = queryObject
const searchParams = {
limit: pageSize,
offset: offset ? Number(offset) : 0,
order,
created_at: created_at ? JSON.parse(created_at) : undefined,
updated_at: updated_at ? JSON.parse(updated_at) : undefined,
q,
}
return {
searchParams,
raw: queryObject,
}
}
@@ -8,7 +8,12 @@ export const UserDetail = () => {
const initialData = useLoaderData() as Awaited<ReturnType<typeof userLoader>> const initialData = useLoaderData() as Awaited<ReturnType<typeof userLoader>>
const { id } = useParams() const { id } = useParams()
const { user, isLoading, isError, error } = useUser(id!, undefined, { const {
user,
isPending: isLoading,
isError,
error,
} = useUser(id!, undefined, {
initialData, initialData,
}) })
@@ -8,7 +8,7 @@ import { EditUserForm } from "./components/edit-user-form"
export const UserEdit = () => { export const UserEdit = () => {
const { t } = useTranslation() const { t } = useTranslation()
const { id } = useParams() const { id } = useParams()
const { user, isLoading, isError, error } = useUser(id!) const { user, isPending: isLoading, isError, error } = useUser(id!)
if (isError) { if (isError) {
throw error throw error
@@ -7,28 +7,19 @@ import {
Heading, Heading,
Input, Input,
StatusBadge, StatusBadge,
Table,
Text, Text,
Tooltip, Tooltip,
clx,
usePrompt, usePrompt,
} from "@medusajs/ui" } from "@medusajs/ui"
import { import { createColumnHelper } from "@tanstack/react-table"
createColumnHelper,
flexRender,
getCoreRowModel,
getPaginationRowModel,
useReactTable,
} from "@tanstack/react-table"
import { format } from "date-fns" import { format } from "date-fns"
import { useMemo } from "react" import { useMemo } from "react"
import { useForm } from "react-hook-form" import { useForm } from "react-hook-form"
import { Trans, useTranslation } from "react-i18next" import { Trans, useTranslation } from "react-i18next"
import * as zod from "zod" import * as zod from "zod"
import { ActionMenu } from "../../../../../components/common/action-menu" import { ActionMenu } from "../../../../../components/common/action-menu"
import { NoRecords } from "../../../../../components/common/empty-table-content" import { useDataTable } from "../../../../../hooks/use-data-table"
import { Form } from "../../../../../components/common/form" import { Form } from "../../../../../components/common/form"
import { LocalizedTablePagination } from "../../../../../components/localization/localized-table-pagination"
import { RouteFocusModal } from "../../../../../components/route-modal" import { RouteFocusModal } from "../../../../../components/route-modal"
import { import {
useCreateInvite, useCreateInvite,
@@ -36,12 +27,15 @@ import {
useInvites, useInvites,
useResendInvite, useResendInvite,
} from "../../../../../hooks/api/invites" } from "../../../../../hooks/api/invites"
import { DataTable } from "../../../../../components/table/data-table"
import { useUserInviteTableQuery } from "../../../../../hooks/table/query/use-user-invite-table-query"
const InviteUserSchema = zod.object({ const InviteUserSchema = zod.object({
email: zod.string().email(), email: zod.string().email(),
}) })
const PAGE_SIZE = 10 const PAGE_SIZE = 10
const PREFIX = "usr_invite"
export const InviteUserForm = () => { export const InviteUserForm = () => {
const { t } = useTranslation() const { t } = useTranslation()
@@ -53,20 +47,28 @@ export const InviteUserForm = () => {
resolver: zodResolver(InviteUserSchema), resolver: zodResolver(InviteUserSchema),
}) })
const { invites, isLoading, isError, error } = useInvites() const { raw, searchParams } = useUserInviteTableQuery({
const count = invites?.length ?? 0 prefix: PREFIX,
pageSize: PAGE_SIZE,
})
const noRecords = !isLoading && count === 0 const {
invites,
count,
isPending: isLoading,
isError,
error,
} = useInvites(searchParams)
const columns = useColumns() const columns = useColumns()
const table = useReactTable({ const { table } = useDataTable({
// TODO: Update type when medusa-react is 2.0 compatible data: (invites ?? []) as InviteDTO[],
data: (invites ?? []) as unknown as InviteDTO[],
columns, columns,
pageCount: Math.ceil(count / PAGE_SIZE), count,
getCoreRowModel: getCoreRowModel(), enablePagination: true,
getPaginationRowModel: getPaginationRowModel(), getRowId: (row) => row.id,
pageSize: PAGE_SIZE,
}) })
const { mutateAsync, isPending } = useCreateInvite() const { mutateAsync, isPending } = useCreateInvite()
@@ -136,68 +138,18 @@ export const InviteUserForm = () => {
<div className="flex flex-col gap-y-4"> <div className="flex flex-col gap-y-4">
<Heading level="h2">{t("users.pendingInvites")}</Heading> <Heading level="h2">{t("users.pendingInvites")}</Heading>
<Container className="overflow-hidden p-0"> <Container className="overflow-hidden p-0">
{!noRecords ? ( <DataTable
<div> table={table}
<Table> columns={columns}
<Table.Header className="border-t-0">
{table.getHeaderGroups().map((headerGroup) => {
return (
<Table.Row
key={headerGroup.id}
className="[&_th:last-of-type]:w-[1%] [&_th:last-of-type]:whitespace-nowrap [&_th]:w-1/3"
>
{headerGroup.headers.map((header) => {
return (
<Table.HeaderCell key={header.id}>
{flexRender(
header.column.columnDef.header,
header.getContext()
)}
</Table.HeaderCell>
)
})}
</Table.Row>
)
})}
</Table.Header>
<Table.Body>
{table.getRowModel().rows.map((row) => (
<Table.Row
key={row.id}
className={clx(
"transition-fg[&_td:last-of-type]:w-[1%] [&_td:last-of-type]:whitespace-nowrap",
{
"bg-ui-bg-highlight hover:bg-ui-bg-highlight-hover":
row.getIsSelected(),
}
)}
>
{row.getVisibleCells().map((cell) => (
<Table.Cell key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</Table.Cell>
))}
</Table.Row>
))}
</Table.Body>
</Table>
<LocalizedTablePagination
canNextPage={table.getCanNextPage()}
canPreviousPage={table.getCanPreviousPage()}
nextPage={table.nextPage}
previousPage={table.previousPage}
count={count} count={count}
pageIndex={table.getState().pagination.pageIndex}
pageCount={table.getPageCount()}
pageSize={PAGE_SIZE} pageSize={PAGE_SIZE}
prefix={PREFIX}
pagination
search
isLoading={isLoading}
queryObject={raw}
orderBy={["email", "created_at", "updated_at"]}
/> />
</div>
) : (
<NoRecords className="h-[200px]" />
)}
</Container> </Container>
</div> </div>
</div> </div>
@@ -14,7 +14,13 @@ export const UserListTable = () => {
const { raw, searchParams } = useUserTableQuery({ const { raw, searchParams } = useUserTableQuery({
pageSize: PAGE_SIZE, pageSize: PAGE_SIZE,
}) })
const { users, count, isLoading, isError, error } = useUsers(searchParams, { const {
users,
count,
isPending: isLoading,
isError,
error,
} = useUsers(searchParams, {
placeholderData: keepPreviousData, placeholderData: keepPreviousData,
}) })
@@ -70,6 +70,13 @@ export class AdminGetInvitesParams extends extendedFindParamsMixin({
@IsOptional() @IsOptional()
@IsString() @IsString()
fields?: string fields?: string
/**
* The term to search invites emails.
*/
@IsString()
@IsOptional()
q?: string
} }
export class AdminCreateInviteRequest { export class AdminCreateInviteRequest {
@@ -20,6 +20,7 @@ export const AdminGetUsersParams = createFindParams({
email: z.string().optional(), email: z.string().optional(),
first_name: z.string().optional(), first_name: z.string().optional(),
last_name: z.string().optional(), last_name: z.string().optional(),
q: z.string().optional(),
}) })
) )
+2
View File
@@ -14,6 +14,7 @@ import {
DALUtils, DALUtils,
createPsqlIndexStatementHelper, createPsqlIndexStatementHelper,
generateEntityId, generateEntityId,
Searchable,
} from "@medusajs/utils" } from "@medusajs/utils"
const inviteEmailIndexName = "IDX_invite_email" const inviteEmailIndexName = "IDX_invite_email"
@@ -57,6 +58,7 @@ export default class Invite {
name: inviteEmailIndexName, name: inviteEmailIndexName,
expression: inviteEmailIndexStatement, expression: inviteEmailIndexStatement,
}) })
@Searchable()
@Property({ columnType: "text" }) @Property({ columnType: "text" })
email: string email: string
+5 -1
View File
@@ -14,6 +14,7 @@ import {
DALUtils, DALUtils,
createPsqlIndexStatementHelper, createPsqlIndexStatementHelper,
generateEntityId, generateEntityId,
Searchable,
} from "@medusajs/utils" } from "@medusajs/utils"
const userEmailIndexName = "IDX_user_email" const userEmailIndexName = "IDX_user_email"
@@ -47,17 +48,20 @@ export default class User {
@PrimaryKey({ columnType: "text" }) @PrimaryKey({ columnType: "text" })
id!: string id!: string
@Searchable()
@Property({ columnType: "text", nullable: true }) @Property({ columnType: "text", nullable: true })
first_name: string | null = null first_name: string | null = null
@Searchable()
@Property({ columnType: "text", nullable: true }) @Property({ columnType: "text", nullable: true })
last_name: string | null = null last_name: string | null = null
@Property({ columnType: "text" })
@Index({ @Index({
name: userEmailIndexName, name: userEmailIndexName,
expression: userEmailIndexStatement, expression: userEmailIndexStatement,
}) })
@Searchable()
@Property({ columnType: "text" })
email: string email: string
@Property({ columnType: "text", nullable: true }) @Property({ columnType: "text", nullable: true })