feat(dashboard, medusa, medusa-js, medusa-react, icons): DataGrid, partial Product domain, and ProductVariant hook (#6428)
The PR for the Products section is growing quite large, so I would like to merge this PR that contains a lot of the ground work before moving onto finalizing the rest of the domain. **Note** Since the PR contains changes to the core, that the dashboard depends on, the staging env will not work. To preview this PR, you will need to run it locally. ## `@medusajs/medusa` **What** - Adds missing query params to `GET /admin/products/:id/variants` - `options.values` has been added to the default relations of admin product endpoints. ## `medusa-react` **What** - Adds missing hook for `GET /admin/products/:id/variants` ## `@medusajs/dashboard` - Adds base implementation for `DataGrid` component (formerly `BulkEditor`) (WIP) - Adds `/products` overview page - Adds partial `/products/create` page for creating new products (WIP - need to go over design w/ Ludvig before continuing) - Adds `/products/:id` details page - Adds `/products/:id/gallery` page for inspecting a products images in fullscreen. - Adds `/products/:id/edit` page for editing the general information of a product - Adds `/products/:id/attributes` page for editing the attributes information of a product - Adds `/products/:id/sales-channels` page for editing which sales channels a product is available in - Fixes a bug in `DataTable` where a table with two fixed columns would not display correctly For the review its not important to test the DataGrid, as it is still WIP, and I need to go through some minor changes to the behaviour with Ludvig, as virtualizing it adds some constraints. ## `@medusajs/icons` **What** - Pulls latest icons from Figma ## TODO in next PR - [ ] Fix the typing of POST /admin/products/:id as it is currently not possible to delete any of the nullable fields once they have been added. Be aware of this when reviewing this PR. - [ ] Wrap up `/products/create` page - [ ] Add `/products/:id/media` page for managing media associated with the product. - [ ] Add `/products/id/options` for managing product options (need Ludvig to rethink this as the current API is very limited and we can implement the current design as is.) - [ ] Add `/products/:id/variants/:id` page for editing a variant. (Possibly concat all of these into one BulkEditor page?)
This commit is contained in:
+30
@@ -0,0 +1,30 @@
|
||||
import { SalesChannel } from "@medusajs/medusa"
|
||||
import { createColumnHelper } from "@tanstack/react-table"
|
||||
|
||||
import { useMemo } from "react"
|
||||
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<SalesChannel>()
|
||||
|
||||
export const useSalesChannelTableColumns = () => {
|
||||
return useMemo(
|
||||
() => [
|
||||
columnHelper.accessor("name", {
|
||||
header: () => <NameHeader />,
|
||||
cell: ({ getValue }) => <NameCell name={getValue()} />,
|
||||
}),
|
||||
columnHelper.accessor("description", {
|
||||
header: () => <DescriptionHeader />,
|
||||
cell: ({ getValue }) => <DescriptionCell description={getValue()} />,
|
||||
}),
|
||||
],
|
||||
[]
|
||||
)
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
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
|
||||
}
|
||||
@@ -23,7 +23,8 @@ export const useCustomerTableQuery = ({
|
||||
prefix
|
||||
)
|
||||
|
||||
const { offset, groups, has_account, q, order } = queryObject
|
||||
const { offset, groups, created_at, updated_at, has_account, q, order } =
|
||||
queryObject
|
||||
|
||||
const searchParams: AdminGetCustomersParams = {
|
||||
limit: pageSize,
|
||||
@@ -31,12 +32,8 @@ export const useCustomerTableQuery = ({
|
||||
groups: groups?.split(","),
|
||||
has_account: has_account ? has_account === "true" : undefined,
|
||||
order,
|
||||
created_at: queryObject.created_at
|
||||
? JSON.parse(queryObject.created_at)
|
||||
: undefined,
|
||||
updated_at: queryObject.updated_at
|
||||
? JSON.parse(queryObject.updated_at)
|
||||
: undefined,
|
||||
created_at: created_at ? JSON.parse(created_at) : undefined,
|
||||
updated_at: updated_at ? JSON.parse(updated_at) : undefined,
|
||||
q,
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { AdminGetSalesChannelsParams } from "@medusajs/medusa"
|
||||
import { useQueryParams } from "../../use-query-params"
|
||||
|
||||
type UseCustomerTableQueryProps = {
|
||||
prefix?: string
|
||||
pageSize?: number
|
||||
}
|
||||
|
||||
export const useSalesChannelTableQuery = ({
|
||||
prefix,
|
||||
pageSize = 20,
|
||||
}: UseCustomerTableQueryProps) => {
|
||||
const queryObject = useQueryParams(
|
||||
["offset", "q", "order", "created_at", "updated_at"],
|
||||
prefix
|
||||
)
|
||||
|
||||
const { offset, created_at, updated_at, q, order } = queryObject
|
||||
|
||||
const searchParams: AdminGetSalesChannelsParams = {
|
||||
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,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import { useCallback, useState } from "react"
|
||||
|
||||
/**
|
||||
* Base interface for a command that can be managed
|
||||
* by the `useCommandHistory` hook.
|
||||
*/
|
||||
export interface Command {
|
||||
execute: () => void
|
||||
undo: () => void
|
||||
redo: () => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to manage a history of commands that can be undone, redone, and executed.
|
||||
*/
|
||||
export const useCommandHistory = (maxHistory = 20) => {
|
||||
const [past, setPast] = useState<Command[]>([])
|
||||
const [future, setFuture] = useState<Command[]>([])
|
||||
|
||||
const canUndo = past.length > 0
|
||||
const canRedo = future.length > 0
|
||||
|
||||
const undo = useCallback(() => {
|
||||
if (!canUndo) {
|
||||
return
|
||||
}
|
||||
|
||||
const previous = past[past.length - 1]
|
||||
const newPast = past.slice(0, past.length - 1)
|
||||
|
||||
previous.undo()
|
||||
|
||||
setPast(newPast)
|
||||
setFuture([previous, ...future.slice(0, maxHistory - 1)])
|
||||
}, [canUndo, future, past, maxHistory])
|
||||
|
||||
const redo = useCallback(() => {
|
||||
if (!canRedo) {
|
||||
return
|
||||
}
|
||||
|
||||
const next = future[0]
|
||||
const newFuture = future.slice(1)
|
||||
|
||||
next.redo()
|
||||
|
||||
setPast([...past, next].slice(0, maxHistory - 1))
|
||||
setFuture(newFuture)
|
||||
}, [canRedo, future, past, maxHistory])
|
||||
|
||||
const execute = useCallback(
|
||||
(command: Command) => {
|
||||
command.execute()
|
||||
setPast((past) => [...past, command].slice(0, maxHistory - 1))
|
||||
setFuture([])
|
||||
},
|
||||
[maxHistory]
|
||||
)
|
||||
|
||||
return {
|
||||
undo,
|
||||
redo,
|
||||
execute,
|
||||
canUndo,
|
||||
canRedo,
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
OnChangeFn,
|
||||
PaginationState,
|
||||
Row,
|
||||
RowSelectionState,
|
||||
getCoreRowModel,
|
||||
getPaginationRowModel,
|
||||
useReactTable,
|
||||
@@ -16,6 +17,10 @@ type UseDataTableProps<TData> = {
|
||||
count?: number
|
||||
pageSize?: number
|
||||
enableRowSelection?: boolean | ((row: Row<TData>) => boolean)
|
||||
rowSelection?: {
|
||||
state: RowSelectionState
|
||||
updater: OnChangeFn<RowSelectionState>
|
||||
}
|
||||
enablePagination?: boolean
|
||||
getRowId?: (original: TData, index: number) => string
|
||||
meta?: Record<string, unknown>
|
||||
@@ -29,6 +34,7 @@ export const useDataTable = <TData,>({
|
||||
pageSize: _pageSize = 20,
|
||||
enablePagination = true,
|
||||
enableRowSelection = false,
|
||||
rowSelection: _rowSelection,
|
||||
getRowId,
|
||||
meta,
|
||||
prefix,
|
||||
@@ -48,7 +54,9 @@ export const useDataTable = <TData,>({
|
||||
}),
|
||||
[pageIndex, pageSize]
|
||||
)
|
||||
const [rowSelection, setRowSelection] = useState({})
|
||||
const [localRowSelection, setLocalRowSelection] = useState({})
|
||||
const rowSelection = _rowSelection?.state ?? localRowSelection
|
||||
const setRowSelection = _rowSelection?.updater ?? setLocalRowSelection
|
||||
|
||||
useEffect(() => {
|
||||
if (!enablePagination) {
|
||||
@@ -93,7 +101,7 @@ export const useDataTable = <TData,>({
|
||||
data,
|
||||
columns,
|
||||
state: {
|
||||
rowSelection,
|
||||
rowSelection: rowSelection, // We always pass a selection state to the table even if it's not enabled
|
||||
pagination: enablePagination ? pagination : undefined,
|
||||
},
|
||||
pageCount: Math.ceil((count ?? 0) / pageSize),
|
||||
|
||||
Reference in New Issue
Block a user