How to get total of numbers in an array from SharePoint list items - reactjs

I'm creating a spfx web part using Reactjs. I have a function getting an array of items from a SharePoint list that includes a number column of "Hours". I need to get a total for all the hours that have been returned but can't figure out how to calculate that.
I feel like I'm missing something simple but I've run through all kinds of loops and for some reason I can't get it to work. I've verified that I am getting data from the Hours column.
I'll also state the obligatory "I'm new to spfx and react". :) TIA for any help!
private readItem(): void {
this.props.spHttpClient.get(`${this.props.siteUrl}/_api/web/lists/getbytitle('Time Off')/items?$select=Title,Id,Hours`,
SPHttpClient.configurations.v1,
{
headers: {
'Accept': 'application/json;odata=nometadata',
'odata-version': ''
}
}).then((response: SPHttpClientResponse): Promise<ITimeOffItem[]> => {
return response.json();
})
.then((item: ITimeOffItem[]): void => {
console.log(item); //the data is here including Hours
this.setState({
items: item,
hoursTotal: //????How do I get the sum of "Hours" and assign it to a number in state
});
});
}

Create a function to loop through the items and add the hours
function countHours(items) {
if (!items) {
return 0;
}
let total = 0;
for (let i = 0; i < items.length; i++) {
total += items[i].Hours;
}
return total;
}
const item = [
{ Id: 25, Title: "Comp Time", Hours: 6, ID: 25 },
{ Id: 26, Title: "Comp Time", Hours: 5, ID: 26 },
{ Id: 27, Title: "Work from Home", Hours: 3, ID: 27 },
{ Id: 28, Title: "Comp Time", Hours: 7, ID: 28 },
{ Id: 29, Title: "Work from Home", Hours: 8, ID: 29 },
{ Id: 30, Title: "Holiday", Hours: 8, ID: 30 },
{ Id: 31, Title: "Work from Home", Hours: 32, ID: 31 }
];
console.log(countHours(item));
Use it like
this.setState({
items: item,
hoursTotal: countHours(item)
});
you can also use reduce
const item = [
{ Id: 25, Title: "Comp Time", Hours: 6, ID: 25 },
{ Id: 26, Title: "Comp Time", Hours: 5, ID: 26 },
{ Id: 27, Title: "Work from Home", Hours: 3, ID: 27 },
{ Id: 28, Title: "Comp Time", Hours: 7, ID: 28 },
{ Id: 29, Title: "Work from Home", Hours: 8, ID: 29 },
{ Id: 30, Title: "Holiday", Hours: 8, ID: 30 },
{ Id: 31, Title: "Work from Home", Hours: 32, ID: 31 }
];
const sum = item.reduce(function(a, b) { return a + b.Hours; }, 0);
console.log(sum)

It's hard to answer this without knowing what the data structure looks like, but if you are trying to sum an array of numbers you could use reduce.
const hours = [7, 5, 3, 1, 7]
const totalHours = hours.reduce((accumulator, hour) => accumulator + hour)

Related

Mapping through nested arrays in es6 javascript

