diff --git a/packages/network/README.md b/packages/network/README.md index ba61a6886..590dbc499 100644 --- a/packages/network/README.md +++ b/packages/network/README.md @@ -210,6 +210,20 @@ Delete a damage of an inspection. |-----------|---------------------|-----------------------------|----------| | options | DeleteDamageOptions | The options of the request. | ✔️ | +### getInspections +```typescript +import { MonkApi } from '@monkvision/network'; + +MonkApi.getInspections(options, apiConfig, dispatch); +``` + +Fetch the details of multiple inspections using the provided filters. The resulting action of this request will contain +a list of all entities that match the specified criteria. + +| Parameter | Type | Description | Required | +|-----------|-----------------------|-----------------------------|----------| +| options | getInspectionsOptions | The options of the request. | ✔️ | + # React Tools In order to simply integrate the Monk Api requests into your React app, you can make use of the `useMonkApi` hook. This custom hook returns a custom version of the `MonkApi` object described in the section above, in which the requests do diff --git a/packages/network/src/api/api.ts b/packages/network/src/api/api.ts index 0ae7b7fdf..e15ba5534 100644 --- a/packages/network/src/api/api.ts +++ b/packages/network/src/api/api.ts @@ -1,4 +1,9 @@ -import { getInspection, createInspection, updateAdditionalData } from './inspection'; +import { + getInspection, + createInspection, + updateAdditionalData, + getInspections, +} from './inspection'; import { addImage } from './image'; import { startInspectionTasks, updateTaskStatus } from './task'; import { getLiveConfig } from './liveConfigs'; @@ -12,6 +17,7 @@ import { createDamage, deleteDamage } from './damage'; */ export const MonkApi = { getInspection, + getInspections, createInspection, addImage, updateTaskStatus, diff --git a/packages/network/src/api/inspection/mappers.ts b/packages/network/src/api/inspection/mappers.ts index e42724289..572128d88 100644 --- a/packages/network/src/api/inspection/mappers.ts +++ b/packages/network/src/api/inspection/mappers.ts @@ -24,6 +24,7 @@ import { Severity, SeverityResult, SeverityResultTargetType, + SortOrder, Task, TaskName, Vehicle, @@ -40,6 +41,7 @@ import { ApiImagesOCRTaskPostComponent, ApiInspectionGet, ApiInspectionPost, + ApiInspectionsGet, ApiPartSeverityValue, ApiPricingTaskPostComponent, ApiPricingV2Details, @@ -374,6 +376,60 @@ function mapInspection( }; } +export function mapApiInspectionsGet( + response: ApiInspectionsGet, + thumbnailDomain: string, +): MonkState { + const state: MonkState = { + damages: [], + images: [], + inspections: [], + parts: [], + renderedOutputs: [], + severityResults: [], + tasks: [], + vehicles: [], + views: [], + pricings: [], + partOperations: [], + }; + if (!response.data) { + return state; + } + return response.data.reduce((acc, inspection) => { + const { images, renderedOutputs, imageIds, renderedOutputIds, viewIds } = mapImages( + inspection as ApiInspectionGet, + thumbnailDomain, + ); + const { damages, damageIds } = mapDamages(inspection as ApiInspectionGet); + const { parts, partIds } = mapParts(inspection as ApiInspectionGet); + const { pricings, pricingIds } = mapPricingV2(inspection as ApiInspectionGet); + const vehicle = mapVehicle(inspection as ApiInspectionGet); + const mappedInspection = mapInspection(inspection as ApiInspectionGet, { + imageIds, + renderedOutputIds, + viewIds, + damageIds, + partIds, + severityResultIds: [], + taskIds: [], + pricingIds, + vehicleId: vehicle?.id, + }); + acc.damages.push(...damages); + acc.images.push(...images); + acc.inspections.push(mappedInspection); + acc.parts.push(...parts); + acc.renderedOutputs.push(...renderedOutputs); + if (vehicle) { + acc.vehicles.push(vehicle); + } + acc.pricings.push(...pricings); + + return acc; + }, state); +} + export function mapApiInspectionGet( response: ApiInspectionGet, thumbnailDomain: string, @@ -569,3 +625,86 @@ export function mapApiInspectionPost(options: CreateInspectionOptions): ApiInspe }, }; } + +/** + * Parameters for pagination requests. + */ +export interface PaginationRequestParams { + /** + * The number of inspections fetched. + * + * @default 100 + */ + limit?: number; + /** + * The inspection ID to filter that occurred before this ID. + */ + before?: string; + /** + * The inspection ID to filter that occurred after this date. + */ + after?: string; +} + +/** + * Parameters for sorting requests. + */ +export interface SortRequestParams { + /** + * The property to sort by. + */ + sortByProperty: string; + /** + * The order of the pagination. + * + * @default SortOrder.DESC + */ + sortOrder?: SortOrder; +} + +/** + * Options passed to the `getInspections` API request. + */ +export interface GetInspectionsOptions { + /** + * If true, only the total count of inspections will be returned. + * + * @default false + */ + count?: boolean; + /** + * The filter request parameters. + */ + filters?: Record; + /** + * The pagination request parameters. + */ + pagination?: PaginationRequestParams; + /** + * The sort request parameters. + */ + sort?: SortRequestParams; +} + +export function mapApiInspectionsUrlParamsGet(options: GetInspectionsOptions): string { + const params = new URLSearchParams(); + let url = options.count ? '/count' : ''; + url = options.filters || options.pagination ? `${url}?` : url; + + if (options.filters) { + Object.entries(options.filters).forEach(([key, value]) => { + params.append(key, value.toString()); + }); + } + if (options.pagination) { + Object.entries(options.pagination).forEach(([key, value]) => { + params.append(key, value.toString()); + }); + } + if (options.sort) { + Object.entries(options.sort).forEach(([key, value]) => { + params.append(key, value.toString()); + }); + } + return `${url}${params.toString()}`; +} diff --git a/packages/network/src/api/inspection/requests.ts b/packages/network/src/api/inspection/requests.ts index 7461a3ebc..54ac2e942 100644 --- a/packages/network/src/api/inspection/requests.ts +++ b/packages/network/src/api/inspection/requests.ts @@ -9,8 +9,14 @@ import { import { AdditionalData, ComplianceOptions, CreateInspectionOptions } from '@monkvision/types'; import { Dispatch } from 'react'; import { getDefaultOptions, MonkApiConfig } from '../config'; -import { ApiIdColumn, ApiInspectionGet } from '../models'; -import { mapApiInspectionGet, mapApiInspectionPost } from './mappers'; +import { ApiIdColumn, ApiInspectionGet, ApiInspectionsGet } from '../models'; +import { + GetInspectionsOptions, + mapApiInspectionGet, + mapApiInspectionPost, + mapApiInspectionsGet, + mapApiInspectionsUrlParamsGet, +} from './mappers'; import { MonkApiResponse } from '../types'; /** @@ -143,3 +149,27 @@ export async function updateAdditionalData( body, }; } + +/** + * Fetch the details of multiple inspections. + * + * @param options The options of the request. + * @param config The API config. + * @param [dispatch] Optional MonkState dispatch function that you can pass if you want this request to handle React + * state management for you. + */ +export async function getInspections( + options: GetInspectionsOptions, + config: MonkApiConfig, + dispatch?: Dispatch, +): Promise> { + const kyOptions = getDefaultOptions(config); + const response = await ky.get(`inspections${mapApiInspectionsUrlParamsGet(options)}`, kyOptions); + const body = await response.json(); + const entities = mapApiInspectionsGet(body, config.thumbnailDomain); + dispatch?.({ + type: MonkActionType.GOT_ONE_INSPECTION, + payload: entities, + }); + return { entities, response, body }; +} diff --git a/packages/network/src/api/models/inspection.ts b/packages/network/src/api/models/inspection.ts index 118b2df50..d88437ac8 100644 --- a/packages/network/src/api/models/inspection.ts +++ b/packages/network/src/api/models/inspection.ts @@ -1,3 +1,4 @@ +import { SortOrder } from '@monkvision/types'; import type { ApiAdditionalData } from './common'; import type { ApiDamages } from './damage'; import type { ApiImagePost, ApiImages } from './image'; @@ -29,6 +30,37 @@ export interface ApiInspectionGet { wheel_analysis?: ApiWheelAnalysis; } +interface ApiData + extends Pick< + ApiInspectionGet, + 'id' | 'additional_data' | 'images' | 'damages' | 'pricing' | 'parts' | 'vehicle' + > { + pdf_url?: string; +} + +interface ApiPaginationParams { + limit?: number; + before?: string; + after?: string; + pagination_order?: SortOrder; +} + +interface ApiCursors { + before?: Pick; + after?: Pick; + next?: ApiPaginationParams; + previous?: ApiPaginationParams; +} + +interface ApiPagination { + cursors: ApiCursors; +} + +export interface ApiInspectionsGet { + data: ApiData[]; + paging: ApiPagination; +} + export interface ApiDamageSeverity { output_format: ApiBusinessClients; } diff --git a/packages/network/src/api/react.ts b/packages/network/src/api/react.ts index 5f90167c2..92a4edf7a 100644 --- a/packages/network/src/api/react.ts +++ b/packages/network/src/api/react.ts @@ -56,6 +56,12 @@ export function useMonkApi(config: MonkApiConfig) { * @param options The options of the request. */ getInspection: reactify(MonkApi.getInspection, config, dispatch, handleError), + /** + * Fetch multiple inspection. + * + * @param options The options of the request. + */ + getInspections: reactify(MonkApi.getInspections, config, dispatch, handleError), /** * Create a new inspection with the given options. See the `CreateInspectionOptions` interface for more details. * diff --git a/packages/network/test/api/inspection/requests.test.ts b/packages/network/test/api/inspection/requests.test.ts index d37237b61..575aa6459 100644 --- a/packages/network/test/api/inspection/requests.test.ts +++ b/packages/network/test/api/inspection/requests.test.ts @@ -28,6 +28,7 @@ const mockInspection: Inspection = { parts: [], tasks: [], }; +const mockUrlParams = 'test-url-params'; jest.mock('../../../src/api/config', () => ({ getDefaultOptions: jest.fn(() => ({ prefixUrl: 'getDefaultOptionsTest' })), @@ -37,12 +38,26 @@ jest.mock('../../../src/api/inspection/mappers', () => ({ inspections: [mockInspection] as unknown as Inspection[], parts: [], })), + mapApiInspectionsGet: jest.fn(() => ({ + inspections: [mockInspection] as unknown as Inspection[], + parts: [], + })), mapApiInspectionPost: jest.fn(() => ({ test: 'ok-ok-ok' })), + mapApiInspectionsUrlParamsGet: jest.fn(() => mockUrlParams), })); import { getDefaultOptions } from '../../../src/api/config'; -import { createInspection, getInspection, updateAdditionalData } from '../../../src/api/inspection'; -import { mapApiInspectionGet, mapApiInspectionPost } from '../../../src/api/inspection/mappers'; +import { + createInspection, + getInspection, + getInspections, + updateAdditionalData, +} from '../../../src/api/inspection'; +import { + mapApiInspectionGet, + mapApiInspectionPost, + mapApiInspectionsGet, +} from '../../../src/api/inspection/mappers'; const apiConfig = { apiDomain: 'apiDomain', @@ -133,4 +148,30 @@ describe('Inspection requests', () => { }); }); }); + + describe('getInspections request', () => { + it('should make the proper API call and map the resulting response', async () => { + const dispatch = jest.fn(); + const result = await getInspections({ filters: { test: 'test' } }, apiConfig, dispatch); + const response = await (ky.get as jest.Mock).mock.results[0].value; + const body = await response.json(); + + expect(mapApiInspectionsGet).toHaveBeenCalledWith(body, apiConfig.thumbnailDomain); + const entities = (mapApiInspectionsGet as jest.Mock).mock.results[0].value; + expect(getDefaultOptions).toHaveBeenCalledWith(apiConfig); + expect(ky.get).toHaveBeenCalledWith( + `inspections${mockUrlParams}`, + getDefaultOptions(apiConfig), + ); + expect(dispatch).toHaveBeenCalledWith({ + type: MonkActionType.GOT_ONE_INSPECTION, + payload: entities, + }); + expect(result).toEqual({ + entities, + response, + body, + }); + }); + }); }); diff --git a/packages/network/test/api/react.test.ts b/packages/network/test/api/react.test.ts index 8cbc7356a..15310072f 100644 --- a/packages/network/test/api/react.test.ts +++ b/packages/network/test/api/react.test.ts @@ -7,6 +7,7 @@ jest.mock('../../src/api/api', () => ({ updatePricing: jest.fn(() => Promise.resolve({ test: 'updatePricing' })), createDamage: jest.fn(() => Promise.resolve({ test: 'createDamage' })), deleteDamage: jest.fn(() => Promise.resolve({ test: 'deleteDamage' })), + getInspections: jest.fn(() => Promise.resolve({ test: 'getInspections' })), }, })); @@ -96,6 +97,16 @@ describe('Monk API React utilities', () => { expect(requestMock).toHaveBeenCalledWith(param, config, dispatchMock); requestResultMock = await requestMock.mock.results[0].value; expect(resultMock).toBe(requestResultMock); + + dispatchMock.mockClear(); + + param = 'test-getInspections'; + resultMock = await (result.current.getInspections as any)(param); + requestMock = MonkApi.getInspections as jest.Mock; + expect(requestMock).toHaveBeenCalledWith(param, config, dispatchMock); + requestResultMock = await requestMock.mock.results[0].value; + expect(resultMock).toBe(requestResultMock); + unmount(); }); diff --git a/packages/types/src/state/inspection.ts b/packages/types/src/state/inspection.ts index 94c5fc4fc..eda48586a 100644 --- a/packages/types/src/state/inspection.ts +++ b/packages/types/src/state/inspection.ts @@ -44,6 +44,10 @@ export interface Inspection extends MonkEntity { * The details about the cost of the vehicle reparations using the PricingV2 API if it was requested. */ pricings?: string[]; + /** + * The URL of the PDF report generated for this inspection. + */ + pdfUrl?: string; /** * Additional data added during the creation of the inspection. */ diff --git a/packages/types/src/state/vehicle.ts b/packages/types/src/state/vehicle.ts index 2804dff8e..0f3c2b1cd 100644 --- a/packages/types/src/state/vehicle.ts +++ b/packages/types/src/state/vehicle.ts @@ -10,7 +10,7 @@ export enum MileageUnit { } /** - * An object containing all the information abou t a vehicle that is being inspected during an inspection. + * An object containing all the information about a vehicle that is being inspected during an inspection. */ export interface Vehicle extends MonkEntity { /** diff --git a/packages/types/src/utils.ts b/packages/types/src/utils.ts index 86679d09a..6e63552ce 100644 --- a/packages/types/src/utils.ts +++ b/packages/types/src/utils.ts @@ -27,7 +27,21 @@ export enum DeviceOrientation { } /** - * Callbacks used to handle the result of a promise. + * Enumeration of the possible sort orders. + */ +export enum SortOrder { + /** + * Sort in ascending order. + */ + ASC = 'asc', + /** + * Sort in descending order. + */ + DESC = 'desc', +} + +/** + * Callbacks used to handle the result of a promise */ export interface PromiseHandlers { /**