diff --git a/packages/server/src/modules/Customers/Customers.controller.ts b/packages/server/src/modules/Customers/Customers.controller.ts index 577919258..db58df56c 100644 --- a/packages/server/src/modules/Customers/Customers.controller.ts +++ b/packages/server/src/modules/Customers/Customers.controller.ts @@ -21,6 +21,7 @@ import { import { CreateCustomerDto } from './dtos/CreateCustomer.dto'; import { EditCustomerDto } from './dtos/EditCustomer.dto'; import { CustomerResponseDto } from './dtos/CustomerResponse.dto'; +import { CustomersListResponseDto } from './dtos/CustomersListResponse.dto'; import { GetCustomersQueryDto } from './dtos/GetCustomersQuery.dto'; import { BulkDeleteCustomersDto, @@ -36,6 +37,7 @@ import { CustomerAction } from './types/Customers.types'; @Controller('customers') @ApiTags('Customers') @ApiExtraModels(CustomerResponseDto) +@ApiExtraModels(CustomersListResponseDto) @ApiExtraModels(ValidateBulkDeleteCustomersResponseDto) @ApiCommonHeaders() @UseGuards(AuthorizationGuard, PermissionGuard) @@ -60,10 +62,7 @@ export class CustomersController { @ApiResponse({ status: 200, description: 'The customers have been successfully retrieved.', - schema: { - type: 'array', - items: { $ref: getSchemaPath(CustomerResponseDto) }, - }, + schema: { $ref: getSchemaPath(CustomersListResponseDto) }, }) getCustomers(@Query() filterDTO: GetCustomersQueryDto) { return this.customersApplication.getCustomers(filterDTO); diff --git a/packages/server/src/modules/Customers/dtos/CustomersListResponse.dto.ts b/packages/server/src/modules/Customers/dtos/CustomersListResponse.dto.ts new file mode 100644 index 000000000..cf446f871 --- /dev/null +++ b/packages/server/src/modules/Customers/dtos/CustomersListResponse.dto.ts @@ -0,0 +1,21 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { CustomerResponseDto } from './CustomerResponse.dto'; + +class CustomersPaginationDto { + @ApiProperty({ example: 1 }) + page: number; + + @ApiProperty({ example: 12 }) + pageSize: number; + + @ApiProperty({ example: 42 }) + total: number; +} + +export class CustomersListResponseDto { + @ApiProperty({ type: [CustomerResponseDto] }) + data: CustomerResponseDto[]; + + @ApiProperty({ type: CustomersPaginationDto }) + pagination: CustomersPaginationDto; +} diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimateForm/EstimateFormProvider.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimateForm/EstimateFormProvider.tsx index e06523f26..e5d4c65c2 100644 --- a/packages/webapp/src/containers/Sales/Estimates/EstimateForm/EstimateFormProvider.tsx +++ b/packages/webapp/src/containers/Sales/Estimates/EstimateForm/EstimateFormProvider.tsx @@ -1,4 +1,5 @@ import React, { createContext, useContext } from 'react'; +import type { Item, Customer } from '@bigcapital/sdk-ts'; import { useEstimate, @@ -30,11 +31,11 @@ type EstimateFormSubmitPayload = Record; interface EstimateFormContextValue { estimateId?: number; estimate: UseEstimateResult['data']; - items: any; - customers: any; + items: Item[]; + customers: Customer[]; branches: UseBranchesResult['data']; warehouses: UseWarehousesResult['data']; - projects: any; + projects: unknown[]; isNewMode: boolean; isItemsFetching: boolean; @@ -163,11 +164,11 @@ function EstimateFormProvider({ const provider: EstimateFormContextValue = { estimateId, estimate, - items: (itemsData as any)?.items, - customers: (customersData as any)?.customers, + items: itemsData?.data ?? [], + customers: customersData?.data ?? [], branches, warehouses, - projects: (projectsData as any)?.projects, + projects: (projectsData as { data?: { projects?: unknown[] } })?.data?.projects ?? [], isNewMode, isItemsFetching, diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormHeader.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormHeader.tsx index 2295241f4..f2c7e8cab 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormHeader.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormHeader.tsx @@ -1,4 +1,3 @@ -// @ts-nocheck import React from 'react'; import intl from 'react-intl-universal'; import { Group, PageFormBigNumber } from '@/components'; diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormProvider.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormProvider.tsx index 1f3d7d494..cf2db12ff 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormProvider.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormProvider.tsx @@ -1,10 +1,21 @@ -// @ts-nocheck import React, { createContext, useState } from 'react'; +import type { + SaleInvoice, + CreateSaleInvoiceBody, + EditSaleInvoiceBody, + SaleInvoiceStateResponse, + Item, + Customer, + Warehouse, + Branch, + TaxRate, + PdfTemplateResponse, + GetPaymentServicesResponse, +} from '@bigcapital/sdk-ts'; import { isEmpty, pick } from 'lodash'; import { useLocation } from 'react-router-dom'; import { Features } from '@/constants'; import { useFeatureCan } from '@/hooks/state'; -import { DashboardInsider } from '@/components/Dashboard'; import { transformToEditForm, ITEMS_FILTER_ROLES_QUERY } from './utils'; import { useInvoice, @@ -17,28 +28,77 @@ import { useSettingsInvoices, useEstimate, useGetSaleInvoiceState, - GetSaleInvoiceStateResponse, } from '@/hooks/query'; import { useProjects } from '@/containers/Projects/hooks'; import { useTaxRates } from '@/hooks/query/tax-rates'; import { useGetPdfTemplates } from '@/hooks/query/pdf-templates'; import { useGetPaymentServices } from '@/hooks/query/payment-services'; -interface InvoiceFormContextValue { - saleInvoiceState: GetSaleInvoiceStateResponse | null; - isInvoiceStateLoading: boolean; -} +type InvoiceFormSubmitPayload = { + redirect?: boolean; +}; -const InvoiceFormContext = createContext( - {} as InvoiceFormContextValue, +type InvoiceFormContextValue = { + invoice: SaleInvoice | undefined; + items: Item[]; + customers: Customer[]; + newInvoice: ReturnType | []; + estimateId: string | undefined; + invoiceId: number | undefined; + submitPayload: InvoiceFormSubmitPayload | undefined; + branches: Branch[]; + warehouses: Warehouse[]; + projects: unknown[]; + taxRates: TaxRate[]; + brandingTemplates: PdfTemplateResponse[]; + paymentServices: GetPaymentServicesResponse | undefined; + + isInvoiceLoading: boolean; + isItemsLoading: boolean; + isCustomersLoading: boolean; + isSettingsLoading: boolean; + isWarehouesLoading: boolean; + isBranchesLoading: boolean; + isFeatureLoading: boolean; + isBranchesSuccess: boolean; + isWarehousesSuccess: boolean; + isTaxRatesLoading: boolean; + isBrandingTemplatesLoading: boolean; + isInvoiceStateLoading: boolean; + isPaymentServicesLoading: boolean; + isBootLoading: boolean; + isNewMode: boolean; + + createInvoiceMutate: (values: CreateSaleInvoiceBody) => Promise; + editInvoiceMutate: (args: [number, EditSaleInvoiceBody]) => Promise; + setSubmitPayload: React.Dispatch< + React.SetStateAction + >; + + saleInvoiceState: SaleInvoiceStateResponse | undefined; +}; + +const InvoiceFormContext = createContext( + undefined, ); +type InvoiceFormProviderProps = { + invoiceId?: number; + baseCurrency?: string; + children?: React.ReactNode; +}; + /** - * Accounts chart data provider. + * Invoice form data provider. */ -function InvoiceFormProvider({ invoiceId, baseCurrency, ...props }) { +function InvoiceFormProvider({ + invoiceId, + baseCurrency, + ...props +}: InvoiceFormProviderProps) { const { state } = useLocation(); - const estimateId = state?.action; + const estimateId = (state as { action?: string })?.action; + const estimateIdNum = estimateId ? Number(estimateId) : undefined; // Features guard. const { featureCan } = useFeatureCan(); @@ -47,9 +107,7 @@ function InvoiceFormProvider({ invoiceId, baseCurrency, ...props }) { const isProjectsFeatureCan = featureCan(Features.Projects); // Fetch invoice data. - const { data: invoice, isLoading: isInvoiceLoading } = useInvoice(invoiceId, { - enabled: !!invoiceId, - }); + const { data: invoice, isLoading: isInvoiceLoading } = useInvoice(invoiceId); // Fetch tax rates. const { data: taxRates, isLoading: isTaxRatesLoading } = useTaxRates(); @@ -62,8 +120,8 @@ function InvoiceFormProvider({ invoiceId, baseCurrency, ...props }) { // Fetches the estimate by the given id. const { data: estimate, isLoading: isEstimateLoading } = useEstimate( - estimateId, - { enabled: !!estimateId }, + estimateIdNum, + { enabled: !!estimateIdNum }, ); // Fetches branding templates of invoice. @@ -78,7 +136,7 @@ function InvoiceFormProvider({ invoiceId, baseCurrency, ...props }) { ? transformToEditForm({ ...pick(estimate, ['customer_id', 'currency_code', 'entries']), }) - : []; + : ([] as []); // Handle fetching the items table based on the given query. const { @@ -120,7 +178,9 @@ function InvoiceFormProvider({ invoiceId, baseCurrency, ...props }) { const { mutateAsync: editInvoiceMutate } = useEditInvoice(); // Form submit payload. - const [submitPayload, setSubmitPayload] = useState(); + const [submitPayload, setSubmitPayload] = useState< + InvoiceFormSubmitPayload | undefined + >(); // Detarmines whether the form in new mode. const isNewMode = !invoiceId; @@ -140,19 +200,19 @@ function InvoiceFormProvider({ invoiceId, baseCurrency, ...props }) { isSettingsLoading || isInvoiceStateLoading; - const provider = { + const provider: InvoiceFormContextValue = { invoice, - items: itemsData?.items, - customers: customersData?.customers, + items: itemsData?.data ?? [], + customers: customersData?.data ?? [], newInvoice, estimateId, invoiceId, submitPayload, - branches, - warehouses, - projects: projectsData?.projects, - taxRates, - brandingTemplates, + branches: branches ?? [], + warehouses: warehouses ?? [], + projects: (projectsData as { data?: { projects?: unknown[] } })?.data?.projects ?? [], + taxRates: taxRates?.data ?? [], + brandingTemplates: brandingTemplates?.templates ?? [], isInvoiceLoading, isItemsLoading, @@ -166,16 +226,18 @@ function InvoiceFormProvider({ invoiceId, baseCurrency, ...props }) { isTaxRatesLoading, isBrandingTemplatesLoading, - createInvoiceMutate, - editInvoiceMutate, + createInvoiceMutate: createInvoiceMutate as ( + values: CreateSaleInvoiceBody, + ) => Promise, + editInvoiceMutate: editInvoiceMutate as ( + args: [number, EditSaleInvoiceBody], + ) => Promise, setSubmitPayload, isNewMode, - // Payment Services paymentServices, isPaymentServicesLoading, - // Invoice state saleInvoiceState, isInvoiceStateLoading, @@ -185,7 +247,14 @@ function InvoiceFormProvider({ invoiceId, baseCurrency, ...props }) { return ; } -const useInvoiceFormContext = () => - React.useContext(InvoiceFormContext); +const useInvoiceFormContext = (): InvoiceFormContextValue => { + const ctx = React.useContext(InvoiceFormContext); + if (!ctx) { + throw new Error( + 'useInvoiceFormContext must be used within an InvoiceFormProvider', + ); + } + return ctx; +}; export { InvoiceFormProvider, useInvoiceFormContext }; diff --git a/packages/webapp/src/hooks/query/customers/queries.ts b/packages/webapp/src/hooks/query/customers/queries.ts index e247882af..f046f2936 100644 --- a/packages/webapp/src/hooks/query/customers/queries.ts +++ b/packages/webapp/src/hooks/query/customers/queries.ts @@ -37,11 +37,7 @@ export function useCustomers( return useQuery({ ...props, queryKey: customersKeys.list(query), - queryFn: () => - (fetchCustomers as (f: ReturnType, q?: Record) => Promise)( - fetcher, - query - ), + queryFn: () => fetchCustomers(fetcher, query), }); } diff --git a/shared/sdk-ts/src/schema.ts b/shared/sdk-ts/src/schema.ts index 47455d626..635d0d428 100644 --- a/shared/sdk-ts/src/schema.ts +++ b/shared/sdk-ts/src/schema.ts @@ -8375,6 +8375,17 @@ export interface components { */ nonDeletableIds: number[]; }; + CustomersListResponseDto: { + data: components["schemas"]["CustomerResponseDto"][]; + pagination: { + /** @example 1 */ + page: number; + /** @example 12 */ + pageSize: number; + /** @example 42 */ + total: number; + }; + }; CustomerResponseDto: { /** @example 1500 */ balance: number; @@ -19109,7 +19120,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["CustomerResponseDto"][]; + "application/json": components["schemas"]["CustomersListResponseDto"]; }; }; };