From 4b0a866e3caa302ac54878b9ef5902129c3590e6 Mon Sep 17 00:00:00 2001 From: Gabriel Castro Date: Fri, 13 Dec 2024 17:27:58 -0300 Subject: [PATCH 1/2] :sparkles: feat: add pagination and limit size using query params, and some UI improvements --- locales/extracted/en.json | 7 +- locales/extracted/pt.json | 11 +- .../(routes)/ledgers/ledgers-data-table.tsx | 466 +++++++++++------- src/app/(routes)/ledgers/ledgers-view.tsx | 34 +- src/app/(routes)/ledgers/page.tsx | 24 +- src/client/ledgers.ts | 12 +- .../entity-box/entity-box-actions.tsx | 19 - .../entity-box/entity-box-content.tsx | 19 - .../entity-box/entity-box-header.tsx | 22 - src/components/entity-box/entity-box-root.tsx | 25 - src/components/entity-box/index.tsx | 79 ++- src/components/ui/input-with-icon/index.tsx | 2 +- 12 files changed, 433 insertions(+), 287 deletions(-) delete mode 100644 src/components/entity-box/entity-box-actions.tsx delete mode 100644 src/components/entity-box/entity-box-content.tsx delete mode 100644 src/components/entity-box/entity-box-header.tsx delete mode 100644 src/components/entity-box/entity-box-root.tsx diff --git a/locales/extracted/en.json b/locales/extracted/en.json index 7fb3fa5f..e261f431 100644 --- a/locales/extracted/en.json +++ b/locales/extracted/en.json @@ -36,6 +36,7 @@ "common.id": "ID", "common.identification": "Identification", "common.inactive": "Inactive", + "common.itemsPerPage": "Items per page", "common.logoAlt": "Your organization logo", "common.metadata": "Metadata", "common.name": "Name", @@ -52,6 +53,7 @@ "common.selectPlaceholder": "Select...", "common.send": "Send", "common.status": "Status", + "common.support": "Support", "common.table.accounts": "{number, plural, =0 {No accounts} one {# account} other {# accounts}}", "common.table.metadata": "{number, plural, =0 {-} one {# record} other {# records}}", "common.tooltipCopyText": "Click to copy", @@ -71,7 +73,6 @@ "entity.auth.username": "E-mail", "entity.ledger.asset": "Assets", "entity.ledger.name": "Ledger Name", - "entity.ledger.status": "Status", "entity.metadata.key": "Key", "entity.metadata.value": "Value", "entity.organization": "Organization", @@ -160,6 +161,7 @@ "ledgers.accounts.showing": "Showing {count} {number, plural, =0 {accounts} one {account} other {accounts}}.", "ledgers.accounts.subtitle": "{count} {count, plural, =0 {accounts found} one {account found} other {accounts found}}", "ledgers.accounts.title": "Accounts", + "ledgers.assets.count": "{count} assets", "ledgers.assets.createButton": "Create your first Asset", "ledgers.assets.emptyResource": "You haven't added any Asset within this Ledger yet.", "ledgers.assets.emptyResource.createButton": "New Asset", @@ -182,7 +184,6 @@ "ledgers.helperTrigger.question": "What is a Ledger?", "ledgers.helperTrigger.seeMore": "Read the docs", "ledgers.listingTemplate.addButton": "New Ledger", - "ledgers.listingTemplate.newAddButton": "Create your first Ledger", "ledgers.portfolio.deleteDialog.description": "You will delete a portfolio", "ledgers.portfolio.deleteDialog.title": "Are you sure?", "ledgers.portfolio.sheet.description": "Fill in the details of the Portfolio you want to create.", @@ -287,5 +288,7 @@ "signIn.placeholderPassword": "******", "signIn.titleLogin": "Welcome back!", "signIn.toast.error": "Invalid credentials.", + "table.pagination.next": "Next", + "table.pagination.previous": "Previous", "tooltip.passwordInfo": "Contact the system administrator" } \ No newline at end of file diff --git a/locales/extracted/pt.json b/locales/extracted/pt.json index b510a1de..03bde42b 100644 --- a/locales/extracted/pt.json +++ b/locales/extracted/pt.json @@ -22,12 +22,10 @@ "common.edit": "Editar", "entity.ledger.asset": "Ativos", "entity.ledger.name": "Nome do Ledger", - "entity.ledger.status": "Status", "ledgers.columnsTable.tooltipCopyText": "Toque para copiar", "ledgers.helperTrigger.answer": "Livro com o registro de todas as transações e operações da Organização.", "ledgers.helperTrigger.question": "O que é um Ledger?", "ledgers.helperTrigger.seeMore": "Leia a documentação", - "ledgers.listingTemplate.newAddButton": "Criar o primeiro Ledger", "ledgers.listingTemplate.addButton": "Novo Ledger", "ledgers.subtitle": "Visualize e edite os Ledgers da sua Organização.", "ledgers.title": "Ledgers", @@ -92,7 +90,7 @@ "ledgers.assets.emptyResource": "Você ainda não criou nenhum Ativo para este Ledger.", "ledgers.assets.emptyResourceExtra": "Nenhum Ativo encontrado.", "ledgers.emptyResource": "Você ainda não criou nenhum Ledger", - "ledgers.emptyResource.createButton": "Criar Ledger", + "ledgers.emptyResource.createButton": "Novo Ledger", "ledgers.emptyResourceExtra": "Nenhum Ledger encontrado.", "organizations.emptyResource": "Você ainda não criou nenhuma Organização", "settings.tab.organizations": "Organização", @@ -287,5 +285,10 @@ "ledgers.showing": "Mostrando {count} {number, plural, =0 {ledgers} one {ledger} other {ledgers}}.", "ledgers.accounts.showing": "Mostrando {count} {number, plural, =0 {contas} one {conta} other {contas}}.", "ledgers.portfolios.showing": "Mostrando {count} {number, plural, =0 {portfólios} one {portfólio} other {portfólios}}.", - "organizations.showing": "Mostrando {count} {number, plural, =0 {organizações} one {organização} other {organizações}}." + "organizations.showing": "Mostrando {count} {number, plural, =0 {organizações} one {organização} other {organizações}}.", + "common.itemsPerPage": "Itens por página", + "common.support": "Suporte", + "ledgers.assets.count": "{count} ativos", + "table.pagination.next": "Próxima página", + "table.pagination.previous": "Página anterior" } diff --git a/src/app/(routes)/ledgers/ledgers-data-table.tsx b/src/app/(routes)/ledgers/ledgers-data-table.tsx index 4ab27b3a..4d6bd739 100644 --- a/src/app/(routes)/ledgers/ledgers-data-table.tsx +++ b/src/app/(routes)/ledgers/ledgers-data-table.tsx @@ -11,7 +11,14 @@ import { } from '@/components/ui/table' import { EmptyResource } from '@/components/empty-resource' import { Button } from '@/components/ui/button' -import { Plus, MoreVertical, Minus } from 'lucide-react' +import { + Plus, + MoreVertical, + Minus, + HelpCircle, + ChevronRight, + ChevronLeft +} from 'lucide-react' import { Tooltip, TooltipContent, @@ -19,7 +26,7 @@ import { TooltipTrigger } from '@/components/ui/tooltip' import { Arrow } from '@radix-ui/react-tooltip' -import { capitalizeFirstLetter, truncateString } from '@/helpers' +import { truncateString } from '@/helpers' import { DropdownMenu, DropdownMenuContent, @@ -30,27 +37,39 @@ import { import { isNil } from 'lodash' import { useCreateUpdateSheet } from '@/components/sheet/use-create-update-sheet' import useCustomToast from '@/hooks/use-custom-toast' -import { Badge } from '@/components/ui/badge' import Link from 'next/link' import { LedgerEntity } from '@/core/domain/entities/ledger-entity' import { LedgersSheet } from './ledgers-sheet' import { AssetsSheet } from './[id]/assets/assets-sheet' import { EntityDataTable } from '@/components/entity-data-table' +import { EntityBox } from '@/components/entity-box' +import { FormProvider, useForm } from 'react-hook-form' +import { Table as ReactTableType } from '@tanstack/react-table' +import { LedgerResponseDto } from '@/core/application/dto/ledger-response-dto' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue +} from '@/components/ui/select' +import { useRouter, useSearchParams } from 'next/navigation' + +type LedgersExtended = { + items: LedgerEntity[] +} & { limit: number } type LedgersTableProps = { - ledgers: { items: LedgerEntity[] } + ledgers: LedgersExtended isLoading: boolean - table: { - getRowModel: () => { - rows: { id: string; original: LedgerEntity }[] - } - } + table: ReactTableType handleDialogOpen: (id: string, name: string) => void refetch: () => void + pageSizeOptions: number[] } type LedgerRowProps = { - ledger: { id: string; original: LedgerEntity } + ledger: { id: string | undefined; original: LedgerEntity } handleCopyToClipboard: (value: string, message: string) => void handleDialogOpen: (id: string, name: string) => void refetch: () => void @@ -65,12 +84,58 @@ const LedgerRow: React.FC = ({ const intl = useIntl() const id = ledger.original.id || '' const displayId = id && id.length > 8 ? `${truncateString(id, 8)}` : id - const status = ledger.original.status - const badgeVariant = status.code === 'ACTIVE' ? 'active' : 'inactive' const metadataCount = Object.entries(ledger.original.metadata || []).length const assetsItems = ledger.original.assets || [] const { handleCreate, sheetProps } = useCreateUpdateSheet() + const renderAssets = () => { + if (assetsItems.length === 1) { + return

{assetsItems[0].code}

+ } + + if (assetsItems.length > 1) { + return ( +
+

+ {intl.formatMessage( + { + id: 'ledgers.assets.count', + defaultMessage: '{count} assets' + }, + { count: assetsItems.length } + )} +

+ + + + + + + + +

+ {assetsItems.map((asset) => asset.code).join(', ')} +

+ +
+
+
+
+ ) + } + + return ( + + ) + } + return ( @@ -108,29 +173,7 @@ const LedgerRow: React.FC = ({ {ledger.original.name} - - {assetsItems.length > 0 ? ( -

- {truncateString( - assetsItems.map((asset) => asset.code).join(', '), - 13 - )} -

- ) : ( - - )} -
+ {renderAssets()} {metadataCount === 0 ? ( @@ -147,50 +190,45 @@ const LedgerRow: React.FC = ({ ) )} - -
- - {capitalizeFirstLetter(status.code)} - -
-
- - - - - - - +
+ + + + + + + + {intl.formatMessage({ + id: `common.edit`, + defaultMessage: 'Edit' + })} + + + + + handleDialogOpen( + ledger.original.id || '', + ledger.original.name || '' + ) + } + > {intl.formatMessage({ - id: `common.edit`, - defaultMessage: 'Edit' + id: `common.delete`, + defaultMessage: 'Delete' })} - - - - handleDialogOpen( - ledger.original.id || '', - ledger.original.name || '' - ) - } - > - {intl.formatMessage({ - id: `common.delete`, - defaultMessage: 'Delete' - })} - - - + + +
@@ -203,116 +241,212 @@ const LedgerRow: React.FC = ({ ) } -export const LedgersDataTable: React.FC = ({ +export const LedgersDataTable: React.FC< + LedgersTableProps & { + currentPageSize: number + currentPage: number + } +> = ({ ledgers, table, handleDialogOpen, - refetch + pageSizeOptions, + refetch, + currentPageSize, + currentPage }) => { const intl = useIntl() const { handleCreate, sheetProps } = useCreateUpdateSheet() const { showInfo } = useCustomToast() + const methods = useForm() + const router = useRouter() + const searchParams = useSearchParams() + const items = ledgers?.items ?? [] + const limit = ledgers?.limit ?? 10 const handleCopyToClipboard = (value: string, message: string) => { navigator.clipboard.writeText(value) showInfo(message) } + const setQueryParam = (limit: number, page: number) => { + const params = new URLSearchParams(searchParams.toString()) + params.set('limit', String(limit)) + params.set('page', String(page)) + router.push(`?${params.toString()}`) + } + + const handlePageSizeChange = (newPageSize: number) => { + setQueryParam(newPageSize, currentPage) + } + + const handleNextPage = () => { + setQueryParam(currentPageSize, currentPage + 1) + } + + const handlePreviousPage = () => { + setQueryParam(currentPageSize, Math.max(currentPage - 1, 1)) + } + return ( - - {isNil(ledgers?.items) || ledgers.items.length === 0 ? ( - - + + + + +
+ )} - - + + + ) } diff --git a/src/app/(routes)/ledgers/ledgers-view.tsx b/src/app/(routes)/ledgers/ledgers-view.tsx index 5e2d090f..eb0bf396 100644 --- a/src/app/(routes)/ledgers/ledgers-view.tsx +++ b/src/app/(routes)/ledgers/ledgers-view.tsx @@ -26,9 +26,19 @@ type LedgersViewProps = { ledgers: PaginationDto | undefined refetch: () => void isLoading: boolean + pageSizeOptions?: number[] + defaultPageSize?: number + defaultPage?: number } -const LedgersView = ({ ledgers, refetch, isLoading }: LedgersViewProps) => { +const LedgersView = ({ + ledgers, + refetch, + isLoading, + pageSizeOptions = [10, 50, 100], + defaultPageSize = 10, + defaultPage = 1 +}: LedgersViewProps) => { const intl = useIntl() const { currentOrganization } = useOrganization() const { showSuccess, showError } = useCustomToast() @@ -108,20 +118,11 @@ const LedgersView = ({ ledgers, refetch, isLoading }: LedgersViewProps) => { defaultMessage: 'What is a Ledger?' })} /> - @@ -153,6 +154,9 @@ const LedgersView = ({ ledgers, refetch, isLoading }: LedgersViewProps) => { isLoading={isLoading} table={table} handleDialogOpen={handleDialogOpen} + pageSizeOptions={pageSizeOptions} + currentPageSize={defaultPageSize} + currentPage={defaultPage} refetch={refetch} /> )} diff --git a/src/app/(routes)/ledgers/page.tsx b/src/app/(routes)/ledgers/page.tsx index 9e3e29df..1c9dc9b0 100644 --- a/src/app/(routes)/ledgers/page.tsx +++ b/src/app/(routes)/ledgers/page.tsx @@ -7,15 +7,21 @@ import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert' import { Button } from '@/components/ui/button' import { useOrganization } from '@/context/organization-provider/organization-provider-client' import { AlertCircle } from 'lucide-react' -import { useRouter } from 'next/navigation' +import { useRouter, useSearchParams } from 'next/navigation' import React from 'react' import { useIntl } from 'react-intl' +const DEFAULT_LIMIT = 10 +const DEFAULT_PAGE = 1 + const Page = () => { - const { currentOrganization, setOrganization } = useOrganization() - const { data: organizations } = useListOrganizations({}) const intl = useIntl() const router = useRouter() + const searchParams = useSearchParams() + const limit = Number(searchParams.get('limit')) || DEFAULT_LIMIT + const page = Number(searchParams.get('page')) || DEFAULT_PAGE + const { currentOrganization, setOrganization } = useOrganization() + const { data: organizations } = useListOrganizations({}) React.useEffect(() => { if (!currentOrganization && organizations?.items?.length! > 0) { @@ -29,7 +35,9 @@ const Page = () => { isLoading } = useListLedgers({ organizationId: currentOrganization?.id!, - enabled: !!currentOrganization + enabled: !!currentOrganization, + limit, + page }) if (!currentOrganization) { @@ -68,7 +76,13 @@ const Page = () => { } return ( - + ) } diff --git a/src/client/ledgers.ts b/src/client/ledgers.ts index d26e4674..21046591 100644 --- a/src/client/ledgers.ts +++ b/src/client/ledgers.ts @@ -48,14 +48,16 @@ const useCreateLedger = ({ const useListLedgers = ({ organizationId, - ...options -}: UseListLedgersProps) => { + enabled = true, + limit = 10, + page = 1 +}: UseListLedgersProps & { limit?: number; page?: number }) => { return useQuery>({ - queryKey: ['ledgers', organizationId], + queryKey: ['ledgers', organizationId, { limit, page }], queryFn: getFetcher( - `/api/organizations/${organizationId}/ledgers/ledgers-assets` + `/api/organizations/${organizationId}/ledgers/ledgers-assets?limit=${limit}&page=${page}` ), - ...options + enabled }) } diff --git a/src/components/entity-box/entity-box-actions.tsx b/src/components/entity-box/entity-box-actions.tsx deleted file mode 100644 index b7183651..00000000 --- a/src/components/entity-box/entity-box-actions.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import React, { ReactNode } from 'react' -import { cn } from '@/lib/utils' - -type EntityBoxActionsProps = { - children: ReactNode - className?: string -} - -export const EntityBoxActions = ({ - children, - className, - ...props -}: EntityBoxActionsProps) => { - return ( -
- {children} -
- ) -} diff --git a/src/components/entity-box/entity-box-content.tsx b/src/components/entity-box/entity-box-content.tsx deleted file mode 100644 index 6ef89488..00000000 --- a/src/components/entity-box/entity-box-content.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import React, { ReactNode } from 'react' -import { cn } from '@/lib/utils' - -type EntityBoxContentProps = { - children: ReactNode - className?: string -} - -export const EntityBoxContent = ({ - children, - className, - ...props -}: EntityBoxContentProps) => { - return ( -
- {children} -
- ) -} diff --git a/src/components/entity-box/entity-box-header.tsx b/src/components/entity-box/entity-box-header.tsx deleted file mode 100644 index 2601dbc0..00000000 --- a/src/components/entity-box/entity-box-header.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React, { ElementType } from 'react' -import { cn } from '@/lib/utils' - -type EntityBoxHeaderProps = { - title: string - subtitle?: string - className?: string -} - -export const EntityBoxHeader = ({ - title, - subtitle, - className, - ...props -}: EntityBoxHeaderProps) => { - return ( -
-

{title}

-

{subtitle}

-
- ) -} diff --git a/src/components/entity-box/entity-box-root.tsx b/src/components/entity-box/entity-box-root.tsx deleted file mode 100644 index 3a2040b6..00000000 --- a/src/components/entity-box/entity-box-root.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import React, { ReactNode } from 'react' -import { cn } from '@/lib/utils' - -type EntityBoxRootProps = { - children: ReactNode - className?: string -} - -export const EntityBoxRoot = ({ - children, - className, - ...props -}: EntityBoxRootProps) => { - return ( -
- {children} -
- ) -} diff --git a/src/components/entity-box/index.tsx b/src/components/entity-box/index.tsx index 81ab62b4..18b2e576 100644 --- a/src/components/entity-box/index.tsx +++ b/src/components/entity-box/index.tsx @@ -1,7 +1,78 @@ -import { EntityBoxActions } from './entity-box-actions' -import { EntityBoxContent } from './entity-box-content' -import { EntityBoxHeader } from './entity-box-header' -import { EntityBoxRoot } from './entity-box-root' +import React from 'react' +import { cn } from '@/lib/utils' + +type EntityBoxRootProps = React.HTMLAttributes + +const EntityBoxRoot = React.forwardRef( + ({ className, children, ...props }, ref) => { + return ( +
+ {children} +
+ ) + } +) + +EntityBoxRoot.displayName = 'EntityBoxRoot' + +interface EntityBoxHeaderProps extends React.HTMLAttributes { + title: string + subtitle?: string +} + +const EntityBoxHeader = React.forwardRef( + ({ title, subtitle, className, ...props }, ref) => { + return ( +
+

{title}

+ {subtitle &&

{subtitle}

} +
+ ) + } +) + +EntityBoxHeader.displayName = 'EntityBoxHeader' + +type EntityBoxContentProps = React.HTMLAttributes + +const EntityBoxContent = React.forwardRef< + HTMLDivElement, + EntityBoxContentProps +>(({ className, children, ...props }, ref) => { + return ( +
+ {children} +
+ ) +}) + +EntityBoxContent.displayName = 'EntityBoxContent' + +type EntityBoxActionsProps = React.HTMLAttributes + +const EntityBoxActions = React.forwardRef< + HTMLDivElement, + EntityBoxActionsProps +>(({ className, children, ...props }, ref) => { + return ( +
+ {children} +
+ ) +}) + +EntityBoxActions.displayName = 'EntityBoxActions' export const EntityBox = { Root: EntityBoxRoot, diff --git a/src/components/ui/input-with-icon/index.tsx b/src/components/ui/input-with-icon/index.tsx index 1880f84a..85ae6dcc 100644 --- a/src/components/ui/input-with-icon/index.tsx +++ b/src/components/ui/input-with-icon/index.tsx @@ -26,7 +26,7 @@ export interface InputWithIconProps const InputWithIcon = React.forwardRef( ({ className, icon, iconPosition, ...props }, ref) => { return ( -
+
{iconPosition !== 'right' && ( Date: Mon, 16 Dec 2024 15:11:44 -0300 Subject: [PATCH 2/2] :recycle: refactor: remove unused onClick and create a separate pagination type. --- src/app/(routes)/ledgers/ledgers-data-table.tsx | 2 +- src/client/ledgers.ts | 3 ++- src/types/pagination-request-type.ts | 4 ++++ 3 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 src/types/pagination-request-type.ts diff --git a/src/app/(routes)/ledgers/ledgers-data-table.tsx b/src/app/(routes)/ledgers/ledgers-data-table.tsx index 4d6bd739..4a616507 100644 --- a/src/app/(routes)/ledgers/ledgers-data-table.tsx +++ b/src/app/(routes)/ledgers/ledgers-data-table.tsx @@ -199,7 +199,7 @@ const LedgerRow: React.FC = ({ className="h-auto w-max p-2" data-testid="actions" > - {}} /> + diff --git a/src/client/ledgers.ts b/src/client/ledgers.ts index 21046591..63fcfa4a 100644 --- a/src/client/ledgers.ts +++ b/src/client/ledgers.ts @@ -6,6 +6,7 @@ import { patchFetcher, postFetcher } from '@/lib/fetcher' +import { PaginationRequest } from '@/types/pagination-request-type' import { useMutation, UseMutationOptions, @@ -51,7 +52,7 @@ const useListLedgers = ({ enabled = true, limit = 10, page = 1 -}: UseListLedgersProps & { limit?: number; page?: number }) => { +}: UseListLedgersProps & PaginationRequest) => { return useQuery>({ queryKey: ['ledgers', organizationId, { limit, page }], queryFn: getFetcher( diff --git a/src/types/pagination-request-type.ts b/src/types/pagination-request-type.ts new file mode 100644 index 00000000..2ac6349a --- /dev/null +++ b/src/types/pagination-request-type.ts @@ -0,0 +1,4 @@ +export type PaginationRequest = { + limit?: number + page?: number +}