import Utils from "./Utils";
import { renderResults, renderPagination } from "./htmlTemplates";
export default class CompaniesData {
/**
* @constructor
*/
constructor() {
this.wholeData;
this.filteredData;
this.pageNumber = 1;
this.pageSize = 10;
this.utils = new Utils();
this.init();
}
setWholeData(data) {
this.wholeData = data;
}
getWholeData() {
return this.wholeData;
}
setFilteredData(data) {
this.filteredData = data;
}
getFilteredData() {
return this.filteredData;
}
/**
* When application is loaded, it assigning static elements to variables
* fetch and then sort data ascending, slice from it 10 first items and render them.
* It also render pagination and add event listeners to static elements
*/
init() {
this.setupSelectors();
this.fetchData().then((data) => {
this.setWholeData(data);
this.setFilteredData(data);
const sorted = this.sortResults("id", true, this.wholeData);
const sliced = this.slicedData(sorted);
renderResults(sliced, this.dataRows);
renderPagination(this.getWholeData(), this.paginationSelect);
this.addEventsListeners();
});
}
/**
* Assigning to variable static elements of the HTML
*/
setupSelectors() {
// Search selectors
this.searchInput = document.querySelector(".search__input");
this.searchButton = document.querySelector(".search__button");
// Pagination selectors
this.paginationSelect = document.querySelector(".pagination__select");
this.prevBtn = document.querySelector(".previous-page");
this.nextBtn = document.querySelector(".next-page");
// Table selectors
this.headers = document.querySelectorAll("[data-header-id]");
this.dataRows = document.querySelector(".data");
this.loading = document.querySelector(".loading");
}
/**
* Assigning event listeners to static elements of the HTML
*/
addEventsListeners() {
/**
* Search event listener
*/
this.searchButton.addEventListener("click", () => {
const filteredData = this.filterByKeyword(
this.searchInput.value,
this.wholeData
);
this.setFilteredData(filteredData);
renderResults(this.slicedData(this.filteredData), this.dataRows);
renderPagination(this.filteredData, this.paginationSelect);
}); // Search event listeners END
/**
* Check what header button user clicked, get boolean from function
* changeSortBtn and pass them to sorting method with filtered data
* After that get array from slicedData method and pass it
* to renderResults function
*/
this.headers.forEach((el) =>
el.addEventListener("click", (event) => {
const sortKey = event.target.getAttribute("data-header-id");
const isAscending = this.changeSortBtn(event.target);
const sorted = this.sortResults(
sortKey,
isAscending,
this.getFilteredData()
);
const sliced = this.slicedData(sorted);
renderResults(sliced, this.dataRows);
})
);
// Pagination event listeners
this.prevBtn.addEventListener("click", () => {
if (this.paginationSelect.value > 1) {
const sliced = this.slicedData(
this.getFilteredData(),
Number(this.paginationSelect.value) - 1
);
this.paginationSelect.value = Number(this.paginationSelect.value) - 1;
renderResults(sliced, this.dataRows);
}
});
this.nextBtn.addEventListener("click", () => {
if (this.paginationSelect.value < this.paginationSelect.length) {
const sliced = this.slicedData(
this.getFilteredData(),
Number(this.paginationSelect.value) + 1
);
this.paginationSelect.value = Number(this.paginationSelect.value) + 1;
renderResults(sliced, this.dataRows);
}
});
this.paginationSelect.addEventListener("change", (event) => {
const sliced = this.slicedData(
this.getFilteredData(),
Number(event.target.value)
);
renderResults(sliced, this.dataRows);
}); // Pagination event listeners END
}
/**
* Using fetch(url) and get companies data, then convert response to json.
* Next, Promise.all make sure that another data table is loaded and converted to json before next step which is modifying companies array.
* Now the algorithm for every company object add some extra properties, which are calculated by function countIncomes.
* Function fetchData is used in init() with then(), where class variable wholeData and filteredData is
* filled with complete data about companies and their incomes.
* @returns {object}
*/
fetchData() {
return this.utils
.connectToOrigin("https://recruitment.hal.skygate.io/companies")
.then((response) => response.json())
.then((companies) => {
// calculate total, average and last income
return Promise.all(
companies.map((company) =>
this.utils
.connectToOrigin(
`https://recruitment.hal.skygate.io/incomes/${company.id}`
)
.then((response) => response.json())
)
).then((companyIncomes) => {
const mergedCompaniesIncomes = [];
companies.forEach((company, index) => {
mergedCompaniesIncomes.push({
...company,
...this.countIncomes(companyIncomes[index]),
});
});
return mergedCompaniesIncomes;
});
});
}
/**
* Return object with calculated: sum of all company incomes, average income from sum,
* and sum of incomes from last month
* @param {array} companyIncomes Array of objects with income and date
* @returns {{totalIncome: string, averageIncome: string, lastMonthIncomes: string}}
*/
countIncomes(companyIncomes) {
const sum = this.utils.getTotalIncome(companyIncomes.incomes);
const sortedIncomes = this.sortResults(
"date",
false,
companyIncomes.incomes
);
const lastDate = new Date(sortedIncomes[0].date);
const lastMonthDate = new Date(lastDate.getFullYear(), lastDate.getMonth());
return {
totalIncome: sum,
averageIncome: (sum / companyIncomes.incomes.length).toFixed(2),
lastMonthIncomes: this.utils.getTotalIncome(
sortedIncomes.filter((income) => new Date(income.date) >= lastMonthDate)
),
};
}
/**
* Function serves for pagination, it needs filtered, or whole data, page which user requested and number of rows that will be rendered.
* @param {array} filteredData
* @param {number} pageNumber Requested page by user
* @param {number} pageSize Default 10 rows on page
* @returns {array} Part of data array which will fit in one page by, default 10 items
*/
slicedData(filteredData, pageNumber = 1, pageSize = 10) {
const firstRow = (pageNumber - 1) * pageSize;
const lastRow = firstRow + pageSize;
return filteredData.slice(firstRow, lastRow);
}
/**
* Filtering start when input text have some content and Search button is clickedSortBtn, if text is empty table will be filled with defaults start rows.
* Function is using filter with callback on global variable companiesData. If one of the properties is similar to keyWords,
* object from array is returning by filter to newArray. After complete filtering results are sorted by id, in ascending order.
* @param {string} searchKeyword String from search input
* @param {array} data All data from API
* @returns {array}
*/
filterByKeyword(searchKeyword, data) {
searchKeyword = this.searchInput.value;
this.setFilteredData();
return data.filter((company) =>
Object.values(company).some((value) => {
return String(value)
.toUpperCase()
.includes(searchKeyword.toUpperCase());
})
);
}
// SORTING
/**
* Function is using custom method to compare two items
* Parameter from isAscending come from changeSortBtn
* If true sorting ascending else descending
* @param {string} sortKey Column symbol by which sorting is made
* @param {boolean} isAscending If true sorting ascending, else descending
* @param {array} array Filtered or not data from API
* @returns {array}
*/
sortResults(sortKey, isAscending, array) {
function compare(a, b) {
const itemA = a[sortKey];
const itemB = b[sortKey];
let comparison = 0;
comparison = itemA > itemB ? 1 : 0;
comparison = itemA < itemB ? -1 : 1;
isAscending ? (comparison *= 1) : (comparison *= -1);
return comparison;
}
const sorted = array.sort(compare);
return sorted;
}
// SORTING END
/**
* Gives button from header class "active" and remove from previous clicked button
* Also it toggle class "ascending", if add this class return true, else false
* Returned value is used as parameter in sortResults
* @param {object} clickedSortBtn
* @returns {boolean} isAscending
*/
changeSortBtn(clickedSortBtn) {
if (clickedSortBtn.hasAttribute("data-header-id") === false) {
return;
}
if (!clickedSortBtn.classList.contains("active")) {
document.querySelector(".active").classList.remove("active");
clickedSortBtn.classList.add("active");
}
return clickedSortBtn.classList.toggle("ascending");
}
}