From 73674a8f522882773119a356fc0e5bed19dda4fe Mon Sep 17 00:00:00 2001 From: David Ly Date: Thu, 24 Oct 2024 20:55:21 +0200 Subject: [PATCH] Added local damage / pricing state before api request --- packages/common/README/STATE_MANAGEMENT.md | 125 +++++++++++++++++- .../src/state/actions/createdOneDamage.ts | 18 ++- .../src/state/actions/createdOnePricing.ts | 18 ++- packages/network/src/api/damage/requests.ts | 16 +++ packages/network/src/api/pricing/requests.ts | 22 ++- .../network/test/api/damage/requests.test.ts | 16 ++- .../network/test/api/pricing/requests.test.ts | 20 ++- 7 files changed, 223 insertions(+), 12 deletions(-) diff --git a/packages/common/README/STATE_MANAGEMENT.md b/packages/common/README/STATE_MANAGEMENT.md index b666fe066..d2dc64053 100644 --- a/packages/common/README/STATE_MANAGEMENT.md +++ b/packages/common/README/STATE_MANAGEMENT.md @@ -89,7 +89,7 @@ const action: Monk = { ``` ## CreatedOneImage Action -This action can be dispatched after an image has beencreated and uploaded to the API. The payload of this action should +This action can be dispatched after an image has been created and uploaded to the API. The payload of this action should contain the details about the image that has been created, as well as the ID of the inspection. You can also start by creating a local image (with a custom local ID), and then update this image when the API has returned the actual ID of the image. To do so, re-dispatch another time the same action, but with the new Id and the property `localId` in the @@ -122,4 +122,127 @@ const action: Monk = { { id: '4097bc0e-02d0-4ed8-9411-b18f0cb922f2', status: ProgressStatus.IN_PROGRESS }, ] } + +``` +## CreatedOnePricing Action +This action can be dispatched after pricing has been created and uploaded to the API. Similar to the image, you can +first create a local pricing entry with a custom local ID and then update it once the API returns the actual ID. + +```typescript +import { MonkResetStateAction, MonkActionType } from '@monkvision/common'; +import { PricingV2RelatedItemType} from '@monkvision/types'; + +const action: Monk = { + type: MonkActionType.CREATED_ONE_PRICING, + payload: { + pricing: { + entityType: MonkEntityType.PRICING, + id: '2b2ac131-c613-41a9-ac04-d00b942e2290', + inspectionId: 'e1cb2852-77f3-4fb5-a851-e700cf31a7d1', + relatedItemType: PricingV2RelatedItemType.PART, + pricing: 500, + }, + } +}; +``` + +## UpdatedOnePricing Action +This action can be dispatched after a pricing entry has been updated. The payload should contain the details of the +pricing that has been updated along with the inspection ID. + +```typescript +import { MonkResetStateAction, MonkActionType } from '@monkvision/common'; + +const action: Monk = { + type: MonkActionType.UPDATED_ONE_PRICING, + payload: { + inspectionId: 'e1cb2852-77f3-4fb5-a851-e700cf31a7d1', + pricing: { + id: 'pricing-id', + ...updatedPricingDetails, + }, + } +}; + +``` + +## DeletedOnePricing Action +This action can be dispatched after a pricing entry has been deleted from the API. The payload contains the ID of the +inspection and the ID of the pricing that was deleted. + +```typescript +import { MonkResetStateAction, MonkActionType } from '@monkvision/common'; + +const action: Monk = { + type: MonkActionType.DELETED_ONE_PRICING, + payload: { + inspectionId: 'e1cb2852-77f3-4fb5-a851-e700cf31a7d1', + pricingId: 'pricing-id-to-be-deleted', + } +}; + +``` + +## CreatedOneDamage Action +This action can be dispatched after a damage has been created and uploaded to the API. You can start with a locally +created damage and update it later once the API returns the actual ID. + +```typescript +import { MonkResetStateAction, MonkActionType } from '@monkvision/common'; + +const action: Monk = { + type: MonkActionType.CREATED_ONE_DAMAGE, + payload: { + inspectionId: 'e1cb2852-77f3-4fb5-a851-e700cf31a7d1', + damage: { + entityType: MonkEntityType.DAMAGE, + id: '2b2ac131-c613-41a9-ac04-d00b942e2290', + inspectionId: 'e1cb2852-77f3-4fb5-a851-e700cf31a7d1', + parts: [VehiclePart.BUMPER_BACK], + relatedImages: [], + type: DamageType.BODY_CRACK, + }, + } +}; +``` + +## DeletedOneDamage Action +This action can be dispatched after a damage entry has been deleted from the API. The payload contains the ID of the +inspection and the ID of the damage that was deleted. + +```typescript +import { MonkResetStateAction, MonkActionType } from '@monkvision/common'; + +const action: Monk = { + type: MonkActionType.DELETED_ONE_DAMAGE, + payload: { + pricing: { + entityType: MonkEntityType.PRICING, + id: '2b2ac131-c613-41a9-ac04-d00b942e2290', + inspectionId: 'e1cb2852-77f3-4fb5-a851-e700cf31a7d1', + relatedItemType: PricingV2RelatedItemType.PART, + pricing: 800, + }, + } +}; +``` + +## UpdatedOneInspectionAdditionalData Action +This action can be dispatched after the additional data of an inspection has been updated in the API. The payload +contains the ID of the inspection and any additional data used for the update. + +```typescript +import { MonkResetStateAction, MonkActionType } from '@monkvision/common'; +import { AdditionalData } from '@monkvision/types'; + +const action: Monk = { + type: MonkActionType.UPDATED_ONE_INSPECTION_ADDITIONAL_DATA, + payload: { + inspectionId: 'e1cb2852-77f3-4fb5-a851-e700cf31a7d1', + additionalData: { + someKey: 'someValue', + ...otherData, + }, + }, +}; ``` diff --git a/packages/common/src/state/actions/createdOneDamage.ts b/packages/common/src/state/actions/createdOneDamage.ts index caec59b4c..061ef69c5 100644 --- a/packages/common/src/state/actions/createdOneDamage.ts +++ b/packages/common/src/state/actions/createdOneDamage.ts @@ -10,6 +10,12 @@ export interface MonkCreatedOneDamagePayload { * The damage created. */ damage: Damage; + /** + * This ID is used when you first want to create the entity locally while you wait for the API to give you the true + * ID of the damage. You first create the damage with a custom local ID, then you dispatch the action a second time + * and specify this custom ID in the `localId` param. The damage will then be updated instead of added. + */ + localId?: string; } /** @@ -43,8 +49,14 @@ export function createdOneDamage(state: MonkState, action: MonkCreatedOneDamageA const inspection = inspections.find((value) => value.id === payload.damage.inspectionId); if (inspection) { - inspection.damages.push(action.payload.damage.id); + inspection.damages = inspection.damages.filter( + (damageId) => ![payload.damage.id, payload.localId].includes(damageId), + ); + inspection.damages.push(payload.damage.id); } + const newDamages = damages.filter( + (damage) => ![payload.damage.id, payload.localId].includes(damage.id), + ); const partsRelated = action.payload.damage.parts .map((part) => parts.find((value) => value.type === part)?.id) .filter((v) => v !== undefined) as string[]; @@ -54,11 +66,11 @@ export function createdOneDamage(state: MonkState, action: MonkCreatedOneDamageA } return part; }); - damages.push({ ...payload.damage, parts: partsRelated }); + newDamages.push({ ...payload.damage, parts: partsRelated }); return { ...state, parts: newParts, - damages: [...damages], + damages: newDamages, inspections: [...inspections], }; } diff --git a/packages/common/src/state/actions/createdOnePricing.ts b/packages/common/src/state/actions/createdOnePricing.ts index f9885e732..6ee7f0fc5 100644 --- a/packages/common/src/state/actions/createdOnePricing.ts +++ b/packages/common/src/state/actions/createdOnePricing.ts @@ -10,6 +10,12 @@ export interface MonkCreatedOnePricingPayload { * The pricing created. */ pricing: PricingV2; + /** + * This ID is used when you first want to create the entity locally while you wait for the API to give you the true + * ID of the damage. You first create the damage with a custom local ID, then you dispatch the action a second time + * and specify this custom ID in the `localId` param. The damage will then be updated instead of added. + */ + localId?: string; } /** @@ -48,12 +54,18 @@ export function createdOnePricing( const inspection = inspections.find((value) => value.id === payload.pricing.inspectionId); if (inspection) { - inspection.pricings?.push(action.payload.pricing.id); + inspection.pricings = inspection.pricings?.filter( + (pricingId) => ![payload.pricing.id, payload.localId].includes(pricingId), + ); + inspection.pricings?.push(payload.pricing.id); } - pricings.push(action.payload.pricing); + const newPricings = pricings.filter( + (pricing) => ![payload.pricing.id, payload.localId].includes(pricing.id), + ); + newPricings.push(action.payload.pricing); return { ...state, - pricings: [...pricings], + pricings: newPricings, inspections: [...inspections], }; } diff --git a/packages/network/src/api/damage/requests.ts b/packages/network/src/api/damage/requests.ts index 24281252d..d10a3b57a 100644 --- a/packages/network/src/api/damage/requests.ts +++ b/packages/network/src/api/damage/requests.ts @@ -5,6 +5,7 @@ import { } from '@monkvision/common'; import { DamageType, MonkEntityType, VehiclePart } from '@monkvision/types'; import ky from 'ky'; +import { v4 } from 'uuid'; import { Dispatch } from 'react'; import { getDefaultOptions, MonkApiConfig } from '../config'; import { ApiIdColumn } from '../models'; @@ -44,6 +45,20 @@ export async function createDamage( config: MonkApiConfig, dispatch?: Dispatch, ): Promise { + const localId = v4(); + dispatch?.({ + type: MonkActionType.CREATED_ONE_DAMAGE, + payload: { + damage: { + entityType: MonkEntityType.DAMAGE, + id: localId, + inspectionId: options.id, + parts: [options.vehiclePart], + relatedImages: [], + type: options.damageType, + }, + }, + }); const kyOptions = getDefaultOptions(config); const response = await ky.post(`inspections/${options.id}/damages`, { ...kyOptions, @@ -61,6 +76,7 @@ export async function createDamage( relatedImages: [], type: options.damageType, }, + localId, }, }); return { diff --git a/packages/network/src/api/pricing/requests.ts b/packages/network/src/api/pricing/requests.ts index 36084b608..99e0d8273 100644 --- a/packages/network/src/api/pricing/requests.ts +++ b/packages/network/src/api/pricing/requests.ts @@ -6,6 +6,8 @@ import { MonkUpdatedOnePricingAction, } from '@monkvision/common'; import ky from 'ky'; +import { v4 } from 'uuid'; +import { MonkEntityType, PricingV2RelatedItemType } from '@monkvision/types'; import { getDefaultOptions, MonkApiConfig } from '../config'; import { MonkApiResponse } from '../types'; import { ApiIdColumn, ApiPricingV2Details } from '../models'; @@ -41,6 +43,24 @@ export async function createPricing( config: MonkApiConfig, dispatch?: Dispatch, ): Promise { + const localId = v4(); + const localPricing = { + entityType: MonkEntityType.PRICING, + id: localId, + inspectionId: options.id, + relatedItemType: options.pricing.type, + pricing: options.pricing.pricing, + relatedItemId: + options.pricing.type === PricingV2RelatedItemType.PART + ? options.pricing.vehiclePart + : undefined, + }; + dispatch?.({ + type: MonkActionType.CREATED_ONE_PRICING, + payload: { + pricing: localPricing, + }, + }); const kyOptions = getDefaultOptions(config); const response = await ky.post(`inspections/${options.id}/pricing`, { ...kyOptions, @@ -50,7 +70,7 @@ export async function createPricing( const pricing = mapApiPricingPost(options.id, body); dispatch?.({ type: MonkActionType.CREATED_ONE_PRICING, - payload: { pricing }, + payload: { pricing, localId }, }); return { id: body.id, diff --git a/packages/network/test/api/damage/requests.test.ts b/packages/network/test/api/damage/requests.test.ts index 0a1ac9592..5c7c6484b 100644 --- a/packages/network/test/api/damage/requests.test.ts +++ b/packages/network/test/api/damage/requests.test.ts @@ -61,7 +61,20 @@ describe('Damage requests', () => { ...kyOptions, json: apiDamage, }); - expect(dispatch).toHaveBeenCalledWith({ + expect(dispatch.mock.calls[0][0]).toEqual({ + type: MonkActionType.CREATED_ONE_DAMAGE, + payload: { + damage: { + entityType: MonkEntityType.DAMAGE, + id: expect.any(String), + inspectionId: damage.id, + parts: [damage.vehiclePart], + relatedImages: [], + type: damage.damageType, + }, + }, + }); + expect(dispatch.mock.calls[1][0]).toEqual({ type: MonkActionType.CREATED_ONE_DAMAGE, payload: { damage: { @@ -72,6 +85,7 @@ describe('Damage requests', () => { relatedImages: [], type: damage.damageType, }, + localId: expect.any(String), }, }); expect(result).toEqual({ diff --git a/packages/network/test/api/pricing/requests.test.ts b/packages/network/test/api/pricing/requests.test.ts index 8c1605b20..7469cf76d 100644 --- a/packages/network/test/api/pricing/requests.test.ts +++ b/packages/network/test/api/pricing/requests.test.ts @@ -17,7 +17,7 @@ jest.mock('ky', () => ({ ), })); -import { PricingV2RelatedItemType, VehiclePart } from '@monkvision/types'; +import { MonkEntityType, PricingV2RelatedItemType, VehiclePart } from '@monkvision/types'; import { createPricing, deletePricing, @@ -65,9 +65,23 @@ describe('Pricing requests', () => { ...kyOptions, json: apiPricingPost, }); - expect(dispatch).toHaveBeenCalledWith({ + expect(dispatch.mock.calls[0][0]).toEqual({ type: MonkActionType.CREATED_ONE_PRICING, - payload: { pricing: apiPricing }, + payload: { + pricing: { + entityType: MonkEntityType.PRICING, + id: expect.any(String), + inspectionId: id, + relatedItemType: pricing.type, + pricing: pricing.pricing, + relatedItemId: + pricing.type === PricingV2RelatedItemType.PART ? pricing.vehiclePart : undefined, + }, + }, + }); + expect(dispatch.mock.calls[1][0]).toEqual({ + type: MonkActionType.CREATED_ONE_PRICING, + payload: { pricing: apiPricing, localId: expect.any(String) }, }); expect(result).toEqual({ id: body.id,