I have a list of rescues for ambulance drivers in different states that are displayed on a kanban board. My JSON object is divided by the status of the rescues, and then grouped by driver. The structure is like this:
--Status (unassigned/assigned/arrived/rescued)
----Driver group
-------Rescue list
The rescues have a datetime and lat/long associated with them, so I can use map/reduce to find the rescue that the drivers attended most recently, and show that location on a map.
One issue is that a driver group can exist in one swimlane and none of the other.
I want to: filter the list down to the arrived/rescued states only, (although logically this may not be necessary as it's not possible to have an arrival time without a driver associated, but this is out of scope) and for each driver group return their latest rescue by arrival time or rescue time. This will leave me with a distinct list of driver groups, with only the most recent rescue for that group
Here's the working code:
let groups = cases
.filter(swimlane => parseInt(swimlane.rescueStatus) >= 3)
.map(groups => groups.rescuerGroups)
.map(rescuerGroups => rescuerGroups)
.reduce((result, current) => {
if(result.length === 0){
return current;
}
else{
current.forEach(currentRescueGroup => {
let index = result.findIndex(parentRescueGroup => {
return parseInt(parentRescueGroup.rescuer1) == parseInt(currentRescueGroup.rescuer1) &&
parseInt(parentRescueGroup.rescuer2) == parseInt(currentRescueGroup.rescuer2)
})
index > -1 ?
result[index].rescues = result[index].rescues.concat(currentRescueGroup.rescues)
:
result.push(currentRescueGroup);
})
return result;
}}, [])
.map(rescueGroup => {
let maxRescue = rescueGroup.rescues.reduce((current, previous) => {
let currentTime = new Date(current.ambulanceArrivalTime) > (new Date(current.rescueTime) || new Date(1901, 01, 01)) ? current.ambulanceArrivalTime : current.rescueTime;
let previousTime = new Date(previous.ambulanceArrivalTime) > (new Date(previous.rescueTime) || new Date(1901, 01, 01)) ? previous.ambulanceArrivalTime : previous.rescueTime;
return previousTime > currentTime ? current : previous;
})
return {
rescuer1Abbreviation: rescueGroup.rescuer1Abbreviation,
rescuer2Abbreviation: rescueGroup.rescuer2Abbreviation,
latestLocation: maxRescue.latLngLiteral,
rescues: rescueGroup.rescues
}
})
And here's a JSBin including the working code:
https://jsbin.com/hocasob/edit?js,console
Any help to make the reduce sections simpler would be much appreciated
1 Answer 1
Turning a comment into a review (I provide an alternative implementation, but the basic ideas can be applied to the OP implementation).
First, some general thoughts.
As I mention in the comments regarding the parseInt
function, a linter can help spot any problems. A popular one is ESLint. But perhaps more to the point, the data seems to already be a number, so there shouldn't be any need to parse it.
It can help to clean up the code quite a bit by extracting the filters, reducers, mappers, sorters, etc. outside as a variable and give them a descriptive name, for example:
// filter
const arrivedOrRescued = ({rescueStatus}) => rescueStatus >= 3
// sorter
const byArrivalOrRescueTime = (a, b) =>
Date.parse(a.ambulanceArrivalTime || a.rescueTime)
- Date.parse(b.ambulanceArrivalTime || b.rescueTime)
// reducer
const byRescuers = (acc, {rescuerGroups}) => {
rescuerGroups.forEach(({
rescues
, rescuer1Abbreviation: r1
, rescuer2Abbreviation: r2
}) => {
let group = `${r1}-${r2}`
acc.has(group) ? acc.get(group).push(...rescues) : acc.set(group, rescues)
})
return acc
}
so getting a Map
(just an example) by rescuers out of the swimlanes could then become
let rescuers = cases.filter(arrivedOrRescued).reduce(byRescuer, new Map())
and sorting their rescues could become
rescuers.forEach((rescues, abbrevs) => rescues.sort(byArrivalOrRescueTime))
after which the latest rescue is the last element of the array now that they have all been collected into a single array and sorted.
One thing I would like to promote here when there can be somewhat complex nested objects in the data, is to use JSDoc comments to document the data shape. For example:
/*
* @typedef {Object} Swimlane
* @property {Array.<RescuerGroup>} rescuerGroups
* @property {Number} rescueStatus
* @property {String} rescueStatusName
*
* @typedef {Object} RescuerGroup
* @property {Array.<Rescue>} rescues
* @property {?Number} rescuer1
* @property {?Number} rescuer2
* @property {?String} rescuer1Abbreviation
* @property {?String} rescuer2Abbreviation
*
* @typedef {Object} Rescue
* @property {?String} ambulanceArrivalTime
* @property {?String} callDateTime
* @property {?String} callerName
* @property {String} callerNumber
* @property {?Number} callOutcomeId
* @property {Number} emergencyCaseId
* @property {Number} emergencyCodeId
* @property {Number} emergencyNumber
* @property {Number} isLargeAnimal
* @property {Number} latitude
* @property {Number} longitude
* @property {{lat: Number, lng: Number}} latLngLiteral
* @property {String} location
* @property {Array.<String>} patients
* @property {Number} rescuer1
* @property {Number} rescuer2
* @property {?String} rescueTime
* @property {Number} RescueStatus
*/
This makes it easier for others especially in data transformations.
Just a note that I haven't tested any of this, so take the above code with a grain of salt. Also, many will probably tell me to use more braces and semicolons.
parseInt
requires a radix (it does not default to10
). Another option would be to use theNumber
constructor instead. Also you could extract the accumulators outside as a variable and use argument destructuring. \$\endgroup\$