From acf6999aa29ba8ae43ad5072d2beb0de06d070b7 Mon Sep 17 00:00:00 2001 From: Caio Alexandre Troti Caetano Date: Wed, 18 Dec 2024 14:25:39 -0300 Subject: [PATCH 01/19] :recycle: refactor: Moved components --- .../(routes)/{ => ledgers/[id]}/transactions/create/stepper.tsx | 2 +- .../transactions/primitives/paper-collapsible.tsx | 0 .../(routes) => components}/transactions/primitives/stepper.tsx | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename src/app/(routes)/{ => ledgers/[id]}/transactions/create/stepper.tsx (96%) rename src/{app/(routes) => components}/transactions/primitives/paper-collapsible.tsx (100%) rename src/{app/(routes) => components}/transactions/primitives/stepper.tsx (100%) diff --git a/src/app/(routes)/transactions/create/stepper.tsx b/src/app/(routes)/ledgers/[id]/transactions/create/stepper.tsx similarity index 96% rename from src/app/(routes)/transactions/create/stepper.tsx rename to src/app/(routes)/ledgers/[id]/transactions/create/stepper.tsx index f0708cba..e04c1cfa 100644 --- a/src/app/(routes)/transactions/create/stepper.tsx +++ b/src/app/(routes)/ledgers/[id]/transactions/create/stepper.tsx @@ -4,7 +4,7 @@ import { StepperItem, StepperItemNumber, StepperItemText -} from '../primitives/stepper' +} from '@/components/transactions/primitives/stepper' export type StepperProps = { step?: number diff --git a/src/app/(routes)/transactions/primitives/paper-collapsible.tsx b/src/components/transactions/primitives/paper-collapsible.tsx similarity index 100% rename from src/app/(routes)/transactions/primitives/paper-collapsible.tsx rename to src/components/transactions/primitives/paper-collapsible.tsx diff --git a/src/app/(routes)/transactions/primitives/stepper.tsx b/src/components/transactions/primitives/stepper.tsx similarity index 100% rename from src/app/(routes)/transactions/primitives/stepper.tsx rename to src/components/transactions/primitives/stepper.tsx From aca9846fc9e1805f05bed432d38eb78c869147aa Mon Sep 17 00:00:00 2001 From: Caio Alexandre Troti Caetano Date: Wed, 18 Dec 2024 14:26:04 -0300 Subject: [PATCH 02/19] :sparkles: feat: Implemented Metadata Accordion --- .../create/metadata-accordion.tsx | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 src/app/(routes)/ledgers/[id]/transactions/create/metadata-accordion.tsx diff --git a/src/app/(routes)/ledgers/[id]/transactions/create/metadata-accordion.tsx b/src/app/(routes)/ledgers/[id]/transactions/create/metadata-accordion.tsx new file mode 100644 index 00000000..2701e1e9 --- /dev/null +++ b/src/app/(routes)/ledgers/[id]/transactions/create/metadata-accordion.tsx @@ -0,0 +1,58 @@ +import { Separator } from '@/components/ui/separator' +import { + PaperCollapsible, + PaperCollapsibleBanner, + PaperCollapsibleContent +} from '@/components/transactions/primitives/paper-collapsible' +import { MetadataField } from '@/components/form' +import { Control } from 'react-hook-form' +import { useIntl } from 'react-intl' +import { Metadata } from '@/types/metadata-type' + +export type MetadataAccordionProps = { + name: string + values: Metadata + control: Control +} + +export const MetadataAccordion = ({ + name, + values, + control +}: MetadataAccordionProps) => { + const intl = useIntl() + + return ( + <> +
+ {intl.formatMessage({ + id: 'transactions.metadata.title', + defaultMessage: 'Transaction Metadata' + })} +
+ + + +

+ {intl.formatMessage( + { + id: 'organizations.organizationForm.metadataRegisterCountText', + defaultMessage: + '{count} added {count, plural, =0 {records} one {record} other {records}}' + }, + { + count: Object.entries(values || 0).length + } + )} +

+
+ + +
+ +
+
+
+ + ) +} From 6ee7078133244d373caf83e12cfec9a31df9ba32 Mon Sep 17 00:00:00 2001 From: Caio Alexandre Troti Caetano Date: Wed, 18 Dec 2024 14:36:33 -0300 Subject: [PATCH 03/19] :sparkles: feat: Implemented Operation Accordion --- .../create/operation-accordion.tsx | 202 ++++++++++++++++++ .../create/operation-empty-accordion.tsx | 18 -- 2 files changed, 202 insertions(+), 18 deletions(-) create mode 100644 src/app/(routes)/ledgers/[id]/transactions/create/operation-accordion.tsx delete mode 100644 src/app/(routes)/transactions/create/operation-empty-accordion.tsx diff --git a/src/app/(routes)/ledgers/[id]/transactions/create/operation-accordion.tsx b/src/app/(routes)/ledgers/[id]/transactions/create/operation-accordion.tsx new file mode 100644 index 00000000..67bb8da0 --- /dev/null +++ b/src/app/(routes)/ledgers/[id]/transactions/create/operation-accordion.tsx @@ -0,0 +1,202 @@ +import { ArrowLeft, ArrowRight, MinusCircle, PlusCircle } from 'lucide-react' +import { + PaperCollapsible, + PaperCollapsibleBanner, + PaperCollapsibleContent +} from '@/components/transactions/primitives/paper-collapsible' +import { Separator } from '@/components/ui/separator' +import { InputField, MetadataField } from '@/components/form' +import { useIntl } from 'react-intl' +import { Control } from 'react-hook-form' +import { Input } from '@/components/ui/input' +import { + FormControl, + FormField, + FormItem, + FormMessage +} from '@/components/ui/form' +import { cn } from '@/lib/utils' +import { + Tooltip, + TooltipTrigger, + TooltipContent, + TooltipProvider +} from '@/components/ui/tooltip' +import { useTransactionForm } from './provider' +import { useState } from 'react' +import { TransactionSourceFormSchema } from './schemas' + +type ValueFieldProps = { + name: string + error?: string + control: Control +} + +const ValueField = ({ name, error, control }: ValueFieldProps) => { + const [open, setOpen] = useState(false) + + const handleOpen = (value: boolean) => { + if (error) { + setOpen(value) + } else { + setOpen(false) + } + } + + return ( + ( + + + + + + + + + {error} + + + + + )} + /> + ) +} + +export type OperationEmptyAccordionProps = { + title: string + description?: string +} + +export const OperationEmptyAccordion = ({ + title, + description +}: OperationEmptyAccordionProps) => { + return ( +
+
+

{title}

+

{description}

+
+
+ ) +} + +export type OperationAccordionProps = { + type?: 'debit' | 'credit' + name: string + asset?: string + values: TransactionSourceFormSchema[0] + valueEditable?: boolean + control: Control +} + +export const OperationAccordion = ({ + type = 'debit', + name, + asset, + values, + valueEditable, + control +}: OperationAccordionProps) => { + const intl = useIntl() + + const { errors } = useTransactionForm() + + return ( + + +
+ {type === 'debit' && } + {type === 'credit' && ( + + )} + +
+

+ {type === 'debit' + ? intl.formatMessage({ + id: 'common.debit', + defaultMessage: 'Debit' + }) + : intl.formatMessage({ + id: 'common.credit', + defaultMessage: 'Credit' + })} +

+

{values.account}

+
+
+
+ {type === 'debit' && } + {type === 'credit' && } + + {valueEditable ? ( + + ) : ( +

+ {values.value} +

+ )} +
+

{asset}

+
+
+
+ + +
+
+ + +
+
+
+ +
+

+ {intl.formatMessage({ + id: 'transactions.operations.metadata', + defaultMessage: 'Operations Metadata' + })} +

+ +
+ + + ) +} diff --git a/src/app/(routes)/transactions/create/operation-empty-accordion.tsx b/src/app/(routes)/transactions/create/operation-empty-accordion.tsx deleted file mode 100644 index 0d66dd1a..00000000 --- a/src/app/(routes)/transactions/create/operation-empty-accordion.tsx +++ /dev/null @@ -1,18 +0,0 @@ -export type OperationEmptyAccordionProps = { - title: string - description?: string -} - -export const OperationEmptyAccordion = ({ - title, - description -}: OperationEmptyAccordionProps) => { - return ( -
-
-

{title}

-

{description}

-
-
- ) -} From 38fc3907b3f87ea29196806a0253761e99a57772 Mon Sep 17 00:00:00 2001 From: Caio Alexandre Troti Caetano Date: Wed, 18 Dec 2024 14:37:03 -0300 Subject: [PATCH 04/19] :sparkles: feat: Implemented Transaction Receipt --- src/app/globals.css | 5 + .../primitives/transaction-receipt.tsx | 190 ++++++++++++++++++ 2 files changed, 195 insertions(+) create mode 100644 src/components/transactions/primitives/transaction-receipt.tsx diff --git a/src/app/globals.css b/src/app/globals.css index 765ba9a8..8921a649 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -82,3 +82,8 @@ @apply h-full overflow-y-auto bg-background text-foreground; } } + +.ticket { + mask: radial-gradient(21px 13px at 50% 102%, #0000 98%, #000) 50% + calc(100% - 16px) / 64px 100% repeat-x; +} diff --git a/src/components/transactions/primitives/transaction-receipt.tsx b/src/components/transactions/primitives/transaction-receipt.tsx new file mode 100644 index 00000000..328416ed --- /dev/null +++ b/src/components/transactions/primitives/transaction-receipt.tsx @@ -0,0 +1,190 @@ +import { cn } from '@/lib/utils' +import { AlignLeft, ArrowRight } from 'lucide-react' +import { forwardRef, HTMLAttributes, ReactNode } from 'react' +import { useIntl } from 'react-intl' + +export type TransactionReceiptProps = HTMLAttributes & { + type?: 'main' | 'ticket' +} + +export const TransactionReceipt = forwardRef< + HTMLDivElement, + TransactionReceiptProps +>(({ className, type = 'main', ...props }, ref) => ( +
+)) +TransactionReceipt.displayName = 'TransactionReceipt' + +export type TransactionReceiptValueProps = + HTMLAttributes & { + asset: string + value: string | number + } + +export const TransactionReceiptValue = forwardRef< + HTMLDivElement, + TransactionReceiptValueProps +>(({ className, asset, value, children, ...props }, ref) => ( +

+ {asset} {value} +

+)) +TransactionReceiptValue.displayName = 'TransactionReceiptValue' + +export const TransactionReceiptDescription = forwardRef< + HTMLDivElement, + HTMLAttributes +>(({ className, children, ...props }, ref) => ( +
+ + {children} +
+)) +TransactionReceiptDescription.displayName = 'TransactionReceiptDescription' + +export const TransactionReceiptAction = forwardRef< + HTMLDivElement, + HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +TransactionReceiptAction.displayName = 'TransactionReceiptAction' + +export type TransactionReceiptSubjectsProps = HTMLAttributes & { + sources: string[] + destinations: string[] +} + +export const TransactionReceiptSubjects = forwardRef< + HTMLDivElement, + TransactionReceiptSubjectsProps +>(({ className, sources, destinations, children, ...props }, ref) => ( +
+
+ {sources.map((source, index) => ( +

{source}

+ ))} +
+ +
+ {destinations.map((source, index) => ( +

{source}

+ ))} +
+
+)) +TransactionReceiptSubjects.displayName = 'TransactionReceiptSubjects' + +export type TransactionReceiptItemProps = HTMLAttributes & { + label: string + value: ReactNode +} + +export const TransactionReceiptItem = forwardRef< + HTMLDivElement, + TransactionReceiptItemProps +>(({ className, label, value, children, ...props }, ref) => ( +
+

{label}

+ {value} +
+)) +TransactionReceiptItem.displayName = 'TransactionReceiptTicket' + +export type TransactionReceiptOperationProps = + HTMLAttributes & { + type: 'debit' | 'credit' + account: string + asset: string + value: string | number + } + +export const TransactionReceiptOperation = forwardRef< + HTMLDivElement, + TransactionReceiptOperationProps +>(({ className, type, account, asset, value, children, ...props }, ref) => { + const intl = useIntl() + + return ( +
+
+

+ {type === 'debit' + ? intl.formatMessage({ + id: 'common.debit', + defaultMessage: 'Debit' + }) + : intl.formatMessage({ + id: 'common.credit', + defaultMessage: 'Credit' + })} +

+
+

{account}

+

+ {type === 'debit' ? '-' : '+'} {asset} {value} +

+
+
+
+ ) +}) +TransactionReceiptOperation.displayName = 'TransactionReceiptOperation' + +export const TransactionReceiptTicket = forwardRef< + HTMLDivElement, + HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +TransactionReceiptTicket.displayName = 'TransactionReceiptTicket' From 98edd0544dcd33348321c226f51de268e85fd638 Mon Sep 17 00:00:00 2001 From: Caio Alexandre Troti Caetano Date: Wed, 18 Dec 2024 15:20:01 -0300 Subject: [PATCH 05/19] :sparkles: feat: Implemented useStepper hook --- src/hooks/use-stepper.test.tsx | 48 ++++++++++++++++++++++++++++++++++ src/hooks/use-stepper.ts | 36 +++++++++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 src/hooks/use-stepper.test.tsx create mode 100644 src/hooks/use-stepper.ts diff --git a/src/hooks/use-stepper.test.tsx b/src/hooks/use-stepper.test.tsx new file mode 100644 index 00000000..7bcf0503 --- /dev/null +++ b/src/hooks/use-stepper.test.tsx @@ -0,0 +1,48 @@ +import { renderHook, act } from '@testing-library/react' +import { useStepper } from './use-stepper' + +describe('useStepper', () => { + it('should initialize with the correct step', () => { + const { result } = renderHook(() => useStepper({})) + expect(result.current.step).toBe(0) + }) + + it('should go to the next step', () => { + const { result } = renderHook(() => useStepper({})) + act(() => { + result.current.handleNext() + }) + expect(result.current.step).toBe(1) + }) + + it('should go to the previous step', () => { + const { result } = renderHook(() => useStepper({ defaultStep: 1 })) + act(() => { + result.current.handlePrevious() + }) + expect(result.current.step).toBe(0) + }) + + it('should not go below step 0', () => { + const { result } = renderHook(() => useStepper({})) + act(() => { + result.current.handlePrevious() + }) + expect(result.current.step).toBe(0) + }) + + it('should not exceed the maximum steps', () => { + const { result } = renderHook(() => useStepper({ maxSteps: 2 })) + expect(result.current.step).toBe(0) + act(() => { + result.current.handleNext() + }) + act(() => { + result.current.handleNext() + }) + act(() => { + result.current.handleNext() + }) + expect(result.current.step).toBe(1) + }) +}) diff --git a/src/hooks/use-stepper.ts b/src/hooks/use-stepper.ts new file mode 100644 index 00000000..9e1648fc --- /dev/null +++ b/src/hooks/use-stepper.ts @@ -0,0 +1,36 @@ +import { useState } from 'react' + +export type UseStepperProps = { + defaultStep?: number + maxSteps?: number +} + +export const useStepper = ({ + defaultStep = 0, + maxSteps = 2 +}: UseStepperProps) => { + const [step, setStep] = useState(defaultStep) + + const handleNext = () => { + if (maxSteps && step >= maxSteps - 1) { + return + } + + setStep((prev) => prev + 1) + } + + const handlePrevious = () => { + if (step <= 0) { + return + } + + setStep((prev) => prev - 1) + } + + return { + step, + setStep, + handleNext, + handlePrevious + } +} From 8fb41429b14eaadf8d16eb0ff56f02b94ea68204 Mon Sep 17 00:00:00 2001 From: Caio Alexandre Troti Caetano Date: Wed, 18 Dec 2024 15:22:10 -0300 Subject: [PATCH 06/19] :sparkles: feat: Implemented fetcher --- src/client/transactions.ts | 22 ++++++++++++++++++++++ src/hooks/use-stepper.ts | 2 ++ 2 files changed, 24 insertions(+) diff --git a/src/client/transactions.ts b/src/client/transactions.ts index e69de29b..9a30621c 100644 --- a/src/client/transactions.ts +++ b/src/client/transactions.ts @@ -0,0 +1,22 @@ +import { postFetcher } from '@/lib/fetcher' +import { useMutation } from '@tanstack/react-query' + +export type UseCreateTransactionProps = { + organizationId: string + ledgerId: string + onSuccess: () => void +} + +export const useCreateTransaction = ({ + organizationId, + ledgerId, + ...options +}: UseCreateTransactionProps) => { + return useMutation({ + mutationKey: ['transactions', 'create'], + mutationFn: postFetcher( + `/api/organizations/${organizationId}/ledgers/${ledgerId}/transactions` + ), + ...options + }) +} diff --git a/src/hooks/use-stepper.ts b/src/hooks/use-stepper.ts index 9e1648fc..11975029 100644 --- a/src/hooks/use-stepper.ts +++ b/src/hooks/use-stepper.ts @@ -12,6 +12,7 @@ export const useStepper = ({ const [step, setStep] = useState(defaultStep) const handleNext = () => { + // If maxSteps is defined and the current step is the last step, do nothing if (maxSteps && step >= maxSteps - 1) { return } @@ -20,6 +21,7 @@ export const useStepper = ({ } const handlePrevious = () => { + // If the current step is the first step, do nothing if (step <= 0) { return } From fd9be2089b65491cd57f69d03a7131101646059f Mon Sep 17 00:00:00 2001 From: Caio Alexandre Troti Caetano Date: Wed, 18 Dec 2024 15:22:36 -0300 Subject: [PATCH 07/19] :sparkles: feat: Implemented Page layout --- .../[id]/transactions/create/layout.tsx | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/app/(routes)/ledgers/[id]/transactions/create/layout.tsx diff --git a/src/app/(routes)/ledgers/[id]/transactions/create/layout.tsx b/src/app/(routes)/ledgers/[id]/transactions/create/layout.tsx new file mode 100644 index 00000000..56e3ed9b --- /dev/null +++ b/src/app/(routes)/ledgers/[id]/transactions/create/layout.tsx @@ -0,0 +1,49 @@ +'use client' + +import { Breadcrumb } from '@/components/breadcrumb' +import { TransactionProvider } from './provider' +import { PageHeader } from '@/components/page-header' +import { useIntl } from 'react-intl' + +export default function RootLayout({ + children +}: { + children: React.ReactNode +}) { + const intl = useIntl() + + return ( + + + + + + + + + + {children} + + ) +} From 60f67faf6e4611dada9606e9b2e63c8a47bdf4e5 Mon Sep 17 00:00:00 2001 From: Caio Alexandre Troti Caetano Date: Wed, 18 Dec 2024 15:22:50 -0300 Subject: [PATCH 08/19] :sparkles: feat: Added zod schemas --- .../[id]/transactions/create/schemas.ts | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/app/(routes)/ledgers/[id]/transactions/create/schemas.ts diff --git a/src/app/(routes)/ledgers/[id]/transactions/create/schemas.ts b/src/app/(routes)/ledgers/[id]/transactions/create/schemas.ts new file mode 100644 index 00000000..89c01664 --- /dev/null +++ b/src/app/(routes)/ledgers/[id]/transactions/create/schemas.ts @@ -0,0 +1,49 @@ +import { z } from 'zod' +import { transaction } from '@/schema/transactions' + +export const transactionSourceFormSchema = z + .array( + z.object({ + account: transaction.source.account, + value: transaction.value, + description: transaction.description.optional(), + chartOfAccounts: transaction.chartOfAccounts.optional(), + metadata: transaction.metadata + }) + ) + .nonempty() + .default([] as any) + +export const transactionFormSchema = z.object({ + description: transaction.description.optional(), + chartOfAccountsGroupName: transaction.chartOfAccounts.optional(), + asset: transaction.asset, + value: transaction.value, + source: transactionSourceFormSchema, + destination: transactionSourceFormSchema, + metadata: transaction.metadata +}) + +export type TransactionSourceFormSchema = z.infer< + typeof transactionSourceFormSchema +> + +export type TransactionFormSchema = z.infer + +export const initialValues = { + description: '', + chartOfAccountsGroupName: '', + value: '', + asset: '', + source: [], + destination: [], + metadata: {} +} + +export const sourceInitialValues = { + account: '', + value: 0, + description: '', + chartOfAccounts: '', + metadata: {} +} From 981c3cb1a5538de2a68aadd86683ea7d8e620d79 Mon Sep 17 00:00:00 2001 From: Caio Alexandre Troti Caetano Date: Wed, 18 Dec 2024 15:35:58 -0300 Subject: [PATCH 09/19] :sparkles: feat: Added basic info paper --- .../create/basic-information-paper.tsx | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 src/app/(routes)/ledgers/[id]/transactions/create/basic-information-paper.tsx diff --git a/src/app/(routes)/ledgers/[id]/transactions/create/basic-information-paper.tsx b/src/app/(routes)/ledgers/[id]/transactions/create/basic-information-paper.tsx new file mode 100644 index 00000000..21d760f9 --- /dev/null +++ b/src/app/(routes)/ledgers/[id]/transactions/create/basic-information-paper.tsx @@ -0,0 +1,100 @@ +import { useListAssets } from '@/client/assets' +import { InputField, SelectField } from '@/components/form' +import { Paper } from '@/components/ui/paper' +import { SelectItem } from '@/components/ui/select' +import { Separator } from '@/components/ui/separator' +import { useOrganization } from '@/context/organization-provider/organization-provider-client' +import { Control } from 'react-hook-form' +import { useIntl } from 'react-intl' +import DolarSign from '/public/svg/dolar-sign.svg' +import Image from 'next/image' +import { useParams } from 'next/navigation' + +export type BasicInformationPaperProps = { + control: Control +} + +export const BasicInformationPaper = ({ + control +}: BasicInformationPaperProps) => { + const intl = useIntl() + const { id } = useParams<{ id: string }>() + const { currentOrganization } = useOrganization() + + const { data: assets } = useListAssets({ + organizationId: currentOrganization.id!, + ledgerId: id + }) + + return ( + +

+ {intl.formatMessage({ + id: 'transactions.create.paper.description', + defaultMessage: + 'Fill in the details of the Transaction you want to create.' + })} +

+ +
+ + +
+ +
+
+ +
+ + {assets?.items?.map((asset) => ( + + {asset.code} + + ))} + +
+ +
+
+
+ ) +} From 73b897d662fc3a0e9de6ee1e7e2a071e277ac022 Mon Sep 17 00:00:00 2001 From: Caio Alexandre Troti Caetano Date: Wed, 18 Dec 2024 15:41:23 -0300 Subject: [PATCH 10/19] :sparkles: feat: Implemented hook to check for value errors --- .../use-transaction-form-errors.test.ts | 97 +++++++++++++++++++ .../create/use-transaction-form-errors.ts | 55 +++++++++++ 2 files changed, 152 insertions(+) create mode 100644 src/app/(routes)/ledgers/[id]/transactions/create/use-transaction-form-errors.test.ts create mode 100644 src/app/(routes)/ledgers/[id]/transactions/create/use-transaction-form-errors.ts diff --git a/src/app/(routes)/ledgers/[id]/transactions/create/use-transaction-form-errors.test.ts b/src/app/(routes)/ledgers/[id]/transactions/create/use-transaction-form-errors.test.ts new file mode 100644 index 00000000..5810c182 --- /dev/null +++ b/src/app/(routes)/ledgers/[id]/transactions/create/use-transaction-form-errors.test.ts @@ -0,0 +1,97 @@ +import { renderHook, act } from '@testing-library/react' +import { useIntl } from 'react-intl' +import { useTransactionFormErrors } from './use-transaction-form-errors' +import { TransactionFormSchema } from './schemas' + +jest.mock('react-intl', () => ({ + useIntl: jest.fn() +})) + +describe('useTransactionFormErrors', () => { + const intlMock = { + formatMessage: jest.fn(({ defaultMessage }) => defaultMessage) + } + + beforeEach(() => { + ;(useIntl as jest.Mock).mockReturnValue(intlMock) + }) + + it('should return no errors initially', () => { + const { result } = renderHook(() => + useTransactionFormErrors({ + value: 0, + source: [], + destination: [] + } as any) + ) + expect(result.current.errors).toEqual({}) + }) + + it('should add debit error if source sum does not match value', () => { + const { result } = renderHook(() => + useTransactionFormErrors({ + value: 100, + source: [{ value: 50 }], + destination: [] + } as any) + ) + + act(() => { + result.current.errors + }) + + expect(result.current.errors.debit).toBe( + 'Total Debits do not match total Credits' + ) + }) + + it('should add credit error if destination sum does not match value', () => { + const { result } = renderHook(() => + useTransactionFormErrors({ + value: '100', + source: [], + destination: [{ value: '50' }] + } as any) + ) + + act(() => { + result.current.errors + }) + + expect(result.current.errors.credit).toBe( + 'Total Debits do not match total Credits' + ) + }) + + it('should remove debit error if source sum matches value', () => { + const { result } = renderHook(() => + useTransactionFormErrors({ + value: '100', + source: [{ value: '100' }], + destination: [] + } as any) + ) + + act(() => { + result.current.errors + }) + + expect(result.current.errors.debit).toBeUndefined() + }) + + it('should remove credit error if destination sum matches value', () => { + const { result } = renderHook(() => + useTransactionFormErrors({ + value: '100', + source: [], + destination: [{ value: '100' }] + } as any) + ) + + act(() => { + result.current.errors + }) + + expect(result.current.errors.credit).toBeUndefined() + }) +}) diff --git a/src/app/(routes)/ledgers/[id]/transactions/create/use-transaction-form-errors.ts b/src/app/(routes)/ledgers/[id]/transactions/create/use-transaction-form-errors.ts new file mode 100644 index 00000000..9b2ddd06 --- /dev/null +++ b/src/app/(routes)/ledgers/[id]/transactions/create/use-transaction-form-errors.ts @@ -0,0 +1,55 @@ +import { useEffect, useState } from 'react' +import { useIntl } from 'react-intl' +import { TransactionFormSchema, TransactionSourceFormSchema } from './schemas' + +export type TransactionFormErrors = Record + +export const useTransactionFormErrors = (values: TransactionFormSchema) => { + const intl = useIntl() + const [errors, setErrors] = useState({}) + const { value, source, destination } = values + + const sum = (source: TransactionSourceFormSchema) => + source.reduce((acc, curr) => acc + Number(curr.value), 0) + + const addError = (key: string, value: string) => { + setErrors((prev) => ({ ...prev, [key]: value })) + } + + const removeError = (key: string) => { + setErrors((prev) => { + const { [key]: _, ...rest } = prev + return rest + }) + } + + useEffect(() => { + const v = Number(value) + + if (v !== sum(source)) { + addError( + 'debit', + intl.formatMessage({ + id: 'transactions.errors.debit', + defaultMessage: 'Total Debits do not match total Credits' + }) + ) + } else { + removeError('debit') + } + + if (v !== sum(destination)) { + addError( + 'credit', + intl.formatMessage({ + id: 'transactions.errors.debit', + defaultMessage: 'Total Debits do not match total Credits' + }) + ) + } else { + removeError('credit') + } + }, [value, sum(source), sum(destination)]) + + return { errors } +} From c859d4e842b43942837915632a38d2c3e2634a42 Mon Sep 17 00:00:00 2001 From: Caio Alexandre Troti Caetano Date: Wed, 18 Dec 2024 15:46:15 -0300 Subject: [PATCH 11/19] :sparkles: feat: Implemented hook to control form --- .../use-transaction-form-control.test.ts | 70 +++++++++++++++++++ .../create/use-transaction-form-control.ts | 22 ++++++ 2 files changed, 92 insertions(+) create mode 100644 src/app/(routes)/ledgers/[id]/transactions/create/use-transaction-form-control.test.ts create mode 100644 src/app/(routes)/ledgers/[id]/transactions/create/use-transaction-form-control.ts diff --git a/src/app/(routes)/ledgers/[id]/transactions/create/use-transaction-form-control.test.ts b/src/app/(routes)/ledgers/[id]/transactions/create/use-transaction-form-control.test.ts new file mode 100644 index 00000000..47f4abc7 --- /dev/null +++ b/src/app/(routes)/ledgers/[id]/transactions/create/use-transaction-form-control.test.ts @@ -0,0 +1,70 @@ +import { renderHook, act } from '@testing-library/react' +import { useTransactionFormControl } from './use-transaction-form-control' +import { TransactionFormSchema } from './schemas' + +describe('useTransactionFormControl', () => { + const initialValues: TransactionFormSchema = { + asset: '', + value: 0, + source: [], + destination: [] + } as any + + it('should initialize with step 0', () => { + const { result } = renderHook(() => + useTransactionFormControl(initialValues) + ) + expect(result.current.step).toBe(0) + }) + + it('should set step to 1 when value, asset, source, and destination are provided', () => { + const values: TransactionFormSchema = { + asset: 'BTC', + value: 100, + source: [{ account: 'source1' }], + destination: [{ account: 'destination1' }] + } as any + + const { result } = renderHook(() => useTransactionFormControl(values)) + + act(() => { + result.current.step + }) + + expect(result.current.step).toBe(1) + }) + + it('should set step to 0 when any of value, asset, source, or destination are missing', () => { + const values: TransactionFormSchema = { + asset: '', + value: 100, + source: [{ account: 'source1' }], + destination: [{ account: 'destination1' }] + } as any + + const { result } = renderHook(() => useTransactionFormControl(values)) + + act(() => { + result.current.step + }) + + expect(result.current.step).toBe(0) + }) + + it('should not change step if step is 2 or more', () => { + const values: TransactionFormSchema = { + asset: 'BTC', + value: 100, + source: [{ account: 'source1' }], + destination: [{ account: 'destination1' }] + } as any + + const { result } = renderHook(() => useTransactionFormControl(values)) + + act(() => { + result.current.setStep(2) + }) + + expect(result.current.step).toBe(2) + }) +}) diff --git a/src/app/(routes)/ledgers/[id]/transactions/create/use-transaction-form-control.ts b/src/app/(routes)/ledgers/[id]/transactions/create/use-transaction-form-control.ts new file mode 100644 index 00000000..dfea8193 --- /dev/null +++ b/src/app/(routes)/ledgers/[id]/transactions/create/use-transaction-form-control.ts @@ -0,0 +1,22 @@ +import { useEffect } from 'react' +import { useStepper } from '@/hooks/use-stepper' +import { TransactionFormSchema } from './schemas' + +export const useTransactionFormControl = (values: TransactionFormSchema) => { + const { step, setStep, ...props } = useStepper({ maxSteps: 3 }) + const { asset, value, source, destination } = values + + useEffect(() => { + if (step < 2) { + // If the user has filled the required fields, move to the next step + if (value && asset && source?.length > 0 && destination?.length > 0) { + setStep(1) + // Reset back to initial step if the user has removed the required fields + } else { + setStep(0) + } + } + }, [value, asset, source, destination]) + + return { step, setStep, ...props } +} From 7966f91ebcf945c66b674d3c25acca991b1a6a61 Mon Sep 17 00:00:00 2001 From: Caio Alexandre Troti Caetano Date: Thu, 19 Dec 2024 11:04:23 -0300 Subject: [PATCH 12/19] :sparkles: feat: Added OperationSourceField --- .../create/operation-source-field.tsx | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 src/app/(routes)/ledgers/[id]/transactions/create/operation-source-field.tsx diff --git a/src/app/(routes)/ledgers/[id]/transactions/create/operation-source-field.tsx b/src/app/(routes)/ledgers/[id]/transactions/create/operation-source-field.tsx new file mode 100644 index 00000000..42d29021 --- /dev/null +++ b/src/app/(routes)/ledgers/[id]/transactions/create/operation-source-field.tsx @@ -0,0 +1,96 @@ +import { InputField } from '@/components/form' +import { Button } from '@/components/ui/button' +import { Paper } from '@/components/ui/paper' +import { zodResolver } from '@hookform/resolvers/zod' +import { Plus, Trash } from 'lucide-react' +import { Control, useFieldArray, useForm } from 'react-hook-form' +import { useIntl } from 'react-intl' +import { z } from 'zod' +import { transaction } from '@/schema/transactions' +import { TransactionFormSchema, TransactionSourceFormSchema } from './schemas' + +const formSchema = z.object({ + account: transaction.source.account +}) + +type FormSchema = z.infer + +const initialValues = { + account: '' +} + +export type OperationSourceFieldProps = { + name: string + label: string + values?: TransactionSourceFormSchema | [] + onSubmit?: (value: string) => void + control: Control +} + +export const OperationSourceField = ({ + name, + label, + values = [], + onSubmit, + control +}: OperationSourceFieldProps) => { + const intl = useIntl() + + const form = useForm({ + resolver: zodResolver(formSchema), + defaultValues: initialValues + }) + + const { remove } = useFieldArray({ + name: name as 'source' | 'destination', + control + }) + + const handleSubmit = (values: FormSchema) => { + onSubmit?.(values.account) + form.reset() + } + + return ( + +
+
+ +
+ +
+ {values?.map((field, index) => ( +
+
+ {field.account} +
+ +
+ ))} +
+ ) +} From 2fb31a01d7736b689435ffcd619f77679afbef2d Mon Sep 17 00:00:00 2001 From: Caio Alexandre Troti Caetano Date: Thu, 19 Dec 2024 11:06:26 -0300 Subject: [PATCH 13/19] :sparkles: feat: Implemented Provider --- .../[id]/transactions/create/provider.tsx | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 src/app/(routes)/ledgers/[id]/transactions/create/provider.tsx diff --git a/src/app/(routes)/ledgers/[id]/transactions/create/provider.tsx b/src/app/(routes)/ledgers/[id]/transactions/create/provider.tsx new file mode 100644 index 00000000..7b39dd74 --- /dev/null +++ b/src/app/(routes)/ledgers/[id]/transactions/create/provider.tsx @@ -0,0 +1,130 @@ +import { zodResolver } from '@hookform/resolvers/zod' +import { useParams, useRouter } from 'next/navigation' +import { useEffect } from 'react' +import { createContext, PropsWithChildren, useContext } from 'react' +import { useFieldArray, UseFieldArrayReturn, useForm } from 'react-hook-form' +import { useTransactionFormControl } from './use-transaction-form-control' +import { + initialValues, + sourceInitialValues, + transactionFormSchema, + TransactionFormSchema +} from './schemas' +import { + TransactionFormErrors, + useTransactionFormErrors +} from './use-transaction-form-errors' + +type TransactionFormProviderContext = { + form: ReturnType> + errors: TransactionFormErrors + currentStep: number + multipleSources?: boolean + values: TransactionFormSchema + addSource: (account: string) => void + addDestination: (account: string) => void + handleReview: () => void + handleBack: () => void +} + +const TransactionFormProvider = createContext( + {} as never +) + +export const useTransactionForm = () => { + return useContext(TransactionFormProvider) +} + +export type TransactionProviderProps = PropsWithChildren & { + values?: TransactionFormSchema +} + +export const TransactionProvider = ({ + values, + children +}: TransactionProviderProps) => { + const { id } = useParams<{ id: string }>() + const router = useRouter() + const form = useForm({ + resolver: zodResolver(transactionFormSchema), + defaultValues: { ...initialValues, ...values } as TransactionFormSchema + }) + + const formValues = form.watch() + + const { step, handleNext, handlePrevious } = + useTransactionFormControl(formValues) + const { errors } = useTransactionFormErrors(formValues) + + const originFieldArray = useFieldArray({ + name: 'source', + control: form.control + }) + + const destinationFieldArray = useFieldArray({ + name: 'destination', + control: form.control + }) + + // Flag to represent if the transaction has multiple sources or destinations + const multipleSources = + originFieldArray.fields.length > 1 || + destinationFieldArray.fields.length > 1 + + // Add source or destination to the transaction + // The first entity uses the same value as the transaction + // Latter ones will start at 0 + const addSource = (fieldArray: UseFieldArrayReturn, account: string) => { + if (fieldArray.fields.length === 0) { + fieldArray.append({ + ...sourceInitialValues, + account, + value: formValues.value + }) + } else { + fieldArray.append({ + ...sourceInitialValues, + account + }) + } + } + + const handleReview = () => { + router.push(`/ledgers/${id}/transactions/create/review`) + handleNext() + } + + // In case the user adds more than 1 source or destination, + // And then removes to stay with only 1, we need to restore the original + // transaction value to the source or destination + useEffect(() => { + if (formValues.source.length === 1) { + form.setValue('source.0.value', formValues.value) + } + }, [formValues.source.length]) + + useEffect(() => { + if (formValues.destination.length === 1) { + form.setValue('destination.0.value', formValues.value) + } + }, [formValues.destination.length]) + + return ( + addSource(originFieldArray, account), + addDestination: (account: string) => + addSource(destinationFieldArray, account), + handleReview, + handleBack: handlePrevious + }} + > + {children} + + ) +} From ad766b9fdd87b3ad0e7cf09b4fa54617f2a19140 Mon Sep 17 00:00:00 2001 From: Caio Alexandre Troti Caetano Date: Thu, 19 Dec 2024 11:06:35 -0300 Subject: [PATCH 14/19] :sparkles: feat: Main pages --- .../ledgers/[id]/transactions/create/page.tsx | 205 ++++++++++++++++ .../[id]/transactions/create/review/page.tsx | 229 ++++++++++++++++++ src/app/(routes)/transactions/create/page.tsx | 7 - 3 files changed, 434 insertions(+), 7 deletions(-) create mode 100644 src/app/(routes)/ledgers/[id]/transactions/create/page.tsx create mode 100644 src/app/(routes)/ledgers/[id]/transactions/create/review/page.tsx delete mode 100644 src/app/(routes)/transactions/create/page.tsx diff --git a/src/app/(routes)/ledgers/[id]/transactions/create/page.tsx b/src/app/(routes)/ledgers/[id]/transactions/create/page.tsx new file mode 100644 index 00000000..04faa5d4 --- /dev/null +++ b/src/app/(routes)/ledgers/[id]/transactions/create/page.tsx @@ -0,0 +1,205 @@ +'use client' + +import { Button } from '@/components/ui/button' +import { Form } from '@/components/ui/form' +import { LoadingButton } from '@/components/ui/loading-button' +import { ArrowRight, Info } from 'lucide-react' +import { useIntl } from 'react-intl' +import { Stepper } from './stepper' +import { PageFooter, PageFooterSection } from '@/components/page-footer' +import Image from 'next/image' +import { + OperationAccordion, + OperationEmptyAccordion +} from './operation-accordion' +import { OperationSourceField } from './operation-source-field' +import { useTransactionForm } from './provider' +import { StepperContent } from '@/components/transactions/primitives/stepper' +import { MetadataAccordion } from './metadata-accordion' +import ArrowRightCircle from '/public/svg/arrow-right-circle.svg' +import { BasicInformationPaper } from './basic-information-paper' +import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert' +import { useRouter } from 'next/navigation' +import { useConfirmDialog } from '@/components/confirmation-dialog/use-confirm-dialog' +import ConfirmationDialog from '@/components/confirmation-dialog' + +export default function CreateTransactionPage() { + const intl = useIntl() + const router = useRouter() + + const { + form, + currentStep, + multipleSources, + values, + addSource, + addDestination, + handleReview + } = useTransactionForm() + + const { handleDialogOpen, dialogProps } = useConfirmDialog({ + onConfirm: () => router.push('/transactions') + }) + console.log(form.getValues(), form.formState.errors) + return ( + <> + + +
+
+
+ + +
+ + + +
+ + + + + + + + +
+ {intl.formatMessage({ + id: 'common.operations', + defaultMessage: 'Operations' + })} +
+ + {multipleSources && ( + + + + {intl.formatMessage({ + id: 'transactions.operations.alert.title', + defaultMessage: 'Multiple origins and destinations' + })} + + + {intl.formatMessage({ + id: 'transactions.operations.alert.description', + defaultMessage: + 'Fill in the value fields to adjust the amount transacted. Remember: the total Credits must equal the total Debits.' + })} + + + )} + +
+ {values.source?.map((source, index) => ( + 1} + control={form.control} + /> + ))} + {values.destination?.map((destination, index) => ( + 1} + control={form.control} + /> + ))} +
+ + +
+
+ +
+
+ +
+
+
+ + 0}> + + + + + } + iconPlacement="end" + onClick={form.handleSubmit(handleReview)} + > + {intl.formatMessage({ + id: 'transactions.create.review.button', + defaultMessage: 'Go to Review' + })} + + + +
+ + ) +} diff --git a/src/app/(routes)/ledgers/[id]/transactions/create/review/page.tsx b/src/app/(routes)/ledgers/[id]/transactions/create/review/page.tsx new file mode 100644 index 00000000..1e33d7c8 --- /dev/null +++ b/src/app/(routes)/ledgers/[id]/transactions/create/review/page.tsx @@ -0,0 +1,229 @@ +'use client' + +import { SendHorizonal } from 'lucide-react' +import { useTransactionForm } from '../provider' +import { Stepper } from '../stepper' +import { Separator } from '@/components/ui/separator' +import { PageFooter, PageFooterSection } from '@/components/page-footer' +import { Button } from '@/components/ui/button' +import { LoadingButton } from '@/components/ui/loading-button' +import { useIntl } from 'react-intl' +import { useConfirmDialog } from '@/components/confirmation-dialog/use-confirm-dialog' +import ConfirmationDialog from '@/components/confirmation-dialog' +import { useParams, useRouter } from 'next/navigation' +import { + TransactionReceipt, + TransactionReceiptDescription, + TransactionReceiptItem, + TransactionReceiptOperation, + TransactionReceiptSubjects, + TransactionReceiptTicket, + TransactionReceiptValue +} from '@/components/transactions/primitives/transaction-receipt' +import ArrowRightLeftCircle from '/public/svg/arrow-right-left-circle.svg' +import Image from 'next/image' +import { isNil } from 'lodash' +import { useCreateTransaction } from '@/client/transactions' +import { useOrganization } from '@/context/organization-provider/organization-provider-client' + +export default function CreateTransactionReviewPage() { + const intl = useIntl() + const router = useRouter() + + const { id } = useParams<{ id: string }>() + + const { currentOrganization } = useOrganization() + + const { mutate: createTransaction, isPending: createLoading } = + useCreateTransaction({ + organizationId: currentOrganization.id!, + ledgerId: id, + onSuccess: () => router.push('/transactions') + }) + + const { values, currentStep, handleBack } = useTransactionForm() + + const { handleDialogOpen: handleCancelOpen, dialogProps: cancelDialogProps } = + useConfirmDialog({ + onConfirm: () => router.push('/transactions') + }) + + const { handleDialogOpen: handleSubmitOpen, dialogProps: submitDialogProps } = + useConfirmDialog({ + onConfirm: () => createTransaction(values) + }) + + return ( + <> + + + + +
+
+ +
+ +
+ + + +

Manual

+ source.account)} + destinations={values.destination?.map((source) => source.account)} + /> + {values.description && ( + + {values.description} + + )} +
+ + + + {values.source?.map((source, index) => ( +

+ {source.account} +

+ ))} +
+ } + /> + + {values.destination?.map((destination, index) => ( +

+ {destination.account} +

+ ))} +
+ } + /> + + + {values.source?.map((source, index) => ( + + ))} + {values.destination?.map((destination, index) => ( + + ))} + + + + + + +
+
+ + + + + + + } + iconPlacement="end" + loading={createLoading} + onClick={() => handleSubmitOpen('')} + > + {intl.formatMessage({ + id: 'transactions.create.button', + defaultMessage: 'Create Transaction' + })} + + + + + ) +} diff --git a/src/app/(routes)/transactions/create/page.tsx b/src/app/(routes)/transactions/create/page.tsx deleted file mode 100644 index 7bed5a89..00000000 --- a/src/app/(routes)/transactions/create/page.tsx +++ /dev/null @@ -1,7 +0,0 @@ -export default function CreateTransactionPage() { - return ( -
-

Create Transactions

-
- ) -} From dc5c714e886fde899213550ed0e80dffd98bc5b0 Mon Sep 17 00:00:00 2001 From: Caio Alexandre Troti Caetano Date: Thu, 19 Dec 2024 11:33:18 -0300 Subject: [PATCH 15/19] :sparkles: feat: Added translation keys --- locales/extracted/en.json | 40 ++++++++++++++++++- locales/extracted/pt.json | 40 ++++++++++++++++++- .../create/operation-accordion.tsx | 4 +- .../ledgers/[id]/transactions/create/page.tsx | 2 +- 4 files changed, 81 insertions(+), 5 deletions(-) diff --git a/locales/extracted/en.json b/locales/extracted/en.json index 7fb3fa5f..0f060df6 100644 --- a/locales/extracted/en.json +++ b/locales/extracted/en.json @@ -29,6 +29,8 @@ "common.confirmDeletion": "Confirm Deletion", "common.copyMessage": "Copied to clipboard!", "common.create": "Create", + "common.credit": "Credit", + "common.debit": "Debit", "common.delete": "Delete", "common.edit": "Edit", "common.expand": "Expand", @@ -40,6 +42,9 @@ "common.metadata": "Metadata", "common.name": "Name", "common.noOptions": "No options found.", + "common.none": "None", + "common.operations": "Operations", + "common.optional": "Optional", "common.portfolio": "Portfolio", "common.records": "records", "common.remove": "Remove", @@ -52,11 +57,13 @@ "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", "common.type": "Type", "common.typePlaceholder": "Type...", + "common.value": "Value", "entity.account.currency": "Assets", "entity.account.name": "Account Name", "entity.address": "Address", @@ -87,6 +94,8 @@ "entity.portfolio.entityId": "Entity Id", "entity.portfolio.name": "Portfolio Name", "entity.product.name": "Product Name", + "entity.transaction.asset": "Asset", + "entity.transaction.value": "Value", "entity.user.email": "E-mail", "entity.user.name": "Name", "error.midaz.actionNotPermitted": "Error Midaz action not permitted", @@ -115,12 +124,14 @@ "errors.too_big.date.exact": "Date must be exactly {maximum}", "errors.too_big.date.inclusive": "Date must be before or equal to {maximum}", "errors.too_big.date.not_inclusive": "Date must be before {maximum}", + "errors.too_big.number.inclusive": "Field must be less than or equal to {maximum}", "errors.too_big.string.exact": "Field must contain exactly {maximum} {maximum, plural, =0 {characters} one {character} other {characters}}", "errors.too_big.string.inclusive": "Field must contain at most {maximum} {maximum, plural, =0 {characters} one {character} other {characters}}", "errors.too_big.string.not_inclusive": "Field must contain under {maximum} {maximum, plural, =0 {characters} one {character} other {characters}}", "errors.too_small.date.exact": "Date must be exactly {minimum}", "errors.too_small.date.inclusive": "Date must be after or equal to {minimum}", "errors.too_small.date.not_inclusive": "Date must be after {minimum}", + "errors.too_small.number.not_inclusive": "Field must be greater than {minimum}", "errors.too_small.string.exact": "Field must contain exactly {minimum} {minimum, plural, =0 {characters} one {character} other {characters}}", "errors.too_small.string.inclusive": "Field must contain at least {minimum} {minimum, plural, =0 {characters} one {character} other {characters}}", "errors.too_small.string.not_inclusive": "Field must contain over {minimum} {minimum, plural, =0 {characters} one {character} other {characters}}", @@ -287,5 +298,32 @@ "signIn.placeholderPassword": "******", "signIn.titleLogin": "Welcome back!", "signIn.toast.error": "Invalid credentials.", - "tooltip.passwordInfo": "Contact the system administrator" + "tooltip.passwordInfo": "Contact the system administrator", + "transaction.create.cancel.description": "If you cancel this transaction, all filled data will be lost and cannot be recovered.", + "transaction.create.cancel.title": "Do you wish to cancel this transaction?", + "transaction.create.submit.description": "Your transaction will be executed according to the information you entered.", + "transaction.create.submit.title": "Create Transaction", + "transactions.create.button": "Create Transaction", + "transactions.create.field.chartOfAccounts": "Chart of accounts", + "transactions.create.field.chartOfAccountsGroupName": "Accounting route group", + "transactions.create.field.origin.placeholder": "Type ID or alias", + "transactions.create.metadata.accordion.description": "Fill in Value, Source and Destination to edit the Metadata.", + "transactions.create.operations.accordion.description": "Fill in Value, Source and Destination to edit the Operations.", + "transactions.create.paper.description": "Fill in the details of the Transaction you want to create.", + "transactions.create.review.button": "Go to Review", + "transactions.create.stepper.first": "Transaction Data", + "transactions.create.stepper.second": "Operations and Metadata", + "transactions.create.stepper.third": "Review", + "transactions.create.stepper.third.description": "Check the values ​​and parameters entered and confirm to create the transaction.", + "transactions.create.title": "New Transaction", + "transactions.destination": "Destination", + "transactions.errors.debit": "Total Debits do not match total Credits", + "transactions.field.description": "Transaction description", + "transactions.field.operation.description": "Operation description", + "transactions.metadata.title": "Transaction Metadata", + "transactions.operations.alert.description": "Fill in the value fields to adjust the amount transacted. Remember: the total Credits must equal the total Debits.", + "transactions.operations.alert.title": "Multiple origins and destinations", + "transactions.operations.metadata": "Operations Metadata", + "transactions.source": "Source", + "transactions.tab.create": "New Transaction" } \ No newline at end of file diff --git a/locales/extracted/pt.json b/locales/extracted/pt.json index b510a1de..76308979 100644 --- a/locales/extracted/pt.json +++ b/locales/extracted/pt.json @@ -287,5 +287,43 @@ "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.credit": "Crédito", + "common.debit": "Débito", + "common.none": "Nenhum", + "common.operations": "Operações", + "common.optional": "Opcional", + "common.support": "Suporte", + "common.value": "Valor", + "entity.transaction.asset": "Ativo", + "entity.transaction.value": "Valor", + "errors.too_big.number.inclusive": "Campor deve ser menor ou igual a {maximum}", + "errors.too_small.number.not_inclusive": "Campo deve ser maior que {minimum}", + "transaction.create.cancel.description": "Se você cancelar esta transação, os datos preenchidos serão perdidos e não poderão ser recuperados.", + "transaction.create.cancel.title": "Deseja cancelar esta transação?", + "transaction.create.submit.description": "Sua transação será executada de acordo com as informações inseridas.", + "transaction.create.submit.title": "Criar Transação", + "transactions.create.button": "Criar Transação", + "transactions.create.field.chartOfAccounts": "Rota contábil", + "transactions.create.field.chartOfAccountsGroupName": "Grupo de rota contábil", + "transactions.create.field.origin.placeholder": "Digite ID ou alias", + "transactions.create.metadata.accordion.description": "Preencha Valor, Origem e Destino para editar os Metadados.", + "transactions.create.operations.accordion.description": "Preencha Valor, Origem e Destino para editar as Operações.", + "transactions.create.paper.description": "Preencha os dados da Transação que você deseja criar.", + "transactions.create.review.button": "Continuar para Revisão", + "transactions.create.stepper.first": "Dados da Transação", + "transactions.create.stepper.second": "Operações e Metadados", + "transactions.create.stepper.third": "Revisão", + "transactions.create.stepper.third.description": "Confira os valores e parâmetros inseridos e confirme para criar a transação.", + "transactions.create.title": "Nova Transação", + "transactions.destination": "Destino", + "transactions.errors.debit": "Total dos Débitos não bate com o total dos Créditos", + "transactions.field.description": "Descrição da Transação", + "transactions.metadata.title": "Metadados da Transação", + "transactions.operations.alert.description": "Preencha os campos de valor para ajustar o montante transacionado. Lembre-se: o total de Créditos deve ser igual ao total de Débitos.", + "transactions.operations.alert.title": "Múltiplas origens e destinos", + "transactions.operations.metadata": "Metadados da Operação", + "transactions.source": "Origem", + "transactions.tab.create": "Nova Transação", + "transactions.field.operation.description": "Descrição da Operação" } diff --git a/src/app/(routes)/ledgers/[id]/transactions/create/operation-accordion.tsx b/src/app/(routes)/ledgers/[id]/transactions/create/operation-accordion.tsx index 67bb8da0..a2057c32 100644 --- a/src/app/(routes)/ledgers/[id]/transactions/create/operation-accordion.tsx +++ b/src/app/(routes)/ledgers/[id]/transactions/create/operation-accordion.tsx @@ -162,8 +162,8 @@ export const OperationAccordion = ({ Date: Thu, 19 Dec 2024 12:02:19 -0300 Subject: [PATCH 16/19] :recycle: refactor: Renamed file --- src/app/(routes)/ledgers/[id]/transactions/create/layout.tsx | 2 +- .../ledgers/[id]/transactions/create/operation-accordion.tsx | 2 +- src/app/(routes)/ledgers/[id]/transactions/create/page.tsx | 2 +- .../(routes)/ledgers/[id]/transactions/create/review/page.tsx | 2 +- .../create/{provider.tsx => transaction-form-provider.tsx} | 0 5 files changed, 4 insertions(+), 4 deletions(-) rename src/app/(routes)/ledgers/[id]/transactions/create/{provider.tsx => transaction-form-provider.tsx} (100%) diff --git a/src/app/(routes)/ledgers/[id]/transactions/create/layout.tsx b/src/app/(routes)/ledgers/[id]/transactions/create/layout.tsx index 56e3ed9b..ceedc6db 100644 --- a/src/app/(routes)/ledgers/[id]/transactions/create/layout.tsx +++ b/src/app/(routes)/ledgers/[id]/transactions/create/layout.tsx @@ -1,7 +1,7 @@ 'use client' import { Breadcrumb } from '@/components/breadcrumb' -import { TransactionProvider } from './provider' +import { TransactionProvider } from './transaction-form-provider' import { PageHeader } from '@/components/page-header' import { useIntl } from 'react-intl' diff --git a/src/app/(routes)/ledgers/[id]/transactions/create/operation-accordion.tsx b/src/app/(routes)/ledgers/[id]/transactions/create/operation-accordion.tsx index a2057c32..ec079952 100644 --- a/src/app/(routes)/ledgers/[id]/transactions/create/operation-accordion.tsx +++ b/src/app/(routes)/ledgers/[id]/transactions/create/operation-accordion.tsx @@ -22,7 +22,7 @@ import { TooltipContent, TooltipProvider } from '@/components/ui/tooltip' -import { useTransactionForm } from './provider' +import { useTransactionForm } from './transaction-form-provider' import { useState } from 'react' import { TransactionSourceFormSchema } from './schemas' diff --git a/src/app/(routes)/ledgers/[id]/transactions/create/page.tsx b/src/app/(routes)/ledgers/[id]/transactions/create/page.tsx index a5993c30..b3fe20b9 100644 --- a/src/app/(routes)/ledgers/[id]/transactions/create/page.tsx +++ b/src/app/(routes)/ledgers/[id]/transactions/create/page.tsx @@ -13,7 +13,7 @@ import { OperationEmptyAccordion } from './operation-accordion' import { OperationSourceField } from './operation-source-field' -import { useTransactionForm } from './provider' +import { useTransactionForm } from './transaction-form-provider' import { StepperContent } from '@/components/transactions/primitives/stepper' import { MetadataAccordion } from './metadata-accordion' import ArrowRightCircle from '/public/svg/arrow-right-circle.svg' diff --git a/src/app/(routes)/ledgers/[id]/transactions/create/review/page.tsx b/src/app/(routes)/ledgers/[id]/transactions/create/review/page.tsx index 1e33d7c8..d6894e9b 100644 --- a/src/app/(routes)/ledgers/[id]/transactions/create/review/page.tsx +++ b/src/app/(routes)/ledgers/[id]/transactions/create/review/page.tsx @@ -1,7 +1,7 @@ 'use client' import { SendHorizonal } from 'lucide-react' -import { useTransactionForm } from '../provider' +import { useTransactionForm } from '../transaction-form-provider' import { Stepper } from '../stepper' import { Separator } from '@/components/ui/separator' import { PageFooter, PageFooterSection } from '@/components/page-footer' diff --git a/src/app/(routes)/ledgers/[id]/transactions/create/provider.tsx b/src/app/(routes)/ledgers/[id]/transactions/create/transaction-form-provider.tsx similarity index 100% rename from src/app/(routes)/ledgers/[id]/transactions/create/provider.tsx rename to src/app/(routes)/ledgers/[id]/transactions/create/transaction-form-provider.tsx From fbc8ab35e7ba16ee06b7a1fd4d495e2e34e786a3 Mon Sep 17 00:00:00 2001 From: Caio Alexandre Troti Caetano Date: Thu, 19 Dec 2024 12:04:41 -0300 Subject: [PATCH 17/19] :sparkles: feat: Small fixes --- locales/extracted/en.json | 7 +++---- locales/extracted/pt.json | 4 +--- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/locales/extracted/en.json b/locales/extracted/en.json index 30dd8e67..f70f8f6f 100644 --- a/locales/extracted/en.json +++ b/locales/extracted/en.json @@ -298,6 +298,8 @@ "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", "transaction.create.cancel.description": "If you cancel this transaction, all filled data will be lost and cannot be recovered.", "transaction.create.cancel.title": "Do you wish to cancel this transaction?", @@ -325,8 +327,5 @@ "transactions.operations.alert.title": "Multiple origins and destinations", "transactions.operations.metadata": "Operations Metadata", "transactions.source": "Source", - "transactions.tab.create": "New Transaction", - "table.pagination.next": "Next", - "table.pagination.previous": "Previous", - "tooltip.passwordInfo": "Contact the system administrator" + "transactions.tab.create": "New Transaction" } \ No newline at end of file diff --git a/locales/extracted/pt.json b/locales/extracted/pt.json index 18738692..544933ea 100644 --- a/locales/extracted/pt.json +++ b/locales/extracted/pt.json @@ -324,10 +324,8 @@ "transactions.source": "Origem", "transactions.tab.create": "Nova Transação", "transactions.field.operation.description": "Descrição da Operação", - "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" -} +} \ No newline at end of file From 686b4c928d262a664600d6618aad64b991080ea1 Mon Sep 17 00:00:00 2001 From: Caio Alexandre Troti Caetano Date: Fri, 20 Dec 2024 12:54:19 -0300 Subject: [PATCH 18/19] :boom: fix: Removed console.log --- src/app/(routes)/ledgers/[id]/transactions/create/page.tsx | 2 +- src/client/transactions.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/(routes)/ledgers/[id]/transactions/create/page.tsx b/src/app/(routes)/ledgers/[id]/transactions/create/page.tsx index b3fe20b9..eb008ead 100644 --- a/src/app/(routes)/ledgers/[id]/transactions/create/page.tsx +++ b/src/app/(routes)/ledgers/[id]/transactions/create/page.tsx @@ -40,7 +40,7 @@ export default function CreateTransactionPage() { const { handleDialogOpen, dialogProps } = useConfirmDialog({ onConfirm: () => router.push('/transactions') }) - console.log(form.getValues(), form.formState.errors) + return ( <> Date: Fri, 20 Dec 2024 17:47:29 -0300 Subject: [PATCH 19/19] :boom: fix: Small fix on value update --- .../[id]/transactions/create/transaction-form-provider.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/(routes)/ledgers/[id]/transactions/create/transaction-form-provider.tsx b/src/app/(routes)/ledgers/[id]/transactions/create/transaction-form-provider.tsx index 7b39dd74..7c33e8df 100644 --- a/src/app/(routes)/ledgers/[id]/transactions/create/transaction-form-provider.tsx +++ b/src/app/(routes)/ledgers/[id]/transactions/create/transaction-form-provider.tsx @@ -101,13 +101,13 @@ export const TransactionProvider = ({ if (formValues.source.length === 1) { form.setValue('source.0.value', formValues.value) } - }, [formValues.source.length]) + }, [formValues.value, formValues.source.length]) useEffect(() => { if (formValues.destination.length === 1) { form.setValue('destination.0.value', formValues.value) } - }, [formValues.destination.length]) + }, [formValues.value, formValues.destination.length]) return (