I have a simple array that I want to group it's objects by date so I used this function
const groupedDates= Object.entries(
items.reduce((acc, { product, price, type, date }) => {
if (!acc[date]) {
acc[date] = [];
}
acc[date].push({ product, price, type date });
return acc;
}, {})
).map(([date, items]) => ({ date, items }));
the array
const items = [
{
id: 1,
product: "milk",
price: 10,
type: "drink"
date: "01/01/2022",
},
{
id: 2,
product: "coca",
price: 11,
type: "drink"
date: "01/01/2022",
},
{
id: 3,
product: "pepsi",
price: 20,
type: "drink"
date: "01/01/2024",
},
{
id: 4,
product: "carrots",
price: 30,
type: "food",
date: "01/01/2023",
},
];
I got this result
{
0: [
date: 01/01/2022,
items : [
0 : {
id: 1,
product: "milk",
price: 10,
type: "drink"
date: "01/01/2022"
}
1 : {
id: 2,
product: "coca",
price: 11,
type: "drink"
date: "01/01/2022",
}
],
1: [
date: "01/01/2024",
items : [
0 : {
id: 3,
product: "pepsi",
price: 20,
type: "drink"
date: "01/01/2024",
}
],
2: [
date: "01/01/2023",
items: [
0:{
id: 4,
product: "carrots",
price: 30,
type: "food",
date: "01/01/2023"
}
]
]
}
Issue:
I cannot seem to figure out how to access items1 when it exists.
What I have tried
is the map below but it only returns the first level of items which is 0 and if I do items1 it returns an error because not all arrays have a second item.
{groupedDates.map((obj) => (
{obj.items[0].product}))}
UPDATE
I'd also like to get the total for each date so I can have a card that has the Date + The total + each item and it's individual price. After getting some help from #Nick, I've managed to output the date, the item and it's price, now I'm still missing the total price for the date.
You need to iterate the items in each obj to get the list of products:
const items = [
{ id: 1, product: "milk", price: 10, type: "drink", date: "01/01/2022" },
{ id: 2, product: "coca", price: 11, type: "drink", date: "01/01/2022" },
{ id: 3, product: "pepsi", price: 20, type: "drink", date: "01/01/2024" },
{ id: 4, product: "carrots", price: 30, type: "food", date: "01/01/2023" },
];
const groupedDates = Object.entries(
items.reduce((acc, { product, price, type, date }) => {
if (!acc[date]) {
acc[date] = [];
}
acc[date].push({ product, price, type, date });
return acc;
}, {})
).map(([date, items]) => ({ date, items }));
const allProducts = groupedDates.map((obj) => obj.items.map(i => i.product))
console.log(allProducts)
const totalsByDate = groupedDates.map(({ date, items }) => (
{ [date] : items.reduce((acc, item) => acc + item.price, 0) }
))
console.log(totalsByDate)
.as-console-wrapper { max-height:100% !important; top 0 }
Note I would make groupedDates an object with its keys being the dates; that will make looking up data for a given date much easier. For example:
const items = [
{ id: 1, product: "milk", price: 10, type: "drink", date: "01/01/2022" },
{ id: 2, product: "coca", price: 11, type: "drink", date: "01/01/2022" },
{ id: 3, product: "pepsi", price: 20, type: "drink", date: "01/01/2024" },
{ id: 4, product: "carrots", price: 30, type: "food", date: "01/01/2023" },
];
const groupedDates = items.reduce((acc, { date, ...rest }) => {
acc[date] = (acc[date] || []).concat({ ...rest })
return acc;
}, {})
console.log(groupedDates)
const allProducts = Object.values(groupedDates)
.flatMap(arr => arr.map(obj => obj.product))
console.log(allProducts)
const totalsByDate = Object.entries(groupedDates).map(([ date, items ]) => (
{ [date] : items.reduce((acc, item) => acc + item.price, 0) }
))
console.log(totalsByDate)
.as-console-wrapper { max-height:100% !important; top 0; }

I am trying to get a function to search an array of objects and then return the entire object based on the filter

I am trying to get my function working and I can not for the life of me figure out why it isnt. I am trying to use .filter() to search the array of objects to find the object with the tag ketchup. Then return the whole object it is in to the console log
let foodArr = [
{
type: 'Chicken',
rating: 1,
tags: ['chicken', 'free-range', 'no hormones'],
price: 10,
popularity: 80
},
{
type: 'pizza',
rating: 5,
tags: ['pepperoni', 'sauce', 'bread'],
price: 25,
popularity: 56
},
{
type: 'hamburger',
rating: 3,
tags: ['bun', 'patty', 'lettuce'],
price: 8,
popularity: 99
},
{
type: 'wings',
rating: 4,
tags: ['wing', 'bbq', 'ranch'],
price: 12,
popularity: 68
},
{
type: 'fries',
rating: 2,
tags: ['ketchup'],
price: 4,
popularity: 100
}
]
const filteredFood = foodArr.filter(function(obj) {
return obj.tags[''] === 'ketchup'
})
console.log(filteredFood)
const filteredFood = foodArr.filter(function(value){
for (let i=0; i<value.tags.length; i++)
if(value.tags[i] === 'ranch'){
return value.tags[i]
}
}
)
console.log(filteredFood)

React - filter nested array and update the state

I have a state with an array of objects. Objects represent workers. Each worker has a name property and a nested object (projects2021). Inside the nested object there's an array of objects (projectsList):
[{
name: 'John',
projects2021: {
hours: 15,
projectsAll: 10,
projectsNames: ['x', 'y', 'z'],
projectsList: [
{
month: 'january',
projectName: 'germany',
status: 'vodja',
hours: 50,
},
{
month: 'february',
projectName: 'germany',
status: 'vodja',
hours: 50,
},
],
},
},
{
name: 'David',
projects2021: {
hours: 15,
projectsAll: 10,
projectsNames: ['x', 'y', 'z'],
projectsList: [
{
month: 'january',
projectName: 'germany',
status: 'vodja',
hours: 50,
},
{
month: 'february',
projectName: 'germany',
status: 'vodja',
hours: 50,
},
],
},
}
]
const [workers, setWorkers] = useState(users);
I would like to map over the state and render the workers in a way where only the projects with a given month would be shown. For example, if I clicked "january", I would like react to display all the workers (their names), but only the projects with a "month" property of "january". In layman's terms: filtering the table by a given month. This is what I've done so far:
const filterByMonth = (month) => {
let mainArray = [];
workers.map((worker) => {
const result = worker.projects2021.projectsList.filter(
(data) => data.month === month
);
mainArray.push(result)
});
setWorkers(mainArray);
};
With my approach I mutate the state directly (which is not ok) and thus loose certain parts of an object. I want to retain my object and only change the state of a nested array of objects (projectsList).
I was thinking of a way where I would spread the object first and then concat/push the nested array inside of an object.
I do apologize if my question isn't structured the way it should be, but this is my first time posting and I am a self-taught fella :).
Thank you
You can use spread to achieve it. Here's a sample:
const filterByMonth = (month) => {
const result = workers.map((worker) => {
return {...worker , projects2021: worker.projects2021.projectsList.filter(data => data.month === month)}
});
setWorkers(result);
};
One way of accomplishing this is to have workers as constant and filtered workers as state.
const workers= [{
name: 'John',
projects2021: {
hours: 15,
projectsAll: 10,
projectsNames: ['x', 'y', 'z'],
projectsList: [
{
month: 'january',
projectName: 'germany',
status: 'vodja',
hours: 50,
},
],
},
{...}]
const [filteredWorkers, setFilteredWorkers]=useState(workers)
Then apply filtering to the state
const filterByMonth = (month) => {
let newWorkers = workers.filter((worker)=> worker.projects2021.projectsList.month === month);
setFilteredWorkers(newWorkers);
You actually can use this code:
const workers = [{
name: 'John',
projects2021: {
hours: 15,
projectsAll: 10,
projectsNames: ['x', 'y', 'z'],
projectsList: [
{
month: 'january',
projectName: 'germany',
status: 'vodja',
hours: 50,
},
{
month: 'february',
projectName: 'germany',
status: 'vodja',
hours: 50,
},
],
},
},
{
name: 'David',
projects2021: {
hours: 15,
projectsAll: 10,
projectsNames: ['x', 'y', 'z'],
projectsList: [
{
month: 'january',
projectName: 'germany',
status: 'vodja',
hours: 50
},
{
month: 'february',
projectName: 'germany',
status: 'vodja',
hours: 50
},
],
},
}];
const updatedWorkers = workers.map(worker => {
const updatedProjectsList = worker.projects2021.projectsList.filter(item =>
item.month === 'january'
);
return ({...worker, projects2021:{...worker.projects2021, projectsList: updatedProjectsList}});
});
console.log(updatedWorkers)

Multiple date array with same value by name on Highcharts

I want to custom highcharts, but before that I am stuck how to combine or manage multiple arrays with the same date and name value from JSON
// sample data JSON
const dataJson = [
{
"day": 1,
"month": 10,
"name": "John",
"value": 1000.0
},
{
"day": 1,
"month": 10,
"name": "Joe",
"value": 2000.0
},
{
"day": 2,
"month": 10,
"name": "John",
"value": 2000
},
{
"day": 2,
"month": 10,
"name": "Joe",
"value": 500
},
{
"day": 2,
"month": 10,
"name": "Joe",
"value": 500
},
{
"day": 2,
"month": 10,
"name": "Jane",
"value": 500
},
{
"day": 3,
"month": 10,
"name": "John",
"value": 1500
},
{
"day": 3,
"month": 10,
"name": "John",
"value": 2000
},
{
"day": 4,
"month": 10,
"name": "Jane",
"value": 1500
},
{
"day": 5,
"month": 10,
"name": "Janet",
"value": 1000
}
]
I want implement the above JSON into this highchart function:
Like merge day and month then show it in xAxis categories be day/month.
ex: day 1 and month 10 => 1/10.
Merge value same name in same day. Sample JSON day 2 Joe has 500 and 500. So in highchart series value for Joe is 1000 for day 2
Highcharts.chart('container', {
chart: {
type: 'column'
},
title: {
text: 'Total fruit consumption, grouped by gender'
},
xAxis: {
categories: ['1/10', '2/10', '3/10', '4/10', '5/10']
},
yAxis: {
allowDecimals: false,
min: 0,
title: {
text: 'Number of fruits'
}
},
tooltip: {
formatter: function () {
return '<b>' + this.x + '</b><br/>' +
this.series.name + ': ' + this.y + '<br/>' +
'Total: ' + this.point.stackTotal;
}
},
plotOptions: {
column: {
stacking: 'normal'
}
},
series: [{
name: 'John',
data: [1000, 2000, 3500, 0, 0],
}, {
name: 'Joe',
data: [2000, 1000, 0, 0, 0],
}, {
name: 'Jane',
data: [0, 500, 0, 1500, 0],
}, {
name: 'Janet',
data: [0, 0, 0, 0, 1000],
}]
});
Here is link to my fiddle
Thanks
Ok, this way is maybe not the most performant to accomplish this task, but it gets the job done:
const dataJson = [{
day: 1,
month: 10,
name: "John",
value: 1000.0,
},
{
day: 1,
month: 10,
name: "Joe",
value: 2000.0,
},
{
day: 2,
month: 10,
name: "John",
value: 2000,
},
{
day: 2,
month: 10,
name: "Joe",
value: 500,
},
{
day: 2,
month: 10,
name: "Joe",
value: 500,
},
{
day: 2,
month: 10,
name: "Jane",
value: 500,
},
{
day: 3,
month: 10,
name: "John",
value: 1500,
},
{
day: 3,
month: 10,
name: "John",
value: 2000,
},
{
day: 4,
month: 10,
name: "Jane",
value: 1500,
},
{
day: 5,
month: 10,
name: "Janet",
value: 1000,
},
];
const res = dataJson.reduce((acc, cV) => {
if (acc[cV.name]) {
acc[cV.name] = {
day: {
...acc[cV.name].day,
[cV.day]: acc[cV.name].day[cV.day] ?
acc[cV.name].day[cV.day] + cV.value :
cV.value,
},
};
} else {
acc[cV.name] = {
day: {
[cV.day]: cV.value
}
};
}
return acc;
}, {});
let result = [];
Object.keys(res).forEach(name => {
const values = {
name
};
const data = [];
for (let i = 1; i <= 5; i++) {
if (res[name].day[i]) {
data.push(res[name].day[i]);
} else {
data.push(0);
}
}
values.data = data;
result.push(values);
});
console.log(result);
In the res variable, I change the array of json data into an object with the name and day as keys. If the day exists, I add the new amount to the current one, otherwise I create the key.
After that, I loop through the object and check whether values exist in a given day. If yes, then I push it to the data, if no, I push 0 to the data. My solution is for 5 days, but you can easily adjust it for any given amount of days.

Merge two arrays avoiding O(n^2) complexity

Given two arrays, farmers and collections, I want to be able to merge the farmer information to each collection when farmer_id in the collection is equal to id in farmers. if there is no id of the farmer that matches farmer_id in the collection then that collection should have a an empty farmer object
const farmers = [{
id: 10,
name: 'John Doe',
email: 'jdoe#gmail.com'
},
{
id: 11,
name: 'James Bond',
email: 'james#gmail.com'
}
]
const collections = [{
id: 9,
name: 'Book',
farmer_id: 10,
date: 'June'
},
{
id: 10,
name: 'Game',
farmer_id: 11,
date: 'July'
},
{
id: 13,
name: 'Car',
farmer_id: 10,
date: 'August'
},
{
id: 11,
name: 'Wristwatches',
farmer_id: 20,
date: 'August'
}
]
The result should be in this format below
const result = [{
id: 9,
name: 'Book',
farmer_id: 10,
date: 'June',
farmer: {
id: 10,
name: 'John Doe',
email: 'jdoe#gmail.com'
}
},
{
id: 10,
name: 'Game',
farmer_id: 11,
date: 'July',
farmer: {
id: 11,
name: 'James Bond',
email: 'james#gmail.com'
}
},
{
id: 13,
name: 'Car',
farmer_id: 10,
date: 'August',
farmer: {
id: 10,
name: 'John Doe',
email: 'jdoe#gmail.com'
}
},
{
id: 11,
name: 'Wristwatches',
farmer_id: 20,
date: 'August',
farmer: {}
}
]
This is what i have been able to come up with but am stuck right now
function mapper(farmers, collectors) {
for (let k = 0; k < farmers.length; k++) {
const idToFarmerInfo = {};
idToFarmerInfo[farmers[k].id] = farmers[k];
for (let j = 0; j < collectors.length; j++) {
let mapper = idToFarmerInfo[collectors[j].farmer_id];
farmers[mapper] = collectors[j]
}
}
return farmers
}
i followed this link as am trying to avoid O of N squared but O of N complexity
For a better performance you could create a hash of farmers where the complexity is O(N) because we're iterating the farmers list only once.
const farmers = [{ id: 10, name: 'John Doe', email: 'jdoe#gmail.com' }, { id: 11, name: 'James Bond', email: 'james#gmail.com' } ]; const collections = [{ id: 9, name: 'Book', farmer_id: 10, date: 'June' }, { id: 10, name: 'Game', farmer_id: 11, date: 'July' }, { id: 13, name: 'Car', farmer_id: 10, date: 'August' }, { id: 11, name: 'Wristwatches', farmer_id: 20, date: 'August' } ]
var farmers_hash = farmers.reduce((hash, item) => {
hash[item.id] = item;
return hash;
}, {});
console.log(farmers_hash);
The following step is to build the desired output by assigning one farmer using hash keys.
This can be achieved using map method in combination with Object.assign.
const farmers = [{ id: 10, name: 'John Doe', email: 'jdoe#gmail.com' }, { id: 11, name: 'James Bond', email: 'james#gmail.com' } ]; const collections = [{ id: 9, name: 'Book', farmer_id: 10, date: 'June' }, { id: 10, name: 'Game', farmer_id: 11, date: 'July' }, { id: 13, name: 'Car', farmer_id: 10, date: 'August' }, { id: 11, name: 'Wristwatches', farmer_id: 20, date: 'August' } ]
var farmers_hash = farmers.reduce((hash, item) => {
hash[item.id] = item;
return hash;
}, {});
var result = collections.map((item) => {
item.farmer = Object.assign({}, farmers_hash[item.farmer_id])
return item;
});
console.log(result);
As you can see the final complexity is O(N) + O(M) where N is the length of farmers array and M is the length of collections array.
Demo on stackblitz
You can use a more declarative approach and use Array.map and Array.find
const result = collections.map(collection => {
return {
...collection,
farmer: farmers.find(farmer => collection.farmer_id == farmer.id) || {}
};
});
console.log(result);
You can create a Map collection to have O(N) of access to desired farmer by id. Then mapping becomes faster in terms of performance:
const unique = new Map(farmers.map(f=> [f.id, f]));
const result = collections.map(s => ({
...s, farmer_id: unique.get(s.farmer_id) || s.farmer_id
}))
Now mapping of collections has complexity O(N). However, do not forget to sum complexity of making unique farmers. The overall complexity is O(N) + O(M).
An example:
const farmers = [{
id: 10,
name: 'John Doe',
email: 'jdoe#gmail.com'
},
{
id: 11,
name: 'James Bond',
email: 'james#gmail.com'
}
];
const collections = [{
id: 9,
name: 'Book',
farmer_id: 10,
date: 'June'
},
{
id: 10,
name: 'Game',
farmer_id: 11,
date: 'July'
},
{
id: 13,
name: 'Car',
farmer_id: 10,
date: 'August'
},
{
id: 11,
name: 'Wristwatches',
farmer_id: 20,
date: 'August'
}
];
const unique = new Map(farmers.map(f=> [f.id, f]));
const result = collections.map(s => ({
...s, farmer_id: unique.get(s.farmer_id) || s.farmer_id
}))
console.log(result);

Resources