analytics.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. const Transaction = require("../classes/Transaction.js");
  2. let analytics = {
  3. isPopulated: false,
  4. ingredient: undefined,
  5. category: undefined,
  6. recipe: undefined,
  7. transactionsByDate: [],
  8. display: function(){
  9. if(!this.isPopulated){
  10. let ingredientTab = document.getElementById("analIngredientsTab");
  11. let recipeTab = document.getElementById("analRecipesTab");
  12. let categoryTab = document.getElementById("analCategoriesTab");
  13. let individualTab = document.getElementById("analIndividualTab");
  14. ingredientTab.onclick = ()=>{this.tab(ingredientTab)};
  15. categoryTab.onclick = ()=>{this.tab(categoryTab)};
  16. recipeTab.onclick = ()=>{this.tab(recipeTab)};
  17. individualTab.onclick = ()=>{this.tab(individualTab)};
  18. let to = new Date();
  19. let from = new Date(to.getFullYear(), to.getMonth() - 1, to.getDate());
  20. document.getElementById("analStartDate").valueAsDate = from;
  21. document.getElementById("analEndDate").valueAsDate = to;
  22. document.getElementById("analDateBtn").onclick = ()=>{this.newDates()};
  23. this.populateButtons();
  24. if(merchant.inventory.length > 0) this.ingredient = merchant.inventory[0].ingredient;
  25. if(merchant.recipes.length > 0) this.recipe = merchant.recipes[0];
  26. this.newDates();
  27. this.isPopulated = true;
  28. }
  29. },
  30. populateButtons: function(){
  31. let ingredientButtons = document.getElementById("analIngredientList");
  32. let categoryButtons = document.getElementById("analCategoriesList");
  33. let recipeButtons = document.getElementById("analRecipeList");
  34. while(ingredientButtons.children.length > 0){
  35. ingredientButtons.removeChild(ingredientButtons.firstChild);
  36. }
  37. for(let i = 0; i < merchant.inventory.length; i++){
  38. let button = document.createElement("button");
  39. button.innerText = merchant.inventory[i].ingredient.name;
  40. button.classList.add("choosable");
  41. button.onclick = ()=>{
  42. this.ingredient = merchant.inventory[i].ingredient;
  43. this.displayIngredient();
  44. };
  45. ingredientButtons.appendChild(button);
  46. }
  47. while(categoryButtons.children.length > 0){
  48. categoryButtons.removeChild(categoryButtons.firstChild);
  49. }
  50. let categories = merchant.categorizeIngredients();
  51. for(let i = 0; i < categories.length; i++){
  52. let button = document.createElement("button");
  53. button.innerText = categories[i].name;
  54. button.classList.add("choosable");
  55. button.onclick = ()=>{
  56. this.category = categories[i];
  57. this.displayIngredientCategory();
  58. }
  59. categoryButtons.appendChild(button);
  60. }
  61. while(recipeButtons.children.length > 0){
  62. recipeButtons.removeChild(recipeButtons.firstChild);
  63. }
  64. for(let i = 0; i < merchant.recipes.length; i++){
  65. let button = document.createElement("button");
  66. button.innerText = merchant.recipes[i].name;
  67. button.classList.add("choosable");
  68. button.onclick = ()=>{
  69. this.recipe = merchant.recipes[i];
  70. this.displayRecipe();
  71. };
  72. recipeButtons.appendChild(button);
  73. }
  74. },
  75. getData: function(from, to){
  76. let data = {
  77. from: from,
  78. to: to,
  79. recipes: []
  80. }
  81. let loader = document.getElementById("loaderContainer");
  82. loader.style.display = "flex";
  83. return fetch("/transaction", {
  84. method: "post",
  85. headers: {
  86. "Content-Type": "application/json"
  87. },
  88. body: JSON.stringify(data)
  89. })
  90. .then(response => response.json())
  91. .then((response)=>{
  92. if(typeof(response) === "string"){
  93. controller.createBanner(response, "error");
  94. }else{
  95. this.transactionsByDate = [];
  96. response.reverse();
  97. let startOfDay = new Date(from.getTime());
  98. startOfDay.setHours(0, 0, 0, 0);
  99. let endOfDay = new Date(from.getTime());
  100. endOfDay.setDate(endOfDay.getDate() + 1);
  101. endOfDay.setHours(0, 0, 0, 0);
  102. let transactionIndex = 0;
  103. while(startOfDay <= to){
  104. let currentTransactions = [];
  105. while(transactionIndex < response.length && new Date(response[transactionIndex].date) < endOfDay){
  106. currentTransactions.push(new Transaction(
  107. response[transactionIndex]._id,
  108. response[transactionIndex].date,
  109. response[transactionIndex].recipes,
  110. merchant
  111. ));
  112. transactionIndex++;
  113. }
  114. let thing = {
  115. date: new Date(startOfDay.getTime()),
  116. transactions: currentTransactions
  117. };
  118. this.transactionsByDate.push(thing);
  119. startOfDay.setDate(startOfDay.getDate() + 1);
  120. endOfDay.setDate(endOfDay.getDate() + 1);
  121. }
  122. }
  123. })
  124. .catch((err)=>{
  125. controller.createBanner("UNABLE TO UPDATE THE PAGE", "error");
  126. })
  127. .finally(()=>{
  128. loader.style.display = "none";
  129. });
  130. },
  131. displayIngredient: function(){
  132. if(this.ingredient === undefined || this.transactionsByDate.length === 0) return;
  133. //break down data into dates and quantities
  134. let dates = [];
  135. let quantities = [];
  136. for(let i = 0; i < this.transactionsByDate.length; i++){
  137. dates.push(this.transactionsByDate[i].date);
  138. let sum = 0;
  139. for(let j = 0; j < this.transactionsByDate[i].transactions.length; j++){
  140. let transaction = this.transactionsByDate[i].transactions[j];
  141. sum += transaction.getIngredientQuantity(this.ingredient);
  142. }
  143. quantities.push(sum);
  144. }
  145. //create and display the graph
  146. let trace = {
  147. x: dates,
  148. y: quantities,
  149. mode: "lines+markers",
  150. line: {
  151. color: "rgb(255, 99, 107)"
  152. }
  153. }
  154. let yaxis = `QUANTITY (${this.ingredient.unit.toUpperCase()})`;
  155. let layout = {
  156. title: this.ingredient.name.toUpperCase(),
  157. xaxis: {title: "DATE"},
  158. yaxis: {title: yaxis},
  159. margin: {
  160. l: 40,
  161. r: 10,
  162. b: 20,
  163. t: 30
  164. },
  165. paper_bgcolor: "rgba(0, 0, 0, 0)"
  166. }
  167. Plotly.newPlot("itemUseGraph", [trace], layout);
  168. //Create min/max/avg
  169. //Current ingredient is stored on the "analMinUse" element
  170. let min = quantities[0];
  171. let max = quantities[0];
  172. let sum = 0;
  173. for(let i = 0; i < quantities.length; i++){
  174. if(quantities[i] < min) min = quantities[i];
  175. if(quantities[i] > max) max = quantities[i];
  176. sum += quantities[i];
  177. }
  178. document.getElementById("analMinUse").innerText = `${min.toFixed(2)} ${this.ingredient.unit.toUpperCase()}`;
  179. document.getElementById("analAvgUse").innerText = `${(sum / quantities.length).toFixed(2)} ${this.ingredient.unit.toUpperCase()}`;
  180. document.getElementById("analMaxUse").innerText = `${max.toFixed(2)} ${this.ingredient.unit.toUpperCase()}`;
  181. //Create weekday averages
  182. let dayUse = [0, 0, 0, 0, 0, 0, 0];
  183. let dayCount = [0, 0, 0, 0, 0, 0, 0];
  184. for(let i = 0; i < quantities.length; i++){
  185. dayUse[dates[i].getDay()] += quantities[i];
  186. dayCount[dates[i].getDay()]++;
  187. }
  188. document.getElementById("analDayOne").innerText = `${(dayUse[0] / dayCount[0]).toFixed(2)} ${this.ingredient.unit.toUpperCase()}`;
  189. document.getElementById("analDayTwo").innerText = `${(dayUse[1] / dayCount[1]).toFixed(2)} ${this.ingredient.unit.toUpperCase()}`;
  190. document.getElementById("analDayThree").innerText = `${(dayUse[2] / dayCount[2]).toFixed(2)} ${this.ingredient.unit.toUpperCase()}`;
  191. document.getElementById("analDayFour").innerText = `${(dayUse[3] / dayCount[3]).toFixed(2)} ${this.ingredient.unit.toUpperCase()}`;
  192. document.getElementById("analDayFive").innerText = `${(dayUse[4] / dayCount[4]).toFixed(2)} ${this.ingredient.unit.toUpperCase()}`;
  193. document.getElementById("analDaySix").innerText = `${(dayUse[5] / dayCount[5]).toFixed(2)} ${this.ingredient.unit.toUpperCase()}`;
  194. document.getElementById("analDaySeven").innerText = `${(dayUse[6] / dayCount[6]).toFixed(2)} ${this.ingredient.unit.toUpperCase()}`;
  195. },
  196. displayIngredientCategory: function(){
  197. if(this.category === undefined) this.category = merchant.categorizeIngredients()[0];
  198. let dates = [];
  199. let quantities = [];
  200. for(let i = 0; i < this.transactionsByDate.length; i++){
  201. dates.push(this.transactionsByDate[i].date);
  202. let total = 0;
  203. for(let j = 0; j < this.transactionsByDate[i].transactions.length; j++){
  204. let transaction = this.transactionsByDate[i].transactions[j];
  205. for(let k = 0; k < this.category.ingredients.length; k++){
  206. total += transaction.getIngredientQuantity(this.category.ingredients[k].ingredient);
  207. }
  208. }
  209. quantities.push(total);
  210. }
  211. let trace = {
  212. x: dates,
  213. y: quantities,
  214. mode: "lines+markers",
  215. line: {
  216. color: "rgb(255, 99, 107)"
  217. }
  218. };
  219. let layout = {
  220. title: this.category.name,
  221. xaxis: {title: "DATE"},
  222. yaxis: {title: "COST ($)"},
  223. margin: {
  224. l: 40,
  225. r: 10,
  226. b: 20,
  227. t: 30
  228. },
  229. paper_bgcolor: "white"
  230. }
  231. Plotly.newPlot("analCategoriesGraph", [trace], layout);
  232. },
  233. displayRecipe: function(){
  234. if(this.recipe === undefined || this.transactionsByDate.length === 0) return;
  235. //break down data into dates and quantities
  236. let dates = [];
  237. let quantities = [];
  238. for(let i = 0; i < this.transactionsByDate.length; i++){
  239. dates.push(this.transactionsByDate[i].date);
  240. let sum = 0;
  241. for(let j = 0; j < this.transactionsByDate[i].transactions.length; j++){
  242. const transaction = this.transactionsByDate[i].transactions[j];
  243. for(let k = 0; k < transaction.recipes.length; k++){
  244. if(transaction.recipes[k].recipe === this.recipe){
  245. sum += transaction.recipes[k].quantity;
  246. }
  247. }
  248. }
  249. quantities.push(sum);
  250. }
  251. //create and display the graph
  252. const trace = {
  253. x: dates,
  254. y: quantities,
  255. mode: "lines+markers",
  256. line: {
  257. color: "rgb(255, 99, 107)"
  258. }
  259. }
  260. const layout = {
  261. title: this.recipe.name.toUpperCase(),
  262. xaxis: {title: "DATE"},
  263. yaxis: {title: "QUANTITY"},
  264. margin: {
  265. l: 40,
  266. r: 10,
  267. b: 20,
  268. t: 30
  269. },
  270. paper_bgcolor: "rgba(0, 0, 0, 0)"
  271. }
  272. Plotly.newPlot("recipeSalesGraph", [trace], layout);
  273. //Display the boxes at the bottom
  274. //Current recipe is stored on the "recipeAvgUse" element
  275. let avg = 0;
  276. for(let i = 0; i < quantities.length; i++){
  277. avg += quantities[i];
  278. }
  279. avg = avg / quantities.length;
  280. document.getElementById("recipeAvgUse").innerText = avg.toFixed(2);
  281. document.getElementById("recipeAvgRevenue").innerText = `$${(avg * this.recipe.price).toFixed(2)}`;
  282. },
  283. newDates: async function(){
  284. const from = document.getElementById("analStartDate").valueAsDate;
  285. const to = document.getElementById("analEndDate").valueAsDate;
  286. from.setHours(0, 0, 0, 0);
  287. to.setDate(to.getDate() + 1);
  288. to.setHours(0, 0, 0, 0);
  289. await this.getData(from, to);
  290. let analTabs = document.getElementById("analTabs");
  291. for(let i = 0; i < analTabs.children.length; i++){
  292. if(analTabs.children[i].classList.contains("active")){
  293. switch(analTabs.children[i].innerText.toLowerCase()){
  294. case "ingredients":
  295. this.displayIngredient();
  296. break;
  297. case "categories":
  298. this.displayIngredientCategory();
  299. break;
  300. case "recipes":
  301. this.displayRecipe();
  302. break;
  303. }
  304. }
  305. }
  306. },
  307. tab: function(tab){
  308. let upperGroup = document.getElementById("topAnalTabs");
  309. let lowerGroup = document.getElementById("bottomAnalTabs");
  310. let strand = document.getElementById("analyticsStrand");
  311. for(let i = 0; i < tab.parentElement.children.length; i++){
  312. tab.parentElement.children[i].classList.remove("active");
  313. }
  314. tab.classList.add("active");
  315. for(let i = 1; i < strand.children.length; i++){
  316. strand.children[i].style.display = "none";
  317. }
  318. if(upperGroup.children[0].classList.contains("active")){
  319. if(lowerGroup.children[0].classList.contains("active")){
  320. document.getElementById("analIndividualIngredient").style.display = "flex";
  321. this.displayIngredient();
  322. }else{
  323. document.getElementById("analCategoryIngredient").style.display = "flex";
  324. this.displayIngredientCategory();
  325. }
  326. }else{
  327. if(lowerGroup.children[0].classList.contains("active")){
  328. document.getElementById("analIndividualRecipe").style.display = "flex";
  329. this.displayRecipe();
  330. }else{
  331. document.getElementById("analCategoryRecipe").style.display = "flex";
  332. this.displayRecipeCategory();
  333. }
  334. }
  335. }
  336. }
  337. module.exports = analytics;