From 11f27aba18476208d6791e95395da702f93f7881 Mon Sep 17 00:00:00 2001 From: Aaron Chong Date: Wed, 27 Mar 2024 00:47:13 +0800 Subject: [PATCH 01/28] Moved custom deliveries to separate file naively and import naively Signed-off-by: Aaron Chong --- .../lib/tasks/create-task.tsx | 824 +----------------- .../tasks/descriptions/delivery_custom.tsx | 715 +++++++++++++++ .../lib/tasks/descriptions/index.ts | 3 + .../lib/tasks/descriptions/utils.ts | 73 ++ packages/react-components/lib/tasks/index.ts | 1 + 5 files changed, 801 insertions(+), 815 deletions(-) create mode 100644 packages/react-components/lib/tasks/descriptions/delivery_custom.tsx create mode 100644 packages/react-components/lib/tasks/descriptions/index.ts create mode 100644 packages/react-components/lib/tasks/descriptions/utils.ts diff --git a/packages/react-components/lib/tasks/create-task.tsx b/packages/react-components/lib/tasks/create-task.tsx index 1aedab997..6e6285552 100644 --- a/packages/react-components/lib/tasks/create-task.tsx +++ b/packages/react-components/lib/tasks/create-task.tsx @@ -43,6 +43,14 @@ import React from 'react'; import { Loading } from '..'; import { ConfirmationDialog, ConfirmationDialogProps } from '../confirmation-dialog'; import { PositiveIntField } from '../form-inputs'; +import { + defaultDeliveryCustomTaskDescription, + defaultDeliveryTaskDescription, + DeliveryCustomTaskForm, + DeliveryCustomTaskDescription, + DeliveryTaskDescription, + DeliveryTaskForm, +} from './descriptions/delivery_custom'; // A bunch of manually defined descriptions to avoid using `any`. export interface PatrolTaskDescription { @@ -50,124 +58,6 @@ export interface PatrolTaskDescription { rounds: number; } -interface LotPickupActivity { - category: string; - description: { - unix_millis_action_duration_estimate: number; - category: string; - description: { - cart_id: string; - pickup_lot: string; - }; - }; -} - -interface ZonePickupActivity { - category: string; - description: { - unix_millis_action_duration_estimate: number; - category: string; - description: { - cart_id: string; - pickup_zone: string; - }; - }; -} - -interface GoToPlaceActivity { - category: string; - description: string; -} - -interface CartCustomPickupPhase { - activity: { - category: string; - description: { - activities: [go_to_pickup: GoToPlaceActivity, pickup_cart: ZonePickupActivity]; - }; - }; -} - -interface CartPickupPhase { - activity: { - category: string; - description: { - activities: [go_to_pickup: GoToPlaceActivity, pickup_cart: LotPickupActivity]; - }; - }; -} - -interface DropoffActivity { - category: string; - description: { - unix_millis_action_duration_estimate: number; - category: string; - description: {}; - }; -} - -interface OneOfWaypoint { - waypoint: string; -} - -interface GoToOneOfThePlacesActivity { - category: string; - description: { - one_of: OneOfWaypoint[]; - constraints: [ - { - category: string; - description: string; - }, - ]; - }; -} - -interface OnCancelDropoff { - category: string; - description: [ - go_to_one_of_the_places: GoToOneOfThePlacesActivity, - delivery_dropoff: DropoffActivity, - ]; -} - -interface DeliveryWithCancellationPhase { - activity: { - category: string; - description: { - activities: [go_to_place: GoToPlaceActivity]; - }; - }; - on_cancel: OnCancelDropoff[]; -} - -interface CartDropoffPhase { - activity: { - category: string; - description: { - activities: [delivery_dropoff: DropoffActivity]; - }; - }; -} - -export interface DeliveryCustomTaskDescription { - category: string; - phases: [ - pickup_phase: CartCustomPickupPhase, - delivery_phase: DeliveryWithCancellationPhase, - dropoff_phase: CartDropoffPhase, - ]; -} - -export interface DeliveryTaskDescription { - category: string; - phases: [ - pickup_phase: CartPickupPhase, - delivery_phase: DeliveryWithCancellationPhase, - dropoff_phase: CartDropoffPhase, - ]; -} - type CustomComposeTaskDescription = string; type TaskDescription = @@ -175,44 +65,6 @@ type TaskDescription = | DeliveryCustomTaskDescription | PatrolTaskDescription; -const isNonEmptyString = (value: string): boolean => value.length > 0; - -const isDeliveryTaskDescriptionValid = ( - taskDescription: DeliveryTaskDescription, - pickupPoints: Record, - dropoffPoints: Record, -): boolean => { - const goToPickup = taskDescription.phases[0].activity.description.activities[0]; - const pickup = taskDescription.phases[0].activity.description.activities[1]; - const goToDropoff = taskDescription.phases[1].activity.description.activities[0]; - return ( - isNonEmptyString(goToPickup.description) && - Object.keys(pickupPoints).includes(goToPickup.description) && - pickupPoints[goToPickup.description] === pickup.description.description.pickup_lot && - isNonEmptyString(pickup.description.description.cart_id) && - isNonEmptyString(goToDropoff.description) && - Object.keys(dropoffPoints).includes(goToDropoff.description) - ); -}; - -const isDeliveryCustomTaskDescriptionValid = ( - taskDescription: DeliveryCustomTaskDescription, - pickupZones: string[], - dropoffPoints: string[], -): boolean => { - const goToPickup = taskDescription.phases[0].activity.description.activities[0]; - const pickup = taskDescription.phases[0].activity.description.activities[1]; - const goToDropoff = taskDescription.phases[1].activity.description.activities[0]; - return ( - isNonEmptyString(goToPickup.description) && - isNonEmptyString(pickup.description.description.pickup_zone) && - pickupZones.includes(pickup.description.description.pickup_zone) && - isNonEmptyString(pickup.description.description.cart_id) && - isNonEmptyString(goToDropoff.description) && - dropoffPoints.includes(goToDropoff.description) - ); -}; - const isPatrolTaskDescriptionValid = (taskDescription: PatrolTaskDescription): boolean => { if (taskDescription.places.length === 0) { return false; @@ -267,499 +119,6 @@ const StyledDialog = styled((props: DialogProps) => )(({ th }, })); -export function getShortDescription(taskRequest: TaskRequest): string | undefined { - if (taskRequest.category === 'patrol') { - const formattedPlaces = taskRequest.description.places.map((place: string) => `[${place}]`); - return `[Patrol] [${taskRequest.description.rounds}] round/s, along ${formattedPlaces.join( - ', ', - )}`; - } - - // This section is only valid for custom delivery types - // FIXME: This block looks like it makes assumptions about the structure of - // the task description in order to parse it, but it is following the - // statically defined description (object) at the top of this file. The - // descriptions should be replaced by a schema in general, however the better - // approach now should be to make each task description testable and in charge - // of their own short descriptions. - try { - const goToPickup: GoToPlaceActivity = - taskRequest.description.phases[0].activity.description.activities[0]; - const pickup: LotPickupActivity = - taskRequest.description.phases[0].activity.description.activities[1]; - const cartId = pickup.description.description.cart_id; - const goToDropoff: GoToPlaceActivity = - taskRequest.description.phases[1].activity.description.activities[0]; - - switch (taskRequest.description.category) { - case 'delivery_pickup': { - return `[Delivery - 1:1] payload [${cartId}] from [${goToPickup.description}] to [${goToDropoff.description}]`; - } - case 'delivery_sequential_lot_pickup': { - return `[Delivery - Sequential lot pick up] payload [${cartId}] from [${goToPickup.description}] to [${goToDropoff.description}]`; - } - case 'delivery_area_pickup': { - return `[Delivery - Area pick up] payload [${cartId}] from [${goToPickup.description}] to [${goToDropoff.description}]`; - } - default: - return `[Unknown] type "${taskRequest.description.category}"`; - } - } catch (e) { - if (e instanceof TypeError) { - console.error(`Failed to parse custom delivery: ${e.message}`); - } else { - console.error( - `Failed to generate short description from task of category: ${taskRequest.category}: ${ - (e as Error).message - }`, - ); - } - - try { - const descriptionString = JSON.stringify(taskRequest.description); - console.error(descriptionString); - return descriptionString; - } catch (e) { - console.error( - `Failed to parse description of task of category: ${taskRequest.category}: ${ - (e as Error).message - }`, - ); - return undefined; - } - } -} - -export function deliveryInsertPickup( - taskDescription: DeliveryTaskDescription, - pickupPlace: string, - pickupLot: string, -): DeliveryTaskDescription { - taskDescription.phases[0].activity.description.activities[0].description = pickupPlace; - taskDescription.phases[0].activity.description.activities[1].description.description.pickup_lot = - pickupLot; - return taskDescription; -} - -export function deliveryInsertCartId( - taskDescription: DeliveryTaskDescription, - cartId: string, -): DeliveryTaskDescription { - taskDescription.phases[0].activity.description.activities[1].description.description.cart_id = - cartId; - return taskDescription; -} - -export function deliveryInsertDropoff( - taskDescription: DeliveryTaskDescription, - dropoffPlace: string, -): DeliveryTaskDescription { - taskDescription.phases[1].activity.description.activities[0].description = dropoffPlace; - return taskDescription; -} - -export function deliveryInsertOnCancel( - taskDescription: DeliveryTaskDescription, - onCancelPlaces: string[], -): DeliveryTaskDescription { - const goToOneOfThePlaces: GoToOneOfThePlacesActivity = { - category: 'go_to_place', - description: { - one_of: onCancelPlaces.map((placeName) => { - return { - waypoint: placeName, - }; - }), - constraints: [ - { - category: 'prefer_same_map', - description: '', - }, - ], - }, - }; - const deliveryDropoff: DropoffActivity = { - category: 'perform_action', - description: { - unix_millis_action_duration_estimate: 60000, - category: 'delivery_dropoff', - description: {}, - }, - }; - const onCancelDropoff: OnCancelDropoff = { - category: 'sequence', - description: [goToOneOfThePlaces, deliveryDropoff], - }; - taskDescription.phases[1].on_cancel = [onCancelDropoff]; - return taskDescription; -} - -interface DeliveryTaskFormProps { - taskDesc: DeliveryTaskDescription; - pickupPoints: Record; - cartIds: string[]; - dropoffPoints: Record; - onChange(taskDesc: TaskDescription): void; - allowSubmit(allow: boolean): void; -} - -function DeliveryTaskForm({ - taskDesc, - pickupPoints = {}, - cartIds = [], - dropoffPoints = {}, - onChange, - allowSubmit, -}: DeliveryTaskFormProps) { - const theme = useTheme(); - const isScreenHeightLessThan800 = useMediaQuery('(max-height:800px)'); - const onInputChange = (desc: DeliveryTaskDescription) => { - allowSubmit(isDeliveryTaskDescriptionValid(desc, pickupPoints, dropoffPoints)); - onChange(desc); - }; - - return ( - - - { - const pickupLot = pickupPoints[newValue] ?? ''; - let newTaskDesc = { ...taskDesc }; - newTaskDesc = deliveryInsertPickup(newTaskDesc, newValue, pickupLot); - onInputChange(newTaskDesc); - }} - onBlur={(ev) => { - const place = (ev.target as HTMLInputElement).value; - const pickupLot = pickupPoints[place] ?? ''; - let newTaskDesc = { ...taskDesc }; - newTaskDesc = deliveryInsertPickup(newTaskDesc, place, pickupLot); - onInputChange(newTaskDesc); - }} - sx={{ - '& .MuiOutlinedInput-root': { - height: isScreenHeightLessThan800 ? '3rem' : '3.5rem', - fontSize: isScreenHeightLessThan800 ? 14 : 20, - }, - }} - renderInput={(params) => ( - - )} - /> - - - option} - onInputChange={(_ev, newValue) => { - let newTaskDesc = { ...taskDesc }; - newTaskDesc = deliveryInsertCartId(newTaskDesc, newValue); - onInputChange(newTaskDesc); - }} - onBlur={(ev) => { - let newTaskDesc = { ...taskDesc }; - newTaskDesc = deliveryInsertCartId(newTaskDesc, (ev.target as HTMLInputElement).value); - onInputChange(newTaskDesc); - }} - sx={{ - '& .MuiOutlinedInput-root': { - height: isScreenHeightLessThan800 ? '3rem' : '3.5rem', - fontSize: isScreenHeightLessThan800 ? 14 : 20, - }, - }} - renderInput={(params) => ( - - )} - /> - - - { - let newTaskDesc = { ...taskDesc }; - newTaskDesc = deliveryInsertDropoff(newTaskDesc, newValue); - onInputChange(newTaskDesc); - }} - onBlur={(ev) => { - let newTaskDesc = { ...taskDesc }; - newTaskDesc = deliveryInsertDropoff(newTaskDesc, (ev.target as HTMLInputElement).value); - onInputChange(newTaskDesc); - }} - sx={{ - '& .MuiOutlinedInput-root': { - height: isScreenHeightLessThan800 ? '3rem' : '3.5rem', - fontSize: isScreenHeightLessThan800 ? 14 : 20, - }, - }} - renderInput={(params) => ( - - )} - /> - - - ); -} - -export function deliveryCustomInsertPickup( - taskDescription: DeliveryCustomTaskDescription, - pickupPlace: string, - pickupZone: string, -): DeliveryCustomTaskDescription { - taskDescription.phases[0].activity.description.activities[0].description = pickupPlace; - taskDescription.phases[0].activity.description.activities[1].description.description.pickup_zone = - pickupZone; - return taskDescription; -} - -export function deliveryCustomInsertCartId( - taskDescription: DeliveryCustomTaskDescription, - cartId: string, -): DeliveryCustomTaskDescription { - taskDescription.phases[0].activity.description.activities[1].description.description.cart_id = - cartId; - return taskDescription; -} - -export function deliveryCustomInsertDropoff( - taskDescription: DeliveryCustomTaskDescription, - dropoffPlace: string, -): DeliveryCustomTaskDescription { - taskDescription.phases[1].activity.description.activities[0].description = dropoffPlace; - return taskDescription; -} - -export function deliveryCustomInsertOnCancel( - taskDescription: DeliveryCustomTaskDescription, - onCancelPlaces: string[], -): DeliveryCustomTaskDescription { - const goToOneOfThePlaces: GoToOneOfThePlacesActivity = { - category: 'go_to_place', - description: { - one_of: onCancelPlaces.map((placeName) => { - return { - waypoint: placeName, - }; - }), - constraints: [ - { - category: 'prefer_same_map', - description: '', - }, - ], - }, - }; - const deliveryDropoff: DropoffActivity = { - category: 'perform_action', - description: { - unix_millis_action_duration_estimate: 60000, - category: 'delivery_dropoff', - description: {}, - }, - }; - const onCancelDropoff: OnCancelDropoff = { - category: 'sequence', - description: [goToOneOfThePlaces, deliveryDropoff], - }; - taskDescription.phases[1].on_cancel = [onCancelDropoff]; - return taskDescription; -} - -interface DeliveryCustomProps { - taskDesc: DeliveryCustomTaskDescription; - pickupZones: string[]; - cartIds: string[]; - dropoffPoints: string[]; - onChange(taskDesc: DeliveryCustomTaskDescription): void; - allowSubmit(allow: boolean): void; -} - -function DeliveryCustomTaskForm({ - taskDesc, - pickupZones = [], - cartIds = [], - dropoffPoints = [], - onChange, - allowSubmit, -}: DeliveryCustomProps) { - const theme = useTheme(); - const isScreenHeightLessThan800 = useMediaQuery('(max-height:800px)'); - const onInputChange = (desc: DeliveryCustomTaskDescription) => { - allowSubmit(isDeliveryCustomTaskDescriptionValid(desc, pickupZones, dropoffPoints)); - onChange(desc); - }; - - return ( - - - { - let newTaskDesc = { ...taskDesc }; - newTaskDesc = deliveryCustomInsertPickup(newTaskDesc, newValue, newValue); - onInputChange(newTaskDesc); - }} - onBlur={(ev) => { - const zone = (ev.target as HTMLInputElement).value; - let newTaskDesc = { ...taskDesc }; - newTaskDesc = deliveryCustomInsertPickup(newTaskDesc, zone, zone); - onInputChange(newTaskDesc); - }} - sx={{ - '& .MuiOutlinedInput-root': { - height: isScreenHeightLessThan800 ? '3rem' : '3.5rem', - fontSize: isScreenHeightLessThan800 ? 14 : 20, - }, - }} - renderInput={(params) => ( - - )} - /> - - - option} - onInputChange={(_ev, newValue) => { - let newTaskDesc = { ...taskDesc }; - newTaskDesc = deliveryCustomInsertCartId(newTaskDesc, newValue); - onInputChange(newTaskDesc); - }} - onBlur={(ev) => { - let newTaskDesc = { ...taskDesc }; - newTaskDesc = deliveryCustomInsertCartId( - newTaskDesc, - (ev.target as HTMLInputElement).value, - ); - onInputChange(newTaskDesc); - }} - sx={{ - '& .MuiOutlinedInput-root': { - height: isScreenHeightLessThan800 ? '3rem' : '3.5rem', - fontSize: isScreenHeightLessThan800 ? 14 : 20, - }, - }} - renderInput={(params) => ( - - )} - /> - - - { - let newTaskDesc = { ...taskDesc }; - newTaskDesc = deliveryCustomInsertDropoff(newTaskDesc, newValue); - onInputChange(newTaskDesc); - }} - onBlur={(ev) => { - let newTaskDesc = { ...taskDesc }; - newTaskDesc = deliveryCustomInsertDropoff( - newTaskDesc, - (ev.target as HTMLInputElement).value, - ); - onInputChange(newTaskDesc); - }} - sx={{ - '& .MuiOutlinedInput-root': { - height: isScreenHeightLessThan800 ? '3rem' : '3.5rem', - fontSize: isScreenHeightLessThan800 ? 14 : 20, - }, - }} - renderInput={(params) => ( - - )} - /> - - - ); -} - interface PlaceListProps { places: string[]; onClick(places_index: number): void; @@ -976,134 +335,6 @@ function FavoriteTask({ ); } -export function defaultDeliveryTaskDescription(): DeliveryTaskDescription { - return { - category: 'delivery_pickup', - phases: [ - { - activity: { - category: 'sequence', - description: { - activities: [ - { - category: 'go_to_place', - description: '', - }, - { - category: 'perform_action', - description: { - unix_millis_action_duration_estimate: 60000, - category: 'delivery_pickup', - description: { - cart_id: '', - pickup_lot: '', - }, - }, - }, - ], - }, - }, - }, - { - activity: { - category: 'sequence', - description: { - activities: [ - { - category: 'go_to_place', - description: '', - }, - ], - }, - }, - on_cancel: [], - }, - { - activity: { - category: 'sequence', - description: { - activities: [ - { - category: 'perform_action', - description: { - unix_millis_action_duration_estimate: 60000, - category: 'delivery_dropoff', - description: {}, - }, - }, - ], - }, - }, - }, - ], - }; -} - -export function defaultDeliveryCustomTaskDescription( - taskCategory: string, -): DeliveryCustomTaskDescription { - return { - category: taskCategory, - phases: [ - { - activity: { - category: 'sequence', - description: { - activities: [ - { - category: 'go_to_place', - description: '', - }, - { - category: 'perform_action', - description: { - unix_millis_action_duration_estimate: 60000, - category: taskCategory, - description: { - cart_id: '', - pickup_zone: '', - }, - }, - }, - ], - }, - }, - }, - { - activity: { - category: 'sequence', - description: { - activities: [ - { - category: 'go_to_place', - description: '', - }, - ], - }, - }, - on_cancel: [], - }, - { - activity: { - category: 'sequence', - description: { - activities: [ - { - category: 'perform_action', - description: { - unix_millis_action_duration_estimate: 60000, - category: 'delivery_dropoff', - description: {}, - }, - }, - ], - }, - }, - }, - ], - }; -} - export function defaultPatrolTask(): PatrolTaskDescription { return { places: [], @@ -1449,44 +680,7 @@ export function CreateTaskForm({ request.requester = requester; request.unix_millis_request_time = Date.now(); - if ( - taskType === 'delivery_pickup' || - taskType === 'delivery_sequential_lot_pickup' || - taskType === 'delivery_area_pickup' - ) { - const goToOneOfThePlaces: GoToOneOfThePlacesActivity = { - category: 'go_to_place', - description: { - one_of: emergencyLots.map((placeName) => { - return { - waypoint: placeName, - }; - }), - constraints: [ - { - category: 'prefer_same_map', - description: '', - }, - ], - }, - }; - - // FIXME: there should not be any statically defined duration estimates as - // it makes assumptions of the deployments. - const deliveryDropoff: DropoffActivity = { - category: 'perform_action', - description: { - unix_millis_action_duration_estimate: 60000, - category: 'delivery_dropoff', - description: {}, - }, - }; - const onCancelDropoff: OnCancelDropoff = { - category: 'sequence', - description: [goToOneOfThePlaces, deliveryDropoff], - }; - request.description.phases[1].on_cancel = [onCancelDropoff]; - } else if (taskType === 'custom_compose') { + if (taskType === 'custom_compose') { try { const obj = JSON.parse(request.description); request.category = 'compose'; diff --git a/packages/react-components/lib/tasks/descriptions/delivery_custom.tsx b/packages/react-components/lib/tasks/descriptions/delivery_custom.tsx new file mode 100644 index 000000000..c4ceb2305 --- /dev/null +++ b/packages/react-components/lib/tasks/descriptions/delivery_custom.tsx @@ -0,0 +1,715 @@ +import { Autocomplete, Grid, TextField, useMediaQuery, useTheme } from '@mui/material'; +import React from 'react'; +import { isNonEmptyString } from './utils'; + +export interface LotPickupActivity { + category: string; + description: { + unix_millis_action_duration_estimate: number; + category: string; + description: { + cart_id: string; + pickup_lot: string; + }; + }; +} + +interface ZonePickupActivity { + category: string; + description: { + unix_millis_action_duration_estimate: number; + category: string; + description: { + cart_id: string; + pickup_zone: string; + }; + }; +} + +export interface GoToPlaceActivity { + category: string; + description: string; +} + +interface CartCustomPickupPhase { + activity: { + category: string; + description: { + activities: [go_to_pickup: GoToPlaceActivity, pickup_cart: ZonePickupActivity]; + }; + }; +} + +interface CartPickupPhase { + activity: { + category: string; + description: { + activities: [go_to_pickup: GoToPlaceActivity, pickup_cart: LotPickupActivity]; + }; + }; +} + +interface DropoffActivity { + category: string; + description: { + unix_millis_action_duration_estimate: number; + category: string; + description: {}; + }; +} + +interface OneOfWaypoint { + waypoint: string; +} + +interface GoToOneOfThePlacesActivity { + category: string; + description: { + one_of: OneOfWaypoint[]; + constraints: [ + { + category: string; + description: string; + }, + ]; + }; +} + +interface OnCancelDropoff { + category: string; + description: [ + go_to_one_of_the_places: GoToOneOfThePlacesActivity, + delivery_dropoff: DropoffActivity, + ]; +} + +interface DeliveryWithCancellationPhase { + activity: { + category: string; + description: { + activities: [go_to_place: GoToPlaceActivity]; + }; + }; + on_cancel: OnCancelDropoff[]; +} + +interface CartDropoffPhase { + activity: { + category: string; + description: { + activities: [delivery_dropoff: DropoffActivity]; + }; + }; +} + +export interface DeliveryCustomTaskDescription { + category: string; + phases: [ + pickup_phase: CartCustomPickupPhase, + delivery_phase: DeliveryWithCancellationPhase, + dropoff_phase: CartDropoffPhase, + ]; +} + +export interface DeliveryTaskDescription { + category: string; + phases: [ + pickup_phase: CartPickupPhase, + delivery_phase: DeliveryWithCancellationPhase, + dropoff_phase: CartDropoffPhase, + ]; +} + +const isDeliveryTaskDescriptionValid = ( + taskDescription: DeliveryTaskDescription, + pickupPoints: Record, + dropoffPoints: Record, +): boolean => { + const goToPickup = taskDescription.phases[0].activity.description.activities[0]; + const pickup = taskDescription.phases[0].activity.description.activities[1]; + const goToDropoff = taskDescription.phases[1].activity.description.activities[0]; + return ( + isNonEmptyString(goToPickup.description) && + Object.keys(pickupPoints).includes(goToPickup.description) && + pickupPoints[goToPickup.description] === pickup.description.description.pickup_lot && + isNonEmptyString(pickup.description.description.cart_id) && + isNonEmptyString(goToDropoff.description) && + Object.keys(dropoffPoints).includes(goToDropoff.description) + ); +}; + +const isDeliveryCustomTaskDescriptionValid = ( + taskDescription: DeliveryCustomTaskDescription, + pickupZones: string[], + dropoffPoints: string[], +): boolean => { + const goToPickup = taskDescription.phases[0].activity.description.activities[0]; + const pickup = taskDescription.phases[0].activity.description.activities[1]; + const goToDropoff = taskDescription.phases[1].activity.description.activities[0]; + return ( + isNonEmptyString(goToPickup.description) && + isNonEmptyString(pickup.description.description.pickup_zone) && + pickupZones.includes(pickup.description.description.pickup_zone) && + isNonEmptyString(pickup.description.description.cart_id) && + isNonEmptyString(goToDropoff.description) && + dropoffPoints.includes(goToDropoff.description) + ); +}; + +export function deliveryInsertPickup( + taskDescription: DeliveryTaskDescription, + pickupPlace: string, + pickupLot: string, +): DeliveryTaskDescription { + taskDescription.phases[0].activity.description.activities[0].description = pickupPlace; + taskDescription.phases[0].activity.description.activities[1].description.description.pickup_lot = + pickupLot; + return taskDescription; +} + +export function deliveryInsertCartId( + taskDescription: DeliveryTaskDescription, + cartId: string, +): DeliveryTaskDescription { + taskDescription.phases[0].activity.description.activities[1].description.description.cart_id = + cartId; + return taskDescription; +} + +export function deliveryInsertDropoff( + taskDescription: DeliveryTaskDescription, + dropoffPlace: string, +): DeliveryTaskDescription { + taskDescription.phases[1].activity.description.activities[0].description = dropoffPlace; + return taskDescription; +} + +export function deliveryInsertOnCancel( + taskDescription: DeliveryTaskDescription, + onCancelPlaces: string[], +): DeliveryTaskDescription { + const goToOneOfThePlaces: GoToOneOfThePlacesActivity = { + category: 'go_to_place', + description: { + one_of: onCancelPlaces.map((placeName) => { + return { + waypoint: placeName, + }; + }), + constraints: [ + { + category: 'prefer_same_map', + description: '', + }, + ], + }, + }; + const deliveryDropoff: DropoffActivity = { + category: 'perform_action', + description: { + unix_millis_action_duration_estimate: 60000, + category: 'delivery_dropoff', + description: {}, + }, + }; + const onCancelDropoff: OnCancelDropoff = { + category: 'sequence', + description: [goToOneOfThePlaces, deliveryDropoff], + }; + taskDescription.phases[1].on_cancel = [onCancelDropoff]; + return taskDescription; +} + +interface DeliveryTaskFormProps { + taskDesc: DeliveryTaskDescription; + pickupPoints: Record; + cartIds: string[]; + dropoffPoints: Record; + onChange(taskDesc: DeliveryTaskDescription): void; + allowSubmit(allow: boolean): void; +} + +export function DeliveryTaskForm({ + taskDesc, + pickupPoints = {}, + cartIds = [], + dropoffPoints = {}, + onChange, + allowSubmit, +}: DeliveryTaskFormProps) { + const theme = useTheme(); + const isScreenHeightLessThan800 = useMediaQuery('(max-height:800px)'); + const onInputChange = (desc: DeliveryTaskDescription) => { + allowSubmit(isDeliveryTaskDescriptionValid(desc, pickupPoints, dropoffPoints)); + onChange(desc); + }; + + return ( + + + { + const pickupLot = pickupPoints[newValue] ?? ''; + let newTaskDesc = { ...taskDesc }; + newTaskDesc = deliveryInsertPickup(newTaskDesc, newValue, pickupLot); + onInputChange(newTaskDesc); + }} + onBlur={(ev) => { + const place = (ev.target as HTMLInputElement).value; + const pickupLot = pickupPoints[place] ?? ''; + let newTaskDesc = { ...taskDesc }; + newTaskDesc = deliveryInsertPickup(newTaskDesc, place, pickupLot); + onInputChange(newTaskDesc); + }} + sx={{ + '& .MuiOutlinedInput-root': { + height: isScreenHeightLessThan800 ? '3rem' : '3.5rem', + fontSize: isScreenHeightLessThan800 ? 14 : 20, + }, + }} + renderInput={(params) => ( + + )} + /> + + + option} + onInputChange={(_ev, newValue) => { + let newTaskDesc = { ...taskDesc }; + newTaskDesc = deliveryInsertCartId(newTaskDesc, newValue); + onInputChange(newTaskDesc); + }} + onBlur={(ev) => { + let newTaskDesc = { ...taskDesc }; + newTaskDesc = deliveryInsertCartId(newTaskDesc, (ev.target as HTMLInputElement).value); + onInputChange(newTaskDesc); + }} + sx={{ + '& .MuiOutlinedInput-root': { + height: isScreenHeightLessThan800 ? '3rem' : '3.5rem', + fontSize: isScreenHeightLessThan800 ? 14 : 20, + }, + }} + renderInput={(params) => ( + + )} + /> + + + { + let newTaskDesc = { ...taskDesc }; + newTaskDesc = deliveryInsertDropoff(newTaskDesc, newValue); + onInputChange(newTaskDesc); + }} + onBlur={(ev) => { + let newTaskDesc = { ...taskDesc }; + newTaskDesc = deliveryInsertDropoff(newTaskDesc, (ev.target as HTMLInputElement).value); + onInputChange(newTaskDesc); + }} + sx={{ + '& .MuiOutlinedInput-root': { + height: isScreenHeightLessThan800 ? '3rem' : '3.5rem', + fontSize: isScreenHeightLessThan800 ? 14 : 20, + }, + }} + renderInput={(params) => ( + + )} + /> + + + ); +} + +export function deliveryCustomInsertPickup( + taskDescription: DeliveryCustomTaskDescription, + pickupPlace: string, + pickupZone: string, +): DeliveryCustomTaskDescription { + taskDescription.phases[0].activity.description.activities[0].description = pickupPlace; + taskDescription.phases[0].activity.description.activities[1].description.description.pickup_zone = + pickupZone; + return taskDescription; +} + +export function deliveryCustomInsertCartId( + taskDescription: DeliveryCustomTaskDescription, + cartId: string, +): DeliveryCustomTaskDescription { + taskDescription.phases[0].activity.description.activities[1].description.description.cart_id = + cartId; + return taskDescription; +} + +export function deliveryCustomInsertDropoff( + taskDescription: DeliveryCustomTaskDescription, + dropoffPlace: string, +): DeliveryCustomTaskDescription { + taskDescription.phases[1].activity.description.activities[0].description = dropoffPlace; + return taskDescription; +} + +export function deliveryCustomInsertOnCancel( + taskDescription: DeliveryCustomTaskDescription, + onCancelPlaces: string[], +): DeliveryCustomTaskDescription { + const goToOneOfThePlaces: GoToOneOfThePlacesActivity = { + category: 'go_to_place', + description: { + one_of: onCancelPlaces.map((placeName) => { + return { + waypoint: placeName, + }; + }), + constraints: [ + { + category: 'prefer_same_map', + description: '', + }, + ], + }, + }; + const deliveryDropoff: DropoffActivity = { + category: 'perform_action', + description: { + unix_millis_action_duration_estimate: 60000, + category: 'delivery_dropoff', + description: {}, + }, + }; + const onCancelDropoff: OnCancelDropoff = { + category: 'sequence', + description: [goToOneOfThePlaces, deliveryDropoff], + }; + taskDescription.phases[1].on_cancel = [onCancelDropoff]; + return taskDescription; +} + +interface DeliveryCustomProps { + taskDesc: DeliveryCustomTaskDescription; + pickupZones: string[]; + cartIds: string[]; + dropoffPoints: string[]; + onChange(taskDesc: DeliveryCustomTaskDescription): void; + allowSubmit(allow: boolean): void; +} + +export function DeliveryCustomTaskForm({ + taskDesc, + pickupZones = [], + cartIds = [], + dropoffPoints = [], + onChange, + allowSubmit, +}: DeliveryCustomProps) { + const theme = useTheme(); + const isScreenHeightLessThan800 = useMediaQuery('(max-height:800px)'); + const onInputChange = (desc: DeliveryCustomTaskDescription) => { + allowSubmit(isDeliveryCustomTaskDescriptionValid(desc, pickupZones, dropoffPoints)); + onChange(desc); + }; + + return ( + + + { + let newTaskDesc = { ...taskDesc }; + newTaskDesc = deliveryCustomInsertPickup(newTaskDesc, newValue, newValue); + onInputChange(newTaskDesc); + }} + onBlur={(ev) => { + const zone = (ev.target as HTMLInputElement).value; + let newTaskDesc = { ...taskDesc }; + newTaskDesc = deliveryCustomInsertPickup(newTaskDesc, zone, zone); + onInputChange(newTaskDesc); + }} + sx={{ + '& .MuiOutlinedInput-root': { + height: isScreenHeightLessThan800 ? '3rem' : '3.5rem', + fontSize: isScreenHeightLessThan800 ? 14 : 20, + }, + }} + renderInput={(params) => ( + + )} + /> + + + option} + onInputChange={(_ev, newValue) => { + let newTaskDesc = { ...taskDesc }; + newTaskDesc = deliveryCustomInsertCartId(newTaskDesc, newValue); + onInputChange(newTaskDesc); + }} + onBlur={(ev) => { + let newTaskDesc = { ...taskDesc }; + newTaskDesc = deliveryCustomInsertCartId( + newTaskDesc, + (ev.target as HTMLInputElement).value, + ); + onInputChange(newTaskDesc); + }} + sx={{ + '& .MuiOutlinedInput-root': { + height: isScreenHeightLessThan800 ? '3rem' : '3.5rem', + fontSize: isScreenHeightLessThan800 ? 14 : 20, + }, + }} + renderInput={(params) => ( + + )} + /> + + + { + let newTaskDesc = { ...taskDesc }; + newTaskDesc = deliveryCustomInsertDropoff(newTaskDesc, newValue); + onInputChange(newTaskDesc); + }} + onBlur={(ev) => { + let newTaskDesc = { ...taskDesc }; + newTaskDesc = deliveryCustomInsertDropoff( + newTaskDesc, + (ev.target as HTMLInputElement).value, + ); + onInputChange(newTaskDesc); + }} + sx={{ + '& .MuiOutlinedInput-root': { + height: isScreenHeightLessThan800 ? '3rem' : '3.5rem', + fontSize: isScreenHeightLessThan800 ? 14 : 20, + }, + }} + renderInput={(params) => ( + + )} + /> + + + ); +} + +export function defaultDeliveryTaskDescription(): DeliveryTaskDescription { + return { + category: 'delivery_pickup', + phases: [ + { + activity: { + category: 'sequence', + description: { + activities: [ + { + category: 'go_to_place', + description: '', + }, + { + category: 'perform_action', + description: { + unix_millis_action_duration_estimate: 60000, + category: 'delivery_pickup', + description: { + cart_id: '', + pickup_lot: '', + }, + }, + }, + ], + }, + }, + }, + { + activity: { + category: 'sequence', + description: { + activities: [ + { + category: 'go_to_place', + description: '', + }, + ], + }, + }, + on_cancel: [], + }, + { + activity: { + category: 'sequence', + description: { + activities: [ + { + category: 'perform_action', + description: { + unix_millis_action_duration_estimate: 60000, + category: 'delivery_dropoff', + description: {}, + }, + }, + ], + }, + }, + }, + ], + }; +} + +export function defaultDeliveryCustomTaskDescription( + taskCategory: string, +): DeliveryCustomTaskDescription { + return { + category: taskCategory, + phases: [ + { + activity: { + category: 'sequence', + description: { + activities: [ + { + category: 'go_to_place', + description: '', + }, + { + category: 'perform_action', + description: { + unix_millis_action_duration_estimate: 60000, + category: taskCategory, + description: { + cart_id: '', + pickup_zone: '', + }, + }, + }, + ], + }, + }, + }, + { + activity: { + category: 'sequence', + description: { + activities: [ + { + category: 'go_to_place', + description: '', + }, + ], + }, + }, + on_cancel: [], + }, + { + activity: { + category: 'sequence', + description: { + activities: [ + { + category: 'perform_action', + description: { + unix_millis_action_duration_estimate: 60000, + category: 'delivery_dropoff', + description: {}, + }, + }, + ], + }, + }, + }, + ], + }; +} diff --git a/packages/react-components/lib/tasks/descriptions/index.ts b/packages/react-components/lib/tasks/descriptions/index.ts new file mode 100644 index 000000000..3f7e37356 --- /dev/null +++ b/packages/react-components/lib/tasks/descriptions/index.ts @@ -0,0 +1,3 @@ +// export * from './clean'; +export * from './delivery_custom'; +export * from './utils'; diff --git a/packages/react-components/lib/tasks/descriptions/utils.ts b/packages/react-components/lib/tasks/descriptions/utils.ts new file mode 100644 index 000000000..fb8dfccb5 --- /dev/null +++ b/packages/react-components/lib/tasks/descriptions/utils.ts @@ -0,0 +1,73 @@ +import { TaskRequest } from 'api-client'; +import { GoToPlaceActivity, LotPickupActivity } from './delivery_custom'; + +export function isNonEmptyString(value: string): boolean { + return value.length > 0; +} + +export function isPositiveNumber(value: number): boolean { + return value > 0; +} + +export function getShortDescription(taskRequest: TaskRequest): string | undefined { + if (taskRequest.category === 'patrol') { + const formattedPlaces = taskRequest.description.places.map((place: string) => `[${place}]`); + return `[Patrol] [${taskRequest.description.rounds}] round/s, along ${formattedPlaces.join( + ', ', + )}`; + } + + // This section is only valid for custom delivery types + // FIXME: This block looks like it makes assumptions about the structure of + // the task description in order to parse it, but it is following the + // statically defined description (object) at the top of this file. The + // descriptions should be replaced by a schema in general, however the better + // approach now should be to make each task description testable and in charge + // of their own short descriptions. + try { + const goToPickup: GoToPlaceActivity = + taskRequest.description.phases[0].activity.description.activities[0]; + const pickup: LotPickupActivity = + taskRequest.description.phases[0].activity.description.activities[1]; + const cartId = pickup.description.description.cart_id; + const goToDropoff: GoToPlaceActivity = + taskRequest.description.phases[1].activity.description.activities[0]; + + switch (taskRequest.description.category) { + case 'delivery_pickup': { + return `[Delivery - 1:1] payload [${cartId}] from [${goToPickup.description}] to [${goToDropoff.description}]`; + } + case 'delivery_sequential_lot_pickup': { + return `[Delivery - Sequential lot pick up] payload [${cartId}] from [${goToPickup.description}] to [${goToDropoff.description}]`; + } + case 'delivery_area_pickup': { + return `[Delivery - Area pick up] payload [${cartId}] from [${goToPickup.description}] to [${goToDropoff.description}]`; + } + default: + return `[Unknown] type "${taskRequest.description.category}"`; + } + } catch (e) { + if (e instanceof TypeError) { + console.error(`Failed to parse custom delivery: ${e.message}`); + } else { + console.error( + `Failed to generate short description from task of category: ${taskRequest.category}: ${ + (e as Error).message + }`, + ); + } + + try { + const descriptionString = JSON.stringify(taskRequest.description); + console.error(descriptionString); + return descriptionString; + } catch (e) { + console.error( + `Failed to parse description of task of category: ${taskRequest.category}: ${ + (e as Error).message + }`, + ); + return undefined; + } + } +} diff --git a/packages/react-components/lib/tasks/index.ts b/packages/react-components/lib/tasks/index.ts index 6cc17baaf..30b764f1c 100644 --- a/packages/react-components/lib/tasks/index.ts +++ b/packages/react-components/lib/tasks/index.ts @@ -1,4 +1,5 @@ export * from './create-task'; +export * from './descriptions'; export * from './task-info'; export * from './task-logs'; export * from './task-table'; From 6eae24431a7c14ee0bf8be8e674ecaed01dbfcb5 Mon Sep 17 00:00:00 2001 From: Aaron Chong Date: Wed, 27 Mar 2024 11:25:16 +0800 Subject: [PATCH 02/28] Moved patrol Signed-off-by: Aaron Chong --- .../lib/tasks/create-task.tsx | 146 +--------------- .../lib/tasks/descriptions/patrol.tsx | 161 ++++++++++++++++++ 2 files changed, 162 insertions(+), 145 deletions(-) create mode 100644 packages/react-components/lib/tasks/descriptions/patrol.tsx diff --git a/packages/react-components/lib/tasks/create-task.tsx b/packages/react-components/lib/tasks/create-task.tsx index 6e6285552..c883f4c66 100644 --- a/packages/react-components/lib/tasks/create-task.tsx +++ b/packages/react-components/lib/tasks/create-task.tsx @@ -5,9 +5,7 @@ import UpdateIcon from '@mui/icons-material/Create'; import DeleteIcon from '@mui/icons-material/Delete'; -import PlaceOutlined from '@mui/icons-material/PlaceOutlined'; import { - Autocomplete, Box, Button, Checkbox, @@ -25,7 +23,6 @@ import { IconButton, List, ListItem, - ListItemIcon, ListItemSecondaryAction, ListItemText, MenuItem, @@ -42,7 +39,6 @@ import type { TaskFavoritePydantic as TaskFavorite, TaskRequest } from 'api-clie import React from 'react'; import { Loading } from '..'; import { ConfirmationDialog, ConfirmationDialogProps } from '../confirmation-dialog'; -import { PositiveIntField } from '../form-inputs'; import { defaultDeliveryCustomTaskDescription, defaultDeliveryTaskDescription, @@ -51,12 +47,7 @@ import { DeliveryTaskDescription, DeliveryTaskForm, } from './descriptions/delivery_custom'; - -// A bunch of manually defined descriptions to avoid using `any`. -export interface PatrolTaskDescription { - places: string[]; - rounds: number; -} +import { defaultPatrolTask, PatrolTaskDescription, PatrolTaskForm } from './descriptions/patrol'; type CustomComposeTaskDescription = string; @@ -65,18 +56,6 @@ type TaskDescription = | DeliveryCustomTaskDescription | PatrolTaskDescription; -const isPatrolTaskDescriptionValid = (taskDescription: PatrolTaskDescription): boolean => { - if (taskDescription.places.length === 0) { - return false; - } - for (const place of taskDescription.places) { - if (place.length === 0) { - return false; - } - } - return taskDescription.rounds > 0; -}; - const isCustomTaskDescriptionValid = (taskDescription: string): boolean => { if (taskDescription.length === 0) { return false; @@ -119,122 +98,6 @@ const StyledDialog = styled((props: DialogProps) => )(({ th }, })); -interface PlaceListProps { - places: string[]; - onClick(places_index: number): void; -} - -function PlaceList({ places, onClick }: PlaceListProps) { - const theme = useTheme(); - return ( - - {places.map((value, index) => ( - onClick(index)}> - - - } - > - - - - - - ))} - - ); -} - -interface PatrolTaskFormProps { - taskDesc: PatrolTaskDescription; - patrolWaypoints: string[]; - onChange(patrolTaskDescription: PatrolTaskDescription): void; - allowSubmit(allow: boolean): void; -} - -function PatrolTaskForm({ taskDesc, patrolWaypoints, onChange, allowSubmit }: PatrolTaskFormProps) { - const theme = useTheme(); - const isScreenHeightLessThan800 = useMediaQuery('(max-height:800px)'); - const onInputChange = (desc: PatrolTaskDescription) => { - allowSubmit(isPatrolTaskDescriptionValid(desc)); - onChange(desc); - }; - allowSubmit(isPatrolTaskDescriptionValid(taskDesc)); - - return ( - - - - newValue !== null && - onInputChange({ - ...taskDesc, - places: taskDesc.places.concat(newValue).filter((el: string) => el), - }) - } - sx={{ - '& .MuiOutlinedInput-root': { - height: isScreenHeightLessThan800 ? '3rem' : '3.5rem', - fontSize: isScreenHeightLessThan800 ? 14 : 20, - }, - }} - renderInput={(params) => ( - - )} - /> - - - { - onInputChange({ - ...taskDesc, - rounds: val, - }); - }} - /> - - - - taskDesc.places.splice(places_index, 1) && - onInputChange({ - ...taskDesc, - }) - } - /> - - - ); -} - interface CustomComposeTaskFormProps { taskDesc: CustomComposeTaskDescription; onChange(customComposeTaskDescription: CustomComposeTaskDescription): void; @@ -335,13 +198,6 @@ function FavoriteTask({ ); } -export function defaultPatrolTask(): PatrolTaskDescription { - return { - places: [], - rounds: 1, - }; -} - function defaultTaskDescription(taskCategory: string): TaskDescription | undefined { switch (taskCategory) { case 'delivery_pickup': diff --git a/packages/react-components/lib/tasks/descriptions/patrol.tsx b/packages/react-components/lib/tasks/descriptions/patrol.tsx new file mode 100644 index 000000000..3b1e3ecdd --- /dev/null +++ b/packages/react-components/lib/tasks/descriptions/patrol.tsx @@ -0,0 +1,161 @@ +import DeleteIcon from '@mui/icons-material/Delete'; +import PlaceOutlined from '@mui/icons-material/PlaceOutlined'; +import { + Autocomplete, + Grid, + IconButton, + List, + ListItem, + ListItemIcon, + ListItemText, + TextField, + useMediaQuery, + useTheme, +} from '@mui/material'; +import React from 'react'; +import { PositiveIntField } from '../../form-inputs'; + +export interface PatrolTaskDescription { + places: string[]; + rounds: number; +} + +export const isPatrolTaskDescriptionValid = (taskDescription: PatrolTaskDescription): boolean => { + if (taskDescription.places.length === 0) { + return false; + } + for (const place of taskDescription.places) { + if (place.length === 0) { + return false; + } + } + return taskDescription.rounds > 0; +}; + +export function defaultPatrolTask(): PatrolTaskDescription { + return { + places: [], + rounds: 1, + }; +} + +interface PlaceListProps { + places: string[]; + onClick(places_index: number): void; +} + +function PlaceList({ places, onClick }: PlaceListProps) { + const theme = useTheme(); + return ( + + {places.map((value, index) => ( + onClick(index)}> + + + } + > + + + + + + ))} + + ); +} + +interface PatrolTaskFormProps { + taskDesc: PatrolTaskDescription; + patrolWaypoints: string[]; + onChange(patrolTaskDescription: PatrolTaskDescription): void; + allowSubmit(allow: boolean): void; +} + +export function PatrolTaskForm({ + taskDesc, + patrolWaypoints, + onChange, + allowSubmit, +}: PatrolTaskFormProps) { + const theme = useTheme(); + const isScreenHeightLessThan800 = useMediaQuery('(max-height:800px)'); + const onInputChange = (desc: PatrolTaskDescription) => { + allowSubmit(isPatrolTaskDescriptionValid(desc)); + onChange(desc); + }; + allowSubmit(isPatrolTaskDescriptionValid(taskDesc)); + + return ( + + + + newValue !== null && + onInputChange({ + ...taskDesc, + places: taskDesc.places.concat(newValue).filter((el: string) => el), + }) + } + sx={{ + '& .MuiOutlinedInput-root': { + height: isScreenHeightLessThan800 ? '3rem' : '3.5rem', + fontSize: isScreenHeightLessThan800 ? 14 : 20, + }, + }} + renderInput={(params) => ( + + )} + /> + + + { + onInputChange({ + ...taskDesc, + rounds: val, + }); + }} + /> + + + + taskDesc.places.splice(places_index, 1) && + onInputChange({ + ...taskDesc, + }) + } + /> + + + ); +} From 9bda5085dc33b6afa843ad02810fc5c185c8e4df Mon Sep 17 00:00:00 2001 From: Aaron Chong Date: Wed, 27 Mar 2024 11:38:43 +0800 Subject: [PATCH 03/28] Moved custom-compose Signed-off-by: Aaron Chong --- .../lib/tasks/create-task.tsx | 48 +---------------- .../lib/tasks/descriptions/custom-compose.tsx | 53 +++++++++++++++++++ .../lib/tasks/descriptions/index.ts | 2 + 3 files changed, 56 insertions(+), 47 deletions(-) create mode 100644 packages/react-components/lib/tasks/descriptions/custom-compose.tsx diff --git a/packages/react-components/lib/tasks/create-task.tsx b/packages/react-components/lib/tasks/create-task.tsx index c883f4c66..db14a6b5d 100644 --- a/packages/react-components/lib/tasks/create-task.tsx +++ b/packages/react-components/lib/tasks/create-task.tsx @@ -39,6 +39,7 @@ import type { TaskFavoritePydantic as TaskFavorite, TaskRequest } from 'api-clie import React from 'react'; import { Loading } from '..'; import { ConfirmationDialog, ConfirmationDialogProps } from '../confirmation-dialog'; +import { CustomComposeTaskDescription, CustomComposeTaskForm } from './descriptions/custom-compose'; import { defaultDeliveryCustomTaskDescription, defaultDeliveryTaskDescription, @@ -49,27 +50,11 @@ import { } from './descriptions/delivery_custom'; import { defaultPatrolTask, PatrolTaskDescription, PatrolTaskForm } from './descriptions/patrol'; -type CustomComposeTaskDescription = string; - type TaskDescription = | DeliveryTaskDescription | DeliveryCustomTaskDescription | PatrolTaskDescription; -const isCustomTaskDescriptionValid = (taskDescription: string): boolean => { - if (taskDescription.length === 0) { - return false; - } - - try { - JSON.parse(taskDescription); - } catch (e) { - return false; - } - - return true; -}; - const classes = { title: 'dialogue-info-value', selectFileBtn: 'create-task-selected-file-btn', @@ -98,37 +83,6 @@ const StyledDialog = styled((props: DialogProps) => )(({ th }, })); -interface CustomComposeTaskFormProps { - taskDesc: CustomComposeTaskDescription; - onChange(customComposeTaskDescription: CustomComposeTaskDescription): void; - allowSubmit(allow: boolean): void; -} - -function CustomComposeTaskForm({ taskDesc, onChange, allowSubmit }: CustomComposeTaskFormProps) { - const theme = useTheme(); - const onInputChange = (desc: CustomComposeTaskDescription) => { - allowSubmit(isCustomTaskDescriptionValid(desc)); - onChange(desc); - }; - - return ( - - - { - onInputChange(ev.target.value); - }} - /> - - - ); -} - interface FavoriteTaskProps { listItemText: string; listItemClick: () => void; diff --git a/packages/react-components/lib/tasks/descriptions/custom-compose.tsx b/packages/react-components/lib/tasks/descriptions/custom-compose.tsx new file mode 100644 index 000000000..8101cb6f8 --- /dev/null +++ b/packages/react-components/lib/tasks/descriptions/custom-compose.tsx @@ -0,0 +1,53 @@ +import { Grid, TextField, useTheme } from '@mui/material'; +import React from 'react'; + +export type CustomComposeTaskDescription = string; + +const isCustomTaskDescriptionValid = (taskDescription: string): boolean => { + if (taskDescription.length === 0) { + return false; + } + + try { + JSON.parse(taskDescription); + } catch (e) { + return false; + } + + return true; +}; + +interface CustomComposeTaskFormProps { + taskDesc: CustomComposeTaskDescription; + onChange(customComposeTaskDescription: CustomComposeTaskDescription): void; + allowSubmit(allow: boolean): void; +} + +export function CustomComposeTaskForm({ + taskDesc, + onChange, + allowSubmit, +}: CustomComposeTaskFormProps) { + const theme = useTheme(); + const onInputChange = (desc: CustomComposeTaskDescription) => { + allowSubmit(isCustomTaskDescriptionValid(desc)); + onChange(desc); + }; + + return ( + + + { + onInputChange(ev.target.value); + }} + /> + + + ); +} diff --git a/packages/react-components/lib/tasks/descriptions/index.ts b/packages/react-components/lib/tasks/descriptions/index.ts index 3f7e37356..e2f96dae5 100644 --- a/packages/react-components/lib/tasks/descriptions/index.ts +++ b/packages/react-components/lib/tasks/descriptions/index.ts @@ -1,3 +1,5 @@ // export * from './clean'; +export * from './custom-compose'; export * from './delivery_custom'; +export * from './patrol'; export * from './utils'; From e0d07f8973978c22e8bf484ceb5025a5af068bcf Mon Sep 17 00:00:00 2001 From: Aaron Chong Date: Wed, 27 Mar 2024 11:54:26 +0800 Subject: [PATCH 04/28] Added clean and delivery Signed-off-by: Aaron Chong --- .../lib/tasks/descriptions/clean.tsx | 123 ++++++++++ .../lib/tasks/descriptions/delivery.tsx | 231 ++++++++++++++++++ 2 files changed, 354 insertions(+) create mode 100644 packages/react-components/lib/tasks/descriptions/clean.tsx create mode 100644 packages/react-components/lib/tasks/descriptions/delivery.tsx diff --git a/packages/react-components/lib/tasks/descriptions/clean.tsx b/packages/react-components/lib/tasks/descriptions/clean.tsx new file mode 100644 index 000000000..73269c02c --- /dev/null +++ b/packages/react-components/lib/tasks/descriptions/clean.tsx @@ -0,0 +1,123 @@ +import { Autocomplete, TextField } from '@mui/material'; +import React from 'react'; + +interface GoToPlaceActivity { + category: string; + description: string; +} + +interface CleanActivity { + category: string; + description: { + unix_millis_action_duration_estimate: number; + category: string; + expected_finish_location: string; + description: { + zone: string; + }; + use_tool_sink: boolean; + }; +} + +export interface CleanTaskDescription { + category: string; + phases: [ + cleanPhase: { + activity: { + category: string; + description: { + activities: [goToPlaceActivity: GoToPlaceActivity, cleanActivity: CleanActivity]; + }; + }; + }, + ]; +} + +export function isCleanTaskDescriptionValid(taskDescription: CleanTaskDescription): boolean { + const goToPlaceActivity = taskDescription.phases[0].activity.description.activities[0]; + const cleanActivity = taskDescription.phases[0].activity.description.activities[1]; + return ( + goToPlaceActivity.description.length !== 0 && + cleanActivity.description.description.zone.length !== 0 && + goToPlaceActivity.description === cleanActivity.description.description.zone + ); +} + +export function makeDefaultCleanTaskDescription(): CleanTaskDescription { + return { + category: 'clean', + phases: [ + { + activity: { + category: 'sequence', + description: { + activities: [ + { + category: 'go_to_place', + description: '', + }, + { + category: 'perform_action', + description: { + unix_millis_action_duration_estimate: 60000, + category: 'clean', + expected_finish_location: '', + description: { + zone: '', + }, + use_tool_sink: true, + }, + }, + ], + }, + }, + }, + ], + }; +} + +interface CleanTaskFormProps { + taskDesc: CleanTaskDescription; + cleaningZones: string[]; + onChange(cleanTaskDescription: CleanTaskDescription): void; + allowSubmit(allow: boolean): void; +} + +export function CleanTaskForm({ + taskDesc, + cleaningZones, + onChange, + allowSubmit, +}: CleanTaskFormProps) { + const onInputChange = (desc: CleanTaskDescription) => { + allowSubmit(isCleanTaskDescriptionValid(desc)); + onChange(desc); + }; + + return ( + { + const zone = newValue ?? ''; + taskDesc.phases[0].activity.description.activities[0].description = zone; + taskDesc.phases[0].activity.description.activities[1].description.expected_finish_location = + zone; + taskDesc.phases[0].activity.description.activities[1].description.description.zone = zone; + onInputChange(taskDesc); + }} + onBlur={(ev) => { + const zone = (ev.target as HTMLInputElement).value; + taskDesc.phases[0].activity.description.activities[0].description = zone; + taskDesc.phases[0].activity.description.activities[1].description.expected_finish_location = + zone; + taskDesc.phases[0].activity.description.activities[1].description.description.zone = zone; + onInputChange(taskDesc); + }} + renderInput={(params) => } + /> + ); +} diff --git a/packages/react-components/lib/tasks/descriptions/delivery.tsx b/packages/react-components/lib/tasks/descriptions/delivery.tsx new file mode 100644 index 000000000..312c004b7 --- /dev/null +++ b/packages/react-components/lib/tasks/descriptions/delivery.tsx @@ -0,0 +1,231 @@ +import { isNonEmptyString, isPositiveNumber } from './utils'; +import { Autocomplete, Grid, TextField, useTheme } from '@mui/material'; +import { PositiveIntField } from '../../form-inputs'; +import React from 'react'; + +interface TaskPlace { + place: string; + handler: string; + payload: { + sku: string; + quantity: number; + }; +} + +export interface DeliveryTaskDescription { + pickup: TaskPlace; + dropoff: TaskPlace; +} + +function isTaskPlaceValid(place: TaskPlace): boolean { + return ( + isNonEmptyString(place.place) && + isNonEmptyString(place.handler) && + isNonEmptyString(place.payload.sku) && + isPositiveNumber(place.payload.quantity) + ); +} + +function isDeliveryTaskDescriptionValid(taskDescription: DeliveryTaskDescription): boolean { + return isTaskPlaceValid(taskDescription.pickup) && isTaskPlaceValid(taskDescription.dropoff); +} + +export function makeDefaultDeliveryTaskDescription(): DeliveryTaskDescription { + return { + pickup: { + place: '', + handler: '', + payload: { + sku: '', + quantity: 1, + }, + }, + dropoff: { + place: '', + handler: '', + payload: { + sku: '', + quantity: 1, + }, + }, + }; +} + +export interface DeliveryTaskFormProps { + taskDesc: DeliveryTaskDescription; + pickupPoints: Record; + dropoffPoints: Record; + onChange(taskDesc: DeliveryTaskDescription): void; + allowSubmit(allow: boolean): void; +} + +export function DeliveryTaskForm({ + taskDesc, + pickupPoints = {}, + dropoffPoints = {}, + onChange, + allowSubmit, +}: DeliveryTaskFormProps) { + const theme = useTheme(); + const onInputChange = (desc: DeliveryTaskDescription) => { + allowSubmit(isDeliveryTaskDescriptionValid(desc)); + onChange(desc); + }; + + return ( + + + { + const place = newValue ?? ''; + const handler = + newValue !== null && pickupPoints[newValue] ? pickupPoints[newValue] : ''; + onInputChange({ + ...taskDesc, + pickup: { + ...taskDesc.pickup, + place: place, + handler: handler, + }, + }); + }} + onBlur={(ev) => + pickupPoints[(ev.target as HTMLInputElement).value] && + onInputChange({ + ...taskDesc, + pickup: { + ...taskDesc.pickup, + place: (ev.target as HTMLInputElement).value, + handler: pickupPoints[(ev.target as HTMLInputElement).value], + }, + }) + } + renderInput={(params) => ( + + )} + /> + + + { + onInputChange({ + ...taskDesc, + pickup: { + ...taskDesc.pickup, + payload: { + ...taskDesc.pickup.payload, + sku: ev.target.value, + }, + }, + }); + }} + /> + + + { + onInputChange({ + ...taskDesc, + pickup: { + ...taskDesc.pickup, + payload: { + ...taskDesc.pickup.payload, + quantity: val, + }, + }, + }); + }} + /> + + + { + const place = newValue ?? ''; + const handler = + newValue !== null && dropoffPoints[newValue] ? dropoffPoints[newValue] : ''; + onInputChange({ + ...taskDesc, + dropoff: { + ...taskDesc.dropoff, + place: place, + handler: handler, + }, + }); + }} + onBlur={(ev) => + dropoffPoints[(ev.target as HTMLInputElement).value] && + onInputChange({ + ...taskDesc, + dropoff: { + ...taskDesc.dropoff, + place: (ev.target as HTMLInputElement).value, + handler: dropoffPoints[(ev.target as HTMLInputElement).value], + }, + }) + } + renderInput={(params) => ( + + )} + /> + + + { + onInputChange({ + ...taskDesc, + dropoff: { + ...taskDesc.dropoff, + payload: { + ...taskDesc.dropoff.payload, + sku: ev.target.value, + }, + }, + }); + }} + /> + + + { + onInputChange({ + ...taskDesc, + dropoff: { + ...taskDesc.dropoff, + payload: { + ...taskDesc.dropoff.payload, + quantity: val, + }, + }, + }); + }} + /> + + + ); +} From 9947d87c7d38b672b99fcc0132b4da462b1f2e1e Mon Sep 17 00:00:00 2001 From: Aaron Chong Date: Wed, 27 Mar 2024 14:18:41 +0800 Subject: [PATCH 05/28] Added delivery, renamed to SimpleDelivery Signed-off-by: Aaron Chong --- .../lib/tasks/create-task.tsx | 36 +++++++++++++++++-- ...elivery_custom.tsx => delivery-custom.tsx} | 0 .../{delivery.tsx => delivery-simple.tsx} | 22 ++++++------ .../lib/tasks/descriptions/index.ts | 3 +- .../lib/tasks/descriptions/utils.ts | 17 +++++---- 5 files changed, 58 insertions(+), 20 deletions(-) rename packages/react-components/lib/tasks/descriptions/{delivery_custom.tsx => delivery-custom.tsx} (100%) rename packages/react-components/lib/tasks/descriptions/{delivery.tsx => delivery-simple.tsx} (90%) diff --git a/packages/react-components/lib/tasks/create-task.tsx b/packages/react-components/lib/tasks/create-task.tsx index db14a6b5d..f20dfcf98 100644 --- a/packages/react-components/lib/tasks/create-task.tsx +++ b/packages/react-components/lib/tasks/create-task.tsx @@ -40,6 +40,11 @@ import React from 'react'; import { Loading } from '..'; import { ConfirmationDialog, ConfirmationDialogProps } from '../confirmation-dialog'; import { CustomComposeTaskDescription, CustomComposeTaskForm } from './descriptions/custom-compose'; +import { + SimpleDeliveryTaskDescription, + SimpleDeliveryTaskForm, + makeDefaultSimpleDeliveryTaskDescription, +} from './descriptions/delivery-simple'; import { defaultDeliveryCustomTaskDescription, defaultDeliveryTaskDescription, @@ -47,13 +52,14 @@ import { DeliveryCustomTaskDescription, DeliveryTaskDescription, DeliveryTaskForm, -} from './descriptions/delivery_custom'; +} from './descriptions/delivery-custom'; import { defaultPatrolTask, PatrolTaskDescription, PatrolTaskForm } from './descriptions/patrol'; type TaskDescription = | DeliveryTaskDescription | DeliveryCustomTaskDescription - | PatrolTaskDescription; + | PatrolTaskDescription + | SimpleDeliveryTaskDescription; const classes = { title: 'dialogue-info-value', @@ -161,6 +167,8 @@ function defaultTaskDescription(taskCategory: string): TaskDescription | undefin return defaultDeliveryCustomTaskDescription(taskCategory); case 'patrol': return defaultPatrolTask(); + case 'delivery': + return makeDefaultSimpleDeliveryTaskDescription(); default: return undefined; } @@ -403,6 +411,16 @@ export function CreateTaskForm({ allowSubmit={allowSubmit} /> ); + } else if (taskRequest.category === 'delivery') { + return ( + handleTaskDescriptionChange('delivery', desc)} + allowSubmit={allowSubmit} + /> + ); } else if (taskRequest.category === 'custom_compose') { return ( Patrol + + Delivery + Custom Compose Task diff --git a/packages/react-components/lib/tasks/descriptions/delivery_custom.tsx b/packages/react-components/lib/tasks/descriptions/delivery-custom.tsx similarity index 100% rename from packages/react-components/lib/tasks/descriptions/delivery_custom.tsx rename to packages/react-components/lib/tasks/descriptions/delivery-custom.tsx diff --git a/packages/react-components/lib/tasks/descriptions/delivery.tsx b/packages/react-components/lib/tasks/descriptions/delivery-simple.tsx similarity index 90% rename from packages/react-components/lib/tasks/descriptions/delivery.tsx rename to packages/react-components/lib/tasks/descriptions/delivery-simple.tsx index 312c004b7..a540ca15d 100644 --- a/packages/react-components/lib/tasks/descriptions/delivery.tsx +++ b/packages/react-components/lib/tasks/descriptions/delivery-simple.tsx @@ -12,7 +12,7 @@ interface TaskPlace { }; } -export interface DeliveryTaskDescription { +export interface SimpleDeliveryTaskDescription { pickup: TaskPlace; dropoff: TaskPlace; } @@ -26,11 +26,13 @@ function isTaskPlaceValid(place: TaskPlace): boolean { ); } -function isDeliveryTaskDescriptionValid(taskDescription: DeliveryTaskDescription): boolean { +function isSimpleDeliveryTaskDescriptionValid( + taskDescription: SimpleDeliveryTaskDescription, +): boolean { return isTaskPlaceValid(taskDescription.pickup) && isTaskPlaceValid(taskDescription.dropoff); } -export function makeDefaultDeliveryTaskDescription(): DeliveryTaskDescription { +export function makeDefaultSimpleDeliveryTaskDescription(): SimpleDeliveryTaskDescription { return { pickup: { place: '', @@ -51,24 +53,24 @@ export function makeDefaultDeliveryTaskDescription(): DeliveryTaskDescription { }; } -export interface DeliveryTaskFormProps { - taskDesc: DeliveryTaskDescription; +export interface SimpleDeliveryTaskFormProps { + taskDesc: SimpleDeliveryTaskDescription; pickupPoints: Record; dropoffPoints: Record; - onChange(taskDesc: DeliveryTaskDescription): void; + onChange(taskDesc: SimpleDeliveryTaskDescription): void; allowSubmit(allow: boolean): void; } -export function DeliveryTaskForm({ +export function SimpleDeliveryTaskForm({ taskDesc, pickupPoints = {}, dropoffPoints = {}, onChange, allowSubmit, -}: DeliveryTaskFormProps) { +}: SimpleDeliveryTaskFormProps) { const theme = useTheme(); - const onInputChange = (desc: DeliveryTaskDescription) => { - allowSubmit(isDeliveryTaskDescriptionValid(desc)); + const onInputChange = (desc: SimpleDeliveryTaskDescription) => { + allowSubmit(isSimpleDeliveryTaskDescriptionValid(desc)); onChange(desc); }; diff --git a/packages/react-components/lib/tasks/descriptions/index.ts b/packages/react-components/lib/tasks/descriptions/index.ts index e2f96dae5..7f6f36c30 100644 --- a/packages/react-components/lib/tasks/descriptions/index.ts +++ b/packages/react-components/lib/tasks/descriptions/index.ts @@ -1,5 +1,6 @@ // export * from './clean'; export * from './custom-compose'; -export * from './delivery_custom'; +export * from './delivery-custom'; +export * from './delivery-simple'; export * from './patrol'; export * from './utils'; diff --git a/packages/react-components/lib/tasks/descriptions/utils.ts b/packages/react-components/lib/tasks/descriptions/utils.ts index fb8dfccb5..31ca915e0 100644 --- a/packages/react-components/lib/tasks/descriptions/utils.ts +++ b/packages/react-components/lib/tasks/descriptions/utils.ts @@ -1,5 +1,5 @@ import { TaskRequest } from 'api-client'; -import { GoToPlaceActivity, LotPickupActivity } from './delivery_custom'; +import { GoToPlaceActivity, LotPickupActivity } from './delivery-custom'; export function isNonEmptyString(value: string): boolean { return value.length > 0; @@ -10,11 +10,16 @@ export function isPositiveNumber(value: number): boolean { } export function getShortDescription(taskRequest: TaskRequest): string | undefined { - if (taskRequest.category === 'patrol') { - const formattedPlaces = taskRequest.description.places.map((place: string) => `[${place}]`); - return `[Patrol] [${taskRequest.description.rounds}] round/s, along ${formattedPlaces.join( - ', ', - )}`; + switch (taskRequest.category) { + case 'patrol': { + const formattedPlaces = taskRequest.description.places.map((place: string) => `[${place}]`); + return `[Patrol] [${taskRequest.description.rounds}] round/s, along ${formattedPlaces.join( + ', ', + )}`; + } + case 'delivery': { + return `[Delivery] Pickup [${taskRequest.description.pickup.payload.sku}] from [${taskRequest.description.pickup.place}], dropoff [${taskRequest.description.dropoff.payload.sku}] at [${taskRequest.description.dropoff.place}]`; + } } // This section is only valid for custom delivery types From c780d63114b6b411a86e7c91ebdca2470c60416a Mon Sep 17 00:00:00 2001 From: Aaron Chong Date: Wed, 27 Mar 2024 14:56:33 +0800 Subject: [PATCH 06/28] Clean task added Signed-off-by: Aaron Chong --- .../lib/tasks/create-task.tsx | 33 ++++++++++++++++--- .../lib/tasks/descriptions/utils.ts | 7 ++++ 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/packages/react-components/lib/tasks/create-task.tsx b/packages/react-components/lib/tasks/create-task.tsx index f20dfcf98..43ef1bd3a 100644 --- a/packages/react-components/lib/tasks/create-task.tsx +++ b/packages/react-components/lib/tasks/create-task.tsx @@ -39,6 +39,11 @@ import type { TaskFavoritePydantic as TaskFavorite, TaskRequest } from 'api-clie import React from 'react'; import { Loading } from '..'; import { ConfirmationDialog, ConfirmationDialogProps } from '../confirmation-dialog'; +import { + makeDefaultCleanTaskDescription, + CleanTaskDescription, + CleanTaskForm, +} from './descriptions/clean'; import { CustomComposeTaskDescription, CustomComposeTaskForm } from './descriptions/custom-compose'; import { SimpleDeliveryTaskDescription, @@ -59,7 +64,8 @@ type TaskDescription = | DeliveryTaskDescription | DeliveryCustomTaskDescription | PatrolTaskDescription - | SimpleDeliveryTaskDescription; + | SimpleDeliveryTaskDescription + | CleanTaskDescription; const classes = { title: 'dialogue-info-value', @@ -169,6 +175,8 @@ function defaultTaskDescription(taskCategory: string): TaskDescription | undefin return defaultPatrolTask(); case 'delivery': return makeDefaultSimpleDeliveryTaskDescription(); + case 'clean': + return makeDefaultCleanTaskDescription(); default: return undefined; } @@ -434,6 +442,18 @@ export function CreateTaskForm({ } switch (taskRequest.description.category) { + case 'clean': + return ( + { + desc.category = taskRequest.description.category; + handleTaskDescriptionChange('compose', desc); + }} + allowSubmit={allowSubmit} + /> + ); case 'delivery_pickup': return ( Delivery + + Clean + Custom Compose Task diff --git a/packages/react-components/lib/tasks/descriptions/utils.ts b/packages/react-components/lib/tasks/descriptions/utils.ts index 31ca915e0..261a446ce 100644 --- a/packages/react-components/lib/tasks/descriptions/utils.ts +++ b/packages/react-components/lib/tasks/descriptions/utils.ts @@ -22,6 +22,13 @@ export function getShortDescription(taskRequest: TaskRequest): string | undefine } } + // FIXME: This block is only for non-primary task cateogries, that utilize + // compose. + if (taskRequest.description.category === 'clean') { + const cleanActivity = taskRequest.description.phases[0].activity.description.activities[1]; + return `[Clean] zone [${cleanActivity.description.description.zone}]`; + } + // This section is only valid for custom delivery types // FIXME: This block looks like it makes assumptions about the structure of // the task description in order to parse it, but it is following the From ec851b7b74df6c87be614879f595e1eb995fdb32 Mon Sep 17 00:00:00 2001 From: Aaron Chong Date: Wed, 27 Mar 2024 16:47:41 +0800 Subject: [PATCH 07/28] Moved delivery-custom tests, added return type for forms Signed-off-by: Aaron Chong --- .../lib/tasks/create-task.tsx | 20 +++++++++++-------- .../lib/tasks/descriptions/clean.tsx | 2 +- .../lib/tasks/descriptions/custom-compose.tsx | 2 +- .../delivery-custom.spec.tsx} | 2 +- .../tasks/descriptions/delivery-custom.tsx | 8 ++++---- .../tasks/descriptions/delivery-simple.tsx | 2 +- .../lib/tasks/descriptions/patrol.tsx | 4 ++-- 7 files changed, 22 insertions(+), 18 deletions(-) rename packages/react-components/lib/tasks/{create-task.spec.tsx => descriptions/delivery-custom.spec.tsx} (99%) diff --git a/packages/react-components/lib/tasks/create-task.tsx b/packages/react-components/lib/tasks/create-task.tsx index 43ef1bd3a..3905a65a1 100644 --- a/packages/react-components/lib/tasks/create-task.tsx +++ b/packages/react-components/lib/tasks/create-task.tsx @@ -51,14 +51,18 @@ import { makeDefaultSimpleDeliveryTaskDescription, } from './descriptions/delivery-simple'; import { - defaultDeliveryCustomTaskDescription, - defaultDeliveryTaskDescription, DeliveryCustomTaskForm, DeliveryCustomTaskDescription, DeliveryTaskDescription, DeliveryTaskForm, + makeDefaultDeliveryCustomTaskDescription, + makeDefaultDeliveryTaskDescription, } from './descriptions/delivery-custom'; -import { defaultPatrolTask, PatrolTaskDescription, PatrolTaskForm } from './descriptions/patrol'; +import { + makeDefaultPatrolTask, + PatrolTaskDescription, + PatrolTaskForm, +} from './descriptions/patrol'; type TaskDescription = | DeliveryTaskDescription @@ -167,12 +171,12 @@ function FavoriteTask({ function defaultTaskDescription(taskCategory: string): TaskDescription | undefined { switch (taskCategory) { case 'delivery_pickup': - return defaultDeliveryTaskDescription(); + return makeDefaultDeliveryTaskDescription(); case 'delivery_sequential_lot_pickup': case 'delivery_area_pickup': - return defaultDeliveryCustomTaskDescription(taskCategory); + return makeDefaultDeliveryCustomTaskDescription(taskCategory); case 'patrol': - return defaultPatrolTask(); + return makeDefaultPatrolTask(); case 'delivery': return makeDefaultSimpleDeliveryTaskDescription(); case 'clean': @@ -185,7 +189,7 @@ function defaultTaskDescription(taskCategory: string): TaskDescription | undefin function defaultTask(): TaskRequest { return { category: 'compose', - description: defaultDeliveryTaskDescription(), + description: makeDefaultDeliveryTaskDescription(), unix_millis_earliest_start_time: 0, unix_millis_request_time: Date.now(), priority: { type: 'binary', value: 0 }, @@ -261,7 +265,7 @@ const defaultFavoriteTask = (): TaskFavorite => { id: '', name: '', category: 'compose', - description: defaultDeliveryTaskDescription(), + description: makeDefaultDeliveryTaskDescription(), unix_millis_earliest_start_time: 0, priority: { type: 'binary', value: 0 }, user: '', diff --git a/packages/react-components/lib/tasks/descriptions/clean.tsx b/packages/react-components/lib/tasks/descriptions/clean.tsx index 73269c02c..a0cd53f4b 100644 --- a/packages/react-components/lib/tasks/descriptions/clean.tsx +++ b/packages/react-components/lib/tasks/descriptions/clean.tsx @@ -88,7 +88,7 @@ export function CleanTaskForm({ cleaningZones, onChange, allowSubmit, -}: CleanTaskFormProps) { +}: CleanTaskFormProps): React.JSX.Element { const onInputChange = (desc: CleanTaskDescription) => { allowSubmit(isCleanTaskDescriptionValid(desc)); onChange(desc); diff --git a/packages/react-components/lib/tasks/descriptions/custom-compose.tsx b/packages/react-components/lib/tasks/descriptions/custom-compose.tsx index 8101cb6f8..e6d7b78dd 100644 --- a/packages/react-components/lib/tasks/descriptions/custom-compose.tsx +++ b/packages/react-components/lib/tasks/descriptions/custom-compose.tsx @@ -27,7 +27,7 @@ export function CustomComposeTaskForm({ taskDesc, onChange, allowSubmit, -}: CustomComposeTaskFormProps) { +}: CustomComposeTaskFormProps): React.JSX.Element { const theme = useTheme(); const onInputChange = (desc: CustomComposeTaskDescription) => { allowSubmit(isCustomTaskDescriptionValid(desc)); diff --git a/packages/react-components/lib/tasks/create-task.spec.tsx b/packages/react-components/lib/tasks/descriptions/delivery-custom.spec.tsx similarity index 99% rename from packages/react-components/lib/tasks/create-task.spec.tsx rename to packages/react-components/lib/tasks/descriptions/delivery-custom.spec.tsx index 0d3d268b9..03fe9cbf2 100644 --- a/packages/react-components/lib/tasks/create-task.spec.tsx +++ b/packages/react-components/lib/tasks/descriptions/delivery-custom.spec.tsx @@ -11,7 +11,7 @@ import { deliveryCustomInsertCartId, deliveryCustomInsertDropoff, deliveryCustomInsertOnCancel, -} from './create-task'; +} from '.'; describe('Custom deliveries', () => { it('delivery 1:1', () => { diff --git a/packages/react-components/lib/tasks/descriptions/delivery-custom.tsx b/packages/react-components/lib/tasks/descriptions/delivery-custom.tsx index c4ceb2305..108db5e8d 100644 --- a/packages/react-components/lib/tasks/descriptions/delivery-custom.tsx +++ b/packages/react-components/lib/tasks/descriptions/delivery-custom.tsx @@ -236,7 +236,7 @@ export function DeliveryTaskForm({ dropoffPoints = {}, onChange, allowSubmit, -}: DeliveryTaskFormProps) { +}: DeliveryTaskFormProps): React.JSX.Element { const theme = useTheme(); const isScreenHeightLessThan800 = useMediaQuery('(max-height:800px)'); const onInputChange = (desc: DeliveryTaskDescription) => { @@ -449,7 +449,7 @@ export function DeliveryCustomTaskForm({ dropoffPoints = [], onChange, allowSubmit, -}: DeliveryCustomProps) { +}: DeliveryCustomProps): React.JSX.Element { const theme = useTheme(); const isScreenHeightLessThan800 = useMediaQuery('(max-height:800px)'); const onInputChange = (desc: DeliveryCustomTaskDescription) => { @@ -586,7 +586,7 @@ export function DeliveryCustomTaskForm({ ); } -export function defaultDeliveryTaskDescription(): DeliveryTaskDescription { +export function makeDefaultDeliveryTaskDescription(): DeliveryTaskDescription { return { category: 'delivery_pickup', phases: [ @@ -649,7 +649,7 @@ export function defaultDeliveryTaskDescription(): DeliveryTaskDescription { }; } -export function defaultDeliveryCustomTaskDescription( +export function makeDefaultDeliveryCustomTaskDescription( taskCategory: string, ): DeliveryCustomTaskDescription { return { diff --git a/packages/react-components/lib/tasks/descriptions/delivery-simple.tsx b/packages/react-components/lib/tasks/descriptions/delivery-simple.tsx index a540ca15d..eeded7ac8 100644 --- a/packages/react-components/lib/tasks/descriptions/delivery-simple.tsx +++ b/packages/react-components/lib/tasks/descriptions/delivery-simple.tsx @@ -67,7 +67,7 @@ export function SimpleDeliveryTaskForm({ dropoffPoints = {}, onChange, allowSubmit, -}: SimpleDeliveryTaskFormProps) { +}: SimpleDeliveryTaskFormProps): React.JSX.Element { const theme = useTheme(); const onInputChange = (desc: SimpleDeliveryTaskDescription) => { allowSubmit(isSimpleDeliveryTaskDescriptionValid(desc)); diff --git a/packages/react-components/lib/tasks/descriptions/patrol.tsx b/packages/react-components/lib/tasks/descriptions/patrol.tsx index 3b1e3ecdd..9b4c14f7d 100644 --- a/packages/react-components/lib/tasks/descriptions/patrol.tsx +++ b/packages/react-components/lib/tasks/descriptions/patrol.tsx @@ -32,7 +32,7 @@ export const isPatrolTaskDescriptionValid = (taskDescription: PatrolTaskDescript return taskDescription.rounds > 0; }; -export function defaultPatrolTask(): PatrolTaskDescription { +export function makeDefaultPatrolTask(): PatrolTaskDescription { return { places: [], rounds: 1, @@ -86,7 +86,7 @@ export function PatrolTaskForm({ patrolWaypoints, onChange, allowSubmit, -}: PatrolTaskFormProps) { +}: PatrolTaskFormProps): React.JSX.Element { const theme = useTheme(); const isScreenHeightLessThan800 = useMediaQuery('(max-height:800px)'); const onInputChange = (desc: PatrolTaskDescription) => { From 25fe0f3295edf9fae869f6362aee4fcb580cf6be Mon Sep 17 00:00:00 2001 From: Aaron Chong Date: Thu, 28 Mar 2024 11:16:26 +0800 Subject: [PATCH 08/28] Configurable supported tasks and name remapping Signed-off-by: Aaron Chong --- packages/dashboard/src/components/appbar.tsx | 2 + .../components/tasks/task-schedule-utils.ts | 7 +- .../src/components/tasks/task-schedule.tsx | 7 +- .../src/managers/resource-manager-tasks.ts | 16 ++ .../src/managers/resource-manager.ts | 9 + .../lib/tasks/create-task.tsx | 169 ++++++++++++++---- .../lib/tasks/descriptions/utils.ts | 35 +++- 7 files changed, 192 insertions(+), 53 deletions(-) create mode 100644 packages/dashboard/src/managers/resource-manager-tasks.ts diff --git a/packages/dashboard/src/components/appbar.tsx b/packages/dashboard/src/components/appbar.tsx index df0a51218..e23411671 100644 --- a/packages/dashboard/src/components/appbar.tsx +++ b/packages/dashboard/src/components/appbar.tsx @@ -547,6 +547,8 @@ export const AppBar = React.memo(({ extraToolbarItems }: AppBarProps): React.Rea {openCreateTaskForm && ( }; }; -export const getScheduledTaskTitle = (task: ScheduledTask): string => { - const shortDescription = getShortDescription(task.task_request); +export const getScheduledTaskTitle = ( + task: ScheduledTask, + taskNameRemap?: Record, +): string => { + const shortDescription = getShortDescription(task.task_request, taskNameRemap); if (!task.task_request || !task.task_request.category || !shortDescription) { return `[${task.id}] Unknown`; } diff --git a/packages/dashboard/src/components/tasks/task-schedule.tsx b/packages/dashboard/src/components/tasks/task-schedule.tsx index d8d789a68..3e3452237 100644 --- a/packages/dashboard/src/components/tasks/task-schedule.tsx +++ b/packages/dashboard/src/components/tasks/task-schedule.tsx @@ -23,7 +23,7 @@ import { } from 'react-components'; import { useCreateTaskFormData } from '../../hooks/useCreateTaskForm'; import useGetUsername from '../../hooks/useFetchUser'; -import { AppControllerContext } from '../app-contexts'; +import { AppControllerContext, ResourcesContext } from '../app-contexts'; import { UserProfileContext } from 'rmf-auth'; import { AppEvents } from '../app-events'; import { RmfAppContext } from '../rmf-app'; @@ -69,6 +69,7 @@ const disablingCellsWithoutEvents = ( export const TaskSchedule = () => { const rmf = React.useContext(RmfAppContext); + const resourceManager = React.useContext(ResourcesContext); const { showAlert } = React.useContext(AppControllerContext); const profile = React.useContext(UserProfileContext); @@ -136,7 +137,7 @@ export const TaskSchedule = () => { return tasks.flatMap((t: ScheduledTask) => t.schedules.flatMap((s: ApiSchedule) => { const events = scheduleToEvents(params.start, params.end, s, t, getEventId, () => - getScheduledTaskTitle(t), + getScheduledTaskTitle(t, resourceManager?.tasks.taskNameRemap), ); events.forEach((ev) => { eventsMap.current[Number(ev.event_id)] = t; @@ -146,7 +147,7 @@ export const TaskSchedule = () => { }), ); }, - [rmf], + [rmf, resourceManager], ); const CustomCalendarEditor = ({ scheduler, value, onChange }: CustomCalendarEditorProps) => { diff --git a/packages/dashboard/src/managers/resource-manager-tasks.ts b/packages/dashboard/src/managers/resource-manager-tasks.ts new file mode 100644 index 000000000..5bfe5c703 --- /dev/null +++ b/packages/dashboard/src/managers/resource-manager-tasks.ts @@ -0,0 +1,16 @@ +export class TaskResourceManager { + supportedTasks: string[] = ['patrol', 'delivery', 'clean']; + taskNameRemap: Record = {}; + + constructor( + supportedTasks: string[] | undefined, + taskNameRemap: Record | undefined, + ) { + if (supportedTasks) { + this.supportedTasks = supportedTasks; + } + if (taskNameRemap) { + this.taskNameRemap = taskNameRemap; + } + } +} diff --git a/packages/dashboard/src/managers/resource-manager.ts b/packages/dashboard/src/managers/resource-manager.ts index 2f872d855..ed3dc08d3 100644 --- a/packages/dashboard/src/managers/resource-manager.ts +++ b/packages/dashboard/src/managers/resource-manager.ts @@ -2,6 +2,7 @@ import Debug from 'debug'; import { DispenserResourceManager, RawDispenserResource } from './resource-manager-dispensers'; import { LogoResource, LogoResourceManager } from './resource-manager-logos'; import { RobotResource, RobotResourceManager } from './resource-manager-robots'; +import { TaskResourceManager } from './resource-manager-tasks'; const debug = Debug('ResourceManager'); const ResourceFile = 'resources/main.json'; @@ -19,6 +20,8 @@ export interface ResourceConfigurationsType { cartIds?: string[]; loggedInDisplayLevel?: string; emergencyLots?: string[]; + supportedTasks?: string[]; + taskNameRemap?: Record; // Record } export default class ResourceManager { @@ -34,6 +37,8 @@ export default class ResourceManager { cartIds?: string[]; loggedInDisplayLevel?: string; emergencyLots?: string[]; + supportedTasks?: string[]; + tasks: TaskResourceManager; /** * Gets the default resource manager using the embedded resource file (aka "assets/resources/main.json"). @@ -74,6 +79,10 @@ export default class ResourceManager { this.cartIds = resources.cartIds || []; this.loggedInDisplayLevel = resources.loggedInDisplayLevel; this.emergencyLots = resources.emergencyLots || []; + this.tasks = new TaskResourceManager( + resources.supportedTasks || ['patrol', 'delivery', 'clean'], + resources.taskNameRemap, + ); } } diff --git a/packages/react-components/lib/tasks/create-task.tsx b/packages/react-components/lib/tasks/create-task.tsx index 3905a65a1..0a0263e62 100644 --- a/packages/react-components/lib/tasks/create-task.tsx +++ b/packages/react-components/lib/tasks/create-task.tsx @@ -168,13 +168,13 @@ function FavoriteTask({ ); } -function defaultTaskDescription(taskCategory: string): TaskDescription | undefined { - switch (taskCategory) { +function defaultTaskDescription(taskName: string): TaskDescription | undefined { + switch (taskName) { case 'delivery_pickup': return makeDefaultDeliveryTaskDescription(); case 'delivery_sequential_lot_pickup': case 'delivery_area_pickup': - return makeDefaultDeliveryCustomTaskDescription(taskCategory); + return makeDefaultDeliveryCustomTaskDescription(taskName); case 'patrol': return makeDefaultPatrolTask(); case 'delivery': @@ -186,10 +186,29 @@ function defaultTaskDescription(taskCategory: string): TaskDescription | undefin } } -function defaultTask(): TaskRequest { +function taskRequestCategory(taskName: string): string | undefined { + switch (taskName) { + case 'patrol': + case 'delivery': + return taskName; + case 'delivery_pickup': + case 'delivery_sequential_lot_pickup': + case 'delivery_area_pickup': + case 'clean': + case 'custom_compose': + return 'compose'; + default: + return undefined; + } +} + +function defaultTaskRequest(taskName: string): TaskRequest { + const category = taskRequestCategory(taskName); + const description = defaultTaskDescription(taskName); + return { - category: 'compose', - description: makeDefaultDeliveryTaskDescription(), + category: category ?? 'compose', + description: description ?? makeDefaultDeliveryTaskDescription(), unix_millis_earliest_start_time: 0, unix_millis_request_time: Date.now(), priority: { type: 'binary', value: 0 }, @@ -278,6 +297,8 @@ export interface CreateTaskFormProps * Shows extra UI elements suitable for submittng batched tasks. Default to 'false'. */ user: string; + supportedTasks?: string[]; + taskNameRemap?: Record; allowBatch?: boolean; cleaningZones?: string[]; patrolWaypoints?: string[]; @@ -304,6 +325,8 @@ export interface CreateTaskFormProps export function CreateTaskForm({ user, + supportedTasks = ['patrol', 'delivery', 'clean'], + taskNameRemap = {}, /* eslint-disable @typescript-eslint/no-unused-vars */ cleaningZones = [], /* eslint-disable @typescript-eslint/no-unused-vars */ @@ -346,7 +369,7 @@ export function CreateTaskForm({ const [taskType, setTaskType] = React.useState(undefined); const [taskRequest, setTaskRequest] = React.useState( - () => requestTask ?? defaultTask(), + () => requestTask ?? defaultTaskRequest(supportedTasks[0]), ); const [submitting, setSubmitting] = React.useState(false); @@ -414,35 +437,36 @@ export function CreateTaskForm({ }; const renderTaskDescriptionForm = () => { - if (taskRequest.category === 'patrol') { - return ( - handleTaskDescriptionChange('patrol', desc)} - allowSubmit={allowSubmit} - /> - ); - } else if (taskRequest.category === 'delivery') { - return ( - handleTaskDescriptionChange('delivery', desc)} - allowSubmit={allowSubmit} - /> - ); - } else if (taskRequest.category === 'custom_compose') { - return ( - { - handleCustomComposeTaskDescriptionChange(desc); - }} - allowSubmit={allowSubmit} - /> - ); + switch (taskRequest.category) { + case 'patrol': + return ( + handleTaskDescriptionChange('patrol', desc)} + allowSubmit={allowSubmit} + /> + ); + case 'delivery': + return ( + handleTaskDescriptionChange('delivery', desc)} + allowSubmit={allowSubmit} + /> + ); + case 'custom_compose': + return ( + { + handleCustomComposeTaskDescriptionChange(desc); + }} + allowSubmit={allowSubmit} + /> + ); } switch (taskRequest.description.category) { @@ -619,7 +643,7 @@ export function CreateTaskForm({ onSuccessFavoriteTask && onSuccessFavoriteTask('Deleted favorite task successfully', favoriteTaskBuffer); - setTaskRequest(defaultTask()); + setTaskRequest(defaultTaskRequest(supportedTasks[0])); setOpenFavoriteDialog(false); setCallToDeleteFavoriteTask(false); setCallToUpdateFavoriteTask(false); @@ -629,6 +653,69 @@ export function CreateTaskForm({ } }; + // Order the menu items based on the supported tasks + const renderMenuItem = (taskName: string, taskNameRemap: Record) => { + switch (taskName) { + case 'patrol': + return ( + + {taskNameRemap[taskName] ?? 'Patrol'} + + ); + case 'delivery': + return ( + + {taskNameRemap[taskName] ?? 'Delivery'} + + ); + case 'clean': + return ( + + {taskNameRemap[taskName] ?? 'Clean'} + + ); + case 'custom_compose': + return ( + {taskNameRemap[taskName] ?? 'Custom Compose Task'} + ); + case 'delivery_pickup': + return ( + + {taskNameRemap[taskName] ?? 'Delivery - 1:1'} + + ); + case 'delivery_sequential_lot_pickup': + return ( + + {taskNameRemap[taskName] ?? 'Delivery - Sequential lot pick up'} + + ); + case 'delivery_area_pickup': + return ( + + {taskNameRemap[taskName] ?? 'Delivery - Area pick up'} + + ); + default: + return undefined; + } + }; + return ( <> - { + return renderMenuItem(taskName, taskNameRemap); + })} + + {/* Clean - Custom Compose Task + Custom Compose Task */} diff --git a/packages/react-components/lib/tasks/descriptions/utils.ts b/packages/react-components/lib/tasks/descriptions/utils.ts index 261a446ce..f02af001e 100644 --- a/packages/react-components/lib/tasks/descriptions/utils.ts +++ b/packages/react-components/lib/tasks/descriptions/utils.ts @@ -9,16 +9,23 @@ export function isPositiveNumber(value: number): boolean { return value > 0; } -export function getShortDescription(taskRequest: TaskRequest): string | undefined { +export function getShortDescription( + taskRequest: TaskRequest, + taskNameRemap?: Record, +): string | undefined { switch (taskRequest.category) { case 'patrol': { const formattedPlaces = taskRequest.description.places.map((place: string) => `[${place}]`); - return `[Patrol] [${taskRequest.description.rounds}] round/s, along ${formattedPlaces.join( - ', ', - )}`; + return `[${(taskNameRemap && taskNameRemap[taskRequest.category]) ?? 'Patrol'}] [${ + taskRequest.description.rounds + }] round/s, along ${formattedPlaces.join(', ')}`; } case 'delivery': { - return `[Delivery] Pickup [${taskRequest.description.pickup.payload.sku}] from [${taskRequest.description.pickup.place}], dropoff [${taskRequest.description.dropoff.payload.sku}] at [${taskRequest.description.dropoff.place}]`; + return `[${(taskNameRemap && taskNameRemap[taskRequest.category]) ?? 'Delivery'}] Pickup [${ + taskRequest.description.pickup.payload.sku + }] from [${taskRequest.description.pickup.place}], dropoff [${ + taskRequest.description.dropoff.payload.sku + }] at [${taskRequest.description.dropoff.place}]`; } } @@ -26,7 +33,9 @@ export function getShortDescription(taskRequest: TaskRequest): string | undefine // compose. if (taskRequest.description.category === 'clean') { const cleanActivity = taskRequest.description.phases[0].activity.description.activities[1]; - return `[Clean] zone [${cleanActivity.description.description.zone}]`; + return `[${ + (taskNameRemap && taskNameRemap[taskRequest.description.category]) ?? 'Clean' + }] zone [${cleanActivity.description.description.zone}]`; } // This section is only valid for custom delivery types @@ -47,13 +56,21 @@ export function getShortDescription(taskRequest: TaskRequest): string | undefine switch (taskRequest.description.category) { case 'delivery_pickup': { - return `[Delivery - 1:1] payload [${cartId}] from [${goToPickup.description}] to [${goToDropoff.description}]`; + return `[${ + (taskNameRemap && taskNameRemap[taskRequest.description.category]) ?? 'Delivery - 1:1' + }] payload [${cartId}] from [${goToPickup.description}] to [${goToDropoff.description}]`; } case 'delivery_sequential_lot_pickup': { - return `[Delivery - Sequential lot pick up] payload [${cartId}] from [${goToPickup.description}] to [${goToDropoff.description}]`; + return `[${ + (taskNameRemap && taskNameRemap[taskRequest.description.category]) ?? + 'Delivery - Sequential lot pick up' + }] payload [${cartId}] from [${goToPickup.description}] to [${goToDropoff.description}]`; } case 'delivery_area_pickup': { - return `[Delivery - Area pick up] payload [${cartId}] from [${goToPickup.description}] to [${goToDropoff.description}]`; + return `[${ + (taskNameRemap && taskNameRemap[taskRequest.description.category]) ?? + 'Delivery - Area pick up' + }] payload [${cartId}] from [${goToPickup.description}] to [${goToDropoff.description}]`; } default: return `[Unknown] type "${taskRequest.description.category}"`; From b5a8b76a053e669bd8cbdfb83bc2710266a0b6ac Mon Sep 17 00:00:00 2001 From: Aaron Chong Date: Thu, 28 Mar 2024 11:24:40 +0800 Subject: [PATCH 09/28] Changed directory to types, since it doesn't just handle descriptions Signed-off-by: Aaron Chong --- .../lib/tasks/create-task.tsx | 63 ++----------------- packages/react-components/lib/tasks/index.ts | 2 +- .../tasks/{descriptions => types}/clean.tsx | 0 .../custom-compose.tsx | 0 .../delivery-custom.spec.tsx | 0 .../delivery-custom.tsx | 0 .../delivery-simple.tsx | 0 .../tasks/{descriptions => types}/index.ts | 0 .../tasks/{descriptions => types}/patrol.tsx | 0 .../tasks/{descriptions => types}/utils.ts | 0 10 files changed, 6 insertions(+), 59 deletions(-) rename packages/react-components/lib/tasks/{descriptions => types}/clean.tsx (100%) rename packages/react-components/lib/tasks/{descriptions => types}/custom-compose.tsx (100%) rename packages/react-components/lib/tasks/{descriptions => types}/delivery-custom.spec.tsx (100%) rename packages/react-components/lib/tasks/{descriptions => types}/delivery-custom.tsx (100%) rename packages/react-components/lib/tasks/{descriptions => types}/delivery-simple.tsx (100%) rename packages/react-components/lib/tasks/{descriptions => types}/index.ts (100%) rename packages/react-components/lib/tasks/{descriptions => types}/patrol.tsx (100%) rename packages/react-components/lib/tasks/{descriptions => types}/utils.ts (100%) diff --git a/packages/react-components/lib/tasks/create-task.tsx b/packages/react-components/lib/tasks/create-task.tsx index 0a0263e62..14da07aee 100644 --- a/packages/react-components/lib/tasks/create-task.tsx +++ b/packages/react-components/lib/tasks/create-task.tsx @@ -43,13 +43,13 @@ import { makeDefaultCleanTaskDescription, CleanTaskDescription, CleanTaskForm, -} from './descriptions/clean'; -import { CustomComposeTaskDescription, CustomComposeTaskForm } from './descriptions/custom-compose'; +} from './types/clean'; +import { CustomComposeTaskDescription, CustomComposeTaskForm } from './types/custom-compose'; import { SimpleDeliveryTaskDescription, SimpleDeliveryTaskForm, makeDefaultSimpleDeliveryTaskDescription, -} from './descriptions/delivery-simple'; +} from './types/delivery-simple'; import { DeliveryCustomTaskForm, DeliveryCustomTaskDescription, @@ -57,12 +57,8 @@ import { DeliveryTaskForm, makeDefaultDeliveryCustomTaskDescription, makeDefaultDeliveryTaskDescription, -} from './descriptions/delivery-custom'; -import { - makeDefaultPatrolTask, - PatrolTaskDescription, - PatrolTaskForm, -} from './descriptions/patrol'; +} from './types/delivery-custom'; +import { makeDefaultPatrolTask, PatrolTaskDescription, PatrolTaskForm } from './types/patrol'; type TaskDescription = | DeliveryTaskDescription @@ -797,55 +793,6 @@ export function CreateTaskForm({ {supportedTasks.map((taskName) => { return renderMenuItem(taskName, taskNameRemap); })} - - {/* - Delivery - 1:1 - - - Delivery - Sequential lot pick up - - - Delivery - Area pick up - - - Patrol - - - Delivery - - - Clean - - Custom Compose Task */} diff --git a/packages/react-components/lib/tasks/index.ts b/packages/react-components/lib/tasks/index.ts index 30b764f1c..2a367a382 100644 --- a/packages/react-components/lib/tasks/index.ts +++ b/packages/react-components/lib/tasks/index.ts @@ -1,5 +1,5 @@ export * from './create-task'; -export * from './descriptions'; +export * from './types'; export * from './task-info'; export * from './task-logs'; export * from './task-table'; diff --git a/packages/react-components/lib/tasks/descriptions/clean.tsx b/packages/react-components/lib/tasks/types/clean.tsx similarity index 100% rename from packages/react-components/lib/tasks/descriptions/clean.tsx rename to packages/react-components/lib/tasks/types/clean.tsx diff --git a/packages/react-components/lib/tasks/descriptions/custom-compose.tsx b/packages/react-components/lib/tasks/types/custom-compose.tsx similarity index 100% rename from packages/react-components/lib/tasks/descriptions/custom-compose.tsx rename to packages/react-components/lib/tasks/types/custom-compose.tsx diff --git a/packages/react-components/lib/tasks/descriptions/delivery-custom.spec.tsx b/packages/react-components/lib/tasks/types/delivery-custom.spec.tsx similarity index 100% rename from packages/react-components/lib/tasks/descriptions/delivery-custom.spec.tsx rename to packages/react-components/lib/tasks/types/delivery-custom.spec.tsx diff --git a/packages/react-components/lib/tasks/descriptions/delivery-custom.tsx b/packages/react-components/lib/tasks/types/delivery-custom.tsx similarity index 100% rename from packages/react-components/lib/tasks/descriptions/delivery-custom.tsx rename to packages/react-components/lib/tasks/types/delivery-custom.tsx diff --git a/packages/react-components/lib/tasks/descriptions/delivery-simple.tsx b/packages/react-components/lib/tasks/types/delivery-simple.tsx similarity index 100% rename from packages/react-components/lib/tasks/descriptions/delivery-simple.tsx rename to packages/react-components/lib/tasks/types/delivery-simple.tsx diff --git a/packages/react-components/lib/tasks/descriptions/index.ts b/packages/react-components/lib/tasks/types/index.ts similarity index 100% rename from packages/react-components/lib/tasks/descriptions/index.ts rename to packages/react-components/lib/tasks/types/index.ts diff --git a/packages/react-components/lib/tasks/descriptions/patrol.tsx b/packages/react-components/lib/tasks/types/patrol.tsx similarity index 100% rename from packages/react-components/lib/tasks/descriptions/patrol.tsx rename to packages/react-components/lib/tasks/types/patrol.tsx diff --git a/packages/react-components/lib/tasks/descriptions/utils.ts b/packages/react-components/lib/tasks/types/utils.ts similarity index 100% rename from packages/react-components/lib/tasks/descriptions/utils.ts rename to packages/react-components/lib/tasks/types/utils.ts From 2c52928fe8e58ac8a9625ac5fa8e9bb7704b805c Mon Sep 17 00:00:00 2001 From: Aaron Chong Date: Thu, 28 Mar 2024 11:43:06 +0800 Subject: [PATCH 10/28] Fix test imports Signed-off-by: Aaron Chong --- .../lib/tasks/types/delivery-custom.spec.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/react-components/lib/tasks/types/delivery-custom.spec.tsx b/packages/react-components/lib/tasks/types/delivery-custom.spec.tsx index 03fe9cbf2..b2ac5ae46 100644 --- a/packages/react-components/lib/tasks/types/delivery-custom.spec.tsx +++ b/packages/react-components/lib/tasks/types/delivery-custom.spec.tsx @@ -1,6 +1,6 @@ import { - defaultDeliveryTaskDescription, - defaultDeliveryCustomTaskDescription, + makeDefaultDeliveryTaskDescription, + makeDefaultDeliveryCustomTaskDescription, DeliveryCustomTaskDescription, deliveryInsertCartId, deliveryInsertDropoff, @@ -119,7 +119,7 @@ describe('Custom deliveries', () => { } expect(deliveryTaskDescription).not.toEqual(null); - let description = defaultDeliveryTaskDescription(); + let description = makeDefaultDeliveryTaskDescription(); description = deliveryInsertPickup(description, 'test_pickup_place', 'test_pickup_lot'); description = deliveryInsertCartId(description, 'test_cart_id'); description = deliveryInsertDropoff(description, 'test_dropoff_place'); @@ -236,7 +236,7 @@ describe('Custom deliveries', () => { } expect(deliveryTaskDescription).not.toEqual(null); - let description: DeliveryCustomTaskDescription = defaultDeliveryCustomTaskDescription( + let description: DeliveryCustomTaskDescription = makeDefaultDeliveryCustomTaskDescription( 'delivery_sequential_lot_pickup', ); description = deliveryCustomInsertPickup(description, 'test_pickup_place', 'test_pickup_zone'); @@ -356,7 +356,7 @@ describe('Custom deliveries', () => { expect(deliveryTaskDescription).not.toEqual(null); let description: DeliveryCustomTaskDescription = - defaultDeliveryCustomTaskDescription('delivery_area_pickup'); + makeDefaultDeliveryCustomTaskDescription('delivery_area_pickup'); description = deliveryCustomInsertPickup(description, 'test_pickup_place', 'test_pickup_zone'); description = deliveryCustomInsertCartId(description, 'test_cart_id'); description = deliveryCustomInsertDropoff(description, 'test_dropoff_place'); From eb9aedae37e39d2e764ccd1a840440aa1aa8c8b1 Mon Sep 17 00:00:00 2001 From: Aaron Chong Date: Tue, 2 Apr 2024 14:50:42 +0800 Subject: [PATCH 11/28] Using temporary task definition Signed-off-by: Aaron Chong --- packages/dashboard/src/components/appbar.tsx | 3 +- .../components/tasks/task-schedule-utils.ts | 15 ++++- .../src/components/tasks/task-schedule.tsx | 2 +- .../src/managers/resource-manager-tasks.ts | 18 +++--- .../src/managers/resource-manager.ts | 17 +++--- .../lib/tasks/create-task.tsx | 56 ++++++++++++------- .../react-components/lib/tasks/types/utils.ts | 26 ++++----- 7 files changed, 77 insertions(+), 60 deletions(-) diff --git a/packages/dashboard/src/components/appbar.tsx b/packages/dashboard/src/components/appbar.tsx index e23411671..11aef76e6 100644 --- a/packages/dashboard/src/components/appbar.tsx +++ b/packages/dashboard/src/components/appbar.tsx @@ -547,8 +547,7 @@ export const AppBar = React.memo(({ extraToolbarItems }: AppBarProps): React.Rea {openCreateTaskForm && ( export const getScheduledTaskTitle = ( task: ScheduledTask, - taskNameRemap?: Record, + supportedTasks?: TaskDefinition[], ): string => { - const shortDescription = getShortDescription(task.task_request, taskNameRemap); + let remappedTaskName: string | undefined = undefined; + if (supportedTasks) { + for (const s of supportedTasks) { + if (s.name === task.task_request.category) { + remappedTaskName = s.nameRemap; + } + } + } + + const shortDescription = getShortDescription(task.task_request, remappedTaskName); if (!task.task_request || !task.task_request.category || !shortDescription) { return `[${task.id}] Unknown`; } diff --git a/packages/dashboard/src/components/tasks/task-schedule.tsx b/packages/dashboard/src/components/tasks/task-schedule.tsx index 3e3452237..104d43b8e 100644 --- a/packages/dashboard/src/components/tasks/task-schedule.tsx +++ b/packages/dashboard/src/components/tasks/task-schedule.tsx @@ -137,7 +137,7 @@ export const TaskSchedule = () => { return tasks.flatMap((t: ScheduledTask) => t.schedules.flatMap((s: ApiSchedule) => { const events = scheduleToEvents(params.start, params.end, s, t, getEventId, () => - getScheduledTaskTitle(t, resourceManager?.tasks.taskNameRemap), + getScheduledTaskTitle(t, resourceManager?.supportedTasks), ); events.forEach((ev) => { eventsMap.current[Number(ev.event_id)] = t; diff --git a/packages/dashboard/src/managers/resource-manager-tasks.ts b/packages/dashboard/src/managers/resource-manager-tasks.ts index 5bfe5c703..edf6678ff 100644 --- a/packages/dashboard/src/managers/resource-manager-tasks.ts +++ b/packages/dashboard/src/managers/resource-manager-tasks.ts @@ -1,16 +1,14 @@ +import { TaskDefinition } from 'react-components'; + export class TaskResourceManager { - supportedTasks: string[] = ['patrol', 'delivery', 'clean']; - taskNameRemap: Record = {}; + supportedTasks: Record; - constructor( - supportedTasks: string[] | undefined, - taskNameRemap: Record | undefined, - ) { + constructor(supportedTasks: TaskDefinition[] | undefined) { + this.supportedTasks = {}; if (supportedTasks) { - this.supportedTasks = supportedTasks; - } - if (taskNameRemap) { - this.taskNameRemap = taskNameRemap; + for (const t of supportedTasks) { + this.supportedTasks[t.name] = t; + } } } } diff --git a/packages/dashboard/src/managers/resource-manager.ts b/packages/dashboard/src/managers/resource-manager.ts index ed3dc08d3..521c36c9d 100644 --- a/packages/dashboard/src/managers/resource-manager.ts +++ b/packages/dashboard/src/managers/resource-manager.ts @@ -2,7 +2,7 @@ import Debug from 'debug'; import { DispenserResourceManager, RawDispenserResource } from './resource-manager-dispensers'; import { LogoResource, LogoResourceManager } from './resource-manager-logos'; import { RobotResource, RobotResourceManager } from './resource-manager-robots'; -import { TaskResourceManager } from './resource-manager-tasks'; +import { TaskDefinition } from 'react-components'; const debug = Debug('ResourceManager'); const ResourceFile = 'resources/main.json'; @@ -20,8 +20,7 @@ export interface ResourceConfigurationsType { cartIds?: string[]; loggedInDisplayLevel?: string; emergencyLots?: string[]; - supportedTasks?: string[]; - taskNameRemap?: Record; // Record + supportedTasks?: TaskDefinition[]; } export default class ResourceManager { @@ -37,8 +36,7 @@ export default class ResourceManager { cartIds?: string[]; loggedInDisplayLevel?: string; emergencyLots?: string[]; - supportedTasks?: string[]; - tasks: TaskResourceManager; + supportedTasks?: TaskDefinition[]; /** * Gets the default resource manager using the embedded resource file (aka "assets/resources/main.json"). @@ -79,10 +77,11 @@ export default class ResourceManager { this.cartIds = resources.cartIds || []; this.loggedInDisplayLevel = resources.loggedInDisplayLevel; this.emergencyLots = resources.emergencyLots || []; - this.tasks = new TaskResourceManager( - resources.supportedTasks || ['patrol', 'delivery', 'clean'], - resources.taskNameRemap, - ); + this.supportedTasks = resources.supportedTasks || [ + { name: 'patrol', nameRemap: undefined }, + { name: 'delivery', nameRemap: undefined }, + { name: 'clean', nameRemap: undefined }, + ]; } } diff --git a/packages/react-components/lib/tasks/create-task.tsx b/packages/react-components/lib/tasks/create-task.tsx index 14da07aee..1c413b700 100644 --- a/packages/react-components/lib/tasks/create-task.tsx +++ b/packages/react-components/lib/tasks/create-task.tsx @@ -60,6 +60,13 @@ import { } from './types/delivery-custom'; import { makeDefaultPatrolTask, PatrolTaskDescription, PatrolTaskForm } from './types/patrol'; +// FIXME: temporary task definition workaround, before we start using proper +// schemas. +export interface TaskDefinition { + name: string; + nameRemap: string | undefined; +} + type TaskDescription = | DeliveryTaskDescription | DeliveryCustomTaskDescription @@ -293,8 +300,7 @@ export interface CreateTaskFormProps * Shows extra UI elements suitable for submittng batched tasks. Default to 'false'. */ user: string; - supportedTasks?: string[]; - taskNameRemap?: Record; + supportedTasks?: TaskDefinition[]; allowBatch?: boolean; cleaningZones?: string[]; patrolWaypoints?: string[]; @@ -321,8 +327,11 @@ export interface CreateTaskFormProps export function CreateTaskForm({ user, - supportedTasks = ['patrol', 'delivery', 'clean'], - taskNameRemap = {}, + supportedTasks = [ + { name: 'patrol', nameRemap: undefined }, + { name: 'delivery', nameRemap: undefined }, + { name: 'clean', nameRemap: undefined }, + ], /* eslint-disable @typescript-eslint/no-unused-vars */ cleaningZones = [], /* eslint-disable @typescript-eslint/no-unused-vars */ @@ -364,8 +373,10 @@ export function CreateTaskForm({ const [savingFavoriteTask, setSavingFavoriteTask] = React.useState(false); const [taskType, setTaskType] = React.useState(undefined); - const [taskRequest, setTaskRequest] = React.useState( - () => requestTask ?? defaultTaskRequest(supportedTasks[0]), + const [taskRequest, setTaskRequest] = React.useState(() => + requestTask ?? (supportedTasks && supportedTasks.length > 0) + ? defaultTaskRequest(supportedTasks[0].name) + : defaultTaskRequest('patrol'), ); const [submitting, setSubmitting] = React.useState(false); @@ -639,7 +650,11 @@ export function CreateTaskForm({ onSuccessFavoriteTask && onSuccessFavoriteTask('Deleted favorite task successfully', favoriteTaskBuffer); - setTaskRequest(defaultTaskRequest(supportedTasks[0])); + setTaskRequest( + supportedTasks && supportedTasks.length > 0 + ? defaultTaskRequest(supportedTasks[0].name) + : defaultTaskRequest('patrol'), + ); setOpenFavoriteDialog(false); setCallToDeleteFavoriteTask(false); setCallToUpdateFavoriteTask(false); @@ -650,12 +665,14 @@ export function CreateTaskForm({ }; // Order the menu items based on the supported tasks - const renderMenuItem = (taskName: string, taskNameRemap: Record) => { + const renderMenuItem = (taskDefinition: TaskDefinition) => { + const taskName = taskDefinition.name; + const remappedName = taskDefinition.nameRemap; switch (taskName) { case 'patrol': return ( - {taskNameRemap[taskName] ?? 'Patrol'} + {remappedName ?? 'Patrol'} ); case 'delivery': @@ -666,19 +683,17 @@ export function CreateTaskForm({ Object.keys(pickupPoints).length === 0 || Object.keys(dropoffPoints).length === 0 } > - {taskNameRemap[taskName] ?? 'Delivery'} + {remappedName ?? 'Delivery'} ); case 'clean': return ( - {taskNameRemap[taskName] ?? 'Clean'} + {remappedName ?? 'Clean'} ); case 'custom_compose': - return ( - {taskNameRemap[taskName] ?? 'Custom Compose Task'} - ); + return {remappedName ?? 'Custom Compose Task'}; case 'delivery_pickup': return ( - {taskNameRemap[taskName] ?? 'Delivery - 1:1'} + {remappedName ?? 'Delivery - 1:1'} ); case 'delivery_sequential_lot_pickup': return ( - {taskNameRemap[taskName] ?? 'Delivery - Sequential lot pick up'} + {remappedName ?? 'Delivery - Sequential lot pick up'} ); case 'delivery_area_pickup': return ( - {taskNameRemap[taskName] ?? 'Delivery - Area pick up'} + {remappedName ?? 'Delivery - Area pick up'} ); default: @@ -790,9 +805,10 @@ export function CreateTaskForm({ }} InputLabelProps={{ style: { fontSize: isScreenHeightLessThan800 ? 16 : 20 } }} > - {supportedTasks.map((taskName) => { - return renderMenuItem(taskName, taskNameRemap); - })} + {supportedTasks && + supportedTasks.map((taskDefinition) => { + return renderMenuItem(taskDefinition); + })} diff --git a/packages/react-components/lib/tasks/types/utils.ts b/packages/react-components/lib/tasks/types/utils.ts index f02af001e..5b06a1e5e 100644 --- a/packages/react-components/lib/tasks/types/utils.ts +++ b/packages/react-components/lib/tasks/types/utils.ts @@ -11,17 +11,17 @@ export function isPositiveNumber(value: number): boolean { export function getShortDescription( taskRequest: TaskRequest, - taskNameRemap?: Record, + remappedTaskName?: string, ): string | undefined { switch (taskRequest.category) { case 'patrol': { const formattedPlaces = taskRequest.description.places.map((place: string) => `[${place}]`); - return `[${(taskNameRemap && taskNameRemap[taskRequest.category]) ?? 'Patrol'}] [${ + return `[${remappedTaskName ?? 'Patrol'}] [${ taskRequest.description.rounds }] round/s, along ${formattedPlaces.join(', ')}`; } case 'delivery': { - return `[${(taskNameRemap && taskNameRemap[taskRequest.category]) ?? 'Delivery'}] Pickup [${ + return `[${remappedTaskName ?? 'Delivery'}] Pickup [${ taskRequest.description.pickup.payload.sku }] from [${taskRequest.description.pickup.place}], dropoff [${ taskRequest.description.dropoff.payload.sku @@ -33,9 +33,7 @@ export function getShortDescription( // compose. if (taskRequest.description.category === 'clean') { const cleanActivity = taskRequest.description.phases[0].activity.description.activities[1]; - return `[${ - (taskNameRemap && taskNameRemap[taskRequest.description.category]) ?? 'Clean' - }] zone [${cleanActivity.description.description.zone}]`; + return `[${remappedTaskName ?? 'Clean'}] zone [${cleanActivity.description.description.zone}]`; } // This section is only valid for custom delivery types @@ -56,21 +54,19 @@ export function getShortDescription( switch (taskRequest.description.category) { case 'delivery_pickup': { - return `[${ - (taskNameRemap && taskNameRemap[taskRequest.description.category]) ?? 'Delivery - 1:1' - }] payload [${cartId}] from [${goToPickup.description}] to [${goToDropoff.description}]`; + return `[${remappedTaskName ?? 'Delivery - 1:1'}] payload [${cartId}] from [${ + goToPickup.description + }] to [${goToDropoff.description}]`; } case 'delivery_sequential_lot_pickup': { return `[${ - (taskNameRemap && taskNameRemap[taskRequest.description.category]) ?? - 'Delivery - Sequential lot pick up' + remappedTaskName ?? 'Delivery - Sequential lot pick up' }] payload [${cartId}] from [${goToPickup.description}] to [${goToDropoff.description}]`; } case 'delivery_area_pickup': { - return `[${ - (taskNameRemap && taskNameRemap[taskRequest.description.category]) ?? - 'Delivery - Area pick up' - }] payload [${cartId}] from [${goToPickup.description}] to [${goToDropoff.description}]`; + return `[${remappedTaskName ?? 'Delivery - Area pick up'}] payload [${cartId}] from [${ + goToPickup.description + }] to [${goToDropoff.description}]`; } default: return `[Unknown] type "${taskRequest.description.category}"`; From 01139aa7ef09ddf1322fab9214e9d6b7f4064f11 Mon Sep 17 00:00:00 2001 From: Aaron Chong Date: Mon, 3 Jun 2024 20:53:02 +0800 Subject: [PATCH 12/28] Refactoring new rename changes Signed-off-by: Aaron Chong --- .../components/tasks/task-schedule-utils.ts | 4 +- .../src/managers/resource-manager-tasks.ts | 2 +- .../src/managers/resource-manager.ts | 8 +- .../lib/tasks/create-task.tsx | 153 ++++++++++-------- .../lib/tasks/task-booking-label-utils.tsx | 24 ++- .../types/{clean.tsx => compose-clean.tsx} | 45 ++++-- .../lib/tasks/types/custom-compose.tsx | 11 +- .../lib/tasks/types/delivery-custom.spec.tsx | 50 +++--- .../lib/tasks/types/delivery-custom.tsx | 124 ++++++++++---- .../{delivery-simple.tsx => delivery.tsx} | 44 ++--- .../react-components/lib/tasks/types/index.ts | 4 +- .../lib/tasks/types/patrol.tsx | 19 ++- .../react-components/lib/tasks/types/utils.ts | 139 +++++++--------- 13 files changed, 376 insertions(+), 251 deletions(-) rename packages/react-components/lib/tasks/types/{clean.tsx => compose-clean.tsx} (71%) rename packages/react-components/lib/tasks/types/{delivery-simple.tsx => delivery.tsx} (83%) diff --git a/packages/dashboard/src/components/tasks/task-schedule-utils.ts b/packages/dashboard/src/components/tasks/task-schedule-utils.ts index bab632762..ff8f7023f 100644 --- a/packages/dashboard/src/components/tasks/task-schedule-utils.ts +++ b/packages/dashboard/src/components/tasks/task-schedule-utils.ts @@ -173,8 +173,8 @@ export const getScheduledTaskTitle = ( let remappedTaskName: string | undefined = undefined; if (supportedTasks) { for (const s of supportedTasks) { - if (s.task_definition_id === task.task_request.category) { - remappedTaskName = s.task_display_name; + if (s.taskDefinitionId === task.task_request.category) { + remappedTaskName = s.taskDisplayName; } } } diff --git a/packages/dashboard/src/managers/resource-manager-tasks.ts b/packages/dashboard/src/managers/resource-manager-tasks.ts index 42fcf40a1..b7277e03f 100644 --- a/packages/dashboard/src/managers/resource-manager-tasks.ts +++ b/packages/dashboard/src/managers/resource-manager-tasks.ts @@ -7,7 +7,7 @@ export class TaskResourceManager { this.supportedTasks = {}; if (supportedTasks) { for (const t of supportedTasks) { - this.supportedTasks[t.task_definition_id] = t; + this.supportedTasks[t.taskDefinitionId] = t; } } } diff --git a/packages/dashboard/src/managers/resource-manager.ts b/packages/dashboard/src/managers/resource-manager.ts index d5965a8fb..53f841d70 100644 --- a/packages/dashboard/src/managers/resource-manager.ts +++ b/packages/dashboard/src/managers/resource-manager.ts @@ -4,8 +4,8 @@ import { LogoResource, LogoResourceManager } from './resource-manager-logos'; import { RobotResource, RobotResourceManager } from './resource-manager-robots'; import { DefaultPatrolTaskDefinition, - DefaultSimpleDeliveryTaskDefinition, - DefaultCleanTaskDefinition, + DefaultDeliveryTaskDefinition, + DefaultComposeCleanTaskDefinition, TaskDefinition, DefaultCustomComposeTaskDefinition, } from 'react-components'; @@ -82,8 +82,8 @@ export default class ResourceManager { this.loggedInDisplayLevel = resources.loggedInDisplayLevel; this.supportedTasks = resources.supportedTasks || [ DefaultPatrolTaskDefinition, - DefaultSimpleDeliveryTaskDefinition, - DefaultCleanTaskDefinition, + DefaultDeliveryTaskDefinition, + DefaultComposeCleanTaskDefinition, DefaultCustomComposeTaskDefinition, ]; } diff --git a/packages/react-components/lib/tasks/create-task.tsx b/packages/react-components/lib/tasks/create-task.tsx index 3d2e0e46c..9a903fffb 100644 --- a/packages/react-components/lib/tasks/create-task.tsx +++ b/packages/react-components/lib/tasks/create-task.tsx @@ -40,12 +40,12 @@ import React from 'react'; import { Loading } from '..'; import { ConfirmationDialog, ConfirmationDialogProps } from '../confirmation-dialog'; import { - CleanTaskDescription, - CleanTaskForm, - DefaultCleanTaskDefinition, - makeCleanTaskBookingLabel, - makeDefaultCleanTaskDescription, -} from './types/clean'; + ComposeCleanTaskDescription, + ComposeCleanTaskForm, + DefaultComposeCleanTaskDefinition, + makeComposeCleanTaskBookingLabel, + makeDefaultComposeCleanTaskDescription, +} from './types/compose-clean'; import { DefaultCustomComposeTaskDefinition, CustomComposeTaskDescription, @@ -53,24 +53,24 @@ import { makeCustomComposeTaskBookingLabel, } from './types/custom-compose'; import { - DefaultSimpleDeliveryTaskDefinition, - SimpleDeliveryTaskDescription, - SimpleDeliveryTaskForm, - makeDefaultSimpleDeliveryTaskDescription, - makeSimpleDeliveryTaskBookingLabel, -} from './types/delivery-simple'; + DefaultDeliveryTaskDefinition, + DeliveryTaskDescription, + DeliveryTaskForm, + makeDefaultDeliveryTaskDescription, + makeDeliveryTaskBookingLabel, +} from './types/delivery'; import { DefaultDeliveryPickupTaskDefinition, DefaultDeliveryAreaPickupTaskDefinition, DefaultDeliverySequentialLotPickupTaskDefinition, DeliveryCustomTaskForm, DeliveryCustomTaskDescription, - DeliveryTaskDescription, - DeliveryTaskForm, + DeliveryPickupTaskDescription, + DeliveryPickupTaskForm, makeDefaultDeliveryCustomTaskDescription, - makeDefaultDeliveryTaskDescription, + makeDefaultDeliveryPickupTaskDescription, makeDeliveryCustomTaskBookingLabel, - makeDeliveryTaskBookingLabel, + makeDeliveryPickupTaskBookingLabel, } from './types/delivery-custom'; import { makeDefaultPatrolTask, @@ -79,22 +79,26 @@ import { PatrolTaskDescription, PatrolTaskForm, } from './types/patrol'; -import { serializeTaskBookingLabel } from './task-booking-label-utils'; +import { + getTaskBookingLabelFromTaskRequest, + serializeTaskBookingLabel, +} from './task-booking-label-utils'; export interface TaskDefinition { - task_definition_id: string; - task_display_name: string; + taskDefinitionId: string; + taskDisplayName: string; + requestCategory: string; } // If no task definition id is found in a past task (scheduled or favorite) -const DefaultTaskDefinitionId = DefaultCustomComposeTaskDefinition.task_definition_id; +const DefaultTaskDefinitionId = DefaultCustomComposeTaskDefinition.taskDefinitionId; type TaskDescription = - | DeliveryTaskDescription + | DeliveryPickupTaskDescription | DeliveryCustomTaskDescription | PatrolTaskDescription - | SimpleDeliveryTaskDescription - | CleanTaskDescription; + | DeliveryTaskDescription + | ComposeCleanTaskDescription; const classes = { title: 'dialogue-info-value', @@ -193,43 +197,45 @@ function FavoriteTask({ ); } -function defaultTaskDescription(taskName: string): TaskDescription | undefined { - switch (taskName) { - case 'delivery_pickup': - return makeDefaultDeliveryTaskDescription(); - case 'delivery_sequential_lot_pickup': - case 'delivery_area_pickup': - return makeDefaultDeliveryCustomTaskDescription(taskName); - case 'patrol': +function defaultTaskDescription(taskDefinitionId: string): TaskDescription | undefined { + switch (taskDefinitionId) { + case DefaultDeliveryPickupTaskDefinition.taskDefinitionId: + return makeDefaultDeliveryPickupTaskDescription(); + case DefaultDeliverySequentialLotPickupTaskDefinition.taskDefinitionId: + case DefaultDeliveryAreaPickupTaskDefinition.taskDefinitionId: + return makeDefaultDeliveryCustomTaskDescription(taskDefinitionId); + case DefaultPatrolTaskDefinition.taskDefinitionId: return makeDefaultPatrolTask(); - case 'delivery': - return makeDefaultSimpleDeliveryTaskDescription(); - case 'clean': - return makeDefaultCleanTaskDescription(); + case DefaultDeliveryTaskDefinition.taskDefinitionId: + return makeDefaultDeliveryTaskDescription(); + case DefaultComposeCleanTaskDefinition.taskDefinitionId: + return makeDefaultComposeCleanTaskDescription(); default: return undefined; } } -function taskRequestCategory(task_definition_id: string): string | undefined { - switch (task_definition_id) { - case DefaultPatrolTaskDefinition.task_definition_id: - case DefaultSimpleDeliveryTaskDefinition.task_definition_id: - return task_definition_id; - case DefaultDeliveryPickupTaskDefinition.task_definition_id: - case DefaultDeliverySequentialLotPickupTaskDefinition.task_definition_id: - case DefaultDeliveryAreaPickupTaskDefinition.task_definition_id: - case DefaultCleanTaskDefinition.task_definition_id: - case DefaultCustomComposeTaskDefinition.task_definition_id: +// TODO: Move this into task defintion as well. To consider moving +// description.category into task definition too. +function taskRequestCategory(taskDefinitionId: string): string | undefined { + switch (taskDefinitionId) { + case DefaultPatrolTaskDefinition.taskDefinitionId: + case DefaultDeliveryTaskDefinition.taskDefinitionId: + return taskDefinitionId; + case DefaultDeliveryPickupTaskDefinition.taskDefinitionId: + case DefaultDeliverySequentialLotPickupTaskDefinition.taskDefinitionId: + case DefaultDeliveryAreaPickupTaskDefinition.taskDefinitionId: + case DefaultComposeCleanTaskDefinition.taskDefinitionId: + case DefaultCustomComposeTaskDefinition.taskDefinitionId: return 'compose'; default: return undefined; } } -function defaultTaskRequest(task_definition_id: string): TaskRequest { - const category = taskRequestCategory(task_definition_id); - const description = defaultTaskDescription(task_definition_id); +function defaultTaskRequest(taskDefinitionId: string): TaskRequest { + const category = taskRequestCategory(taskDefinitionId); + const description = defaultTaskDescription(taskDefinitionId); return { category: category ?? 'compose', @@ -351,8 +357,8 @@ export function CreateTaskForm({ user, supportedTasks = [ DefaultPatrolTaskDefinition, - DefaultSimpleDeliveryTaskDefinition, - DefaultCleanTaskDefinition, + DefaultDeliveryTaskDefinition, + DefaultComposeCleanTaskDefinition, DefaultCustomComposeTaskDefinition, ], /* eslint-disable @typescript-eslint/no-unused-vars */ @@ -394,16 +400,21 @@ export function CreateTaskForm({ const [favoriteTaskTitleError, setFavoriteTaskTitleError] = React.useState(false); const [savingFavoriteTask, setSavingFavoriteTask] = React.useState(false); - const [taskDefinitionId, setTaskDefinitionId] = React.useState( - supportedTasks.length > 0 ? supportedTasks[0].task_definition_id : DefaultTaskDefinitionId, - ); const [taskRequest, setTaskRequest] = React.useState( () => requestTask ?? defaultTaskRequest( - supportedTasks.length > 0 ? supportedTasks[0].task_definition_id : DefaultTaskDefinitionId, + supportedTasks.length > 0 ? supportedTasks[0].taskDefinitionId : DefaultTaskDefinitionId, ), ); + const initialBookingLabel = requestTask ? getTaskBookingLabelFromTaskRequest(requestTask) : null; + const [taskDefinitionId, setTaskDefinitionId] = React.useState( + initialBookingLabel + ? initialBookingLabel.description.task_definition_id + : supportedTasks.length > 0 + ? supportedTasks[0].taskDefinitionId + : DefaultTaskDefinitionId, + ); const [submitting, setSubmitting] = React.useState(false); const [formFullyFilled, setFormFullyFilled] = React.useState(requestTask !== undefined || false); @@ -482,8 +493,8 @@ export function CreateTaskForm({ ); case 'delivery': return ( - handleTaskDescriptionChange('delivery', desc)} @@ -505,10 +516,10 @@ export function CreateTaskForm({ switch (taskRequest.description.category) { case 'clean': return ( - { + onChange={(desc: ComposeCleanTaskDescription) => { desc.category = taskRequest.description.category; handleTaskDescriptionChange('compose', desc); }} @@ -517,12 +528,12 @@ export function CreateTaskForm({ ); case 'delivery_pickup': return ( - { + onChange={(desc: DeliveryPickupTaskDescription) => { desc.category = taskRequest.description.category; desc.phases[0].activity.description.activities[1].description.category = taskRequest.description.category; @@ -558,6 +569,7 @@ export function CreateTaskForm({ }; const handleTaskTypeChange = (ev: React.ChangeEvent) => { const newType = ev.target.value; + console.log(`handleTaskTypeChange ${newType}`); setTaskDefinitionId(newType); if (newType === 'custom_compose') { @@ -612,7 +624,7 @@ export function CreateTaskForm({ let requestBookingLabel: TaskBookingLabel | null = null; switch (taskDefinitionId) { case 'delivery_pickup': - requestBookingLabel = makeDeliveryTaskBookingLabel(request.description); + requestBookingLabel = makeDeliveryPickupTaskBookingLabel(request.description); break; case 'delivery_sequential_lot_pickup': case 'delivery_area_pickup': @@ -622,9 +634,11 @@ export function CreateTaskForm({ requestBookingLabel = makePatrolTaskBookingLabel(request.description); break; case 'delivery': - requestBookingLabel = makeSimpleDeliveryTaskBookingLabel(request.description); + requestBookingLabel = makeDeliveryTaskBookingLabel(request.description); + break; case 'clean': - requestBookingLabel = makeCleanTaskBookingLabel(request.description); + requestBookingLabel = makeComposeCleanTaskBookingLabel(request.description); + break; case 'custom_compose': requestBookingLabel = makeCustomComposeTaskBookingLabel(); break; @@ -726,7 +740,7 @@ export function CreateTaskForm({ setTaskRequest( supportedTasks && supportedTasks.length > 0 - ? defaultTaskRequest(supportedTasks[0].task_definition_id) + ? defaultTaskRequest(supportedTasks[0].taskDefinitionId) : defaultTaskRequest('patrol'), ); setOpenFavoriteDialog(false); @@ -819,8 +833,11 @@ export function CreateTaskForm({ > {supportedTasks.map((taskDefinition) => { return ( - - {taskDefinition.task_display_name} + + {taskDefinition.taskDisplayName} ); })} diff --git a/packages/react-components/lib/tasks/task-booking-label-utils.tsx b/packages/react-components/lib/tasks/task-booking-label-utils.tsx index bfd4b0150..82b8983e4 100644 --- a/packages/react-components/lib/tasks/task-booking-label-utils.tsx +++ b/packages/react-components/lib/tasks/task-booking-label-utils.tsx @@ -1,6 +1,6 @@ import { ajv } from '../utils/schema-utils'; import schema from 'api-client/dist/schema'; -import type { TaskBookingLabel, TaskState } from 'api-client'; +import type { TaskBookingLabel, TaskRequest, TaskState } from 'api-client'; const validateTaskBookingLabel = ajv.compile(schema.components.schemas.TaskBookingLabel); @@ -44,3 +44,25 @@ export function getTaskBookingLabelFromTaskState(taskState: TaskState): TaskBook } return requestLabel; } + +export function getTaskBookingLabelFromTaskRequest( + taskRequest: TaskRequest, +): TaskBookingLabel | null { + if (!taskRequest.labels) { + return null; + } + + let requestLabel: TaskBookingLabel | null = null; + for (const label of taskRequest.labels) { + try { + const parsedLabel = getTaskBookingLabelFromJsonString(label); + if (parsedLabel) { + requestLabel = parsedLabel; + break; + } + } catch (e) { + continue; + } + } + return requestLabel; +} diff --git a/packages/react-components/lib/tasks/types/clean.tsx b/packages/react-components/lib/tasks/types/compose-clean.tsx similarity index 71% rename from packages/react-components/lib/tasks/types/clean.tsx rename to packages/react-components/lib/tasks/types/compose-clean.tsx index 22e4eae10..50f1fb33f 100644 --- a/packages/react-components/lib/tasks/types/clean.tsx +++ b/packages/react-components/lib/tasks/types/compose-clean.tsx @@ -3,9 +3,10 @@ import React from 'react'; import type { TaskBookingLabel } from 'api-client'; import { TaskDefinition } from '../create-task'; -export const DefaultCleanTaskDefinition: TaskDefinition = { - task_definition_id: 'clean', - task_display_name: 'Clean', +export const DefaultComposeCleanTaskDefinition: TaskDefinition = { + taskDefinitionId: 'compose-clean', + taskDisplayName: 'Clean', + requestCategory: 'compose', }; interface GoToPlaceActivity { @@ -26,7 +27,7 @@ interface CleanActivity { }; } -export interface CleanTaskDescription { +export interface ComposeCleanTaskDescription { category: string; phases: [ cleanPhase: { @@ -40,19 +41,21 @@ export interface CleanTaskDescription { ]; } -export function makeCleanTaskBookingLabel( - task_description: CleanTaskDescription, +export function makeComposeCleanTaskBookingLabel( + task_description: ComposeCleanTaskDescription, ): TaskBookingLabel { return { description: { - task_definition_id: 'clean', + task_definition_id: DefaultComposeCleanTaskDefinition.taskDefinitionId, destination: task_description.phases[0].activity.description.activities[1].description.description.zone, }, }; } -export function isCleanTaskDescriptionValid(taskDescription: CleanTaskDescription): boolean { +export function isComposeCleanTaskDescriptionValid( + taskDescription: ComposeCleanTaskDescription, +): boolean { const goToPlaceActivity = taskDescription.phases[0].activity.description.activities[0]; const cleanActivity = taskDescription.phases[0].activity.description.activities[1]; return ( @@ -62,7 +65,7 @@ export function isCleanTaskDescriptionValid(taskDescription: CleanTaskDescriptio ); } -export function makeDefaultCleanTaskDescription(): CleanTaskDescription { +export function makeDefaultComposeCleanTaskDescription(): ComposeCleanTaskDescription { return { category: 'clean', phases: [ @@ -95,21 +98,31 @@ export function makeDefaultCleanTaskDescription(): CleanTaskDescription { }; } -interface CleanTaskFormProps { - taskDesc: CleanTaskDescription; +export function makeComposeCleanTaskShortDescription( + desc: ComposeCleanTaskDescription, + displayName: string | undefined, +): string { + const cleanActivity = desc.phases[0].activity.description.activities[1]; + return `[${displayName ?? DefaultComposeCleanTaskDefinition.taskDisplayName}] zone [${ + cleanActivity.description.description.zone + }]`; +} + +interface ComposeCleanTaskFormProps { + taskDesc: ComposeCleanTaskDescription; cleaningZones: string[]; - onChange(cleanTaskDescription: CleanTaskDescription): void; + onChange(cleanTaskDescription: ComposeCleanTaskDescription): void; allowSubmit(allow: boolean): void; } -export function CleanTaskForm({ +export function ComposeCleanTaskForm({ taskDesc, cleaningZones, onChange, allowSubmit, -}: CleanTaskFormProps): React.JSX.Element { - const onInputChange = (desc: CleanTaskDescription) => { - allowSubmit(isCleanTaskDescriptionValid(desc)); +}: ComposeCleanTaskFormProps): React.JSX.Element { + const onInputChange = (desc: ComposeCleanTaskDescription) => { + allowSubmit(isComposeCleanTaskDescriptionValid(desc)); onChange(desc); }; diff --git a/packages/react-components/lib/tasks/types/custom-compose.tsx b/packages/react-components/lib/tasks/types/custom-compose.tsx index cb7454262..9e43a75ca 100644 --- a/packages/react-components/lib/tasks/types/custom-compose.tsx +++ b/packages/react-components/lib/tasks/types/custom-compose.tsx @@ -4,8 +4,9 @@ import type { TaskBookingLabel } from 'api-client'; import { TaskDefinition } from '../create-task'; export const DefaultCustomComposeTaskDefinition: TaskDefinition = { - task_definition_id: 'custom_compose', - task_display_name: 'Custom Compose Task', + taskDefinitionId: 'custom_compose', + taskDisplayName: 'Custom Compose Task', + requestCategory: 'compose', }; export type CustomComposeTaskDescription = string; @@ -13,11 +14,15 @@ export type CustomComposeTaskDescription = string; export function makeCustomComposeTaskBookingLabel(): TaskBookingLabel { return { description: { - task_definition_id: 'custom_compose', + task_definition_id: DefaultCustomComposeTaskDefinition.taskDefinitionId, }, }; } +export function makeCustomComposeTaskShortDescription(desc: CustomComposeTaskDescription): string { + return desc; +} + const isCustomTaskDescriptionValid = (taskDescription: string): boolean => { if (taskDescription.length === 0) { return false; diff --git a/packages/react-components/lib/tasks/types/delivery-custom.spec.tsx b/packages/react-components/lib/tasks/types/delivery-custom.spec.tsx index b2ac5ae46..06cb112cc 100644 --- a/packages/react-components/lib/tasks/types/delivery-custom.spec.tsx +++ b/packages/react-components/lib/tasks/types/delivery-custom.spec.tsx @@ -1,23 +1,23 @@ import { - makeDefaultDeliveryTaskDescription, - makeDefaultDeliveryCustomTaskDescription, + deliveryCustomInsertCartId, + deliveryCustomInsertDropoff, + deliveryCustomInsertOnCancel, + deliveryCustomInsertPickup, DeliveryCustomTaskDescription, deliveryInsertCartId, deliveryInsertDropoff, deliveryInsertOnCancel, deliveryInsertPickup, - DeliveryTaskDescription, - deliveryCustomInsertPickup, - deliveryCustomInsertCartId, - deliveryCustomInsertDropoff, - deliveryCustomInsertOnCancel, + DeliveryPickupTaskDescription, + makeDefaultDeliveryCustomTaskDescription, + makeDefaultDeliveryPickupTaskDescription, } from '.'; describe('Custom deliveries', () => { - it('delivery 1:1', () => { - let deliveryTaskDescription: DeliveryTaskDescription | null = null; + it('delivery pickup', () => { + let deliveryPickupTaskDescription: DeliveryPickupTaskDescription | null = null; try { - deliveryTaskDescription = JSON.parse(`{ + deliveryPickupTaskDescription = JSON.parse(`{ "category": "delivery_pickup", "phases": [ { @@ -113,13 +113,13 @@ describe('Custom deliveries', () => { } ] } - `) as DeliveryTaskDescription; + `) as DeliveryPickupTaskDescription; } catch (e) { - deliveryTaskDescription = null; + deliveryPickupTaskDescription = null; } - expect(deliveryTaskDescription).not.toEqual(null); + expect(deliveryPickupTaskDescription).not.toEqual(null); - let description = makeDefaultDeliveryTaskDescription(); + let description = makeDefaultDeliveryPickupTaskDescription(); description = deliveryInsertPickup(description, 'test_pickup_place', 'test_pickup_lot'); description = deliveryInsertCartId(description, 'test_cart_id'); description = deliveryInsertDropoff(description, 'test_dropoff_place'); @@ -128,13 +128,13 @@ describe('Custom deliveries', () => { 'test_waypoint_2', 'test_waypoint_3', ]); - expect(deliveryTaskDescription).toEqual(description); + expect(deliveryPickupTaskDescription).toEqual(description); }); it('delivery_sequential_lot_pickup', () => { - let deliveryTaskDescription: DeliveryCustomTaskDescription | null = null; + let deliveryCustomTaskDescription: DeliveryCustomTaskDescription | null = null; try { - deliveryTaskDescription = JSON.parse(`{ + deliveryCustomTaskDescription = JSON.parse(`{ "category": "delivery_sequential_lot_pickup", "phases": [ { @@ -232,9 +232,9 @@ describe('Custom deliveries', () => { } `) as DeliveryCustomTaskDescription; } catch (e) { - deliveryTaskDescription = null; + deliveryCustomTaskDescription = null; } - expect(deliveryTaskDescription).not.toEqual(null); + expect(deliveryCustomTaskDescription).not.toEqual(null); let description: DeliveryCustomTaskDescription = makeDefaultDeliveryCustomTaskDescription( 'delivery_sequential_lot_pickup', @@ -247,13 +247,13 @@ describe('Custom deliveries', () => { 'test_waypoint_2', 'test_waypoint_3', ]); - expect(deliveryTaskDescription).toEqual(description); + expect(deliveryCustomTaskDescription).toEqual(description); }); it('delivery_area_pickup', () => { - let deliveryTaskDescription: DeliveryCustomTaskDescription | null = null; + let deliveryCustomTaskDescription: DeliveryCustomTaskDescription | null = null; try { - deliveryTaskDescription = JSON.parse(`{ + deliveryCustomTaskDescription = JSON.parse(`{ "category": "delivery_area_pickup", "phases": [ { @@ -351,9 +351,9 @@ describe('Custom deliveries', () => { } `) as DeliveryCustomTaskDescription; } catch (e) { - deliveryTaskDescription = null; + deliveryCustomTaskDescription = null; } - expect(deliveryTaskDescription).not.toEqual(null); + expect(deliveryCustomTaskDescription).not.toEqual(null); let description: DeliveryCustomTaskDescription = makeDefaultDeliveryCustomTaskDescription('delivery_area_pickup'); @@ -365,6 +365,6 @@ describe('Custom deliveries', () => { 'test_waypoint_2', 'test_waypoint_3', ]); - expect(deliveryTaskDescription).toEqual(description); + expect(deliveryCustomTaskDescription).toEqual(description); }); }); diff --git a/packages/react-components/lib/tasks/types/delivery-custom.tsx b/packages/react-components/lib/tasks/types/delivery-custom.tsx index 5ad247dce..cb473ef9c 100644 --- a/packages/react-components/lib/tasks/types/delivery-custom.tsx +++ b/packages/react-components/lib/tasks/types/delivery-custom.tsx @@ -5,18 +5,21 @@ import type { TaskBookingLabel } from 'api-client'; import { TaskDefinition } from '../create-task'; export const DefaultDeliveryPickupTaskDefinition: TaskDefinition = { - task_definition_id: 'delivery_pickup', - task_display_name: 'Delivery - 1:1', + taskDefinitionId: 'delivery_pickup', + taskDisplayName: 'Delivery - 1:1', + requestCategory: 'compose', }; export const DefaultDeliverySequentialLotPickupTaskDefinition: TaskDefinition = { - task_definition_id: 'delivery_sequential_lot_pickup', - task_display_name: 'Delivery - Sequential lot pick up', + taskDefinitionId: 'delivery_sequential_lot_pickup', + taskDisplayName: 'Delivery - Sequential lot pick up', + requestCategory: 'compose', }; export const DefaultDeliveryAreaPickupTaskDefinition: TaskDefinition = { - task_definition_id: 'delivery_area_pickup', - task_display_name: 'Delivery - Area pick up', + taskDefinitionId: 'delivery_area_pickup', + taskDisplayName: 'Delivery - Area pick up', + requestCategory: 'compose', }; export interface LotPickupActivity { @@ -128,7 +131,7 @@ export interface DeliveryCustomTaskDescription { ]; } -export interface DeliveryTaskDescription { +export interface DeliveryPickupTaskDescription { category: string; phases: [ pickup_phase: CartPickupPhase, @@ -137,8 +140,8 @@ export interface DeliveryTaskDescription { ]; } -export function makeDeliveryTaskBookingLabel( - task_description: DeliveryTaskDescription, +export function makeDeliveryPickupTaskBookingLabel( + task_description: DeliveryPickupTaskDescription, ): TaskBookingLabel { const pickupDescription = task_description.phases[0].activity.description.activities[1].description.description; @@ -167,8 +170,75 @@ export function makeDeliveryCustomTaskBookingLabel( }; } -const isDeliveryTaskDescriptionValid = ( - taskDescription: DeliveryTaskDescription, +export function makeDeliveryPickupTaskShortDescription( + desc: DeliveryPickupTaskDescription, + taskDisplayName: string | undefined, +): string { + try { + const goToPickup: GoToPlaceActivity = desc.phases[0].activity.description.activities[0]; + const pickup: LotPickupActivity = desc.phases[0].activity.description.activities[1]; + const cartId = pickup.description.description.cart_id; + const goToDropoff: GoToPlaceActivity = desc.phases[1].activity.description.activities[0]; + + return `[${ + taskDisplayName ?? DefaultDeliveryPickupTaskDefinition.taskDisplayName + }] payload [${cartId}] from [${goToPickup.description}] to [${goToDropoff.description}]`; + } catch (e) { + try { + const descriptionString = JSON.stringify(desc); + console.error(descriptionString); + return descriptionString; + } catch (e) { + console.error( + `Failed to parse task description of delivery pickup task: ${(e as Error).message}`, + ); + } + } + + return '[Unknown] delivery pickup task'; +} + +export function makeDeliveryCustomTaskShortDescription( + desc: DeliveryCustomTaskDescription, + taskDisplayName: string | undefined, +): string { + try { + const goToPickup: GoToPlaceActivity = desc.phases[0].activity.description.activities[0]; + const pickup: ZonePickupActivity = desc.phases[0].activity.description.activities[1]; + const cartId = pickup.description.description.cart_id; + const goToDropoff: GoToPlaceActivity = desc.phases[1].activity.description.activities[0]; + + switch (desc.category) { + case DefaultDeliverySequentialLotPickupTaskDefinition.taskDefinitionId: { + return `[${ + taskDisplayName ?? DefaultDeliverySequentialLotPickupTaskDefinition.taskDisplayName + }] payload [${cartId}] from [${goToPickup.description}] to [${goToDropoff.description}]`; + } + case DefaultDeliveryAreaPickupTaskDefinition.taskDefinitionId: { + return `[${ + taskDisplayName ?? DefaultDeliveryAreaPickupTaskDefinition.taskDisplayName + }] payload [${cartId}] from [${goToPickup.description}] to [${goToDropoff.description}]`; + } + default: + return `[Unknown] type "${desc.category}"`; + } + } catch (e) { + try { + const descriptionString = JSON.stringify(desc); + console.error(descriptionString); + return descriptionString; + } catch (e) { + console.error( + `Failed to parse task description of delivery pickup task: ${(e as Error).message}`, + ); + } + } + + return '[Unknown] delivery pickup task'; +} + +const isDeliveryPickupTaskDescriptionValid = ( + taskDescription: DeliveryPickupTaskDescription, pickupPoints: Record, dropoffPoints: Record, ): boolean => { @@ -204,10 +274,10 @@ const isDeliveryCustomTaskDescriptionValid = ( }; export function deliveryInsertPickup( - taskDescription: DeliveryTaskDescription, + taskDescription: DeliveryPickupTaskDescription, pickupPlace: string, pickupLot: string, -): DeliveryTaskDescription { +): DeliveryPickupTaskDescription { taskDescription.phases[0].activity.description.activities[0].description = pickupPlace; taskDescription.phases[0].activity.description.activities[1].description.description.pickup_lot = pickupLot; @@ -215,26 +285,26 @@ export function deliveryInsertPickup( } export function deliveryInsertCartId( - taskDescription: DeliveryTaskDescription, + taskDescription: DeliveryPickupTaskDescription, cartId: string, -): DeliveryTaskDescription { +): DeliveryPickupTaskDescription { taskDescription.phases[0].activity.description.activities[1].description.description.cart_id = cartId; return taskDescription; } export function deliveryInsertDropoff( - taskDescription: DeliveryTaskDescription, + taskDescription: DeliveryPickupTaskDescription, dropoffPlace: string, -): DeliveryTaskDescription { +): DeliveryPickupTaskDescription { taskDescription.phases[1].activity.description.activities[0].description = dropoffPlace; return taskDescription; } export function deliveryInsertOnCancel( - taskDescription: DeliveryTaskDescription, + taskDescription: DeliveryPickupTaskDescription, onCancelPlaces: string[], -): DeliveryTaskDescription { +): DeliveryPickupTaskDescription { const goToOneOfThePlaces: GoToOneOfThePlacesActivity = { category: 'go_to_place', description: { @@ -267,27 +337,27 @@ export function deliveryInsertOnCancel( return taskDescription; } -interface DeliveryTaskFormProps { - taskDesc: DeliveryTaskDescription; +interface DeliveryPickupTaskFormProps { + taskDesc: DeliveryPickupTaskDescription; pickupPoints: Record; cartIds: string[]; dropoffPoints: Record; - onChange(taskDesc: DeliveryTaskDescription): void; + onChange(taskDesc: DeliveryPickupTaskDescription): void; allowSubmit(allow: boolean): void; } -export function DeliveryTaskForm({ +export function DeliveryPickupTaskForm({ taskDesc, pickupPoints = {}, cartIds = [], dropoffPoints = {}, onChange, allowSubmit, -}: DeliveryTaskFormProps): React.JSX.Element { +}: DeliveryPickupTaskFormProps): React.JSX.Element { const theme = useTheme(); const isScreenHeightLessThan800 = useMediaQuery('(max-height:800px)'); - const onInputChange = (desc: DeliveryTaskDescription) => { - allowSubmit(isDeliveryTaskDescriptionValid(desc, pickupPoints, dropoffPoints)); + const onInputChange = (desc: DeliveryPickupTaskDescription) => { + allowSubmit(isDeliveryPickupTaskDescriptionValid(desc, pickupPoints, dropoffPoints)); onChange(desc); }; @@ -633,7 +703,7 @@ export function DeliveryCustomTaskForm({ ); } -export function makeDefaultDeliveryTaskDescription(): DeliveryTaskDescription { +export function makeDefaultDeliveryPickupTaskDescription(): DeliveryPickupTaskDescription { return { category: 'delivery_pickup', phases: [ diff --git a/packages/react-components/lib/tasks/types/delivery-simple.tsx b/packages/react-components/lib/tasks/types/delivery.tsx similarity index 83% rename from packages/react-components/lib/tasks/types/delivery-simple.tsx rename to packages/react-components/lib/tasks/types/delivery.tsx index c279b8f29..53a52b157 100644 --- a/packages/react-components/lib/tasks/types/delivery-simple.tsx +++ b/packages/react-components/lib/tasks/types/delivery.tsx @@ -5,9 +5,10 @@ import React from 'react'; import type { TaskBookingLabel } from 'api-client'; import { TaskDefinition } from '../create-task'; -export const DefaultSimpleDeliveryTaskDefinition: TaskDefinition = { - task_definition_id: 'delivery', - task_display_name: 'Simple delivery', +export const DefaultDeliveryTaskDefinition: TaskDefinition = { + taskDefinitionId: 'delivery', + taskDisplayName: 'Delivery', + requestCategory: 'delivery', }; interface TaskPlace { @@ -19,17 +20,17 @@ interface TaskPlace { }; } -export interface SimpleDeliveryTaskDescription { +export interface DeliveryTaskDescription { pickup: TaskPlace; dropoff: TaskPlace; } -export function makeSimpleDeliveryTaskBookingLabel( - task_description: SimpleDeliveryTaskDescription, +export function makeDeliveryTaskBookingLabel( + task_description: DeliveryTaskDescription, ): TaskBookingLabel { return { description: { - task_definition_id: 'delivery', + task_definition_id: DefaultDeliveryTaskDefinition.taskDefinitionId, pickup: task_description.pickup.place, destination: task_description.dropoff.place, cart_id: task_description.pickup.payload.sku, @@ -46,13 +47,11 @@ function isTaskPlaceValid(place: TaskPlace): boolean { ); } -function isSimpleDeliveryTaskDescriptionValid( - taskDescription: SimpleDeliveryTaskDescription, -): boolean { +function isDeliveryTaskDescriptionValid(taskDescription: DeliveryTaskDescription): boolean { return isTaskPlaceValid(taskDescription.pickup) && isTaskPlaceValid(taskDescription.dropoff); } -export function makeDefaultSimpleDeliveryTaskDescription(): SimpleDeliveryTaskDescription { +export function makeDefaultDeliveryTaskDescription(): DeliveryTaskDescription { return { pickup: { place: '', @@ -73,24 +72,33 @@ export function makeDefaultSimpleDeliveryTaskDescription(): SimpleDeliveryTaskDe }; } -export interface SimpleDeliveryTaskFormProps { - taskDesc: SimpleDeliveryTaskDescription; +export function makeDeliveryTaskShortDescription( + desc: DeliveryTaskDescription, + displayName?: string, +): string { + return `[${displayName ?? DefaultDeliveryTaskDefinition.taskDisplayName}] Pickup [${ + desc.pickup.payload.sku + }] from [${desc.pickup.place}], dropoff [${desc.dropoff.payload.sku}] at [${desc.dropoff.place}]`; +} + +export interface DeliveryTaskFormProps { + taskDesc: DeliveryTaskDescription; pickupPoints: Record; dropoffPoints: Record; - onChange(taskDesc: SimpleDeliveryTaskDescription): void; + onChange(taskDesc: DeliveryTaskDescription): void; allowSubmit(allow: boolean): void; } -export function SimpleDeliveryTaskForm({ +export function DeliveryTaskForm({ taskDesc, pickupPoints = {}, dropoffPoints = {}, onChange, allowSubmit, -}: SimpleDeliveryTaskFormProps): React.JSX.Element { +}: DeliveryTaskFormProps): React.JSX.Element { const theme = useTheme(); - const onInputChange = (desc: SimpleDeliveryTaskDescription) => { - allowSubmit(isSimpleDeliveryTaskDescriptionValid(desc)); + const onInputChange = (desc: DeliveryTaskDescription) => { + allowSubmit(isDeliveryTaskDescriptionValid(desc)); onChange(desc); }; diff --git a/packages/react-components/lib/tasks/types/index.ts b/packages/react-components/lib/tasks/types/index.ts index a0f628aac..e00fcdb75 100644 --- a/packages/react-components/lib/tasks/types/index.ts +++ b/packages/react-components/lib/tasks/types/index.ts @@ -1,6 +1,6 @@ -export * from './clean'; +export * from './compose-clean'; export * from './custom-compose'; export * from './delivery-custom'; -export * from './delivery-simple'; +export * from './delivery'; export * from './patrol'; export * from './utils'; diff --git a/packages/react-components/lib/tasks/types/patrol.tsx b/packages/react-components/lib/tasks/types/patrol.tsx index 31e9f9490..407228376 100644 --- a/packages/react-components/lib/tasks/types/patrol.tsx +++ b/packages/react-components/lib/tasks/types/patrol.tsx @@ -18,8 +18,9 @@ import { TaskDefinition } from '../create-task'; import type { TaskBookingLabel } from 'api-client'; export const DefaultPatrolTaskDefinition: TaskDefinition = { - task_definition_id: 'patrol', - task_display_name: 'Patrol', + taskDefinitionId: 'patrol', + taskDisplayName: 'Patrol', + requestCategory: 'patrol', }; export interface PatrolTaskDescription { @@ -32,7 +33,7 @@ export function makePatrolTaskBookingLabel( ): TaskBookingLabel { return { description: { - task_definition_id: 'patrol', + task_definition_id: DefaultPatrolTaskDefinition.taskDefinitionId, destination: task_description.places[task_description.places.length - 1], }, }; @@ -57,6 +58,18 @@ export function makeDefaultPatrolTask(): PatrolTaskDescription { }; } +export function makePatrolTaskShortDescription( + desc: PatrolTaskDescription, + displayName?: string, +): string { + console.log(desc); + + const formattedPlaces = desc.places.map((place: string) => `[${place}]`); + return `[${displayName ?? DefaultPatrolTaskDefinition.taskDisplayName}] [${ + desc.rounds + }] round/s, along ${formattedPlaces.join(', ')}`; +} + interface PlaceListProps { places: string[]; onClick(places_index: number): void; diff --git a/packages/react-components/lib/tasks/types/utils.ts b/packages/react-components/lib/tasks/types/utils.ts index 5b06a1e5e..ed9c49b7b 100644 --- a/packages/react-components/lib/tasks/types/utils.ts +++ b/packages/react-components/lib/tasks/types/utils.ts @@ -1,5 +1,24 @@ import { TaskRequest } from 'api-client'; -import { GoToPlaceActivity, LotPickupActivity } from './delivery-custom'; +import { DefaultPatrolTaskDefinition, makePatrolTaskShortDescription } from './patrol'; +import { + DefaultDeliveryAreaPickupTaskDefinition, + DefaultDeliveryPickupTaskDefinition, + DefaultDeliverySequentialLotPickupTaskDefinition, +} from './delivery-custom'; +import { getTaskBookingLabelFromTaskRequest } from '../task-booking-label-utils'; +import { + DefaultComposeCleanTaskDefinition, + makeComposeCleanTaskShortDescription, +} from './compose-clean'; +import { DefaultDeliveryTaskDefinition, makeDeliveryTaskShortDescription } from './delivery'; +import { + makeDeliveryPickupTaskShortDescription, + makeDeliveryCustomTaskShortDescription, +} from './delivery-custom'; +import { + DefaultCustomComposeTaskDefinition, + makeCustomComposeTaskShortDescription, +} from './custom-compose'; export function isNonEmptyString(value: string): boolean { return value.length > 0; @@ -9,90 +28,48 @@ export function isPositiveNumber(value: number): boolean { return value > 0; } +function rawStringFromJsonRequest(taskRequest: TaskRequest): string | undefined { + try { + const requestString = JSON.stringify(taskRequest); + console.error( + `Task does not have a identifying label, failed to generate short description of task: ${requestString}`, + ); + return requestString; + } catch (e) { + console.error( + `Failed to parse description of task of category: ${taskRequest.category}: ${ + (e as Error).message + }`, + ); + return undefined; + } +} + export function getShortDescription( taskRequest: TaskRequest, - remappedTaskName?: string, + taskDisplayName?: string, ): string | undefined { - switch (taskRequest.category) { - case 'patrol': { - const formattedPlaces = taskRequest.description.places.map((place: string) => `[${place}]`); - return `[${remappedTaskName ?? 'Patrol'}] [${ - taskRequest.description.rounds - }] round/s, along ${formattedPlaces.join(', ')}`; - } - case 'delivery': { - return `[${remappedTaskName ?? 'Delivery'}] Pickup [${ - taskRequest.description.pickup.payload.sku - }] from [${taskRequest.description.pickup.place}], dropoff [${ - taskRequest.description.dropoff.payload.sku - }] at [${taskRequest.description.dropoff.place}]`; - } + const bookingLabel = getTaskBookingLabelFromTaskRequest(taskRequest); + if (!bookingLabel) { + return rawStringFromJsonRequest(taskRequest); } - // FIXME: This block is only for non-primary task cateogries, that utilize - // compose. - if (taskRequest.description.category === 'clean') { - const cleanActivity = taskRequest.description.phases[0].activity.description.activities[1]; - return `[${remappedTaskName ?? 'Clean'}] zone [${cleanActivity.description.description.zone}]`; - } - - // This section is only valid for custom delivery types - // FIXME: This block looks like it makes assumptions about the structure of - // the task description in order to parse it, but it is following the - // statically defined description (object) at the top of this file. The - // descriptions should be replaced by a schema in general, however the better - // approach now should be to make each task description testable and in charge - // of their own short descriptions. - try { - const goToPickup: GoToPlaceActivity = - taskRequest.description.phases[0].activity.description.activities[0]; - const pickup: LotPickupActivity = - taskRequest.description.phases[0].activity.description.activities[1]; - const cartId = pickup.description.description.cart_id; - const goToDropoff: GoToPlaceActivity = - taskRequest.description.phases[1].activity.description.activities[0]; - - switch (taskRequest.description.category) { - case 'delivery_pickup': { - return `[${remappedTaskName ?? 'Delivery - 1:1'}] payload [${cartId}] from [${ - goToPickup.description - }] to [${goToDropoff.description}]`; - } - case 'delivery_sequential_lot_pickup': { - return `[${ - remappedTaskName ?? 'Delivery - Sequential lot pick up' - }] payload [${cartId}] from [${goToPickup.description}] to [${goToDropoff.description}]`; - } - case 'delivery_area_pickup': { - return `[${remappedTaskName ?? 'Delivery - Area pick up'}] payload [${cartId}] from [${ - goToPickup.description - }] to [${goToDropoff.description}]`; - } - default: - return `[Unknown] type "${taskRequest.description.category}"`; - } - } catch (e) { - if (e instanceof TypeError) { - console.error(`Failed to parse custom delivery: ${e.message}`); - } else { - console.error( - `Failed to generate short description from task of category: ${taskRequest.category}: ${ - (e as Error).message - }`, - ); - } - - try { - const descriptionString = JSON.stringify(taskRequest.description); - console.error(descriptionString); - return descriptionString; - } catch (e) { - console.error( - `Failed to parse description of task of category: ${taskRequest.category}: ${ - (e as Error).message - }`, - ); - return undefined; - } + const taskDefinitionId = bookingLabel.description.task_definition_id; + switch (taskDefinitionId) { + case DefaultPatrolTaskDefinition.taskDefinitionId: + return makePatrolTaskShortDescription(taskRequest.description, taskDisplayName); + case DefaultDeliveryTaskDefinition.taskDefinitionId: + return makeDeliveryTaskShortDescription(taskRequest.description, taskDisplayName); + case DefaultComposeCleanTaskDefinition.taskDefinitionId: + return makeComposeCleanTaskShortDescription(taskRequest.description, taskDisplayName); + case DefaultDeliveryPickupTaskDefinition.taskDefinitionId: + return makeDeliveryPickupTaskShortDescription(taskRequest.description, taskDisplayName); + case DefaultDeliverySequentialLotPickupTaskDefinition.taskDefinitionId: + case DefaultDeliveryAreaPickupTaskDefinition.taskDefinitionId: + return makeDeliveryCustomTaskShortDescription(taskRequest.description, taskDisplayName); + case DefaultCustomComposeTaskDefinition.taskDefinitionId: + return makeCustomComposeTaskShortDescription(taskRequest.description); + default: + return `[Unknown] type "${taskRequest.description.category}"`; } } From 10699463873f7705313d08dfda1aa83ffc1d3c42 Mon Sep 17 00:00:00 2001 From: Aaron Chong Date: Tue, 4 Jun 2024 17:14:33 +0800 Subject: [PATCH 13/28] Clean up Signed-off-by: Aaron Chong --- .../lib/tasks/create-task.tsx | 100 +++++------------- .../lib/tasks/types/patrol.tsx | 2 +- .../react-components/lib/tasks/types/utils.ts | 60 ++++++++++- 3 files changed, 85 insertions(+), 77 deletions(-) diff --git a/packages/react-components/lib/tasks/create-task.tsx b/packages/react-components/lib/tasks/create-task.tsx index 9a903fffb..e099ccabe 100644 --- a/packages/react-components/lib/tasks/create-task.tsx +++ b/packages/react-components/lib/tasks/create-task.tsx @@ -44,7 +44,6 @@ import { ComposeCleanTaskForm, DefaultComposeCleanTaskDefinition, makeComposeCleanTaskBookingLabel, - makeDefaultComposeCleanTaskDescription, } from './types/compose-clean'; import { DefaultCustomComposeTaskDefinition, @@ -60,25 +59,23 @@ import { makeDeliveryTaskBookingLabel, } from './types/delivery'; import { - DefaultDeliveryPickupTaskDefinition, - DefaultDeliveryAreaPickupTaskDefinition, - DefaultDeliverySequentialLotPickupTaskDefinition, DeliveryCustomTaskForm, DeliveryCustomTaskDescription, DeliveryPickupTaskDescription, DeliveryPickupTaskForm, - makeDefaultDeliveryCustomTaskDescription, - makeDefaultDeliveryPickupTaskDescription, makeDeliveryCustomTaskBookingLabel, makeDeliveryPickupTaskBookingLabel, + DefaultDeliveryPickupTaskDefinition, + DefaultDeliverySequentialLotPickupTaskDefinition, + DefaultDeliveryAreaPickupTaskDefinition, } from './types/delivery-custom'; import { - makeDefaultPatrolTask, makePatrolTaskBookingLabel, DefaultPatrolTaskDefinition, PatrolTaskDescription, PatrolTaskForm, } from './types/patrol'; +import { getDefaultTaskDescription, getTaskRequestCategory } from './types/utils'; import { getTaskBookingLabelFromTaskRequest, serializeTaskBookingLabel, @@ -93,7 +90,7 @@ export interface TaskDefinition { // If no task definition id is found in a past task (scheduled or favorite) const DefaultTaskDefinitionId = DefaultCustomComposeTaskDefinition.taskDefinitionId; -type TaskDescription = +export type TaskDescription = | DeliveryPickupTaskDescription | DeliveryCustomTaskDescription | PatrolTaskDescription @@ -197,45 +194,9 @@ function FavoriteTask({ ); } -function defaultTaskDescription(taskDefinitionId: string): TaskDescription | undefined { - switch (taskDefinitionId) { - case DefaultDeliveryPickupTaskDefinition.taskDefinitionId: - return makeDefaultDeliveryPickupTaskDescription(); - case DefaultDeliverySequentialLotPickupTaskDefinition.taskDefinitionId: - case DefaultDeliveryAreaPickupTaskDefinition.taskDefinitionId: - return makeDefaultDeliveryCustomTaskDescription(taskDefinitionId); - case DefaultPatrolTaskDefinition.taskDefinitionId: - return makeDefaultPatrolTask(); - case DefaultDeliveryTaskDefinition.taskDefinitionId: - return makeDefaultDeliveryTaskDescription(); - case DefaultComposeCleanTaskDefinition.taskDefinitionId: - return makeDefaultComposeCleanTaskDescription(); - default: - return undefined; - } -} - -// TODO: Move this into task defintion as well. To consider moving -// description.category into task definition too. -function taskRequestCategory(taskDefinitionId: string): string | undefined { - switch (taskDefinitionId) { - case DefaultPatrolTaskDefinition.taskDefinitionId: - case DefaultDeliveryTaskDefinition.taskDefinitionId: - return taskDefinitionId; - case DefaultDeliveryPickupTaskDefinition.taskDefinitionId: - case DefaultDeliverySequentialLotPickupTaskDefinition.taskDefinitionId: - case DefaultDeliveryAreaPickupTaskDefinition.taskDefinitionId: - case DefaultComposeCleanTaskDefinition.taskDefinitionId: - case DefaultCustomComposeTaskDefinition.taskDefinitionId: - return 'compose'; - default: - return undefined; - } -} - function defaultTaskRequest(taskDefinitionId: string): TaskRequest { - const category = taskRequestCategory(taskDefinitionId); - const description = defaultTaskDescription(taskDefinitionId); + const category = getTaskRequestCategory(taskDefinitionId); + const description = getDefaultTaskDescription(taskDefinitionId); return { category: category ?? 'compose', @@ -568,25 +529,22 @@ export function CreateTaskForm({ } }; const handleTaskTypeChange = (ev: React.ChangeEvent) => { - const newType = ev.target.value; - console.log(`handleTaskTypeChange ${newType}`); - setTaskDefinitionId(newType); + const newTaskDefinitionId = ev.target.value; + setTaskDefinitionId(newTaskDefinitionId); - if (newType === 'custom_compose') { - taskRequest.category = 'custom_compose'; - taskRequest.description = ''; - } else { - const newDesc = defaultTaskDescription(newType); - if (newDesc === undefined) { - return; - } - taskRequest.description = newDesc; + const category = + getTaskRequestCategory(newTaskDefinitionId) ?? + DefaultCustomComposeTaskDefinition.requestCategory; + taskRequest.category = category; - const primaryTaskCategories = ['patrol', 'delivery']; - const category = primaryTaskCategories.includes(newType) ? newType : 'compose'; - taskRequest.category = category; + const description = getDefaultTaskDescription(newTaskDefinitionId) ?? ''; + taskRequest.description = description; - setFavoriteTaskBuffer({ ...favoriteTaskBuffer, category, description: newDesc }); + if ( + newTaskDefinitionId !== DefaultCustomComposeTaskDefinition.taskDefinitionId && + typeof description === 'object' + ) { + setFavoriteTaskBuffer({ ...favoriteTaskBuffer, category, description }); } }; @@ -607,10 +565,10 @@ export function CreateTaskForm({ request.requester = requester; request.unix_millis_request_time = Date.now(); - if (taskDefinitionId === 'custom_compose') { + if (taskDefinitionId === DefaultCustomComposeTaskDefinition.taskDefinitionId) { try { const obj = JSON.parse(request.description); - request.category = 'compose'; + request.category = DefaultCustomComposeTaskDefinition.requestCategory; request.description = obj; } catch (e) { console.error('Invalid custom compose task description'); @@ -623,23 +581,23 @@ export function CreateTaskForm({ try { let requestBookingLabel: TaskBookingLabel | null = null; switch (taskDefinitionId) { - case 'delivery_pickup': + case DefaultDeliveryPickupTaskDefinition.taskDefinitionId: requestBookingLabel = makeDeliveryPickupTaskBookingLabel(request.description); break; - case 'delivery_sequential_lot_pickup': - case 'delivery_area_pickup': + case DefaultDeliverySequentialLotPickupTaskDefinition.taskDefinitionId: + case DefaultDeliveryAreaPickupTaskDefinition.taskDefinitionId: requestBookingLabel = makeDeliveryCustomTaskBookingLabel(request.description); break; - case 'patrol': + case DefaultPatrolTaskDefinition.taskDefinitionId: requestBookingLabel = makePatrolTaskBookingLabel(request.description); break; - case 'delivery': + case DefaultDeliveryTaskDefinition.taskDefinitionId: requestBookingLabel = makeDeliveryTaskBookingLabel(request.description); break; - case 'clean': + case DefaultComposeCleanTaskDefinition.taskDefinitionId: requestBookingLabel = makeComposeCleanTaskBookingLabel(request.description); break; - case 'custom_compose': + case DefaultCustomComposeTaskDefinition.taskDefinitionId: requestBookingLabel = makeCustomComposeTaskBookingLabel(); break; } diff --git a/packages/react-components/lib/tasks/types/patrol.tsx b/packages/react-components/lib/tasks/types/patrol.tsx index 407228376..679d813c6 100644 --- a/packages/react-components/lib/tasks/types/patrol.tsx +++ b/packages/react-components/lib/tasks/types/patrol.tsx @@ -51,7 +51,7 @@ export const isPatrolTaskDescriptionValid = (taskDescription: PatrolTaskDescript return taskDescription.rounds > 0; }; -export function makeDefaultPatrolTask(): PatrolTaskDescription { +export function makeDefaultPatrolTaskDescription(): PatrolTaskDescription { return { places: [], rounds: 1, diff --git a/packages/react-components/lib/tasks/types/utils.ts b/packages/react-components/lib/tasks/types/utils.ts index ed9c49b7b..181a994ab 100644 --- a/packages/react-components/lib/tasks/types/utils.ts +++ b/packages/react-components/lib/tasks/types/utils.ts @@ -1,24 +1,34 @@ import { TaskRequest } from 'api-client'; -import { DefaultPatrolTaskDefinition, makePatrolTaskShortDescription } from './patrol'; +import { + DefaultPatrolTaskDefinition, + makePatrolTaskShortDescription, + makeDefaultPatrolTaskDescription, +} from './patrol'; import { DefaultDeliveryAreaPickupTaskDefinition, DefaultDeliveryPickupTaskDefinition, DefaultDeliverySequentialLotPickupTaskDefinition, + makeDeliveryPickupTaskShortDescription, + makeDeliveryCustomTaskShortDescription, + makeDefaultDeliveryCustomTaskDescription, + makeDefaultDeliveryPickupTaskDescription, } from './delivery-custom'; import { getTaskBookingLabelFromTaskRequest } from '../task-booking-label-utils'; import { DefaultComposeCleanTaskDefinition, makeComposeCleanTaskShortDescription, + makeDefaultComposeCleanTaskDescription, } from './compose-clean'; -import { DefaultDeliveryTaskDefinition, makeDeliveryTaskShortDescription } from './delivery'; import { - makeDeliveryPickupTaskShortDescription, - makeDeliveryCustomTaskShortDescription, -} from './delivery-custom'; + DefaultDeliveryTaskDefinition, + makeDeliveryTaskShortDescription, + makeDefaultDeliveryTaskDescription, +} from './delivery'; import { DefaultCustomComposeTaskDefinition, makeCustomComposeTaskShortDescription, } from './custom-compose'; +import { TaskDefinition, TaskDescription } from '../create-task'; export function isNonEmptyString(value: string): boolean { return value.length > 0; @@ -45,6 +55,17 @@ function rawStringFromJsonRequest(taskRequest: TaskRequest): string | undefined } } +export const TaskDefinitionMap: Record = { + [DefaultComposeCleanTaskDefinition.taskDefinitionId]: DefaultComposeCleanTaskDefinition, + [DefaultDeliveryPickupTaskDefinition.taskDefinitionId]: DefaultDeliveryPickupTaskDefinition, + [DefaultDeliverySequentialLotPickupTaskDefinition.taskDefinitionId]: + DefaultDeliverySequentialLotPickupTaskDefinition, + [DefaultDeliveryAreaPickupTaskDefinition.taskDefinitionId]: + DefaultDeliveryAreaPickupTaskDefinition, + [DefaultDeliveryTaskDefinition.taskDefinitionId]: DefaultDeliveryTaskDefinition, + [DefaultPatrolTaskDefinition.taskDefinitionId]: DefaultPatrolTaskDefinition, +}; + export function getShortDescription( taskRequest: TaskRequest, taskDisplayName?: string, @@ -73,3 +94,32 @@ export function getShortDescription( return `[Unknown] type "${taskRequest.description.category}"`; } } + +export function getDefaultTaskDescription( + taskDefinitionId: string, +): TaskDescription | string | undefined { + switch (taskDefinitionId) { + case DefaultComposeCleanTaskDefinition.taskDefinitionId: + return makeDefaultComposeCleanTaskDescription(); + case DefaultDeliveryPickupTaskDefinition.taskDefinitionId: + return makeDefaultDeliveryPickupTaskDescription(); + case DefaultDeliverySequentialLotPickupTaskDefinition.taskDefinitionId: + case DefaultDeliveryAreaPickupTaskDefinition.taskDefinitionId: + return makeDefaultDeliveryCustomTaskDescription(taskDefinitionId); + case DefaultDeliveryTaskDefinition.taskDefinitionId: + return makeDefaultDeliveryTaskDescription(); + case DefaultPatrolTaskDefinition.taskDefinitionId: + return makeDefaultPatrolTaskDescription(); + case DefaultCustomComposeTaskDefinition.taskDefinitionId: + return ''; + default: + return undefined; + } +} + +export function getTaskRequestCategory(taskDefinitionId: string): string | undefined { + if (taskDefinitionId in TaskDefinitionMap) { + return TaskDefinitionMap[taskDefinitionId].requestCategory; + } + return undefined; +} From 1edb5e97b1adbb489287246484b46238830e5515 Mon Sep 17 00:00:00 2001 From: Aaron Chong Date: Tue, 4 Jun 2024 17:55:34 +0800 Subject: [PATCH 14/28] Removed problematic and unsused component and test Signed-off-by: Aaron Chong --- .../src/components/tasks/task-schedule.tsx | 1 + packages/react-components/lib/index.ts | 1 - .../lib/simple-filter.spec.tsx | 16 ------ .../lib/simple-filter.stories.tsx | 22 --------- .../react-components/lib/simple-filter.tsx | 49 ------------------- .../react-components/lib/tasks/types/utils.ts | 28 ++++++----- 6 files changed, 16 insertions(+), 101 deletions(-) delete mode 100644 packages/react-components/lib/simple-filter.spec.tsx delete mode 100644 packages/react-components/lib/simple-filter.stories.tsx delete mode 100644 packages/react-components/lib/simple-filter.tsx diff --git a/packages/dashboard/src/components/tasks/task-schedule.tsx b/packages/dashboard/src/components/tasks/task-schedule.tsx index 104d43b8e..7797a5705 100644 --- a/packages/dashboard/src/components/tasks/task-schedule.tsx +++ b/packages/dashboard/src/components/tasks/task-schedule.tsx @@ -320,6 +320,7 @@ export const TaskSchedule = () => { {openCreateTaskForm && ( { - const mockOnChange = jasmine.createSpy(); - render(); - - userEvent.paste( - screen.getByLabelText('text-input').childNodes[1].childNodes[0] as TargetElement, - 'new text', - ); - - expect(mockOnChange).toHaveBeenCalled(); -}); diff --git a/packages/react-components/lib/simple-filter.stories.tsx b/packages/react-components/lib/simple-filter.stories.tsx deleted file mode 100644 index f77f8da3d..000000000 --- a/packages/react-components/lib/simple-filter.stories.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { Meta, Story } from '@storybook/react'; -import React from 'react'; -import { OnFilterChangeEvent, SimpleFilter } from './simple-filter'; - -export default { - title: 'Simple Filter', - component: SimpleFilter, -} as Meta; - -function SimpleFilterHandler(): JSX.Element { - const [filter, setFilter] = React.useState(''); - - const onChange = (e: React.ChangeEvent) => { - setFilter(e.target.value); - }; - - return ; -} - -export const SimpleFilterStory: Story = (args) => { - return ; -}; diff --git a/packages/react-components/lib/simple-filter.tsx b/packages/react-components/lib/simple-filter.tsx deleted file mode 100644 index 972741019..000000000 --- a/packages/react-components/lib/simple-filter.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import React from 'react'; -import { TextField, Divider, styled } from '@mui/material'; - -export interface OnFilterChangeEvent { - name?: string | undefined; - value: string; -} - -export interface SimpleFilterProps { - onChange?: (e: React.ChangeEvent) => void; - value: string; -} - -const classes = { - simpleFilter: 'simple-filter-root', - filterBar: 'simple-filter-filterbar', - divider: 'simple-filter-divider', -}; -const StyledDiv = styled('div')(({ theme }) => ({ - [`&.${classes.simpleFilter}`]: { - margin: '1rem', - borderColor: theme.palette.success.main, - }, - [`& .${classes.filterBar}`]: { - width: '100%', - }, - [`& .${classes.divider}`]: { - margin: '1.5rem 0', - }, -})); - -export const SimpleFilter = (props: SimpleFilterProps): JSX.Element => { - const { onChange, value } = props; - - return ( - - - - - ); -}; diff --git a/packages/react-components/lib/tasks/types/utils.ts b/packages/react-components/lib/tasks/types/utils.ts index 181a994ab..e98775f15 100644 --- a/packages/react-components/lib/tasks/types/utils.ts +++ b/packages/react-components/lib/tasks/types/utils.ts @@ -55,17 +55,6 @@ function rawStringFromJsonRequest(taskRequest: TaskRequest): string | undefined } } -export const TaskDefinitionMap: Record = { - [DefaultComposeCleanTaskDefinition.taskDefinitionId]: DefaultComposeCleanTaskDefinition, - [DefaultDeliveryPickupTaskDefinition.taskDefinitionId]: DefaultDeliveryPickupTaskDefinition, - [DefaultDeliverySequentialLotPickupTaskDefinition.taskDefinitionId]: - DefaultDeliverySequentialLotPickupTaskDefinition, - [DefaultDeliveryAreaPickupTaskDefinition.taskDefinitionId]: - DefaultDeliveryAreaPickupTaskDefinition, - [DefaultDeliveryTaskDefinition.taskDefinitionId]: DefaultDeliveryTaskDefinition, - [DefaultPatrolTaskDefinition.taskDefinitionId]: DefaultPatrolTaskDefinition, -}; - export function getShortDescription( taskRequest: TaskRequest, taskDisplayName?: string, @@ -118,8 +107,21 @@ export function getDefaultTaskDescription( } export function getTaskRequestCategory(taskDefinitionId: string): string | undefined { - if (taskDefinitionId in TaskDefinitionMap) { - return TaskDefinitionMap[taskDefinitionId].requestCategory; + switch (taskDefinitionId) { + case DefaultComposeCleanTaskDefinition.taskDefinitionId: + return DefaultComposeCleanTaskDefinition.requestCategory; + case DefaultDeliveryPickupTaskDefinition.taskDefinitionId: + return DefaultDeliveryPickupTaskDefinition.requestCategory; + case DefaultDeliverySequentialLotPickupTaskDefinition.taskDefinitionId: + return DefaultDeliverySequentialLotPickupTaskDefinition.requestCategory; + case DefaultDeliveryAreaPickupTaskDefinition.taskDefinitionId: + return DefaultDeliveryAreaPickupTaskDefinition.requestCategory; + case DefaultDeliveryTaskDefinition.taskDefinitionId: + return DefaultDeliveryTaskDefinition.requestCategory; + case DefaultPatrolTaskDefinition.taskDefinitionId: + return DefaultPatrolTaskDefinition.requestCategory; + case DefaultCustomComposeTaskDefinition.taskDefinitionId: + return DefaultCustomComposeTaskDefinition.requestCategory; } return undefined; } From d51646e4dd5100dc1703ba84b77c248090cc5176 Mon Sep 17 00:00:00 2001 From: Aaron Chong Date: Thu, 6 Jun 2024 12:06:41 +0800 Subject: [PATCH 15/28] Lint Signed-off-by: Aaron Chong --- packages/react-components/lib/tasks/types/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-components/lib/tasks/types/utils.ts b/packages/react-components/lib/tasks/types/utils.ts index e98775f15..5b72cd8a2 100644 --- a/packages/react-components/lib/tasks/types/utils.ts +++ b/packages/react-components/lib/tasks/types/utils.ts @@ -28,7 +28,7 @@ import { DefaultCustomComposeTaskDefinition, makeCustomComposeTaskShortDescription, } from './custom-compose'; -import { TaskDefinition, TaskDescription } from '../create-task'; +import { TaskDescription } from '../create-task'; export function isNonEmptyString(value: string): boolean { return value.length > 0; From 1901697d3c2ea20ff6703ad495fff8f5c281d18d Mon Sep 17 00:00:00 2001 From: Aaron Chong Date: Thu, 6 Jun 2024 14:15:19 +0800 Subject: [PATCH 16/28] Updating pnpm version in github workflow Signed-off-by: Aaron Chong --- .github/actions/bootstrap/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/bootstrap/action.yml b/.github/actions/bootstrap/action.yml index 490b293c0..fce560764 100644 --- a/.github/actions/bootstrap/action.yml +++ b/.github/actions/bootstrap/action.yml @@ -13,7 +13,7 @@ runs: steps: - uses: pnpm/action-setup@v2.2.2 with: - version: '8.15.7' + version: '9.2.0' - uses: actions/setup-node@v2 with: node-version: '16' From a1d31409b7f5ce17b5d20d02b2a24bfc1b736c6f Mon Sep 17 00:00:00 2001 From: Aaron Chong Date: Thu, 6 Jun 2024 14:17:24 +0800 Subject: [PATCH 17/28] Reverting update to pnpm version Signed-off-by: Aaron Chong --- .github/actions/bootstrap/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/bootstrap/action.yml b/.github/actions/bootstrap/action.yml index fce560764..490b293c0 100644 --- a/.github/actions/bootstrap/action.yml +++ b/.github/actions/bootstrap/action.yml @@ -13,7 +13,7 @@ runs: steps: - uses: pnpm/action-setup@v2.2.2 with: - version: '9.2.0' + version: '8.15.7' - uses: actions/setup-node@v2 with: node-version: '16' From 114631ea80e52cae9656a5b70a39226b83a4c8ce Mon Sep 17 00:00:00 2001 From: Aaron Chong Date: Thu, 6 Jun 2024 14:39:06 +0800 Subject: [PATCH 18/28] Fix build now that we use key value strings for labels Signed-off-by: Aaron Chong --- packages/react-components/lib/tasks/create-task.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-components/lib/tasks/create-task.tsx b/packages/react-components/lib/tasks/create-task.tsx index e099ccabe..4b69b9354 100644 --- a/packages/react-components/lib/tasks/create-task.tsx +++ b/packages/react-components/lib/tasks/create-task.tsx @@ -370,8 +370,8 @@ export function CreateTaskForm({ ); const initialBookingLabel = requestTask ? getTaskBookingLabelFromTaskRequest(requestTask) : null; const [taskDefinitionId, setTaskDefinitionId] = React.useState( - initialBookingLabel - ? initialBookingLabel.description.task_definition_id + initialBookingLabel && initialBookingLabel.description.task_definition_id + ? (initialBookingLabel.description.task_definition_id as string) : supportedTasks.length > 0 ? supportedTasks[0].taskDefinitionId : DefaultTaskDefinitionId, From c421aa3bba8976d6b5dd37ea9e1f4d12572e7cbf Mon Sep 17 00:00:00 2001 From: Aaron Chong Date: Thu, 6 Jun 2024 15:25:09 +0800 Subject: [PATCH 19/28] Refactored last parts of hard coding categories and rendering forms Signed-off-by: Aaron Chong --- .../lib/tasks/create-task.tsx | 90 ++++++++++++------- 1 file changed, 56 insertions(+), 34 deletions(-) diff --git a/packages/react-components/lib/tasks/create-task.tsx b/packages/react-components/lib/tasks/create-task.tsx index 4b69b9354..103191d41 100644 --- a/packages/react-components/lib/tasks/create-task.tsx +++ b/packages/react-components/lib/tasks/create-task.tsx @@ -435,59 +435,50 @@ export function CreateTaskForm({ setTaskRequest((prev) => { return { ...prev, - category: 'custom_compose', + category: DefaultCustomComposeTaskDefinition.requestCategory, description: newDesc, }; }); }; - const renderTaskDescriptionForm = () => { - switch (taskRequest.category) { - case 'patrol': + const renderTaskDescriptionForm = (definitionId: string) => { + switch (definitionId) { + case DefaultPatrolTaskDefinition.taskDefinitionId: return ( handleTaskDescriptionChange('patrol', desc)} + onChange={(desc) => + handleTaskDescriptionChange(DefaultPatrolTaskDefinition.requestCategory, desc) + } allowSubmit={allowSubmit} /> ); - case 'delivery': + case DefaultDeliveryTaskDefinition.taskDefinitionId: return ( handleTaskDescriptionChange('delivery', desc)} + onChange={(desc) => + handleTaskDescriptionChange(DefaultDeliveryTaskDefinition.requestCategory, desc) + } allowSubmit={allowSubmit} /> ); - case 'custom_compose': - return ( - { - handleCustomComposeTaskDescriptionChange(desc); - }} - allowSubmit={allowSubmit} - /> - ); - } - - switch (taskRequest.description.category) { - case 'clean': + case DefaultComposeCleanTaskDefinition.taskDefinitionId: return ( { desc.category = taskRequest.description.category; - handleTaskDescriptionChange('compose', desc); + handleTaskDescriptionChange(DefaultComposeCleanTaskDefinition.requestCategory, desc); }} allowSubmit={allowSubmit} /> ); - case 'delivery_pickup': + case DefaultDeliveryPickupTaskDefinition.taskDefinitionId: return ( ); - case 'delivery_sequential_lot_pickup': - case 'delivery_area_pickup': + case DefaultDeliverySequentialLotPickupTaskDefinition.taskDefinitionId: return ( ); + case DefaultDeliveryAreaPickupTaskDefinition.taskDefinitionId: + return ( + { + desc.category = taskRequest.description.category; + desc.phases[0].activity.description.activities[1].description.category = + taskRequest.description.category; + handleTaskDescriptionChange( + DefaultDeliveryAreaPickupTaskDefinition.requestCategory, + desc, + ); + }} + allowSubmit={allowSubmit} + /> + ); + case DefaultCustomComposeTaskDefinition.taskDefinitionId: default: - return null; + return ( + { + handleCustomComposeTaskDescriptionChange(desc); + }} + allowSubmit={allowSubmit} + /> + ); } }; const handleTaskTypeChange = (ev: React.ChangeEvent) => { @@ -876,7 +896,7 @@ export function CreateTaskForm({ flexItem style={{ marginTop: theme.spacing(2), marginBottom: theme.spacing(2) }} /> - {renderTaskDescriptionForm()} + {renderTaskDescriptionForm(taskDefinitionId)} From bce846ff06b82b85bbc727e4fbdf912a9135103d Mon Sep 17 00:00:00 2001 From: Aaron Chong Date: Wed, 19 Jun 2024 11:00:32 +0800 Subject: [PATCH 20/28] Refactor callback names and error handling for misconfigs Signed-off-by: Aaron Chong --- .../lib/tasks/create-task.tsx | 165 ++++++++++++------ .../lib/tasks/types/compose-clean.tsx | 6 +- .../lib/tasks/types/custom-compose.tsx | 6 +- .../lib/tasks/types/delivery-custom.tsx | 12 +- .../lib/tasks/types/delivery.tsx | 6 +- .../lib/tasks/types/patrol.tsx | 8 +- 6 files changed, 132 insertions(+), 71 deletions(-) diff --git a/packages/react-components/lib/tasks/create-task.tsx b/packages/react-components/lib/tasks/create-task.tsx index 935c77edc..b9446c478 100644 --- a/packages/react-components/lib/tasks/create-task.tsx +++ b/packages/react-components/lib/tasks/create-task.tsx @@ -55,7 +55,6 @@ import { DefaultDeliveryTaskDefinition, DeliveryTaskDescription, DeliveryTaskForm, - makeDefaultDeliveryTaskDescription, makeDeliveryTaskBookingLabel, } from './types/delivery'; import { @@ -88,7 +87,7 @@ export interface TaskDefinition { } // If no task definition id is found in a past task (scheduled or favorite) -const DefaultTaskDefinitionId = DefaultCustomComposeTaskDefinition.taskDefinitionId; +const FallbackTaskDefinition = DefaultCustomComposeTaskDefinition; export type TaskDescription = | DeliveryPickupTaskDescription @@ -194,18 +193,31 @@ function FavoriteTask({ ); } -function defaultTaskRequest(taskDefinitionId: string): TaskRequest { +function getDefaultTaskRequest(taskDefinitionId: string): TaskRequest | null { const category = getTaskRequestCategory(taskDefinitionId); const description = getDefaultTaskDescription(taskDefinitionId); - return { - category: category ?? 'compose', - description: description ?? makeDefaultDeliveryTaskDescription(), - unix_millis_earliest_start_time: 0, - unix_millis_request_time: Date.now(), - priority: { type: 'binary', value: 0 }, - requester: '', - }; + if (!category) { + console.error(`Unable to retrieve task category for task definition of id ${taskDefinitionId}`); + } + if (!description) { + console.error( + `Unable to retrieve task description for task definition of id ${taskDefinitionId}`, + ); + } + + if (category && description) { + return { + category, + description, + unix_millis_earliest_start_time: 0, + unix_millis_request_time: Date.now(), + priority: { type: 'binary', value: 0 }, + requester: '', + }; + } + + return null; } export type RecurringDays = [boolean, boolean, boolean, boolean, boolean, boolean, boolean]; @@ -271,19 +283,6 @@ const DaySelectorSwitch: React.VFC = ({ disabled, onChan ); }; -const defaultFavoriteTask = (): TaskFavorite => { - return { - id: '', - name: '', - category: 'compose', - description: makeDefaultDeliveryTaskDescription(), - unix_millis_earliest_start_time: 0, - priority: { type: 'binary', value: 0 }, - user: '', - task_definition_id: DefaultTaskDefinitionId, - }; -}; - export interface CreateTaskFormProps extends Omit { /** @@ -355,26 +354,84 @@ export function CreateTaskForm({ const isScreenHeightLessThan800 = useMediaQuery('(max-height:800px)'); - const [favoriteTaskBuffer, setFavoriteTaskBuffer] = React.useState( - defaultFavoriteTask(), - ); + // Note that we are not checking if the number of supported tasks is larger + // than 0, this will cause the dashboard to fail when the create task form is + // opened. This is intentional as it is a misconfiguration and will require + // the build-time configuration to be fixed. + let allSupportedTasksAreValid = true; + const validTasks: TaskDefinition[] = []; + let defaultTaskDescription: string | TaskDescription | null = null; + let defaultTaskRequest: TaskRequest | null = null; + supportedTasks.forEach((supportedTask: TaskDefinition, index: number) => { + const definitionId = supportedTask.taskDefinitionId; + const desc = getDefaultTaskDescription(definitionId); + const req = getDefaultTaskRequest(definitionId); + + if (!desc) { + console.error(`Failed to retrieve task description for definition ID: [${definitionId}]`); + allSupportedTasksAreValid = false; + } + if (!req) { + console.error(`Failed to create task request for definition ID: [${definitionId}]`); + allSupportedTasksAreValid = false; + } + if (desc && req) { + validTasks.push(supportedTask); + + if (!defaultTaskDescription && !defaultTaskRequest) { + defaultTaskDescription = desc; + defaultTaskRequest = req; + } + } + }); + + // for (const supportedTask of supportedTasks) { + // const definitionId = supportedTask.taskDefinitionId; + // const desc = getDefaultTaskDescription(definitionId); + // const req = getDefaultTaskRequest(definitionId); + // if (!desc) { + // console.error(`Failed to retrieve task description for definition ID: [${definitionId}]`); + // allSupportedTasksAreValid = false; + // } + // if (!req) { + // console.error(`Failed to create task request for definition ID: [${definitionId}]`); + // allSupportedTasksAreValid = false; + // } + // if (desc && req) { + // validTasks.push(supportedTask); + // } + // } + // if (!allSupportedTasksAreValid) { + // console.error('Issues found in supported task definitions'); + // } + + if (!defaultTaskDescription || !defaultTaskRequest) { + // We should never reach this state + console.error('Default task could not be generated, this might be a configuration error'); + return <>; + } + + const [favoriteTaskBuffer, setFavoriteTaskBuffer] = React.useState({ + id: '', + name: '', + category: supportedTasks[0].requestCategory, + description: defaultTaskDescription as object, + unix_millis_earliest_start_time: 0, + priority: { type: 'binary', value: 0 }, + user: '', + task_definition_id: supportedTasks[0].taskDefinitionId, + }); const [favoriteTaskTitleError, setFavoriteTaskTitleError] = React.useState(false); const [savingFavoriteTask, setSavingFavoriteTask] = React.useState(false); const [taskRequest, setTaskRequest] = React.useState( - () => - requestTask ?? - defaultTaskRequest( - supportedTasks.length > 0 ? supportedTasks[0].taskDefinitionId : DefaultTaskDefinitionId, - ), + requestTask ?? defaultTaskRequest, ); const initialBookingLabel = requestTask ? getTaskBookingLabelFromTaskRequest(requestTask) : null; const [taskDefinitionId, setTaskDefinitionId] = React.useState( initialBookingLabel && initialBookingLabel.description.task_definition_id ? (initialBookingLabel.description.task_definition_id as string) - : supportedTasks.length > 0 - ? supportedTasks[0].taskDefinitionId - : DefaultTaskDefinitionId, + : supportedTasks[0].taskDefinitionId, ); const [submitting, setSubmitting] = React.useState(false); @@ -455,6 +512,10 @@ export function CreateTaskForm({ }); }; + const onValidate = (valid: boolean) => { + setFormFullyFilled(valid); + }; + const renderTaskDescriptionForm = (definitionId: string) => { switch (definitionId) { case DefaultPatrolTaskDefinition.taskDefinitionId: @@ -465,7 +526,7 @@ export function CreateTaskForm({ onChange={(desc) => handleTaskDescriptionChange(DefaultPatrolTaskDefinition.requestCategory, desc) } - allowSubmit={allowSubmit} + onValidate={onValidate} /> ); case DefaultDeliveryTaskDefinition.taskDefinitionId: @@ -477,7 +538,7 @@ export function CreateTaskForm({ onChange={(desc) => handleTaskDescriptionChange(DefaultDeliveryTaskDefinition.requestCategory, desc) } - allowSubmit={allowSubmit} + onValidate={onValidate} /> ); case DefaultComposeCleanTaskDefinition.taskDefinitionId: @@ -489,7 +550,7 @@ export function CreateTaskForm({ desc.category = taskRequest.description.category; handleTaskDescriptionChange(DefaultComposeCleanTaskDefinition.requestCategory, desc); }} - allowSubmit={allowSubmit} + onValidate={onValidate} /> ); case DefaultDeliveryPickupTaskDefinition.taskDefinitionId: @@ -508,7 +569,7 @@ export function CreateTaskForm({ desc, ); }} - allowSubmit={allowSubmit} + onValidate={onValidate} /> ); case DefaultDeliverySequentialLotPickupTaskDefinition.taskDefinitionId: @@ -527,7 +588,7 @@ export function CreateTaskForm({ desc, ); }} - allowSubmit={allowSubmit} + onValidate={onValidate} /> ); case DefaultDeliveryAreaPickupTaskDefinition.taskDefinitionId: @@ -546,7 +607,7 @@ export function CreateTaskForm({ desc, ); }} - allowSubmit={allowSubmit} + onValidate={onValidate} /> ); case DefaultCustomComposeTaskDefinition.taskDefinitionId: @@ -557,7 +618,7 @@ export function CreateTaskForm({ onChange={(desc) => { handleCustomComposeTaskDescriptionChange(desc); }} - allowSubmit={allowSubmit} + onValidate={onValidate} /> ); } @@ -582,10 +643,6 @@ export function CreateTaskForm({ } }; - const allowSubmit = (allow: boolean) => { - setFormFullyFilled(allow); - }; - // no memo because deps would likely change const handleSubmit = async (scheduling: boolean) => { if (!submitTasks) { @@ -704,7 +761,7 @@ export function CreateTaskForm({ setSavingFavoriteTask(true); const favoriteTask = favoriteTaskBuffer; - favoriteTask.task_definition_id = taskDefinitionId ?? DefaultTaskDefinitionId; + favoriteTask.task_definition_id = taskDefinitionId ?? supportedTasks[0].taskDefinitionId; await submitFavoriteTask(favoriteTask); setSavingFavoriteTask(false); @@ -734,11 +791,15 @@ export function CreateTaskForm({ onSuccessFavoriteTask && onSuccessFavoriteTask('Deleted favorite task successfully', favoriteTaskBuffer); - setTaskRequest( - supportedTasks && supportedTasks.length > 0 - ? defaultTaskRequest(supportedTasks[0].taskDefinitionId) - : defaultTaskRequest('patrol'), - ); + const defaultTaskRequest = getDefaultTaskRequest(supportedTasks[0].taskDefinitionId); + if (!defaultTaskRequest) { + // We should never reach this area as we have already validated that + // each supported task have a valid task request for generation + console.error('Failed to reset task request buffer after deleting favorite task'); + return; + } + + setTaskRequest(defaultTaskRequest); setOpenFavoriteDialog(false); setCallToDeleteFavoriteTask(false); setCallToUpdateFavoriteTask(false); diff --git a/packages/react-components/lib/tasks/types/compose-clean.tsx b/packages/react-components/lib/tasks/types/compose-clean.tsx index 50f1fb33f..da5e1f7cf 100644 --- a/packages/react-components/lib/tasks/types/compose-clean.tsx +++ b/packages/react-components/lib/tasks/types/compose-clean.tsx @@ -112,17 +112,17 @@ interface ComposeCleanTaskFormProps { taskDesc: ComposeCleanTaskDescription; cleaningZones: string[]; onChange(cleanTaskDescription: ComposeCleanTaskDescription): void; - allowSubmit(allow: boolean): void; + onValidate(valid: boolean): void; } export function ComposeCleanTaskForm({ taskDesc, cleaningZones, onChange, - allowSubmit, + onValidate, }: ComposeCleanTaskFormProps): React.JSX.Element { const onInputChange = (desc: ComposeCleanTaskDescription) => { - allowSubmit(isComposeCleanTaskDescriptionValid(desc)); + onValidate(isComposeCleanTaskDescriptionValid(desc)); onChange(desc); }; diff --git a/packages/react-components/lib/tasks/types/custom-compose.tsx b/packages/react-components/lib/tasks/types/custom-compose.tsx index 9e43a75ca..9e60faf60 100644 --- a/packages/react-components/lib/tasks/types/custom-compose.tsx +++ b/packages/react-components/lib/tasks/types/custom-compose.tsx @@ -40,17 +40,17 @@ const isCustomTaskDescriptionValid = (taskDescription: string): boolean => { interface CustomComposeTaskFormProps { taskDesc: CustomComposeTaskDescription; onChange(customComposeTaskDescription: CustomComposeTaskDescription): void; - allowSubmit(allow: boolean): void; + onValidate(valid: boolean): void; } export function CustomComposeTaskForm({ taskDesc, onChange, - allowSubmit, + onValidate, }: CustomComposeTaskFormProps): React.JSX.Element { const theme = useTheme(); const onInputChange = (desc: CustomComposeTaskDescription) => { - allowSubmit(isCustomTaskDescriptionValid(desc)); + onValidate(isCustomTaskDescriptionValid(desc)); onChange(desc); }; diff --git a/packages/react-components/lib/tasks/types/delivery-custom.tsx b/packages/react-components/lib/tasks/types/delivery-custom.tsx index cb473ef9c..bf3f1f241 100644 --- a/packages/react-components/lib/tasks/types/delivery-custom.tsx +++ b/packages/react-components/lib/tasks/types/delivery-custom.tsx @@ -343,7 +343,7 @@ interface DeliveryPickupTaskFormProps { cartIds: string[]; dropoffPoints: Record; onChange(taskDesc: DeliveryPickupTaskDescription): void; - allowSubmit(allow: boolean): void; + onValidate(valid: boolean): void; } export function DeliveryPickupTaskForm({ @@ -352,12 +352,12 @@ export function DeliveryPickupTaskForm({ cartIds = [], dropoffPoints = {}, onChange, - allowSubmit, + onValidate, }: DeliveryPickupTaskFormProps): React.JSX.Element { const theme = useTheme(); const isScreenHeightLessThan800 = useMediaQuery('(max-height:800px)'); const onInputChange = (desc: DeliveryPickupTaskDescription) => { - allowSubmit(isDeliveryPickupTaskDescriptionValid(desc, pickupPoints, dropoffPoints)); + onValidate(isDeliveryPickupTaskDescriptionValid(desc, pickupPoints, dropoffPoints)); onChange(desc); }; @@ -556,7 +556,7 @@ interface DeliveryCustomProps { cartIds: string[]; dropoffPoints: string[]; onChange(taskDesc: DeliveryCustomTaskDescription): void; - allowSubmit(allow: boolean): void; + onValidate(valid: boolean): void; } export function DeliveryCustomTaskForm({ @@ -565,12 +565,12 @@ export function DeliveryCustomTaskForm({ cartIds = [], dropoffPoints = [], onChange, - allowSubmit, + onValidate, }: DeliveryCustomProps): React.JSX.Element { const theme = useTheme(); const isScreenHeightLessThan800 = useMediaQuery('(max-height:800px)'); const onInputChange = (desc: DeliveryCustomTaskDescription) => { - allowSubmit(isDeliveryCustomTaskDescriptionValid(desc, pickupZones, dropoffPoints)); + onValidate(isDeliveryCustomTaskDescriptionValid(desc, pickupZones, dropoffPoints)); onChange(desc); }; diff --git a/packages/react-components/lib/tasks/types/delivery.tsx b/packages/react-components/lib/tasks/types/delivery.tsx index 53a52b157..2422fbbb6 100644 --- a/packages/react-components/lib/tasks/types/delivery.tsx +++ b/packages/react-components/lib/tasks/types/delivery.tsx @@ -86,7 +86,7 @@ export interface DeliveryTaskFormProps { pickupPoints: Record; dropoffPoints: Record; onChange(taskDesc: DeliveryTaskDescription): void; - allowSubmit(allow: boolean): void; + onValidate(valid: boolean): void; } export function DeliveryTaskForm({ @@ -94,11 +94,11 @@ export function DeliveryTaskForm({ pickupPoints = {}, dropoffPoints = {}, onChange, - allowSubmit, + onValidate, }: DeliveryTaskFormProps): React.JSX.Element { const theme = useTheme(); const onInputChange = (desc: DeliveryTaskDescription) => { - allowSubmit(isDeliveryTaskDescriptionValid(desc)); + onValidate(isDeliveryTaskDescriptionValid(desc)); onChange(desc); }; diff --git a/packages/react-components/lib/tasks/types/patrol.tsx b/packages/react-components/lib/tasks/types/patrol.tsx index 679d813c6..6615a7c1e 100644 --- a/packages/react-components/lib/tasks/types/patrol.tsx +++ b/packages/react-components/lib/tasks/types/patrol.tsx @@ -109,22 +109,22 @@ interface PatrolTaskFormProps { taskDesc: PatrolTaskDescription; patrolWaypoints: string[]; onChange(patrolTaskDescription: PatrolTaskDescription): void; - allowSubmit(allow: boolean): void; + onValidate(valid: boolean): void; } export function PatrolTaskForm({ taskDesc, patrolWaypoints, onChange, - allowSubmit, + onValidate, }: PatrolTaskFormProps): React.JSX.Element { const theme = useTheme(); const isScreenHeightLessThan800 = useMediaQuery('(max-height:800px)'); const onInputChange = (desc: PatrolTaskDescription) => { - allowSubmit(isPatrolTaskDescriptionValid(desc)); + onValidate(isPatrolTaskDescriptionValid(desc)); onChange(desc); }; - allowSubmit(isPatrolTaskDescriptionValid(taskDesc)); + onValidate(isPatrolTaskDescriptionValid(taskDesc)); return ( From bde26497a742845d712ddebf66191f9f0b071466 Mon Sep 17 00:00:00 2001 From: Aaron Chong Date: Wed, 19 Jun 2024 11:06:12 +0800 Subject: [PATCH 21/28] Display error as well Signed-off-by: Aaron Chong --- .../lib/tasks/create-task.tsx | 26 +++---------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/packages/react-components/lib/tasks/create-task.tsx b/packages/react-components/lib/tasks/create-task.tsx index b9446c478..02f4b6130 100644 --- a/packages/react-components/lib/tasks/create-task.tsx +++ b/packages/react-components/lib/tasks/create-task.tsx @@ -385,29 +385,11 @@ export function CreateTaskForm({ } }); - // for (const supportedTask of supportedTasks) { - // const definitionId = supportedTask.taskDefinitionId; - // const desc = getDefaultTaskDescription(definitionId); - // const req = getDefaultTaskRequest(definitionId); - // if (!desc) { - // console.error(`Failed to retrieve task description for definition ID: [${definitionId}]`); - // allSupportedTasksAreValid = false; - // } - // if (!req) { - // console.error(`Failed to create task request for definition ID: [${definitionId}]`); - // allSupportedTasksAreValid = false; - // } - // if (desc && req) { - // validTasks.push(supportedTask); - // } - // } - // if (!allSupportedTasksAreValid) { - // console.error('Issues found in supported task definitions'); - // } - if (!defaultTaskDescription || !defaultTaskRequest) { - // We should never reach this state - console.error('Default task could not be generated, this might be a configuration error'); + // We should never reach this state unless a misconfiguration happened. + const err = Error('Default task could not be generated, this might be a configuration error'); + onFail && onFail(err, []); + console.error(err.message); return <>; } From d452affaa133634466a30fb774f3af44a2ca417a Mon Sep 17 00:00:00 2001 From: Aaron Chong Date: Wed, 19 Jun 2024 14:44:41 +0800 Subject: [PATCH 22/28] Fixed more checks and failures Signed-off-by: Aaron Chong --- .../components/tasks/task-schedule-utils.ts | 19 ++++++++-- .../lib/tasks/create-task.tsx | 37 ++++++++++--------- 2 files changed, 35 insertions(+), 21 deletions(-) diff --git a/packages/dashboard/src/components/tasks/task-schedule-utils.ts b/packages/dashboard/src/components/tasks/task-schedule-utils.ts index ff8f7023f..b5a7b9356 100644 --- a/packages/dashboard/src/components/tasks/task-schedule-utils.ts +++ b/packages/dashboard/src/components/tasks/task-schedule-utils.ts @@ -23,7 +23,13 @@ import { nextWednesday, startOfMinute, } from 'date-fns'; -import { getShortDescription, RecurringDays, Schedule, TaskDefinition } from 'react-components'; +import { + getShortDescription, + getTaskBookingLabelFromTaskRequest, + RecurringDays, + Schedule, + TaskDefinition, +} from 'react-components'; /** * Generates a list of ProcessedEvents to occur within the query start and end, @@ -170,10 +176,17 @@ export const getScheduledTaskTitle = ( task: ScheduledTask, supportedTasks?: TaskDefinition[], ): string => { + const taskBookingLabel = getTaskBookingLabelFromTaskRequest(task.task_request); + let remappedTaskName: string | undefined = undefined; - if (supportedTasks) { + if ( + supportedTasks && + taskBookingLabel && + taskBookingLabel.description.task_definition_id && + typeof taskBookingLabel.description.task_definition_id === 'string' + ) { for (const s of supportedTasks) { - if (s.taskDefinitionId === task.task_request.category) { + if (s.taskDefinitionId === taskBookingLabel.description.task_definition_id) { remappedTaskName = s.taskDisplayName; } } diff --git a/packages/react-components/lib/tasks/create-task.tsx b/packages/react-components/lib/tasks/create-task.tsx index 02f4b6130..2edb14209 100644 --- a/packages/react-components/lib/tasks/create-task.tsx +++ b/packages/react-components/lib/tasks/create-task.tsx @@ -86,9 +86,6 @@ export interface TaskDefinition { requestCategory: string; } -// If no task definition id is found in a past task (scheduled or favorite) -const FallbackTaskDefinition = DefaultCustomComposeTaskDefinition; - export type TaskDescription = | DeliveryPickupTaskDescription | DeliveryCustomTaskDescription @@ -197,16 +194,16 @@ function getDefaultTaskRequest(taskDefinitionId: string): TaskRequest | null { const category = getTaskRequestCategory(taskDefinitionId); const description = getDefaultTaskDescription(taskDefinitionId); - if (!category) { + if (category === undefined) { console.error(`Unable to retrieve task category for task definition of id ${taskDefinitionId}`); } - if (!description) { + if (description === undefined) { console.error( `Unable to retrieve task description for task definition of id ${taskDefinitionId}`, ); } - if (category && description) { + if (category !== undefined && description !== undefined) { return { category, description, @@ -367,11 +364,11 @@ export function CreateTaskForm({ const desc = getDefaultTaskDescription(definitionId); const req = getDefaultTaskRequest(definitionId); - if (!desc) { + if (desc === undefined) { console.error(`Failed to retrieve task description for definition ID: [${definitionId}]`); allSupportedTasksAreValid = false; } - if (!req) { + if (req === null) { console.error(`Failed to create task request for definition ID: [${definitionId}]`); allSupportedTasksAreValid = false; } @@ -411,8 +408,10 @@ export function CreateTaskForm({ ); const initialBookingLabel = requestTask ? getTaskBookingLabelFromTaskRequest(requestTask) : null; const [taskDefinitionId, setTaskDefinitionId] = React.useState( - initialBookingLabel && initialBookingLabel.description.task_definition_id - ? (initialBookingLabel.description.task_definition_id as string) + initialBookingLabel && + initialBookingLabel.description.task_definition_id && + typeof initialBookingLabel.description.task_definition_id === 'string' + ? initialBookingLabel.description.task_definition_id : supportedTasks[0].taskDefinitionId, ); @@ -609,9 +608,15 @@ export function CreateTaskForm({ const newTaskDefinitionId = ev.target.value; setTaskDefinitionId(newTaskDefinitionId); - const category = - getTaskRequestCategory(newTaskDefinitionId) ?? - DefaultCustomComposeTaskDefinition.requestCategory; + const category = getTaskRequestCategory(newTaskDefinitionId); + if (category === undefined) { + const err = Error( + `Failed to retrieve task request category for task [${newTaskDefinitionId}], there might be a misconfiguration.`, + ); + console.error(err.message); + onFail && onFail(err, []); + return; + } taskRequest.category = category; const description = getDefaultTaskDescription(newTaskDefinitionId) ?? ''; @@ -856,11 +861,7 @@ export function CreateTaskForm({ variant="outlined" fullWidth margin="normal" - value={ - taskRequest.category !== 'compose' - ? taskRequest.category - : taskRequest.description.category - } + value={taskDefinitionId} onChange={handleTaskTypeChange} sx={{ '& .MuiInputBase-input': { From 7f7eb6203121b1f1631da5978761d2b86a2eb4ec Mon Sep 17 00:00:00 2001 From: Aaron Chong Date: Wed, 19 Jun 2024 17:08:23 +0800 Subject: [PATCH 23/28] Split configuration and definition, only handle configurations in resource manager level Signed-off-by: Aaron Chong --- packages/dashboard/src/components/appbar.tsx | 3 +- .../src/components/tasks/task-schedule.tsx | 8 +- .../src/managers/resource-manager-tasks.ts | 29 +++-- .../src/managers/resource-manager.ts | 34 +++--- .../lib/tasks/create-task.tsx | 112 +++++++++--------- .../lib/tasks/types/compose-clean.tsx | 6 +- .../lib/tasks/types/custom-compose.tsx | 4 +- .../lib/tasks/types/delivery-custom.tsx | 16 +-- .../lib/tasks/types/delivery.tsx | 6 +- .../lib/tasks/types/patrol.tsx | 6 +- .../react-components/lib/tasks/types/utils.ts | 83 +++++++------ 11 files changed, 163 insertions(+), 144 deletions(-) diff --git a/packages/dashboard/src/components/appbar.tsx b/packages/dashboard/src/components/appbar.tsx index 94aa25a03..5ef0e56ad 100644 --- a/packages/dashboard/src/components/appbar.tsx +++ b/packages/dashboard/src/components/appbar.tsx @@ -164,6 +164,7 @@ export const AppBar = React.memo(({ extraToolbarItems }: AppBarProps): React.Rea const location = useLocation(); const tabValue = React.useMemo(() => locationToTabValue(location.pathname), [location]); const logoResourcesContext = React.useContext(ResourcesContext)?.logos; + const taskResourcesContext = React.useContext(ResourcesContext)?.tasks; const [anchorEl, setAnchorEl] = React.useState(null); const { authenticator } = React.useContext(AppConfigContext); const profile = React.useContext(UserProfileContext); @@ -617,7 +618,7 @@ export const AppBar = React.memo(({ extraToolbarItems }: AppBarProps): React.Rea {openCreateTaskForm && ( { const rmf = React.useContext(RmfAppContext); - const resourceManager = React.useContext(ResourcesContext); + const taskResourcesContext = React.useContext(ResourcesContext)?.tasks; const { showAlert } = React.useContext(AppControllerContext); const profile = React.useContext(UserProfileContext); @@ -137,7 +137,7 @@ export const TaskSchedule = () => { return tasks.flatMap((t: ScheduledTask) => t.schedules.flatMap((s: ApiSchedule) => { const events = scheduleToEvents(params.start, params.end, s, t, getEventId, () => - getScheduledTaskTitle(t, resourceManager?.supportedTasks), + getScheduledTaskTitle(t, taskResourcesContext?.tasks), ); events.forEach((ev) => { eventsMap.current[Number(ev.event_id)] = t; @@ -147,7 +147,7 @@ export const TaskSchedule = () => { }), ); }, - [rmf, resourceManager], + [rmf, taskResourcesContext], ); const CustomCalendarEditor = ({ scheduler, value, onChange }: CustomCalendarEditorProps) => { @@ -320,7 +320,7 @@ export const TaskSchedule = () => { {openCreateTaskForm && ( ; + tasks: TaskDefinition[]; - constructor(supportedTasks: TaskDefinition[] | undefined) { - this.supportedTasks = {}; - if (supportedTasks) { - for (const t of supportedTasks) { - this.supportedTasks[t.taskDefinitionId] = t; + constructor(taskResources: TaskResource[]) { + this.tasks = []; + for (const taskResource of taskResources) { + const definition = getTaskDefinition(taskResource.task_definition_id); + if (!definition) { + console.error( + `Invalid tasks configured for dashboard: [${taskResource.task_definition_id}]`, + ); + continue; } + + if (taskResource.display_name !== undefined) { + definition.taskDisplayName = taskResource.display_name; + } + + this.tasks.push(definition); } } } diff --git a/packages/dashboard/src/managers/resource-manager.ts b/packages/dashboard/src/managers/resource-manager.ts index 53f841d70..48707414e 100644 --- a/packages/dashboard/src/managers/resource-manager.ts +++ b/packages/dashboard/src/managers/resource-manager.ts @@ -2,13 +2,7 @@ import Debug from 'debug'; import { DispenserResourceManager, RawDispenserResource } from './resource-manager-dispensers'; import { LogoResource, LogoResourceManager } from './resource-manager-logos'; import { RobotResource, RobotResourceManager } from './resource-manager-robots'; -import { - DefaultPatrolTaskDefinition, - DefaultDeliveryTaskDefinition, - DefaultComposeCleanTaskDefinition, - TaskDefinition, - DefaultCustomComposeTaskDefinition, -} from 'react-components'; +import { TaskResource, TaskResourceManager } from './resource-manager-tasks'; const debug = Debug('ResourceManager'); const ResourceFile = 'resources/main.json'; @@ -25,7 +19,7 @@ export interface ResourceConfigurationsType { attributionPrefix?: string; cartIds?: string[]; loggedInDisplayLevel?: string; - supportedTasks?: TaskDefinition[]; + allowedTasks?: TaskResource[]; } export default class ResourceManager { @@ -40,7 +34,7 @@ export default class ResourceManager { attributionPrefix?: string; cartIds?: string[]; loggedInDisplayLevel?: string; - supportedTasks?: TaskDefinition[]; + tasks: TaskResourceManager; /** * Gets the default resource manager using the embedded resource file (aka "assets/resources/main.json"). @@ -62,6 +56,22 @@ export default class ResourceManager { constructor(resources: ResourceConfigurationsType) { this.robots = new RobotResourceManager(resources.robots || {}); this.logos = new LogoResourceManager(resources.logos || {}); + this.tasks = new TaskResourceManager( + resources.allowedTasks || [ + { + task_definition_id: 'patrol', + display_name: 'Patrol', + }, + { + task_definition_id: 'delivery', + display_name: 'Delivery', + }, + { + task_definition_id: 'clean', + display_name: 'Clean', + }, + ], + ); if (resources.dispensers) { this.dispensers = new DispenserResourceManager(resources.dispensers); } @@ -80,12 +90,6 @@ export default class ResourceManager { this.attributionPrefix = resources.attributionPrefix || 'OSRC-SG'; this.cartIds = resources.cartIds || []; this.loggedInDisplayLevel = resources.loggedInDisplayLevel; - this.supportedTasks = resources.supportedTasks || [ - DefaultPatrolTaskDefinition, - DefaultDeliveryTaskDefinition, - DefaultComposeCleanTaskDefinition, - DefaultCustomComposeTaskDefinition, - ]; } } diff --git a/packages/react-components/lib/tasks/create-task.tsx b/packages/react-components/lib/tasks/create-task.tsx index 2edb14209..44b7115e8 100644 --- a/packages/react-components/lib/tasks/create-task.tsx +++ b/packages/react-components/lib/tasks/create-task.tsx @@ -42,17 +42,17 @@ import { ConfirmationDialog, ConfirmationDialogProps } from '../confirmation-dia import { ComposeCleanTaskDescription, ComposeCleanTaskForm, - DefaultComposeCleanTaskDefinition, + ComposeCleanTaskDefinition, makeComposeCleanTaskBookingLabel, } from './types/compose-clean'; import { - DefaultCustomComposeTaskDefinition, + CustomComposeTaskDefinition, CustomComposeTaskDescription, CustomComposeTaskForm, makeCustomComposeTaskBookingLabel, } from './types/custom-compose'; import { - DefaultDeliveryTaskDefinition, + DeliveryTaskDefinition, DeliveryTaskDescription, DeliveryTaskForm, makeDeliveryTaskBookingLabel, @@ -64,13 +64,13 @@ import { DeliveryPickupTaskForm, makeDeliveryCustomTaskBookingLabel, makeDeliveryPickupTaskBookingLabel, - DefaultDeliveryPickupTaskDefinition, - DefaultDeliverySequentialLotPickupTaskDefinition, - DefaultDeliveryAreaPickupTaskDefinition, + DeliveryPickupTaskDefinition, + DeliverySequentialLotPickupTaskDefinition, + DeliveryAreaPickupTaskDefinition, } from './types/delivery-custom'; import { makePatrolTaskBookingLabel, - DefaultPatrolTaskDefinition, + PatrolTaskDefinition, PatrolTaskDescription, PatrolTaskForm, } from './types/patrol'; @@ -286,7 +286,7 @@ export interface CreateTaskFormProps * Shows extra UI elements suitable for submittng batched tasks. Default to 'false'. */ user: string; - supportedTasks?: TaskDefinition[]; + tasksToDisplay?: TaskDefinition[]; allowBatch?: boolean; cleaningZones?: string[]; patrolWaypoints?: string[]; @@ -312,11 +312,11 @@ export interface CreateTaskFormProps export function CreateTaskForm({ user, - supportedTasks = [ - DefaultPatrolTaskDefinition, - DefaultDeliveryTaskDefinition, - DefaultComposeCleanTaskDefinition, - DefaultCustomComposeTaskDefinition, + tasksToDisplay = [ + PatrolTaskDefinition, + DeliveryTaskDefinition, + ComposeCleanTaskDefinition, + CustomComposeTaskDefinition, ], /* eslint-disable @typescript-eslint/no-unused-vars */ cleaningZones = [], @@ -355,22 +355,22 @@ export function CreateTaskForm({ // than 0, this will cause the dashboard to fail when the create task form is // opened. This is intentional as it is a misconfiguration and will require // the build-time configuration to be fixed. - let allSupportedTasksAreValid = true; + let allTasksToDisplayAreValid = true; const validTasks: TaskDefinition[] = []; let defaultTaskDescription: string | TaskDescription | null = null; let defaultTaskRequest: TaskRequest | null = null; - supportedTasks.forEach((supportedTask: TaskDefinition, index: number) => { + tasksToDisplay.forEach((supportedTask: TaskDefinition, index: number) => { const definitionId = supportedTask.taskDefinitionId; const desc = getDefaultTaskDescription(definitionId); const req = getDefaultTaskRequest(definitionId); if (desc === undefined) { console.error(`Failed to retrieve task description for definition ID: [${definitionId}]`); - allSupportedTasksAreValid = false; + allTasksToDisplayAreValid = false; } if (req === null) { console.error(`Failed to create task request for definition ID: [${definitionId}]`); - allSupportedTasksAreValid = false; + allTasksToDisplayAreValid = false; } if (desc && req) { validTasks.push(supportedTask); @@ -393,12 +393,12 @@ export function CreateTaskForm({ const [favoriteTaskBuffer, setFavoriteTaskBuffer] = React.useState({ id: '', name: '', - category: supportedTasks[0].requestCategory, + category: tasksToDisplay[0].requestCategory, description: defaultTaskDescription as object, unix_millis_earliest_start_time: 0, priority: { type: 'binary', value: 0 }, user: '', - task_definition_id: supportedTasks[0].taskDefinitionId, + task_definition_id: tasksToDisplay[0].taskDefinitionId, }); const [favoriteTaskTitleError, setFavoriteTaskTitleError] = React.useState(false); const [savingFavoriteTask, setSavingFavoriteTask] = React.useState(false); @@ -412,7 +412,7 @@ export function CreateTaskForm({ initialBookingLabel.description.task_definition_id && typeof initialBookingLabel.description.task_definition_id === 'string' ? initialBookingLabel.description.task_definition_id - : supportedTasks[0].taskDefinitionId, + : tasksToDisplay[0].taskDefinitionId, ); const [submitting, setSubmitting] = React.useState(false); @@ -480,14 +480,13 @@ export function CreateTaskForm({ setFavoriteTaskBuffer({ ...favoriteTaskBuffer, description: newDesc, category: newCategory }); }; - // FIXME: Custom compose task descriptions are currently not allowed to be - // saved as favorite tasks. This will probably require a re-write of - // FavoriteTask's pydantic model with better typing. + // FIXME: Favorite tasks are disabled for custom compose tasks for now, as it + // will require a re-write of FavoriteTask's pydantic model with better typing. const handleCustomComposeTaskDescriptionChange = (newDesc: CustomComposeTaskDescription) => { setTaskRequest((prev) => { return { ...prev, - category: DefaultCustomComposeTaskDefinition.requestCategory, + category: CustomComposeTaskDefinition.requestCategory, description: newDesc, }; }); @@ -499,42 +498,42 @@ export function CreateTaskForm({ const renderTaskDescriptionForm = (definitionId: string) => { switch (definitionId) { - case DefaultPatrolTaskDefinition.taskDefinitionId: + case PatrolTaskDefinition.taskDefinitionId: return ( - handleTaskDescriptionChange(DefaultPatrolTaskDefinition.requestCategory, desc) + handleTaskDescriptionChange(PatrolTaskDefinition.requestCategory, desc) } onValidate={onValidate} /> ); - case DefaultDeliveryTaskDefinition.taskDefinitionId: + case DeliveryTaskDefinition.taskDefinitionId: return ( - handleTaskDescriptionChange(DefaultDeliveryTaskDefinition.requestCategory, desc) + handleTaskDescriptionChange(DeliveryTaskDefinition.requestCategory, desc) } onValidate={onValidate} /> ); - case DefaultComposeCleanTaskDefinition.taskDefinitionId: + case ComposeCleanTaskDefinition.taskDefinitionId: return ( { desc.category = taskRequest.description.category; - handleTaskDescriptionChange(DefaultComposeCleanTaskDefinition.requestCategory, desc); + handleTaskDescriptionChange(ComposeCleanTaskDefinition.requestCategory, desc); }} onValidate={onValidate} /> ); - case DefaultDeliveryPickupTaskDefinition.taskDefinitionId: + case DeliveryPickupTaskDefinition.taskDefinitionId: return ( ); - case DefaultDeliverySequentialLotPickupTaskDefinition.taskDefinitionId: + case DeliverySequentialLotPickupTaskDefinition.taskDefinitionId: return ( ); - case DefaultDeliveryAreaPickupTaskDefinition.taskDefinitionId: + case DeliveryAreaPickupTaskDefinition.taskDefinitionId: return ( ); - case DefaultCustomComposeTaskDefinition.taskDefinitionId: + case CustomComposeTaskDefinition.taskDefinitionId: default: return ( - {supportedTasks.map((taskDefinition) => { + {tasksToDisplay.map((taskDefinition) => { return ( {callToUpdateFavoriteTask ? `Confirm edits` : 'Save as a favorite task'} diff --git a/packages/react-components/lib/tasks/types/compose-clean.tsx b/packages/react-components/lib/tasks/types/compose-clean.tsx index da5e1f7cf..38781c4d8 100644 --- a/packages/react-components/lib/tasks/types/compose-clean.tsx +++ b/packages/react-components/lib/tasks/types/compose-clean.tsx @@ -3,7 +3,7 @@ import React from 'react'; import type { TaskBookingLabel } from 'api-client'; import { TaskDefinition } from '../create-task'; -export const DefaultComposeCleanTaskDefinition: TaskDefinition = { +export const ComposeCleanTaskDefinition: TaskDefinition = { taskDefinitionId: 'compose-clean', taskDisplayName: 'Clean', requestCategory: 'compose', @@ -46,7 +46,7 @@ export function makeComposeCleanTaskBookingLabel( ): TaskBookingLabel { return { description: { - task_definition_id: DefaultComposeCleanTaskDefinition.taskDefinitionId, + task_definition_id: ComposeCleanTaskDefinition.taskDefinitionId, destination: task_description.phases[0].activity.description.activities[1].description.description.zone, }, @@ -103,7 +103,7 @@ export function makeComposeCleanTaskShortDescription( displayName: string | undefined, ): string { const cleanActivity = desc.phases[0].activity.description.activities[1]; - return `[${displayName ?? DefaultComposeCleanTaskDefinition.taskDisplayName}] zone [${ + return `[${displayName ?? ComposeCleanTaskDefinition.taskDisplayName}] zone [${ cleanActivity.description.description.zone }]`; } diff --git a/packages/react-components/lib/tasks/types/custom-compose.tsx b/packages/react-components/lib/tasks/types/custom-compose.tsx index 9e60faf60..348e226d7 100644 --- a/packages/react-components/lib/tasks/types/custom-compose.tsx +++ b/packages/react-components/lib/tasks/types/custom-compose.tsx @@ -3,7 +3,7 @@ import React from 'react'; import type { TaskBookingLabel } from 'api-client'; import { TaskDefinition } from '../create-task'; -export const DefaultCustomComposeTaskDefinition: TaskDefinition = { +export const CustomComposeTaskDefinition: TaskDefinition = { taskDefinitionId: 'custom_compose', taskDisplayName: 'Custom Compose Task', requestCategory: 'compose', @@ -14,7 +14,7 @@ export type CustomComposeTaskDescription = string; export function makeCustomComposeTaskBookingLabel(): TaskBookingLabel { return { description: { - task_definition_id: DefaultCustomComposeTaskDefinition.taskDefinitionId, + task_definition_id: CustomComposeTaskDefinition.taskDefinitionId, }, }; } diff --git a/packages/react-components/lib/tasks/types/delivery-custom.tsx b/packages/react-components/lib/tasks/types/delivery-custom.tsx index bf3f1f241..5bebe359c 100644 --- a/packages/react-components/lib/tasks/types/delivery-custom.tsx +++ b/packages/react-components/lib/tasks/types/delivery-custom.tsx @@ -4,19 +4,19 @@ import { isNonEmptyString } from './utils'; import type { TaskBookingLabel } from 'api-client'; import { TaskDefinition } from '../create-task'; -export const DefaultDeliveryPickupTaskDefinition: TaskDefinition = { +export const DeliveryPickupTaskDefinition: TaskDefinition = { taskDefinitionId: 'delivery_pickup', taskDisplayName: 'Delivery - 1:1', requestCategory: 'compose', }; -export const DefaultDeliverySequentialLotPickupTaskDefinition: TaskDefinition = { +export const DeliverySequentialLotPickupTaskDefinition: TaskDefinition = { taskDefinitionId: 'delivery_sequential_lot_pickup', taskDisplayName: 'Delivery - Sequential lot pick up', requestCategory: 'compose', }; -export const DefaultDeliveryAreaPickupTaskDefinition: TaskDefinition = { +export const DeliveryAreaPickupTaskDefinition: TaskDefinition = { taskDefinitionId: 'delivery_area_pickup', taskDisplayName: 'Delivery - Area pick up', requestCategory: 'compose', @@ -181,7 +181,7 @@ export function makeDeliveryPickupTaskShortDescription( const goToDropoff: GoToPlaceActivity = desc.phases[1].activity.description.activities[0]; return `[${ - taskDisplayName ?? DefaultDeliveryPickupTaskDefinition.taskDisplayName + taskDisplayName ?? DeliveryPickupTaskDefinition.taskDisplayName }] payload [${cartId}] from [${goToPickup.description}] to [${goToDropoff.description}]`; } catch (e) { try { @@ -209,14 +209,14 @@ export function makeDeliveryCustomTaskShortDescription( const goToDropoff: GoToPlaceActivity = desc.phases[1].activity.description.activities[0]; switch (desc.category) { - case DefaultDeliverySequentialLotPickupTaskDefinition.taskDefinitionId: { + case DeliverySequentialLotPickupTaskDefinition.taskDefinitionId: { return `[${ - taskDisplayName ?? DefaultDeliverySequentialLotPickupTaskDefinition.taskDisplayName + taskDisplayName ?? DeliverySequentialLotPickupTaskDefinition.taskDisplayName }] payload [${cartId}] from [${goToPickup.description}] to [${goToDropoff.description}]`; } - case DefaultDeliveryAreaPickupTaskDefinition.taskDefinitionId: { + case DeliveryAreaPickupTaskDefinition.taskDefinitionId: { return `[${ - taskDisplayName ?? DefaultDeliveryAreaPickupTaskDefinition.taskDisplayName + taskDisplayName ?? DeliveryAreaPickupTaskDefinition.taskDisplayName }] payload [${cartId}] from [${goToPickup.description}] to [${goToDropoff.description}]`; } default: diff --git a/packages/react-components/lib/tasks/types/delivery.tsx b/packages/react-components/lib/tasks/types/delivery.tsx index 2422fbbb6..fdc32e24c 100644 --- a/packages/react-components/lib/tasks/types/delivery.tsx +++ b/packages/react-components/lib/tasks/types/delivery.tsx @@ -5,7 +5,7 @@ import React from 'react'; import type { TaskBookingLabel } from 'api-client'; import { TaskDefinition } from '../create-task'; -export const DefaultDeliveryTaskDefinition: TaskDefinition = { +export const DeliveryTaskDefinition: TaskDefinition = { taskDefinitionId: 'delivery', taskDisplayName: 'Delivery', requestCategory: 'delivery', @@ -30,7 +30,7 @@ export function makeDeliveryTaskBookingLabel( ): TaskBookingLabel { return { description: { - task_definition_id: DefaultDeliveryTaskDefinition.taskDefinitionId, + task_definition_id: DeliveryTaskDefinition.taskDefinitionId, pickup: task_description.pickup.place, destination: task_description.dropoff.place, cart_id: task_description.pickup.payload.sku, @@ -76,7 +76,7 @@ export function makeDeliveryTaskShortDescription( desc: DeliveryTaskDescription, displayName?: string, ): string { - return `[${displayName ?? DefaultDeliveryTaskDefinition.taskDisplayName}] Pickup [${ + return `[${displayName ?? DeliveryTaskDefinition.taskDisplayName}] Pickup [${ desc.pickup.payload.sku }] from [${desc.pickup.place}], dropoff [${desc.dropoff.payload.sku}] at [${desc.dropoff.place}]`; } diff --git a/packages/react-components/lib/tasks/types/patrol.tsx b/packages/react-components/lib/tasks/types/patrol.tsx index 6615a7c1e..0ae445fa0 100644 --- a/packages/react-components/lib/tasks/types/patrol.tsx +++ b/packages/react-components/lib/tasks/types/patrol.tsx @@ -17,7 +17,7 @@ import { PositiveIntField } from '../../form-inputs'; import { TaskDefinition } from '../create-task'; import type { TaskBookingLabel } from 'api-client'; -export const DefaultPatrolTaskDefinition: TaskDefinition = { +export const PatrolTaskDefinition: TaskDefinition = { taskDefinitionId: 'patrol', taskDisplayName: 'Patrol', requestCategory: 'patrol', @@ -33,7 +33,7 @@ export function makePatrolTaskBookingLabel( ): TaskBookingLabel { return { description: { - task_definition_id: DefaultPatrolTaskDefinition.taskDefinitionId, + task_definition_id: PatrolTaskDefinition.taskDefinitionId, destination: task_description.places[task_description.places.length - 1], }, }; @@ -65,7 +65,7 @@ export function makePatrolTaskShortDescription( console.log(desc); const formattedPlaces = desc.places.map((place: string) => `[${place}]`); - return `[${displayName ?? DefaultPatrolTaskDefinition.taskDisplayName}] [${ + return `[${displayName ?? PatrolTaskDefinition.taskDisplayName}] [${ desc.rounds }] round/s, along ${formattedPlaces.join(', ')}`; } diff --git a/packages/react-components/lib/tasks/types/utils.ts b/packages/react-components/lib/tasks/types/utils.ts index 5b72cd8a2..7b408c307 100644 --- a/packages/react-components/lib/tasks/types/utils.ts +++ b/packages/react-components/lib/tasks/types/utils.ts @@ -1,13 +1,13 @@ import { TaskRequest } from 'api-client'; import { - DefaultPatrolTaskDefinition, + PatrolTaskDefinition, makePatrolTaskShortDescription, makeDefaultPatrolTaskDescription, } from './patrol'; import { - DefaultDeliveryAreaPickupTaskDefinition, - DefaultDeliveryPickupTaskDefinition, - DefaultDeliverySequentialLotPickupTaskDefinition, + DeliveryAreaPickupTaskDefinition, + DeliveryPickupTaskDefinition, + DeliverySequentialLotPickupTaskDefinition, makeDeliveryPickupTaskShortDescription, makeDeliveryCustomTaskShortDescription, makeDefaultDeliveryCustomTaskDescription, @@ -15,20 +15,20 @@ import { } from './delivery-custom'; import { getTaskBookingLabelFromTaskRequest } from '../task-booking-label-utils'; import { - DefaultComposeCleanTaskDefinition, + ComposeCleanTaskDefinition, makeComposeCleanTaskShortDescription, makeDefaultComposeCleanTaskDescription, } from './compose-clean'; import { - DefaultDeliveryTaskDefinition, + DeliveryTaskDefinition, makeDeliveryTaskShortDescription, makeDefaultDeliveryTaskDescription, } from './delivery'; import { - DefaultCustomComposeTaskDefinition, + CustomComposeTaskDefinition, makeCustomComposeTaskShortDescription, } from './custom-compose'; -import { TaskDescription } from '../create-task'; +import { TaskDefinition, TaskDescription } from '../create-task'; export function isNonEmptyString(value: string): boolean { return value.length > 0; @@ -66,40 +66,60 @@ export function getShortDescription( const taskDefinitionId = bookingLabel.description.task_definition_id; switch (taskDefinitionId) { - case DefaultPatrolTaskDefinition.taskDefinitionId: + case PatrolTaskDefinition.taskDefinitionId: return makePatrolTaskShortDescription(taskRequest.description, taskDisplayName); - case DefaultDeliveryTaskDefinition.taskDefinitionId: + case DeliveryTaskDefinition.taskDefinitionId: return makeDeliveryTaskShortDescription(taskRequest.description, taskDisplayName); - case DefaultComposeCleanTaskDefinition.taskDefinitionId: + case ComposeCleanTaskDefinition.taskDefinitionId: return makeComposeCleanTaskShortDescription(taskRequest.description, taskDisplayName); - case DefaultDeliveryPickupTaskDefinition.taskDefinitionId: + case DeliveryPickupTaskDefinition.taskDefinitionId: return makeDeliveryPickupTaskShortDescription(taskRequest.description, taskDisplayName); - case DefaultDeliverySequentialLotPickupTaskDefinition.taskDefinitionId: - case DefaultDeliveryAreaPickupTaskDefinition.taskDefinitionId: + case DeliverySequentialLotPickupTaskDefinition.taskDefinitionId: + case DeliveryAreaPickupTaskDefinition.taskDefinitionId: return makeDeliveryCustomTaskShortDescription(taskRequest.description, taskDisplayName); - case DefaultCustomComposeTaskDefinition.taskDefinitionId: + case CustomComposeTaskDefinition.taskDefinitionId: return makeCustomComposeTaskShortDescription(taskRequest.description); default: return `[Unknown] type "${taskRequest.description.category}"`; } } +export function getTaskDefinition(taskDefinitionId: string): TaskDefinition | undefined { + switch (taskDefinitionId) { + case ComposeCleanTaskDefinition.taskDefinitionId: + return ComposeCleanTaskDefinition; + case DeliveryPickupTaskDefinition.taskDefinitionId: + return DeliveryPickupTaskDefinition; + case DeliverySequentialLotPickupTaskDefinition.taskDefinitionId: + return DeliverySequentialLotPickupTaskDefinition; + case DeliveryAreaPickupTaskDefinition.taskDefinitionId: + return DeliveryAreaPickupTaskDefinition; + case DeliveryTaskDefinition.taskDefinitionId: + return DeliveryTaskDefinition; + case PatrolTaskDefinition.taskDefinitionId: + return PatrolTaskDefinition; + case CustomComposeTaskDefinition.taskDefinitionId: + return CustomComposeTaskDefinition; + } + return undefined; +} + export function getDefaultTaskDescription( taskDefinitionId: string, ): TaskDescription | string | undefined { switch (taskDefinitionId) { - case DefaultComposeCleanTaskDefinition.taskDefinitionId: + case ComposeCleanTaskDefinition.taskDefinitionId: return makeDefaultComposeCleanTaskDescription(); - case DefaultDeliveryPickupTaskDefinition.taskDefinitionId: + case DeliveryPickupTaskDefinition.taskDefinitionId: return makeDefaultDeliveryPickupTaskDescription(); - case DefaultDeliverySequentialLotPickupTaskDefinition.taskDefinitionId: - case DefaultDeliveryAreaPickupTaskDefinition.taskDefinitionId: + case DeliverySequentialLotPickupTaskDefinition.taskDefinitionId: + case DeliveryAreaPickupTaskDefinition.taskDefinitionId: return makeDefaultDeliveryCustomTaskDescription(taskDefinitionId); - case DefaultDeliveryTaskDefinition.taskDefinitionId: + case DeliveryTaskDefinition.taskDefinitionId: return makeDefaultDeliveryTaskDescription(); - case DefaultPatrolTaskDefinition.taskDefinitionId: + case PatrolTaskDefinition.taskDefinitionId: return makeDefaultPatrolTaskDescription(); - case DefaultCustomComposeTaskDefinition.taskDefinitionId: + case CustomComposeTaskDefinition.taskDefinitionId: return ''; default: return undefined; @@ -107,21 +127,6 @@ export function getDefaultTaskDescription( } export function getTaskRequestCategory(taskDefinitionId: string): string | undefined { - switch (taskDefinitionId) { - case DefaultComposeCleanTaskDefinition.taskDefinitionId: - return DefaultComposeCleanTaskDefinition.requestCategory; - case DefaultDeliveryPickupTaskDefinition.taskDefinitionId: - return DefaultDeliveryPickupTaskDefinition.requestCategory; - case DefaultDeliverySequentialLotPickupTaskDefinition.taskDefinitionId: - return DefaultDeliverySequentialLotPickupTaskDefinition.requestCategory; - case DefaultDeliveryAreaPickupTaskDefinition.taskDefinitionId: - return DefaultDeliveryAreaPickupTaskDefinition.requestCategory; - case DefaultDeliveryTaskDefinition.taskDefinitionId: - return DefaultDeliveryTaskDefinition.requestCategory; - case DefaultPatrolTaskDefinition.taskDefinitionId: - return DefaultPatrolTaskDefinition.requestCategory; - case DefaultCustomComposeTaskDefinition.taskDefinitionId: - return DefaultCustomComposeTaskDefinition.requestCategory; - } - return undefined; + const definition = getTaskDefinition(taskDefinitionId); + return definition !== undefined ? definition.requestCategory : undefined; } From 3573c59e6de263c7522ad70915bbf2dfaad38116 Mon Sep 17 00:00:00 2001 From: Aaron Chong Date: Wed, 19 Jun 2024 18:26:35 +0800 Subject: [PATCH 24/28] Lint Signed-off-by: Aaron Chong --- packages/react-components/lib/tasks/create-task.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-components/lib/tasks/create-task.tsx b/packages/react-components/lib/tasks/create-task.tsx index 44b7115e8..453d9bfd0 100644 --- a/packages/react-components/lib/tasks/create-task.tsx +++ b/packages/react-components/lib/tasks/create-task.tsx @@ -387,7 +387,7 @@ export function CreateTaskForm({ const err = Error('Default task could not be generated, this might be a configuration error'); onFail && onFail(err, []); console.error(err.message); - return <>; + throw new TypeError(err.message); } const [favoriteTaskBuffer, setFavoriteTaskBuffer] = React.useState({ From 2f3a1a7c81f7ba5af498fd5e44e8b755b4e34052 Mon Sep 17 00:00:00 2001 From: Aaron Chong Date: Wed, 19 Jun 2024 18:58:20 +0800 Subject: [PATCH 25/28] Not using object as a type Signed-off-by: Aaron Chong --- packages/dashboard/src/managers/resource-manager.ts | 8 ++++---- packages/react-components/lib/tasks/create-task.tsx | 3 +-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/dashboard/src/managers/resource-manager.ts b/packages/dashboard/src/managers/resource-manager.ts index 48707414e..7d5f8abb9 100644 --- a/packages/dashboard/src/managers/resource-manager.ts +++ b/packages/dashboard/src/managers/resource-manager.ts @@ -60,15 +60,15 @@ export default class ResourceManager { resources.allowedTasks || [ { task_definition_id: 'patrol', - display_name: 'Patrol', }, { task_definition_id: 'delivery', - display_name: 'Delivery', }, { - task_definition_id: 'clean', - display_name: 'Clean', + task_definition_id: 'compose-clean', + }, + { + task_definition_id: 'custom_compose', }, ], ); diff --git a/packages/react-components/lib/tasks/create-task.tsx b/packages/react-components/lib/tasks/create-task.tsx index 453d9bfd0..f255ebe85 100644 --- a/packages/react-components/lib/tasks/create-task.tsx +++ b/packages/react-components/lib/tasks/create-task.tsx @@ -394,7 +394,7 @@ export function CreateTaskForm({ id: '', name: '', category: tasksToDisplay[0].requestCategory, - description: defaultTaskDescription as object, + description: defaultTaskDescription, unix_millis_earliest_start_time: 0, priority: { type: 'binary', value: 0 }, user: '', @@ -585,7 +585,6 @@ export function CreateTaskForm({ /> ); case CustomComposeTaskDefinition.taskDefinitionId: - default: return ( Date: Tue, 25 Jun 2024 13:37:55 +0800 Subject: [PATCH 26/28] Address feedback Signed-off-by: Aaron Chong --- .../src/managers/resource-manager-tasks.ts | 15 +++++++++------ .../react-components/lib/tasks/create-task.tsx | 3 --- .../react-components/lib/tasks/types/utils.ts | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/dashboard/src/managers/resource-manager-tasks.ts b/packages/dashboard/src/managers/resource-manager-tasks.ts index 914d45862..c65bbb93e 100644 --- a/packages/dashboard/src/managers/resource-manager-tasks.ts +++ b/packages/dashboard/src/managers/resource-manager-tasks.ts @@ -1,4 +1,4 @@ -import { getTaskDefinition, TaskDefinition } from 'react-components'; +import { getDefaultTaskDefinition, TaskDefinition } from 'react-components'; export interface TaskResource { task_definition_id: string; @@ -11,8 +11,8 @@ export class TaskResourceManager { constructor(taskResources: TaskResource[]) { this.tasks = []; for (const taskResource of taskResources) { - const definition = getTaskDefinition(taskResource.task_definition_id); - if (!definition) { + const defaultTaskDefinition = getDefaultTaskDefinition(taskResource.task_definition_id); + if (!defaultTaskDefinition) { console.error( `Invalid tasks configured for dashboard: [${taskResource.task_definition_id}]`, ); @@ -20,10 +20,13 @@ export class TaskResourceManager { } if (taskResource.display_name !== undefined) { - definition.taskDisplayName = taskResource.display_name; + this.tasks.push({ + ...defaultTaskDefinition, + taskDisplayName: taskResource.display_name, + }); + } else { + this.tasks.push(defaultTaskDefinition); } - - this.tasks.push(definition); } } } diff --git a/packages/react-components/lib/tasks/create-task.tsx b/packages/react-components/lib/tasks/create-task.tsx index f255ebe85..dc2a59fbd 100644 --- a/packages/react-components/lib/tasks/create-task.tsx +++ b/packages/react-components/lib/tasks/create-task.tsx @@ -355,7 +355,6 @@ export function CreateTaskForm({ // than 0, this will cause the dashboard to fail when the create task form is // opened. This is intentional as it is a misconfiguration and will require // the build-time configuration to be fixed. - let allTasksToDisplayAreValid = true; const validTasks: TaskDefinition[] = []; let defaultTaskDescription: string | TaskDescription | null = null; let defaultTaskRequest: TaskRequest | null = null; @@ -366,11 +365,9 @@ export function CreateTaskForm({ if (desc === undefined) { console.error(`Failed to retrieve task description for definition ID: [${definitionId}]`); - allTasksToDisplayAreValid = false; } if (req === null) { console.error(`Failed to create task request for definition ID: [${definitionId}]`); - allTasksToDisplayAreValid = false; } if (desc && req) { validTasks.push(supportedTask); diff --git a/packages/react-components/lib/tasks/types/utils.ts b/packages/react-components/lib/tasks/types/utils.ts index 7b408c307..d91285bf7 100644 --- a/packages/react-components/lib/tasks/types/utils.ts +++ b/packages/react-components/lib/tasks/types/utils.ts @@ -84,7 +84,7 @@ export function getShortDescription( } } -export function getTaskDefinition(taskDefinitionId: string): TaskDefinition | undefined { +export function getDefaultTaskDefinition(taskDefinitionId: string): TaskDefinition | undefined { switch (taskDefinitionId) { case ComposeCleanTaskDefinition.taskDefinitionId: return ComposeCleanTaskDefinition; @@ -127,6 +127,6 @@ export function getDefaultTaskDescription( } export function getTaskRequestCategory(taskDefinitionId: string): string | undefined { - const definition = getTaskDefinition(taskDefinitionId); + const definition = getDefaultTaskDefinition(taskDefinitionId); return definition !== undefined ? definition.requestCategory : undefined; } From 0e803cb1dd44fee6dadd46e3c37ae8757e12d7eb Mon Sep 17 00:00:00 2001 From: Aaron Chong Date: Tue, 25 Jun 2024 23:40:06 +0800 Subject: [PATCH 27/28] Render using validTasks instead Signed-off-by: Aaron Chong --- packages/react-components/lib/tasks/create-task.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/react-components/lib/tasks/create-task.tsx b/packages/react-components/lib/tasks/create-task.tsx index dc2a59fbd..b1e8de3df 100644 --- a/packages/react-components/lib/tasks/create-task.tsx +++ b/packages/react-components/lib/tasks/create-task.tsx @@ -355,10 +355,11 @@ export function CreateTaskForm({ // than 0, this will cause the dashboard to fail when the create task form is // opened. This is intentional as it is a misconfiguration and will require // the build-time configuration to be fixed. + // TODO(ac): Use a memo to prevent re-checking if task configurations are valid. const validTasks: TaskDefinition[] = []; let defaultTaskDescription: string | TaskDescription | null = null; let defaultTaskRequest: TaskRequest | null = null; - tasksToDisplay.forEach((supportedTask: TaskDefinition, index: number) => { + tasksToDisplay.forEach((supportedTask: TaskDefinition) => { const definitionId = supportedTask.taskDefinitionId; const desc = getDefaultTaskDescription(definitionId); const req = getDefaultTaskRequest(definitionId); @@ -860,7 +861,7 @@ export function CreateTaskForm({ }} InputLabelProps={{ style: { fontSize: isScreenHeightLessThan800 ? 16 : 20 } }} > - {tasksToDisplay.map((taskDefinition) => { + {validTasks.map((taskDefinition) => { return ( Date: Wed, 26 Jun 2024 00:26:44 +0800 Subject: [PATCH 28/28] Use useMemo Signed-off-by: Aaron Chong --- .../lib/tasks/create-task.tsx | 45 ++++++++++--------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/packages/react-components/lib/tasks/create-task.tsx b/packages/react-components/lib/tasks/create-task.tsx index b1e8de3df..c8031fc38 100644 --- a/packages/react-components/lib/tasks/create-task.tsx +++ b/packages/react-components/lib/tasks/create-task.tsx @@ -355,30 +355,33 @@ export function CreateTaskForm({ // than 0, this will cause the dashboard to fail when the create task form is // opened. This is intentional as it is a misconfiguration and will require // the build-time configuration to be fixed. - // TODO(ac): Use a memo to prevent re-checking if task configurations are valid. - const validTasks: TaskDefinition[] = []; - let defaultTaskDescription: string | TaskDescription | null = null; - let defaultTaskRequest: TaskRequest | null = null; - tasksToDisplay.forEach((supportedTask: TaskDefinition) => { - const definitionId = supportedTask.taskDefinitionId; - const desc = getDefaultTaskDescription(definitionId); - const req = getDefaultTaskRequest(definitionId); + const { defaultTaskDescription, defaultTaskRequest, validTasks } = React.useMemo(() => { + let defaultTaskDescription: string | TaskDescription | null = null; + let defaultTaskRequest: TaskRequest | null = null; + const validTasks: TaskDefinition[] = []; - if (desc === undefined) { - console.error(`Failed to retrieve task description for definition ID: [${definitionId}]`); - } - if (req === null) { - console.error(`Failed to create task request for definition ID: [${definitionId}]`); - } - if (desc && req) { - validTasks.push(supportedTask); + tasksToDisplay.forEach((supportedTask: TaskDefinition) => { + const definitionId = supportedTask.taskDefinitionId; + const desc = getDefaultTaskDescription(definitionId); + const req = getDefaultTaskRequest(definitionId); - if (!defaultTaskDescription && !defaultTaskRequest) { - defaultTaskDescription = desc; - defaultTaskRequest = req; + if (desc === undefined) { + console.error(`Failed to retrieve task description for definition ID: [${definitionId}]`); } - } - }); + if (req === null) { + console.error(`Failed to create task request for definition ID: [${definitionId}]`); + } + if (desc && req) { + validTasks.push(supportedTask); + + if (!defaultTaskDescription && !defaultTaskRequest) { + defaultTaskDescription = desc; + defaultTaskRequest = req; + } + } + }); + return { defaultTaskDescription, defaultTaskRequest, validTasks }; + }, [tasksToDisplay]); if (!defaultTaskDescription || !defaultTaskRequest) { // We should never reach this state unless a misconfiguration happened.