analytics.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. const Transaction = require("../classes/Transaction.js");
  2. let analytics = {
  3. isPopulated: false,
  4. ingredient: undefined,
  5. recipe: undefined,
  6. transactionsByDate: [],
  7. display: function(){
  8. if(!this.isPopulated){
  9. document.getElementById("analRecipeContent").style.display = "none";
  10. let to = new Date();
  11. let from = new Date(to.getFullYear(), to.getMonth() - 1, to.getDate());
  12. document.getElementById("analStartDate").valueAsDate = from;
  13. document.getElementById("analEndDate").valueAsDate = to;
  14. let analSlider = document.getElementById("analSlider");
  15. analSlider.onclick = ()=>{this.switchDisplay()};
  16. analSlider.checked = false;
  17. document.getElementById("analDateBtn").onclick = ()=>{this.newDates()};
  18. this.populateButtons();
  19. if(merchant.inventory.length > 0) this.ingredient = merchant.inventory[0].ingredient;
  20. if(merchant.recipes.length > 0) this.recipe = merchant.recipes[0];
  21. this.newDates();
  22. this.isPopulated = true;
  23. }
  24. },
  25. populateButtons: function(){
  26. let ingredientButtons = document.getElementById("analIngredientList");
  27. let recipeButtons = document.getElementById("analRecipeList");
  28. while(ingredientButtons.children.length > 0){
  29. ingredientButtons.removeChild(ingredientButtons.firstChild);
  30. }
  31. for(let i = 0; i < merchant.inventory.length; i++){
  32. let button = document.createElement("button");
  33. button.innerText = merchant.inventory[i].ingredient.name;
  34. button.classList.add("choosable");
  35. button.onclick = ()=>{
  36. this.ingredient = merchant.inventory[i].ingredient;
  37. this.displayIngredient();
  38. };
  39. ingredientButtons.appendChild(button);
  40. }
  41. while(recipeButtons.children.length > 0){
  42. recipeButtons.removeChild(recipeButtons.firstChild);
  43. }
  44. for(let i = 0; i < merchant.recipes.length; i++){
  45. let button = document.createElement("button");
  46. button.innerText = merchant.recipes[i].name;
  47. button.classList.add("choosable");
  48. button.onclick = ()=>{
  49. this.recipe = merchant.recipes[i];
  50. this.displayRecipe();
  51. };
  52. recipeButtons.appendChild(button);
  53. }
  54. },
  55. getData: function(from, to){
  56. let data = {
  57. from: from,
  58. to: to,
  59. recipes: []
  60. }
  61. let loader = document.getElementById("loaderContainer");
  62. loader.style.display = "flex";
  63. return fetch("/transaction", {
  64. method: "post",
  65. headers: {
  66. "Content-Type": "application/json"
  67. },
  68. body: JSON.stringify(data)
  69. })
  70. .then(response => response.json())
  71. .then((response)=>{
  72. if(typeof(response) === "string"){
  73. controller.createBanner(response, "error");
  74. }else{
  75. this.transactionsByDate = [];
  76. response.reverse();
  77. let startOfDay = new Date(from.getTime());
  78. startOfDay.setHours(0, 0, 0, 0);
  79. let endOfDay = new Date(from.getTime());
  80. endOfDay.setDate(endOfDay.getDate() + 1);
  81. endOfDay.setHours(0, 0, 0, 0);
  82. let transactionIndex = 0;
  83. while(startOfDay <= to){
  84. let currentTransactions = [];
  85. while(transactionIndex < response.length && new Date(response[transactionIndex].date) < endOfDay){
  86. currentTransactions.push(new Transaction(
  87. response[transactionIndex]._id,
  88. response[transactionIndex].date,
  89. response[transactionIndex].recipes,
  90. merchant
  91. ));
  92. transactionIndex++;
  93. }
  94. let thing = {
  95. date: new Date(startOfDay.getTime()),
  96. transactions: currentTransactions
  97. };
  98. this.transactionsByDate.push(thing);
  99. startOfDay.setDate(startOfDay.getDate() + 1);
  100. endOfDay.setDate(endOfDay.getDate() + 1);
  101. }
  102. }
  103. })
  104. .catch((err)=>{
  105. controller.createBanner("UNABLE TO UPDATE THE PAGE", "error");
  106. })
  107. .finally(()=>{
  108. loader.style.display = "none";
  109. });
  110. },
  111. displayIngredient: function(){
  112. if(this.ingredient === undefined || this.transactionsByDate.length === 0) return;
  113. //break down data into dates and quantities
  114. let dates = [];
  115. let quantities = [];
  116. for(let i = 0; i < this.transactionsByDate.length; i++){
  117. dates.push(this.transactionsByDate[i].date);
  118. let sum = 0;
  119. for(let j = 0; j < this.transactionsByDate[i].transactions.length; j++){
  120. let transaction = this.transactionsByDate[i].transactions[j];
  121. sum += transaction.getIngredientQuantity(this.ingredient);
  122. }
  123. quantities.push(sum);
  124. }
  125. //create and display the graph
  126. let trace = {
  127. x: dates,
  128. y: quantities,
  129. mode: "lines+markers",
  130. line: {
  131. color: "rgb(255, 99, 107)"
  132. }
  133. }
  134. let yaxis = `QUANTITY (${this.ingredient.unit.toUpperCase()})`;
  135. const layout = {
  136. title: this.ingredient.name.toUpperCase(),
  137. xaxis: {title: "DATE"},
  138. yaxis: {title: yaxis},
  139. margin: {
  140. l: 40,
  141. r: 10,
  142. b: 20,
  143. t: 30
  144. },
  145. paper_bgcolor: "rgba(0, 0, 0, 0)"
  146. }
  147. Plotly.newPlot("itemUseGraph", [trace], layout);
  148. //Create min/max/avg
  149. //Current ingredient is stored on the "analMinUse" element
  150. let min = quantities[0];
  151. let max = quantities[0];
  152. let sum = 0;
  153. for(let i = 0; i < quantities.length; i++){
  154. if(quantities[i] < min){
  155. min = quantities[i];
  156. }
  157. if(quantities[i] > max){
  158. max = quantities[i];
  159. }
  160. sum += quantities[i];
  161. }
  162. document.getElementById("analMinUse").innerText = `${min.toFixed(2)} ${this.ingredient.unit.toUpperCase()}`;
  163. document.getElementById("analAvgUse").innerText = `${(sum / quantities.length).toFixed(2)} ${this.ingredient.unit.toUpperCase()}`;
  164. document.getElementById("analMaxUse").innerText = `${max.toFixed(2)} ${this.ingredient.unit.toUpperCase()}`;
  165. //Create weekday averages
  166. let dayUse = [0, 0, 0, 0, 0, 0, 0];
  167. let dayCount = [0, 0, 0, 0, 0, 0, 0];
  168. for(let i = 0; i < quantities.length; i++){
  169. dayUse[dates[i].getDay()] += quantities[i];
  170. dayCount[dates[i].getDay()]++;
  171. }
  172. document.getElementById("analDayOne").innerText = `${(dayUse[0] / dayCount[0]).toFixed(2)} ${this.ingredient.unit.toUpperCase()}`;
  173. document.getElementById("analDayTwo").innerText = `${(dayUse[1] / dayCount[1]).toFixed(2)} ${this.ingredient.unit.toUpperCase()}`;
  174. document.getElementById("analDayThree").innerText = `${(dayUse[2] / dayCount[2]).toFixed(2)} ${this.ingredient.unit.toUpperCase()}`;
  175. document.getElementById("analDayFour").innerText = `${(dayUse[3] / dayCount[3]).toFixed(2)} ${this.ingredient.unit.toUpperCase()}`;
  176. document.getElementById("analDayFive").innerText = `${(dayUse[4] / dayCount[4]).toFixed(2)} ${this.ingredient.unit.toUpperCase()}`;
  177. document.getElementById("analDaySix").innerText = `${(dayUse[5] / dayCount[5]).toFixed(2)} ${this.ingredient.unit.toUpperCase()}`;
  178. document.getElementById("analDaySeven").innerText = `${(dayUse[6] / dayCount[6]).toFixed(2)} ${this.ingredient.unit.toUpperCase()}`;
  179. },
  180. displayRecipe: function(){
  181. if(this.recipe === undefined || this.transactionsByDate.length === 0) return;
  182. //break down data into dates and quantities
  183. let dates = [];
  184. let quantities = [];
  185. for(let i = 0; i < this.transactionsByDate.length; i++){
  186. dates.push(this.transactionsByDate[i].date);
  187. let sum = 0;
  188. for(let j = 0; j < this.transactionsByDate[i].transactions.length; j++){
  189. const transaction = this.transactionsByDate[i].transactions[j];
  190. for(let k = 0; k < transaction.recipes.length; k++){
  191. if(transaction.recipes[k].recipe === this.recipe){
  192. sum += transaction.recipes[k].quantity;
  193. }
  194. }
  195. }
  196. quantities.push(sum);
  197. }
  198. //create and display the graph
  199. const trace = {
  200. x: dates,
  201. y: quantities,
  202. mode: "lines+markers",
  203. line: {
  204. color: "rgb(255, 99, 107)"
  205. }
  206. }
  207. const layout = {
  208. title: this.recipe.name.toUpperCase(),
  209. xaxis: {title: "DATE"},
  210. yaxis: {title: "QUANTITY"},
  211. margin: {
  212. l: 40,
  213. r: 10,
  214. b: 20,
  215. t: 30
  216. },
  217. paper_bgcolor: "rgba(0, 0, 0, 0)"
  218. }
  219. Plotly.newPlot("recipeSalesGraph", [trace], layout);
  220. //Display the boxes at the bottom
  221. //Current recipe is stored on the "recipeAvgUse" element
  222. let avg = 0;
  223. for(let i = 0; i < quantities.length; i++){
  224. avg += quantities[i];
  225. }
  226. avg = avg / quantities.length;
  227. document.getElementById("recipeAvgUse").innerText = avg.toFixed(2);
  228. document.getElementById("recipeAvgRevenue").innerText = `$${(avg * this.recipe.price).toFixed(2)}`;
  229. },
  230. switchDisplay: function(){
  231. const checkbox = document.getElementById("analSlider");
  232. let ingredient = document.getElementById("analIngredientContent");
  233. let recipe = document.getElementById("analRecipeContent");
  234. if(checkbox.checked === true){
  235. ingredient.style.display = "none";
  236. recipe.style.display = "flex";
  237. this.displayRecipe();
  238. }else{
  239. ingredient.style.display = "flex";
  240. recipe.style.display = "none";
  241. this.displayIngredient();
  242. }
  243. },
  244. newDates: async function(){
  245. const from = document.getElementById("analStartDate").valueAsDate;
  246. const to = document.getElementById("analEndDate").valueAsDate;
  247. from.setHours(0, 0, 0, 0);
  248. to.setDate(to.getDate() + 1);
  249. to.setHours(0, 0, 0, 0);
  250. await this.getData(from, to);
  251. if(document.getElementById("analSlider").checked === true){
  252. this.displayRecipe();
  253. }else{
  254. this.displayIngredient();
  255. }
  256. }
  257. }
  258. module.exports = analytics;