diff --git a/.changeset/spotty-chairs-tie.md b/.changeset/spotty-chairs-tie.md new file mode 100644 index 00000000..c9982d6c --- /dev/null +++ b/.changeset/spotty-chairs-tie.md @@ -0,0 +1,6 @@ +--- +"@example/erp": patch +"@genseki/react": patch +--- + +feat: add action select diff --git a/examples/erp/genseki/collections/posts.client.tsx b/examples/erp/genseki/collections/posts.client.tsx index 5842eebb..a5c2d476 100644 --- a/examples/erp/genseki/collections/posts.client.tsx +++ b/examples/erp/genseki/collections/posts.client.tsx @@ -5,24 +5,20 @@ import { useState } from 'react' import { type SubmitErrorHandler, type SubmitHandler, useFormContext } from 'react-hook-form' import { zodResolver } from '@hookform/resolvers/zod' -import { DotsThreeVerticalIcon } from '@phosphor-icons/react' import { useQueryClient } from '@tanstack/react-query' import { createColumnHelper } from '@tanstack/react-table' import z from 'zod' -import type { BaseData, CollectionLayoutProps, InferCreateFields } from '@genseki/react' +import type { CollectionLayoutProps, InferCreateFields } from '@genseki/react' import { - BaseIcon, + actionsColumn, Button, - Checkbox, CollectionListToolbar, + createDeleteActionItem, + createEditActionItem, + createSeparatorItem, Form, type InferFields, - Menu, - MenuContent, - MenuItem, - MenuSeparator, - MenuTrigger, SubmitButton, TanstackTable, toast, @@ -71,6 +67,7 @@ export const columns = [ header: 'Updated At', cell: (info) =>
{new Date(info.getValue()).toLocaleDateString('en-GB')}
, }), + actionsColumn([createEditActionItem(), createSeparatorItem(), createDeleteActionItem()]), ] /** @@ -82,7 +79,7 @@ export function PostClientToolbar() { return (
- +
) } @@ -115,105 +112,10 @@ export const PostClientTable = (props: { children?: React.ReactNode }) => { }, }) - const columnHelper = createColumnHelper() - // You can setup your own custom columns - const enhancedColumns = [ - ...(context.actions?.delete - ? [ - columnHelper.display({ - id: 'select', - header: ({ table }) => ( - - table.getToggleAllRowsSelectedHandler()({ target: { checked } }) - } - /> - ), - cell: ({ row }) => ( - { - const handler = row.getToggleSelectedHandler() - handler(event) - }} - /> - ), - }), - ] - : []), - ...context.columns, - columnHelper.display({ - id: 'actions', - cell: ({ row }) => { - if (!context.actions?.one && !context.actions?.update && !context.actions?.delete) { - return null - } - - return ( -
- - - - - - {context.actions?.one && ( - { - navigation.navigate(`./${context.slug}/${row.original.__id}`) - }} - > - View - - )} - {context.actions?.update && ( - { - navigation.navigate(`./${context.slug}/update/${row.original.__id}`) - }} - > - Edit - - )} - {context.actions?.delete && ( - <> - {context.actions?.one || (context.actions?.update && )} - { - deleteMutation.mutate([row.original.__id.toString()]) - }} - > - Delete - - - )} - - { - confirm('Confirm to revert the action?') - }} - > - Revert - - - -
- ) - }, - }), - ] - const table = useListTable({ total: query.data?.total, data: query.data?.data || [], - columns: enhancedColumns, + columns: context.columns, }) return ( diff --git a/examples/erp/genseki/collections/posts.tsx b/examples/erp/genseki/collections/posts.tsx index 2fd00994..7e8edd8c 100644 --- a/examples/erp/genseki/collections/posts.tsx +++ b/examples/erp/genseki/collections/posts.tsx @@ -192,7 +192,10 @@ export const postsCollection = createPlugin('posts', (app) => { search: ['title'], sortBy: ['updatedAt', 'title'], }, - actions: { delete: true, update: true, create: true }, + toolbar: { + create: true, + delete: true, + }, layout: Layout, page: CustomListPage, }) diff --git a/examples/erp/genseki/collections/tags.client.ts b/examples/erp/genseki/collections/tags.client.tsx similarity index 52% rename from examples/erp/genseki/collections/tags.client.ts rename to examples/erp/genseki/collections/tags.client.tsx index 3fcdf121..ab8fd8bf 100644 --- a/examples/erp/genseki/collections/tags.client.ts +++ b/examples/erp/genseki/collections/tags.client.tsx @@ -2,7 +2,15 @@ import { createColumnHelper } from '@tanstack/react-table' -import type { InferFields } from '@genseki/react' +import { + actionsColumn, + createDeleteActionItem, + createEditActionItem, + createSeparatorItem, + createViewActionItem, + type InferFields, + selectColumn, +} from '@genseki/react' import type { fields } from './tags' @@ -10,10 +18,17 @@ type Tag = InferFields const columnHelper = createColumnHelper() export const columns = [ + selectColumn(), columnHelper.accessor('__id', { cell: (info) => info.getValue(), }), columnHelper.accessor('name', { cell: (info) => info.getValue(), }), + actionsColumn([ + createViewActionItem(), + createEditActionItem(), + createSeparatorItem(), + createDeleteActionItem(), + ]), ] diff --git a/examples/erp/genseki/collections/tags.ts b/examples/erp/genseki/collections/tags.ts index fd7d36f9..7658c3a4 100644 --- a/examples/erp/genseki/collections/tags.ts +++ b/examples/erp/genseki/collections/tags.ts @@ -23,14 +23,14 @@ export const tagsCollection = createPlugin('tags', (app) => { search: ['name'], sortBy: ['name'], }, - actions: { + toolbar: { create: true, - update: true, delete: true, }, }) ) .addPageAndApiRouter(collection.create(fields, {})) .addPageAndApiRouter(collection.update(fields, {})) + .addPageAndApiRouter(collection.one(fields)) .addApiRouter(collection.deleteApiRouter(fields)) }) diff --git a/packages/react/src/core/collection/index.tsx b/packages/react/src/core/collection/index.tsx index 50ce7379..163871d6 100644 --- a/packages/react/src/core/collection/index.tsx +++ b/packages/react/src/core/collection/index.tsx @@ -319,12 +319,7 @@ export type CollectionListConfig< /** * @param actions will decide whether or not to show actios in `list` view screen, This is not related to available features of collection, but rather only visible UI part of the `list` page */ - actions?: { - create?: boolean - update?: boolean - delete?: boolean - one?: boolean - } + toolbar?: CollectionToolbarActions } export type CollectionUpdateApiArgs< @@ -538,7 +533,7 @@ export class CollectionBuilder< columns={config.columns} search={config.configuration?.search} sortBy={config.configuration?.sortBy} - actions={config.actions} + toolbar={config.toolbar} > {page} @@ -774,9 +769,7 @@ export class CollectionBuilder< } } -export interface CollectionListActions { +export interface CollectionToolbarActions { create?: boolean - update?: boolean delete?: boolean - one?: boolean } diff --git a/packages/react/src/react/views/collections/list/context.tsx b/packages/react/src/react/views/collections/list/context.tsx index 879c7aa3..affea1ea 100644 --- a/packages/react/src/react/views/collections/list/context.tsx +++ b/packages/react/src/react/views/collections/list/context.tsx @@ -13,7 +13,7 @@ import { CollectionListPagination } from './table/pagination' import { CollectionListToolbar, type CollectionListToolbarProps } from './toolbar' import { toast } from '../../../..' -import type { CollectionListActions } from '../../../../core/collection' +import type { CollectionToolbarActions } from '../../../../core/collection' import type { FieldsClient } from '../../../../core/field' import { TableStatesProvider, useTableStatesContext } from '../../../providers/table' import { useCollection } from '../context' @@ -51,7 +51,7 @@ export interface CollectionListContextValue { columns: ColumnDef[] search?: string[] sortBy?: string[] - actions?: CollectionListActions + toolbar?: CollectionToolbarActions // Helper functions deleteRows: (rows?: string[]) => void @@ -66,7 +66,7 @@ export interface CollectionListProviderProps { columns: ColumnDef[] search?: string[] sortBy?: string[] - actions?: CollectionListActions + toolbar?: CollectionToolbarActions } /** diff --git a/packages/react/src/react/views/collections/list/default.tsx b/packages/react/src/react/views/collections/list/default.tsx index 5626059e..aabc1645 100644 --- a/packages/react/src/react/views/collections/list/default.tsx +++ b/packages/react/src/react/views/collections/list/default.tsx @@ -1,10 +1,5 @@ 'use client' -import React, { useMemo } from 'react' - -import { DotsThreeVerticalIcon } from '@phosphor-icons/react' -import { type ColumnDef, createColumnHelper } from '@tanstack/react-table' - import { Banner } from './banner' import { CollectionListTableContainer } from './container' import { useCollectionList } from './context' @@ -12,113 +7,15 @@ import { CollectionListTable } from './table' import { CollectionListPagination } from './table/pagination' import { CollectionListToolbar } from './toolbar' -import { - BaseIcon, - Checkbox, - Menu, - MenuContent, - MenuItem, - MenuSeparator, - MenuTrigger, -} from '../../../components' -import { useNavigation } from '../../../providers' -import type { BaseData } from '../types' - export function DefaultCollectionListPage() { - const navigation = useNavigation() - const context = useCollectionList() - const columns = useMemo(() => { - if (context.isQuerying) return context.columns - - const columnHelper = createColumnHelper() - return [ - ...(context.actions?.delete - ? [ - columnHelper.display({ - id: 'select', - header: ({ table }) => ( - - table.getToggleAllRowsSelectedHandler()({ target: { checked } }) - } - /> - ), - cell: ({ row }) => ( - row.getToggleSelectedHandler()({ target: { checked } })} - /> - ), - }), - ] - : []), - ...context.columns, - columnHelper.display({ - id: 'actions', - cell: ({ row }) => { - if (!context.actions?.one && !context.actions?.update && !context.actions?.delete) { - return null - } - - return ( -
- - - - - - {context.actions.one && ( - { - navigation.navigate(`./${context.slug}/${row.original.__id}`) - }} - > - View - - )} - {context.actions.update && ( - { - navigation.navigate(`./${context.slug}/update/${row.original.__id}`) - }} - > - Edit - - )} - {context.actions?.delete && ( - <> - {context.actions.one || (context.actions.update && )} - context.deleteRows([row.original.__id as string])} - > - Delete - - - )} - - -
- ) - }, - }), - ] as ColumnDef<{ __pk: string; __id: string }>[] - }, [context.columns, context.actions, context.isQuerying]) - return ( <> - columns={columns} /> + columns={context.columns} onRowClick={undefined} /> diff --git a/packages/react/src/react/views/collections/list/helper/actionsColumn.tsx b/packages/react/src/react/views/collections/list/helper/actionsColumn.tsx new file mode 100644 index 00000000..139fedf5 --- /dev/null +++ b/packages/react/src/react/views/collections/list/helper/actionsColumn.tsx @@ -0,0 +1,113 @@ +import { useId } from 'react' + +import type { Icon } from '@phosphor-icons/react' +import { DotsThreeVerticalIcon } from '@phosphor-icons/react/dist/ssr' +import { createColumnHelper, type Row } from '@tanstack/react-table' + +import { + BaseIcon, + Menu, + MenuContent, + MenuItem, + MenuSeparator, + MenuTrigger, +} from '../../../../components' +import { type NavigationContextValue, useNavigation } from '../../../../providers' +import type { BaseData } from '../../types' +import { useCollectionList } from '../context' + +type ActionItem = (row: Row, key: string) => React.ReactNode + +export function createActionItem(render: (row: Row, key: string) => React.ReactNode) { + return render +} + +function createDefaultActionItem( + title: string, + icon?: Icon, + onAction?: ( + context: ReturnType, + row: Row, + navigation: NavigationContextValue + ) => string | void, + isDanger?: boolean +) { + return createActionItem((row, key) => { + const context = useCollectionList() + const navigation = useNavigation() + + return ( + { + if (onAction) { + onAction(context, row, navigation) + } + }} + > +
+ {icon && } + {title} +
+
+ ) + }) +} + +export function createViewActionItem(title: string = 'View', icon: Icon | undefined = undefined) { + return createDefaultActionItem(title, icon, (context, row, navigation) => + navigation.navigate(`./${context.slug}/${row.original.__id}`) + ) +} + +export function createEditActionItem(title: string = 'Edit', icon: Icon | undefined = undefined) { + return createDefaultActionItem(title, icon, (context, row, navigation) => + navigation.navigate(`./${context.slug}/update/${row.original.__id}`) + ) +} + +export function createDeleteActionItem( + title: string = 'Delete', + icon: Icon | undefined = undefined +) { + return createDefaultActionItem( + title, + icon, + (context, row) => { + context.deleteRows([row.original.__id as string]) + }, + true + ) +} + +export function createSeparatorItem() { + return createActionItem((row, key) => ) +} + +export function actionsColumn(actionItems: ActionItem[] = []) { + const columnHelper = createColumnHelper() + + return columnHelper.display({ + id: 'actions', + cell: ({ row }) => { + return ( +
+ + + + + {actionItems.length !== 0 ? ( + + {actionItems.map((createActionItem) => { + return createActionItem(row, useId()) + })} + + ) : null} + +
+ ) + }, + }) +} diff --git a/packages/react/src/react/views/collections/list/helper/index.ts b/packages/react/src/react/views/collections/list/helper/index.ts new file mode 100644 index 00000000..c7ae9417 --- /dev/null +++ b/packages/react/src/react/views/collections/list/helper/index.ts @@ -0,0 +1,2 @@ +export * from './actionsColumn' +export * from './selectColumn' diff --git a/packages/react/src/react/views/collections/list/helper/selectColumn.tsx b/packages/react/src/react/views/collections/list/helper/selectColumn.tsx new file mode 100644 index 00000000..71cec0d1 --- /dev/null +++ b/packages/react/src/react/views/collections/list/helper/selectColumn.tsx @@ -0,0 +1,26 @@ +import { createColumnHelper } from '@tanstack/react-table' + +import { Checkbox } from '../../../../components' +import type { BaseData } from '../../types' + +export function selectColumn() { + const columnHelper = createColumnHelper() + + return columnHelper.display({ + id: 'select', + header: ({ table }) => ( + table.getToggleAllRowsSelectedHandler()({ target: { checked } })} + /> + ), + cell: ({ row }) => ( + row.getToggleSelectedHandler()({ target: { checked } })} + /> + ), + }) +} diff --git a/packages/react/src/react/views/collections/list/index.ts b/packages/react/src/react/views/collections/list/index.ts index c00b9419..9d5540bb 100644 --- a/packages/react/src/react/views/collections/list/index.ts +++ b/packages/react/src/react/views/collections/list/index.ts @@ -1,5 +1,6 @@ export * from './banner' export * from './context' +export * from './helper' export * from './hooks' export * from './table' export * from './table/pagination' diff --git a/packages/react/src/react/views/collections/list/toolbar/index.tsx b/packages/react/src/react/views/collections/list/toolbar/index.tsx index 018b6c0d..01e9ec50 100644 --- a/packages/react/src/react/views/collections/list/toolbar/index.tsx +++ b/packages/react/src/react/views/collections/list/toolbar/index.tsx @@ -8,7 +8,7 @@ import { CollectionListDelete } from './delete' import { CollectionListFilter } from './filter' import { CollectionListSearch } from './search' -import type { CollectionListActions } from '../../../../../core/collection' +import type { CollectionToolbarActions } from '../../../../../core/collection' import { toast } from '../../../..' import { BaseIcon, ButtonLink } from '../../../../components' import { useTableStatesContext } from '../../../../providers/table' @@ -16,7 +16,7 @@ import { useCollectionList } from '../context' import { useCollectionDeleteMutation } from '../hooks/use-collection-delete' export interface CollectionListToolbarProps { - actions?: Partial + toolbar?: Partial } export function CollectionListToolbar(props: CollectionListToolbarProps) { @@ -24,11 +24,9 @@ export function CollectionListToolbar(props: CollectionListToolbarProps) { const queryClient = useQueryClient() const { rowSelectionIds, setRowSelection, isRowsSelected } = useTableStatesContext() - const actions: CollectionListActions = { - create: props.actions?.create ?? context.actions?.create, - update: props.actions?.update ?? context.actions?.update, - delete: props.actions?.delete ?? context.actions?.delete, - one: props.actions?.one ?? context.actions?.one, + const toolbar: CollectionToolbarActions = { + create: props.toolbar?.create ?? context.toolbar?.create, + delete: props.toolbar?.delete ?? context.toolbar?.delete, } const deleteMutation = useCollectionDeleteMutation({ slug: context.slug, @@ -56,13 +54,13 @@ export function CollectionListToolbar(props: CollectionListToolbarProps) { Back
- {actions?.delete && isRowsSelected && ( + {toolbar?.delete && isRowsSelected && ( deleteMutation.mutate(rowSelectionIds)} /> )} {/* TODO: Filter */} - {actions?.create && } + {toolbar?.create && }
)