Lee Morgan 5 лет назад
Родитель
Сommit
5bb414d746

+ 1 - 1
controllers/orderData.js

@@ -96,7 +96,7 @@ module.exports = {
         ingredients: [{
             ingredient: id of the ingredient
             quantity: amount of the ingredient purchased
-            price: price per unit for ingredient
+            price: price for ingredient
         }]
     } 
     */ 

+ 1 - 1
controllers/renderer.js

@@ -93,7 +93,7 @@ module.exports = {
                         merchant: new ObjectId(req.session.user),
                         date: {$gte: firstDay},
                     }},
-                    {$sort: {date: -1}},
+                    {$sort: {date: 1}},
                     {$project: {
                         date: 1,
                         recipes: 1

+ 28 - 0
controllers/transactionData.js

@@ -110,6 +110,34 @@ module.exports = {
             });
     },
 
+    getTransactionsByDate: function(req, res){
+        if(!req.session.user){
+            req.session.error = "MUST BE LOGGED IN TO DO THAT";
+            return res.redirect("/");
+        }
+
+        const from = new Date(req.body.from);
+        let to = new Date(req.body.to);
+        to.setDate(to.getDate() + 1);
+
+        Transaction.aggregate([
+            {$match: {
+                merchant: ObjectId(req.session.user),
+                date: {
+                    $gte: from,
+                    $lt: to
+                }
+            }},
+            {$sort: {date: 1}}
+        ])
+            .then((transactions)=>{
+                return res.json(transactions);
+            })
+            .catch((err)=>{
+                return res.json("ERROR: UNABLE TO RETRIEVE YOUR DATA");
+            });
+    },
+
     /*
     GET - Creates 5000 transactions for logged in merchant for testing
     */

+ 1 - 0
routes.js

@@ -44,6 +44,7 @@ module.exports = function(app){
     app.post("/transaction", transactionData.getTransactions);
     app.post("/transaction/create", transactionData.createTransaction);
     app.delete("/transaction/:id", transactionData.remove);
+    app.post("/transaction/retrieve", transactionData.getTransactionsByDate);
     app.get("/populatesometransactions", transactionData.populate);
 
     //Other

+ 138 - 0
views/dashboardPage/dashboard.css

@@ -40,6 +40,7 @@ Multi-strand use classes
         padding: 10px 50px 0 50px;
         align-items: center;
         box-sizing: border-box;
+        height: 10%;
     }
 
         .strandTitle{
@@ -435,6 +436,143 @@ Recipe Book Strand
         overflow-y: auto;
     }
 
+/*
+Analytics Strand
+*/
+.switch{
+    position: relative;
+    display: inline-block;
+    width: 239px;
+    height: 34px;
+    border: 3px solid rgb(0, 27, 45);
+    color: white;
+}
+
+    .switch input{
+        opacity: 0;
+        width: 0;
+        height: 0;
+    }
+
+        .switch input:focus + .slider{
+            box-shadow: 0 0 1px #2196F3;
+        }
+
+        .switch input:checked + .slider:before {
+            -webkit-transform: translateX(105px);
+            -ms-transform: translateX(92px);
+            transform: translateX(125px);
+        }
+
+    .slider{
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        position: absolute;
+        cursor: pointer;
+        top: 0;
+        left: 0;
+        right: 0;
+        bottom: 0;
+        padding: 0 24px 0 5px;
+        background: rgb(255, 99, 107);
+        transition: 0.2s;
+    }
+
+        .slider:before{
+            position: absolute;
+            content: "";
+            height: 30px;
+            width: 110px;
+            left: 1px;
+            bottom: 1px;
+            background: rgba(255, 255, 255, 0.6);
+            border: 1px solid rgb(0, 27, 45);
+            transition: 0.2s;
+        }
+
+    
+
+.analContent{
+    display: flex;
+    justify-content: space-around;
+    align-items:center;
+    padding: 15px;
+    box-sizing: border-box;
+    height: 90%;
+}
+
+    .itemsList{
+        list-style-type: none;
+        width: 15%;
+        max-height: 95%;
+        overflow-y: auto;
+    }
+
+        .itemButton{
+            background: rgb(0, 27, 45);
+            color: white;
+            border-radius: 5px;
+            padding: 5px;
+            margin: 2px;
+            width: 90%;
+            text-align: center;
+            cursor: pointer;
+        }
+
+            .itemButton:hover{
+                background: rgb(201, 201, 201);
+                color: black;
+            }
+
+        .analItemActive{
+            background: rgb(179, 191, 209);
+            cursor: default;
+        }
+
+    .analData{
+        display: flex;
+        flex-direction: column;
+        justify-content: space-around;
+        width: 100%;
+        height: 100%;
+    }
+
+        #itemUseGraph{
+            padding: 10px;
+            height: 60%;
+            flex-basis: 100px;
+            flex-grow: 5;
+        }
+
+        .dataRow{
+            display: flex;
+            justify-content: space-between;
+            flex-basis: 150px;
+            flex-grow: 0;
+        }
+
+            .analCard{
+                display: flex;
+                flex-direction: column;
+                justify-content: space-around;
+                margin: 5px;
+                height: inherit;
+            }
+
+                .analCard > *{
+                    margin: 0;
+                }
+
+#analRecipeContent{
+    display: flex;
+    justify-content: space-around;
+    align-items: center;
+    padding: 15px;
+    box-sizing: border-box;
+    height: 90%;
+}
+
 /* 
 Orders Strand 
 */

+ 129 - 9
views/dashboardPage/dashboard.ejs

@@ -9,6 +9,7 @@
         <link rel="stylesheet" href="/dashboardPage/dashboard.css">
         <link rel="stylesheet" href="/dashboardPage/sidebars/sidebars.css">
         <link href="https://fonts.googleapis.com/css?family=Saira&display=swap" rel="stylesheet"> 
+        <script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
     </head>
     <body>
         <div id="menu" class="menu">
@@ -29,7 +30,7 @@
                 <button id="menuShifter2" onclick="controller.changeMenu()">&#8801;</button>
             </div>
         
-            <button class="menuButton active" id="homeBtn" class="active" onclick="controller.openStrand('home')">
+            <button class="menuButton active" id="homeBtn" onclick="controller.openStrand('home')">
                 <svg width="25" height="25" viewBox="0 0 24 24" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                     <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
                     <polyline points="9 22 9 12 15 12 15 22"></polyline>
@@ -53,6 +54,14 @@
                 </svg>
                 <p>RECIPE BOOK</p>
             </button>
+
+            <button class="menuButton" id="analyticsBtn" onclick="controller.openStrand('analytics')">
+                <svg width="25" height="25" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+                    <polyline points="23 6 13.5 15.5 8.5 10.5 1 18"></polyline>
+                    <polyline points="17 6 23 6 23 12"></polyline>
+                </svg>
+                <p>ANALYTICS</p>
+            </button>
         
             <button class="menuButton" id="ordersBtn" onclick="controller.openStrand('orders')">
                 <svg width="25" height="25" viewBox="0 0 24 24" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
@@ -110,9 +119,7 @@
                         </div>
                     </div>
 
-                    <div id="graphCard" class="card">
-                        <canvas id="graphCanvas">
-                    </div>
+                    <div id="graphCard" class="card"></div>
                 </div>
 
                 <div class="flexRow">
@@ -124,11 +131,7 @@
                         <button id="inventoryCheck" class="button">Update</button>
                     </div>
 
-                    <div id=popularIngredientsCard class="card">
-                        <p>Most Popular Ingredients (month)</p>
-
-                        <canvas id="popularCanvas"></canvas>
-                    </div>
+                    <div id=popularIngredientsCard class="card"></div>
                 </div>
 
                 <template id="ingredientCheck">
@@ -246,6 +249,123 @@
                 </template>
             </div>
 
+            <div id="analyticsStrand" class="strand">
+                <div class="strandHead">
+                    <h1 class="strandTitle">ANALYTICS</h1>
+
+                    <label class="switch">
+                        <input id="analSlider" type="checkbox">
+                        <span class="slider"><p>INGREDIENTS</p><p>RECIPES</p></span>
+                    </label>
+
+                    <div>
+                        <label>FROM:
+                            <input id="analStartDate" type="date">
+                        </label>
+    
+                        <label>TO:
+                            <input id="analEndDate" type="date">
+                        </label>
+    
+                        <button id="analDateBtn" class="button">SUBMIT</button>
+                    </div>
+                </div>
+
+                <div id="analIngredientContent" class="analContent">
+                    <ul id="itemsList" class="itemsList"></ul>
+
+                    <div class="analData">
+                        <div id="itemUseGraph" class="card analCard"></div>
+
+                        <div class="dataRow">
+                            <div class="card analCard">
+                                <p>MINIMUM DAILY USE</p>
+
+                                <h1 id="analMinUse"></h1>
+                            </div>
+
+                            <div class="card analCard">
+                                <p>AVERAGE DAILY USE</p>
+
+                                <h1 id="analAvgUse"></h1>
+                            </div>
+
+                            <div class="card analCard">
+                                <p>MAXIMUM DAILY USE</p>
+
+                                <h1 id="analMaxUse"></h1>
+                            </div>
+                        </div>
+
+                        <div class="dataRow">
+                            <div class="card analCard">
+                                <p>Sunday Average</p>
+
+                                <h1 id="analDayOne"></h1>
+                            </div>
+
+                            <div class="card analCard">
+                                <p>Monday Average</p>
+
+                                <h1 id="analDayTwo"></h1>
+                            </div>
+
+                            <div class="card analCard">
+                                <p>Tuesday Average</p>
+
+                                <h1 id="analDayThree"></h1>
+                            </div>
+
+                            <div class="card analCard">
+                                <p>Wednesday Average</p>
+
+                                <h1 id="analDayFour"></h1>
+                            </div>
+
+                            <div class="card analCard">
+                                <p>Thursday Average</p>
+
+                                <h1 id="analDayFive"></h1>
+                            </div>
+
+                            <div class="card analCard">
+                                <p>Friday Average</p>
+
+                                <h1 id="analDaySix"></h1>
+                            </div>
+
+                            <div class="card analCard">
+                                <p>Saturday Average</p>
+
+                                <h1 id="analDaySeven"></h1>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+
+                <div id="analRecipeContent">
+                    <ul id="analRecipeList" class="itemsList"></ul>
+
+                    <div class="analData">
+                        <div id="recipeSalesGraph" class="card analCard"></div>
+
+                        <div class="dataRow">
+                            <div class="card analCard">
+                                <p>Average Daily Sales</p>
+
+                                <h1 id="recipeAvgUse"></h1>
+                            </div>
+
+                            <div class="card analCard">
+                                <p>Average Daily Revenue</p>
+
+                                <h1 id="recipeAvgRevenue"></h1>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+
             <div id="ordersStrand" class="strand">
                 <div class="strandHead">
                     <h1 class="strandTitle">ORDERS</h1>

+ 0 - 63
views/dashboardPage/js/Merchant.js

@@ -201,39 +201,6 @@ class Merchant{
         controller.closeSidebar();
     }
 
-    /*
-    Gets the indices of two dates from transactions
-    Inputs
-    from: starting date
-    to: ending date (default to now)
-    Output
-    Array containing starting index and ending index
-    Note: Will return false if it cannot find both necessary dates
-    */
-    transactionIndices(from, to = new Date()){
-        let indices = [];
-
-        for(let i = 0; i < this.transactions.length; i++){
-            if(this.transactions[i].date > from){
-                indices.push(i);
-                break;
-            }
-        }
-
-        for(let i = this.transactions.length - 1; i >=0; i--){
-            if(this.transactions[i].date < to){
-                indices.push(i);
-                break;
-            }
-        }
-
-        if(indices.length < 2){
-            return false;
-        }
-
-        return indices;
-    }
-
     revenue(indices){
         let total = 0;
 
@@ -344,36 +311,6 @@ class Merchant{
 
         return recipeList;
     }
-
-    /*
-    Create revenue data for graphing
-    Input:
-        dateRange: [start index, end index] (this.transactionIndices)
-    Return:
-        [total revenue for each day]
-    */
-    graphDailyRevenue(dateRange){
-        if(!dateRange){
-            return false;
-        }
-
-        let dataList = new Array(30).fill(0);
-        let currentDate = this.transactions[dateRange[0]].date;
-        let arrayIndex = 0;
-
-        for(let i = dateRange[0]; i <= dateRange[1]; i++){
-            if(this.transactions[i].date.getDate() !== currentDate.getDate()){
-                currentDate = this.transactions[i].date;
-                arrayIndex++;
-            }
-
-            for(let j = 0; j < this.transactions[i].recipes.length; j++){
-                dataList[arrayIndex] += (this.transactions[i].recipes[j].recipe.price / 100) * this.transactions[i].recipes[j].quantity;
-            }
-        }
-
-        return dataList;
-    }
     
     /*
     Groups all of the merchant's ingredients by their category

+ 2 - 0
views/dashboardPage/js/Order.js

@@ -14,6 +14,8 @@ class Order{
                         quantity: ingredients[i].quantity,
                         price: ingredients[i].price
                     });
+
+                    break;
                 }
             }
         }

+ 308 - 0
views/dashboardPage/js/analytics.js

@@ -0,0 +1,308 @@
+let analytics = {
+    dateChange: false,
+    transactions: [],
+    ingredient: {},
+    recipe: {},
+
+    display: function(Transaction){
+        document.getElementById("analDateBtn").onclick = ()=>{this.changeDates(Transaction)};
+
+        if(this.transactions.length === 0){
+            let startDate = new Date();
+            startDate.setMonth(startDate.getMonth() - 1);
+            const dateIndices = controller.transactionIndices(merchant.transactions, startDate);
+
+            this.transactions = merchant.transactions.slice(dateIndices[0], dateIndices[1]);
+        }
+
+        let slider = document.getElementById("analSlider");
+        slider.onchange = ()=>{this.display(Transaction)};
+
+        let ingredientContent = document.getElementById("analIngredientContent");
+        let recipeContent = document.getElementById("analRecipeContent");
+
+        if(slider.checked){
+            ingredientContent.style.display = "none";
+            recipeContent.style.display = "flex";
+            this.displayRecipes();
+        }else{
+            ingredientContent.style.display = "flex";
+            recipeContent.style.display = "none"
+            this.displayIngredients();
+        }
+    },
+
+    displayIngredients: function(){
+        const itemsList = document.getElementById("itemsList");
+
+        while(itemsList.children.length > 0){
+            itemsList.removeChild(itemsList.firstChild);
+        }
+
+        for(let i = 0; i < merchant.ingredients.length; i++){
+            let li = document.createElement("li");
+            li.classList.add("itemButton");
+            li.item = merchant.ingredients[i];
+            li.innerText = merchant.ingredients[i].ingredient.name;
+            li.onclick = ()=>{
+                const itemsList = document.getElementById("itemsList");
+                for(let i = 0; i < itemsList.children.length; i++){
+                    itemsList.children[i].classList.remove("analItemActive");
+                }
+
+                li.classList.add("analItemActive");
+
+                this.ingredient = merchant.ingredients[i];
+                this.ingredientDisplay();
+            };
+            itemsList.appendChild(li);
+        }
+
+        if(this.dateChange && Object.keys(this.ingredient).length !== 0){
+            this.ingredientDisplay();
+        }
+        this.dateChange = false;
+    },
+
+    displayRecipes: function(){
+        let recipeList = document.getElementById("analRecipeList");
+        while(recipeList.children.length > 0){
+            recipeList.removeChild(recipeList.firstChild);
+        }
+
+        for(let i = 0; i < merchant.recipes.length; i++){
+            let li = document.createElement("li");
+            li.classList.add("itemButton");
+            li.recipe = merchant.recipes[i];
+            li.innerText = merchant.recipes[i].name;
+            li.onclick = ()=>{
+                let recipeList = document.getElementById("analRecipeList");
+                for(let i = 0; i < recipeList.children.length; i++){
+                    recipeList.children[i].classList.remove("analItemActive");
+                }
+                li.classList.add("analItemActive");
+
+                this.recipe = merchant.recipes[i];
+                this.recipeDisplay();
+            }
+
+            recipeList.appendChild(li);
+        }
+
+        if(this.dateChange  && Object.keys(this.recipe).length !== 0){
+            this.recipeDisplay();
+        }
+        this.dateChange = false;
+    },
+
+    ingredientDisplay: function(){
+        //Get list of recipes that contain the ingredient
+        let containingRecipes = [];
+
+        for(let i = 0; i < merchant.recipes.length; i++){
+            for(let j = 0; j < merchant.recipes[i].ingredients.length; j++){
+                if(merchant.recipes[i].ingredients[j].ingredient === this.ingredient.ingredient){
+                    containingRecipes.push({
+                        recipe: merchant.recipes[i],
+                        quantity: merchant.recipes[i].ingredients[j].quantity
+                    });
+
+                    break;
+                }
+            }
+        }
+
+        //Create Graph
+        let quantities = [];
+        let dates = [];
+        let currentDate = this.transactions[0].date;
+        let currentQuantity = 0;
+
+        for(let i = 0; i < this.transactions.length; i++){
+            if(currentDate.getDate() !== this.transactions[i].date.getDate()){
+                quantities.push(this.ingredient.ingredient.convert(currentQuantity));
+                dates.push(currentDate);
+                currentQuantity = 0;
+                currentDate = this.transactions[i].date;
+            }
+
+            for(let j = 0; j < this.transactions[i].recipes.length; j++){
+                for(let k = 0; k < containingRecipes.length; k++){
+                    if(this.transactions[i].recipes[j].recipe === containingRecipes[k].recipe){
+                        for(let l = 0; l < this.transactions[i].recipes[j].recipe.ingredients.length; l++){
+                            const transIngredient = this.transactions[i].recipes[j].recipe.ingredients[l];
+
+                            if(transIngredient.ingredient === this.ingredient.ingredient){
+                                currentQuantity += transIngredient.quantity * this.transactions[i].recipes[j].quantity;
+
+                                break;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        let trace = {
+            x: dates,
+            y: quantities,
+            mode: "lines+markers",
+            line: {
+                color: "rgb(255, 99, 107)"
+            }
+        }
+
+        const layout = {
+            title: this.ingredient.ingredient.name.toUpperCase(),
+            xaxis: {
+                title: "DATE"
+            },
+            yaxis: {
+                title: `QUANTITY (${this.ingredient.ingredient.unit.toUpperCase()})`,
+            }
+        }
+
+        Plotly.newPlot("itemUseGraph", [trace], layout);
+
+        //Create use cards
+        let sum = 0;
+        let max = 0;
+        let min = quantities[0];
+        for(let i = 0; i < quantities.length; i++){
+            sum += quantities[i];
+            if(quantities[i] > max){
+                max = quantities[i];
+            }else if(quantities[i] < min){
+                min = quantities[i];
+            }
+        }
+        document.getElementById("analMinUse").innerText = `${min.toFixed(2)} ${this.ingredient.ingredient.unit}`;
+        document.getElementById("analAvgUse").innerText = `${(sum / quantities.length).toFixed(2)} ${this.ingredient.ingredient.unit}`;        
+        document.getElementById("analMaxUse").innerText = `${max.toFixed(2)} ${this.ingredient.ingredient.unit}`;
+
+        let dayUse = [0, 0, 0, 0, 0, 0, 0];
+        let dayCount = [0, 0, 0, 0, 0, 0, 0];
+        for(let i = 0; i < quantities.length; i++){
+            dayUse[dates[i].getDay()] += quantities[i];
+            dayCount[dates[i].getDay()]++;
+        }
+
+        document.getElementById("analDayOne").innerText = `${(dayUse[0] / dayCount[0]).toFixed(2)} ${this.ingredient.ingredient.unit}`;
+        document.getElementById("analDayTwo").innerText = `${(dayUse[1] / dayCount[1]).toFixed(2)} ${this.ingredient.ingredient.unit}`;
+        document.getElementById("analDayThree").innerText = `${(dayUse[2] / dayCount[2]).toFixed(2)} ${this.ingredient.ingredient.unit}`;
+        document.getElementById("analDayFour").innerText = `${(dayUse[3] / dayCount[3]).toFixed(2)} ${this.ingredient.ingredient.unit}`;
+        document.getElementById("analDayFive").innerText = `${(dayUse[4] / dayCount[4]).toFixed(2)} ${this.ingredient.ingredient.unit}`;
+        document.getElementById("analDaySix").innerText = `${(dayUse[5] / dayCount[5]).toFixed(2)} ${this.ingredient.ingredient.unit}`;
+        document.getElementById("analDaySeven").innerText = `${(dayUse[6] / dayCount[6]).toFixed(2)} ${this.ingredient.ingredient.unit}`;
+    },
+
+    recipeDisplay: function(){
+        let quantities = [];
+        let dates = [];
+        let currentDate = this.transactions[0].date;
+        let quantity = 0;
+
+        for(let i = 0; i < this.transactions.length; i++){
+            if(currentDate.getDate() !== this.transactions[i].date.getDate()){
+                quantities.push(quantity);
+                quantity = 0;
+                dates.push(currentDate);
+                currentDate = this.transactions[i].date;
+            }
+
+            for(let j = 0; j < this.transactions[i].recipes.length; j++){
+                const recipe = this.transactions[i].recipes[j];
+
+                if(recipe.recipe === this.recipe){
+                    quantity += recipe.quantity;
+                }
+            }
+        }
+
+        const trace = {
+            x: dates,
+            y: quantities,
+            mode: "lines+markers",
+            line: {
+                color: "rgb(255, 99, 107"
+            }
+        }
+
+        const layout = {
+            title: this.recipe.name.toUpperCase(),
+            xaxis: {
+                title: "DATE"
+            },
+            yaxis: {
+                title: "Quantity"
+            }
+        }
+
+        Plotly.newPlot("recipeSalesGraph", [trace], layout);
+
+        let sum = 0;
+        for(let i = 0; i < quantities.length; i++){
+            sum += quantities[i];
+        }
+
+        document.getElementById("recipeAvgUse").innerText = (sum / quantities.length).toFixed(2);
+        document.getElementById("recipeAvgRevenue").innerText = `$${(((sum / quantities.length) * this.recipe.price) / 100).toFixed(2)}`;
+    },
+
+    changeDates: function(Transaction){
+        let dates = {
+            from: document.getElementById("analStartDate").valueAsDate,
+            to: document.getElementById("analEndDate").valueAsDate
+        }
+
+        if(dates.from > dates.to || dates.from === "" || dates.to === "" || dates.to > new Date()){
+            banner.createError("INVALID DATE");
+            return;
+        }
+
+        let loader = document.getElementById("loaderContainer");
+        loader.style.display = "flex";
+
+        fetch("/transaction/retrieve", {
+            method: "post",
+            headers: {
+                "Content-Type": "application/json;charset=utf-8"
+            },
+            body: JSON.stringify(dates)
+        })
+            .then((response)=>response.json())
+            .then((response)=>{
+                if(typeof(response) === "string"){
+                    banner.createError(response.data);
+                }else{
+                    this.transactions = [];
+
+                    for(let i = 0; i < response.length; i++){
+                        this.transactions.push(new Transaction(
+                            response[i]._id,
+                            new Date(response[i].date),
+                            response[i].recipes,
+                            merchant
+                        ));
+                    }
+
+                    let isRecipe = document.getElementById("analSlider").checked;
+                    if(isRecipe && Object.keys(this.recipe).length !== 0){
+                        this.recipeDisplay();
+                    }else if(!isRecipe && Object.keys(this.ingredient).length !== 0){
+                        this.ingredientDisplay();
+                    }
+                    
+                    this.dateChange = true;
+                }
+            })
+            .catch((err)=>{
+                banner.createError("ERROR: UNABLE TO DISPLAY THE DATA");
+            })
+            .finally(()=>{
+                loader.style.display = "none";
+            });
+    }
+}
+
+module.exports = analytics;

+ 40 - 0
views/dashboardPage/js/dashboard.js

@@ -1,6 +1,7 @@
 const home = require("./home.js");
 const ingredients = require("./ingredients.js");
 const recipeBook = require("./recipeBook.js");
+const analytics = require("./analytics.js");
 const orders = require("./orders.js");
 const transactions = require("./transactions.js");
 
@@ -53,6 +54,11 @@ controller = {
                 document.getElementById("recipeBookStrand").style.display = "flex";
                 recipeBook.display(Recipe);
                 break;
+            case "analytics":
+                activeButton = document.getElementById("analyticsBtn");
+                document.getElementById("analyticsStrand").style.display = "flex";
+                analytics.display(Transaction);
+                break;
             case "orders":
                 activeButton = document.getElementById("ordersBtn");
                 document.getElementById("ordersStrand").style.display = "flex";
@@ -236,6 +242,40 @@ controller = {
                 transactions.display(Transaction);
                 break;
         }
+    },
+
+    /*
+    Gets the indices of two dates from transactions
+    Inputs
+    transactions: transaction list to find indices on
+    from: starting date
+    to: ending date (default to now)
+    Output
+    Array containing starting index and ending index
+    Note: Will return false if it cannot find both necessary dates
+    */
+    transactionIndices(transactions, from, to = new Date()){
+        let indices = [];
+
+        for(let i = 0; i < transactions.length; i++){
+            if(transactions[i].date > from){
+                indices.push(i);
+                break;
+            }
+        }
+
+        for(let i = transactions.length - 1; i >=0; i--){
+            if(transactions[i].date < to){
+                indices.push(i);
+                break;
+            }
+        }
+
+        if(indices.length < 2){
+            return false;
+        }
+
+        return indices;
     }
 }
 

+ 80 - 61
views/dashboardPage/js/home.js

@@ -1,6 +1,5 @@
 let home = {
     isPopulated: false,
-    graph: {},
 
     display: function(){
         if(!this.isPopulated){
@@ -17,10 +16,10 @@ let home = {
         let today = new Date();
         let firstOfMonth = new Date(today.getFullYear(), today.getMonth(), 1);
         let firstOfLastMonth = new Date(today.getFullYear(), today.getMonth() - 1, 1);
-        let lastMonthtoDay = new Date(new Date().setMonth(today.getMonth() - 1));
+        let lastMonthToDay = new Date(new Date().setMonth(today.getMonth() - 1));
 
-        let revenueThisMonth = merchant.revenue(merchant.transactionIndices(firstOfMonth));
-        let revenueLastmonthToDay = merchant.revenue(merchant.transactionIndices(firstOfLastMonth, lastMonthtoDay));
+        let revenueThisMonth = merchant.revenue(controller.transactionIndices(merchant.transactions, firstOfMonth));
+        let revenueLastmonthToDay = merchant.revenue(controller.transactionIndices(merchant.transactions, firstOfLastMonth, lastMonthToDay));
 
         document.getElementById("revenue").innerText = `$${revenueThisMonth.toLocaleString("en")}`;
 
@@ -37,34 +36,50 @@ let home = {
     },
 
     drawRevenueGraph: function(){
-        let graphCanvas = document.getElementById("graphCanvas");
-        let today = new Date();
+        let monthAgo = new Date();
+        monthAgo.setMonth(monthAgo.getMonth() - 1);
+        
+        let dateIndices = controller.transactionIndices(merchant.transactions, monthAgo);
+
+        let revenue = [];
+        let dates = [];
+        let dayRevenue = 0;
+        let currentDate = merchant.transactions[dateIndices[0]].date;
+        for(let i = dateIndices[0]; i < dateIndices[1]; i++){
+            if(merchant.transactions[i].date.getDate() !== currentDate.getDate()){
+                revenue.push(dayRevenue / 100);
+                dayRevenue = 0;
+                dates.push(currentDate);
+                currentDate = merchant.transactions[i].date;
+            }
 
-        graphCanvas.height = graphCanvas.parentElement.clientHeight;
-        graphCanvas.width = graphCanvas.parentElement.clientWidth;
+            for(let j = 0; j < merchant.transactions[i].recipes.length; j++){
+                const recipe = merchant.transactions[i].recipes[j];
 
-        let LineGraph = require("../../shared/graphs.js").LineGraph;
-        this.graph = new LineGraph(graphCanvas);
-        this.graph.addTitle("Revenue");
+                dayRevenue += recipe.recipe.price * recipe.quantity;
+            }
+        }
 
-        let thirtyAgo = new Date(today);
-        thirtyAgo.setDate(today.getDate() - 29);
+        const trace = {
+            x: dates,
+            y: revenue,
+            mode: "lines+markers",
+            line: {
+                color: "rgb(255, 99, 107)"
+            }
+        }
 
-        let data = merchant.graphDailyRevenue(merchant.transactionIndices(thirtyAgo));
-        if(data){
-            this.graph.addData(
-                data,
-                [thirtyAgo, new Date()],
-                "Revenue"
-            );
-        }else{
-            document.getElementById("graphCanvas").style.display = "none";
-            
-            let notice = document.createElement("h1");
-            notice.innerText = "NO DATA YET";
-            notice.classList = "notice";
-            document.getElementById("graphCard").appendChild(notice);
+        const layout = {
+            title: "REVENUE",
+            xaxis: {
+                title: "DATE"
+            },
+            yaxis: {
+                title: "$"
+            }
         }
+
+        Plotly.newPlot("graphCard", [trace], layout);
     },
 
     drawInventoryCheckCard: function(){
@@ -109,44 +124,50 @@ let home = {
     },
 
     drawPopularCard: function(){
-        let dataArray = [];
-        let now = new Date();
-        let thisMonth = new Date(now.getFullYear(), now.getMonth(), 1);
+        let thisMonth = new Date();
+        thisMonth.setDate(1);
 
-        let ingredientList = merchant.ingredientsSold(merchant.transactionIndices(thisMonth));
+        let ingredientList = merchant.ingredientsSold(controller.transactionIndices(merchant.transactions, thisMonth));
         if(ingredientList !== false){
-            window.ingredientList = [...ingredientList];
-            let iterations = (ingredientList.length < 5) ? ingredientList.length : 5;
-            for(let i = 0; i < iterations; i++){
-                try{
-                    let max = ingredientList[0].quantity;
-                    let index = 0;
-                    for(let j = 0; j < ingredientList.length; j++){
-                        if(ingredientList[j].quantity > max){
-                            max = ingredientList[j].quantity;
-                            index = j;
-                        }
-                    }
-
-                    dataArray.push({
-                        num: max,
-                        label: ingredientList[index].ingredient.name + ": " +
-                        ingredientList[index].ingredient.convert(ingredientList[index].quantity).toFixed(2) +
-                        " " + ingredientList[index].ingredient.unit
-                    });
-                    ingredientList.splice(index, 1);
-                }catch(err){
-                    break;
+            ingredientList.sort((a, b) => a.quantity < b.quantity);
+
+            let quantities = [];
+            let names = [];
+            let labels = [];
+            let colors = [];
+            for(let i = 4; i >= 0; i--){
+                quantities.push(ingredientList[i].quantity);
+                names.push(ingredientList[i].ingredient.name.toUpperCase());
+                labels.push(`${ingredientList[i].ingredient.convert(ingredientList[i].quantity).toFixed(2)} ${ingredientList[i].ingredient.unit.toUpperCase()}`);
+                if(i === 0){
+                    colors.push("rgb(255, 99, 107");
+                }else{
+                    colors.push("rgb(179, 191, 209");
                 }
             }
 
-            let thisCanvas = document.getElementById("popularCanvas");
-            thisCanvas.width = thisCanvas.parentElement.offsetWidth * 0.8;
-            thisCanvas.height = thisCanvas.parentElement.offsetHeight * 0.8;
+            let trace = {
+                x: quantities,
+                y: names,
+                type: "bar",
+                orientation: "h",
+                text: labels,
+                textposition: "auto",
+                hoverinfo: "none",
+                marker: {
+                    color: colors
+                }
+            }
 
-            let HorizontalBarGraph = require("../../shared/graphs.js").HorizontalBarGraph;
-            let popularGraph = new HorizontalBarGraph(thisCanvas);
-            popularGraph.addData(dataArray);
+            let layout = {
+                title: "MOST POPULAR INGREDIENTS",
+                xaxis: {
+                    zeroline: false,
+                    title: "QUANTITY"
+                }
+            }
+            
+            Plotly.newPlot("popularIngredientsCard", [trace], layout);
         }else{
             document.getElementById("popularCanvas").style.display = "none";
 
@@ -203,8 +224,6 @@ let home = {
                     if(typeof(response) === "string"){
                         banner.createError(response);
                     }else{
-                        
-
                         merchant.editIngredients(changes);
                         banner.createNotification("INGREDIENTS UPDATED");
                     }

+ 1 - 2
views/dashboardPage/js/ingredientDetails.js

@@ -27,7 +27,7 @@ let ingredientDetails = {
         for(let i = 1; i < 31; i++){
             let endDay = new Date(now.getFullYear(), now.getMonth(), now.getDate() - i)
             let startDay = new Date(now.getFullYear(), now.getMonth(), now.getDate() - i - 1);
-            let indices = merchant.transactionIndices(startDay, endDay);
+            let indices = controller.transactionIndices(merchant.transactions, startDay, endDay);
 
             if(indices === false){
                 quantities.push(0);
@@ -194,7 +194,6 @@ let ingredientDetails = {
                 }
             })
             .catch((err)=>{
-                console.log(err);
                 banner.createError("SOMETHING WENT WRONG. PLEASE REFRESH THE PAGE");
             })
             .finally(()=>{

+ 70 - 110
views/dashboardPage/js/newOrder.js

@@ -1,158 +1,118 @@
 let newOrder = {
-    isPopulated: false,
-    unused: [],
-
     display: function(Order){
-        if(!this.isPopulated){
-            let categories = merchant.categorizeIngredients();
-            let categoriesList = document.getElementById("newOrderCategories");
-            let template = document.getElementById("addIngredientsCategory").content.children[0];
-            let ingredientTemplate = document.getElementById("newOrderIngredient").content.children[0];
-    
-            for(let i = 0; i < categories.length; i++){
-                let category = template.cloneNode(true);
-    
-                category.children[0].children[0].innerText = categories[i].name;
-                category.children[0].children[1].onclick = ()=>{this.toggleAddIngredient(category)};
-                category.children[0].children[1].children[1].style.display = "none";
-                category.children[1].style.display = "none";
-                
-                categoriesList.appendChild(category);
-    
-                for(let j = 0; j < categories[i].ingredients.length; j++){
-                    let ingredientDiv = ingredientTemplate.cloneNode(true);
-    
-                    ingredientDiv.children[0].children[0].innerText = categories[i].ingredients[j].ingredient.name;
-                    ingredientDiv.children[0].children[1].onclick = ()=>{this.addOne(ingredientDiv, category.children[1])};
-                    ingredientDiv.ingredient = categories[i].ingredients[j].ingredient;
-    
-                    this.unused.push(categories[i].ingredients[j]);
-                    category.children[1].appendChild(ingredientDiv);
-                }
-            }
-
-            document.getElementById("submitNewOrder").onclick = ()=>{this.submit(Order)};
+        document.getElementById("sidebarDiv").classList.add("sidebarWide");
+        document.getElementById("newOrderIngredientList").style.display = "flex";
 
-            this.isPopulated = true;
+        let selectedList = document.getElementById("selectedIngredientList");
+        while(selectedList.children.length > 0){
+            selectedList.removeChild(selectedList.firstChild);
         }
-    },
 
-    addOne: function(ingredientDiv, container){
-        for(let i = 0; i < this.unused.length; i++){
-            if(this.unused[i] === ingredientDiv){
-                this.unused.splice(i, 1);
-                break;
-            }
+        let ingredientList = document.getElementById("newOrderIngredients");
+        while(ingredientList.children.length > 0){
+            ingredientList.removeChild(ingredientList.firstChild);
         }
 
-        ingredientDiv.children[0].children[1].innerText = "-";
-        ingredientDiv.children[0].children[1].onclick = ()=>{this.removeOne(ingredientDiv, container)};
-        ingredientDiv.children[1].style.display = "flex";
+        for(let i = 0; i < merchant.ingredients.length; i++){
+            let ingredient = document.createElement("button");
+            ingredient.classList = "newOrderIngredient";
+            ingredient.innerText = merchant.ingredients[i].ingredient.name;
+            ingredient.onclick = ()=>{this.addIngredient(merchant.ingredients[i].ingredient, ingredient)};
+            ingredientList.appendChild(ingredient);
+        }
 
-        container.removeChild(ingredientDiv);
-        document.getElementById("newOrderAdded").appendChild(ingredientDiv);
+        document.getElementById("submitNewOrder").onclick = ()=>{this.submit(Order)};
     },
 
-    removeOne: function(ingredientDiv, container){
-        this.unused.push(ingredientDiv.ingredient);
+    addIngredient: function(ingredient, element){
+        element.style.display = "none";
 
-        ingredientDiv.children[1].style.display = "none";
-        ingredientDiv.children[0].children[1].innerText = "+";
-        ingredientDiv.children[0].children[1].onclick = ()=>{this.addOne(ingredientDiv, container)};
-        
-        ingredientDiv.parentElement.removeChild(ingredientDiv);
-        container.appendChild(ingredientDiv);
+        let div = document.getElementById("selectedIngredient").content.children[0].cloneNode(true);
+        div.ingredient = ingredient;
+        div.children[0].children[0].innerText = `${ingredient.name} (${ingredient.unit.toUpperCase()})`;
+        div.children[0].children[1].onclick = ()=>{this.removeIngredient(div, element)};
+        document.getElementById("selectedIngredientList").appendChild(div);
     },
 
-    toggleAddIngredient: function(categoryElement){
-        let button = categoryElement.children[0].children[1];
-        let ingredientDisplay = categoryElement.children[1];
+    removeIngredient: function(selectedElement, element){
+        selectedElement.parentElement.removeChild(selectedElement);
+        element.style.display = "block";
+    },
 
-        if(ingredientDisplay.style.display === "none"){
-            ingredientDisplay.style.display = "flex";
+    submit: function(Order){
+        let date = document.getElementById("newOrderDate").value;
+        let time = document.getElementById("newOrderTime").value;
+        let ingredients = document.getElementById("selectedIngredientList").children;
 
-            button.children[0].style.display = "none";
-            button.children[1].style.display = "block";
-        }else{
-            ingredientDisplay.style.display = "none";
+        if(date === ""){
+            banner.createError("DATE IS REQUIRED FOR ORDERS");
+            return;
+        }
+        
+        if(time !== ""){
+            date = `${date}T${time}`;
+        }
 
-            button.children[0].style.display = "block";
-            button.children[1].style.display = "none";
+        let data = {
+            name: document.getElementById("newOrderName").value,
+            date: date,
+            ingredients: []
         }
-    },
 
-    submit: function(Order){
-        let categoriesList = document.getElementById("newOrderAdded");
-        let ingredients = [];
-
-        for(let i = 0; i < categoriesList.children.length; i++){
-            let quantity = categoriesList.children[i].children[1].children[0].value;
-            let price = categoriesList.children[i].children[1].children[1].value;
-
-            let fakeOrder = new Order(undefined, undefined, new Date(), [], undefined);
-            if(quantity !== ""  && price !== ""){
-                ingredients.push({
-                    ingredient: categoriesList.children[i].ingredient.id,
-                    quantity: controller.convertToMain(categoriesList.children[i].ingredient.unit, parseFloat(quantity)),
-                    price: categoriesList.children[i].ingredient.convert(parseInt(price * 100))
-                });
+        for(let i = 0; i < ingredients.length; i++){
+            let quantity = ingredients[i].children[1].children[0].value;
+            let price = ingredients[i].children[1].children[1].value;
+
+            if(quantity === "" || price === ""){
+                banner.createError("MUST PROVIDE QUANTITY AND PRICE PER UNIT FOR ALL INGREDIENTS");
+                return;
             }
-        }
 
-        let time = document.getElementById("orderTime").value;
-        let date = document.getElementById("orderDate").value;
-        let dateTime = "";
-        if(time === "" && date === ""){
-            dateTime = undefined;
-        }else if(time === "" && date !== ""){
-            dateTime = date;
-        }else if(time !== "" && date === ""){
-            banner.createError("PLEASE ADD A DATE IF YOU WISH TO HAVE A TIME");
-        }else{
-            dateTime = `${date}T${time}:00`
-        }
+            if(quantity < 0 || price < 0){
+                banner.createError("QUANTITY AND PRICE MUST BE NON-NEGATIVE NUMBERS");
+            }
 
-        let data = {
-            name: document.getElementById("orderName").value,
-            date: dateTime,
-            ingredients: ingredients
-        };
+            data.ingredients.push({
+                ingredient: ingredients[i].ingredient.id,
+                quantity: controller.convertToMain(ingredients[i].ingredient.unit, quantity),
+                price: price * 100
+            });
+        }
 
         let loader = document.getElementById("loaderContainer");
         loader.style.display = "flex";
-        
+
         fetch("/order/create", {
-            method: "POST",
+            method: "post",
             headers: {
                 "Content-Type": "application/json;charset=utf-8"
             },
             body: JSON.stringify(data)
         })
-            .then(response => response.json())
+            .then((response)=>response.json())
             .then((response)=>{
                 if(typeof(response) === "string"){
                     banner.createError(response);
                 }else{
                     let order = new Order(
-                       response._id,
-                       response.name,
-                       response.date,
-                       response.ingredients,
-                       merchant 
-                    )
+                        response._id,
+                        response.name,
+                        response.date,
+                        response.ingredients,
+                        merchant
+                    );
 
                     merchant.editOrders([order]);
                     merchant.editIngredients(order.ingredients, false, true);
-                    banner.createNotification("ORDER CREATED");
                 }
             })
             .catch((err)=>{
-                banner.createError("SOEMTHING WENT WRONG. PLEASE REFRESH THE PAGE");
+                banner.createError("SOMETHING WENT WRONG, PLEASE REFRESH THE PAGE");
             })
             .finally(()=>{
                 loader.style.display = "none";
             });
-    },
+    }
 }
 
 module.exports = newOrder;

+ 2 - 5
views/dashboardPage/js/orderDetails.js

@@ -15,15 +15,12 @@ let orderDetails = {
         let grandTotal = 0;
         for(let i = 0; i < order.ingredients.length; i++){
             let ingredientDiv = template.cloneNode(true);
-            let price = (order.ingredients[i].quantity * order.ingredients[i].price) / 100;
+            let price = order.ingredients[i].price / 100;
             grandTotal += price;
 
             let ingredient = order.ingredients[i].ingredient;
-            let priceText = ingredient.convert(order.ingredients[i].quantity).toFixed(2) + " " + 
-                ingredient.unit.toUpperCase() + " x $" +
-                (order.convertPrice(ingredient.unitType, ingredient.unit, order.ingredients[i].price) / 100).toFixed(2);
             ingredientDiv.children[0].innerText = order.ingredients[i].ingredient.name;
-            ingredientDiv.children[1].innerText = priceText;
+            ingredientDiv.children[1].innerText = `${ingredient.convert(order.ingredients[i].quantity).toFixed(2)} ${ingredient.unit.toUpperCase()}`;
             ingredientDiv.children[2].innerText = `$${price.toFixed(2)}`;
 
             ingredientList.appendChild(ingredientDiv);

+ 2 - 2
views/dashboardPage/js/orders.js

@@ -79,11 +79,11 @@ let orders = {
             let totalCost = 0;
             
             for(let j = 0; j < merchant.orders[i].ingredients.length; j++){
-                totalCost += merchant.orders[i].ingredients[j].quantity * merchant.orders[i].ingredients[j].price;
+                totalCost += merchant.orders[i].ingredients[j].price;
             }
 
             row.children[0].innerText = merchant.orders[i].name;
-            row.children[1].innerText = `${merchant.orders[i].ingredients.length} items`;
+            row.children[1].innerText = `${merchant.orders[i].ingredients.length} ingredients`;
             row.children[2].innerText = new Date(merchant.orders[i].date).toLocaleDateString("en-US");
             row.children[3].innerText = `$${(totalCost / 100).toFixed(2)}`;
             row.order = merchant.orders[i];

+ 20 - 22
views/dashboardPage/sidebars/newOrder.ejs

@@ -1,4 +1,4 @@
-<div id="newOrder">
+<div id="newOrderIngredientList">
     <div class="sidebarIconButtons">
         <button class="iconButton" onclick="controller.closeSidebar()">
             <svg width="30" height="30" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
@@ -8,45 +8,43 @@
         </button>
     </div>
 
+    <h1>INGREDIENTS</h1>
+
+    <div id="newOrderIngredients"></div>
+</div>
+
+<div id="newOrder">
     <h1>NEW ORDER</h1>
 
-    <label>ID/NAME:
-        <input id="orderName" type="text">
+    <label>NAME/ID:
+        <input id="newOrderName" type="text">
     </label>
 
-    <label>Date:
-        <input id="orderDate" type="date">
+    <label>DATE:
+        <input id="newOrderDate" type="date">
     </label>
 
-    <label>Time:
-        <input id="orderTime" type="time">
+    <label>TIME:
+        <input id="newOrderTime" type="time">
     </label>
-    
-
-    <div id=newOrderCategories></div>
 
-    <div class="lineBorder"></div>
-
-    <h2>INGREDIENTS</h2>
-
-    <div id="newOrderAdded"></div>
-
-    <div class="lineBorder"></div>
+    <div id="selectedIngredientList"></div>
 
     <button id="submitNewOrder" class="button">CREATE</button>
 
-    <template id="newOrderIngredient">
-        <div class="addIngredientsIngredient">
+    <template id="selectedIngredient">
+        <div class="selectedIngredient">
             <div>
                 <p></p>
 
-                <button class="addButton">+</button>
+                <button class="newOrderRemove">REMOVE</button>
             </div>
+            
 
-            <div style="display: none;">
+            <div>
                 <input type="number" min="0" step="0.01" placeholder="QUANTITY">
 
-                <input type="number" min="0" step="0.01" placeholder="PRICE PER UNIT">
+                <input type="number" min="0" step="0.01" placeholder="TOTAL PRICE">
             </div>
         </div>
     </template>

+ 87 - 11
views/dashboardPage/sidebars/sidebars.css

@@ -147,6 +147,10 @@
     box-shadow: -1px 0px 10px gray;
 }
 
+.sidebarWide{
+    width: 40vw;
+}
+
 .sidebarHide{
     width: 0;
     transition: width 0.2s;
@@ -429,28 +433,100 @@ Add Recipe
     }
 
 /* New Order */
+#newOrderIngredientList{
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    width: 40%;
+}
+
+    #newOrderIngredients{
+        display: flex;
+        flex-direction: column;
+        width: 90%;
+        overflow-y: auto;
+    }
+
+        .newOrderIngredient{
+            background: rgb(0, 27, 45);
+            color: white;
+            font-size: 17px;
+            font-weight: bold;
+            border: none;
+            border-radius: 5px;
+            padding: 5px;
+            margin: 1px 0;
+            cursor: pointer;
+        }
+
+            .newOrderIngredient:hover{
+                background: rgb(201, 201, 201);
+                color: black;
+            }
+
 #newOrder{
     display: flex;
     flex-direction: column;
     align-items: center;
-    width: 100%;
+    width: 60%;
+    border-left: 1px solid black;
+    padding-left: 25px;
 }
 
-    #newOrderCategories{
+    #newOrder > label{
+        margin: 5px;
+    }
+
+    #selectedIngredientList{
         display: flex;
         flex-direction: column;
         align-items: center;
-        width: 100%;
-        margin-bottom: 25px;
-        max-height: 25%;
-        overflow: auto;
+        height: 75%;
+        overflow-y: auto;
+        margin-bottom: 10px;
     }
 
-    #newOrderAdded{
-        max-height: 25%;
-        width: 100%;
-        overflow: auto;
-    }
+        .selectedIngredient{
+            display: flex;
+            flex-direction: column;
+            background: rgb(0, 27, 45);
+            color: white;
+            border-radius: 5px;
+            margin: 5px 0;
+            width: 95%;
+        }
+
+            .selectedIngredient > div{
+                margin: 3px;
+                display: flex;
+                justify-content: space-between;
+                align-items: center
+            }
+
+                .selectedIngredient p{
+                    font-weight: bold;
+                    font-size: 17px;
+                }
+
+                .newOrderRemove{
+                    background: rgb(0, 27, 45);
+                    border: 1px solid white;
+                    color: white;
+                    border-radius: 5px;
+                    font-size: 13px;
+                    font-weight: bold;
+                    padding: 5px;
+                    cursor: pointer;
+                }
+
+                    .newOrderRemove:hover{
+                        background: rgb(201, 201, 201);
+                        color: black;
+                    }
+
+                .selectedIngredient input{
+                    width: 45%;
+                }
 
 /* New Ingredient */
 #newIngredient{

+ 0 - 289
views/shared/graphs.js

@@ -1,289 +0,0 @@
-//Creates a line graph within a canvas
-//Will expand or shrink to the size of the canvas
-//Inputs:
-//  canvas = the canvas that you would like to draw on
-//  yName = a string for the name of the Y axis
-//  xName = a string for the name of the X axis
-class LineGraph{
-    constructor(canvas){
-        this.canvas = canvas;
-        this.context = canvas.getContext("2d");
-        this.left = canvas.clientWidth - (canvas.clientWidth * 0.95);
-        this.right = canvas.clientWidth * 1;
-        this.top = canvas.clientHeight - (canvas.clientHeight * 1);
-        this.bottom = canvas.clientHeight * 0.85;
-        this.data = [];
-        this.max = 0;
-        this.xRange = [];
-        this.colors = [];
-        this.colorIndex = 0;
-
-        for(let i = 0; i < 100; i++){
-            let redRand = Math.floor(Math.random() * 200);
-            let greenRand = Math.floor(Math.random() * 200);
-            let blueRand = Math.floor(Math.random() * 200);
-
-            this.colors.push(`rgb(${redRand}, ${greenRand}, ${blueRand})`);
-        }
-    }
-
-    //Add a dataset to the graph to draw
-    //Inputs:
-    //  data = array containing list of numbers as the data points for the graph
-    //      data[0] will be on the left.  data[data.length-1] will be on the right.
-    //  xRange = array containing two elements, start and end for x axis data (currently only dates)
-    //  name = string name for the line.  Used for display and finding lines.  Each must be unique
-    addData(data, xRange, name){
-        data = {
-            set: data,
-            colorIndex: this.colorIndex,
-            name: name
-        }
-        this.colorIndex++;
-        this.data.push(data);
-
-        let isChange = false;
-        for(let i = 0; i < data.set.length; i++){
-            if(data.set[i] > this.max){
-                this.max = data.set[i];
-                this.verticalMultiplier = (this.bottom - this.top) / this.max;
-                this.horizontalMultiplier = (this.right - this.left) / (data.set.length - 1);
-                isChange = true;
-            }
-        }
-
-        if(this.xRange.length === 0){
-            this.xRange = xRange;
-            isChange = true;
-        }else{
-            if(xRange[0] < this.xRange[0]){
-                this.xRange[0] = xRange[0];
-                isChange = true;
-            }
-            if(xRange[1] > this.xRange[1]){
-                this.xRange[1] = xRange[1];
-                isChange = true;
-            }
-        }
-
-        if(isChange){
-            this.drawGraph();
-        }else{
-            this.drawLine(data);
-        }
-    }
-
-    //Removes a single data set from the graph and its line
-    //Inputs:
-    //  id = the unique identifier of the data set that was passed in with addData function
-    removeData(name){
-        for(let i = 0; i < this.data.length; i++){
-            if(this.data[i].name === name){
-                this.data.splice(i, 1);
-                break;
-            }
-        }
-
-        this.drawGraph();
-    }
-
-    //Completely clears all data
-    //Does not delete the current graph displaying
-    clearData(){
-        this.max = 0;
-        this.data = [];
-        this.xRange = [];
-    }
-
-    addTitle(title){
-        this.top = this.canvas.clientHeight - (this.canvas.clientHeight * 0.9);
-        
-        this.title = title;
-    }
-
-    /**********
-    *********PRIVATE*********
-    **********/
-    drawGraph(){
-        this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
-        
-        this.drawYAxis();
-        this.drawXAxis();
-
-        for(let i = 0; i < this.data.length; i++){
-            this.drawLine(this.data[i]);
-        }
-
-        if(this.title){
-            this.context.font = "25px Saira";
-            let xLocation = ((this.right - this.left) / 2) - (this.context.measureText(this.title).width / 2);
-            this.context.fillText(this.title, xLocation, this.top - 10);
-        }
-    }
-
-    drawLine(data){
-        for(let i = 0; i < data.set.length - 1; i++){
-            this.context.beginPath();
-            this.context.moveTo(this.left + (this.horizontalMultiplier * i), this.bottom - (this.verticalMultiplier * data.set[i]));
-            this.context.lineTo(this.left + (this.horizontalMultiplier * (i + 1)), this.bottom - (this.verticalMultiplier * data.set[i + 1]));
-            this.context.strokeStyle = this.colors[data.colorIndex];
-            this.context.lineWidth = 2;
-            this.context.stroke();
-        }
-
-        this.context.strokeStyle = "black";
-
-        if(this.data.length > 1){
-            this.drawLegend(data.colorIndex, data.name);
-        }
-    }
-
-    drawXAxis(){
-        this.context.beginPath();
-        this.context.moveTo(this.left, this.bottom);
-        this.context.lineTo(this.right, this.bottom);
-        this.context.lineWidth = 4;
-        this.context.stroke();
-
-        this.context.setLineDash([5, 10]);
-        this.context.font = "10px Arial";
-        this.context.lineWidth = 1;
-
-        if(Object.prototype.toString.call(this.xRange[0]) === '[object Date]'){
-            let diff = Math.abs(Math.floor((Date.UTC(this.xRange[0].getFullYear(), this.xRange[0].getMonth(), this.xRange[0].getDate()) - Date.UTC(this.xRange[1].getFullYear(), this.xRange[1].getMonth(), this.xRange[1].getDate())) / (1000 * 60 * 60 * 24))) + 1;
-            let showDate = new Date(this.xRange[0]);
-            
-            for(let i = 0; i < diff; i += Math.floor(diff / 10)){
-                this.context.fillText(showDate.toLocaleDateString("en-US", {month: "short", day: "numeric", year: "2-digit"}), this.left + (this.horizontalMultiplier * i) - 20, this.bottom + 15);
-
-                if(i !== 0){
-                    this.context.beginPath()
-                    this.context.moveTo(this.left + (this.horizontalMultiplier * i), this.bottom);
-                    this.context.lineTo(this.left + (this.horizontalMultiplier * i), this.top);
-                    this.context.strokeStyle = "#a5a5a5";
-                    this.context.stroke();
-                }
-
-                showDate.setDate(showDate.getDate() + Math.abs(diff / 10));
-            }
-            
-        }
-
-        this.context.strokeStyle = "black";
-        this.context.setLineDash([]);
-    }
-
-    drawYAxis(){
-        this.context.beginPath();
-        this.context.moveTo(this.left, this.top);
-        this.context.lineTo(this.left, this.bottom);
-        this.context.lineWidth = 2;
-        this.context.stroke();
-
-        this.context.setLineDash([5, 10]);
-        this.context.font = "10px Arial";
-        this.context.lineWidth = 1;
-
-        let axisNum = 0;
-        let verticalIncrement = (this.bottom - this.top) / 10;
-        let verticalOffset = 0;
-        do{
-            this.context.fillText(Math.round(axisNum).toString(), this.left - 20, this.bottom - verticalOffset + 3);
-
-            this.context.beginPath();
-            this.context.moveTo(this.left, this.bottom - verticalOffset);
-            this.context.lineTo(this.right, this.bottom - verticalOffset);
-            this.context.strokeStyle = "#a5a5a5";
-            this.context.stroke();
-
-            verticalOffset += verticalIncrement;
-            axisNum += this.max / 10;
-        }while(verticalOffset <= (this.bottom - this.top));
-
-        this.context.strokeStyle = "black";
-        this.context.setLineDash([]);
-    }
-
-    drawLegend(colorIndex, name){
-        let verticalOffset;
-        for(let i = 0; i < this.data.length; i++){
-            if(this.data[i].name === name){
-                verticalOffset = i * 25;
-                break;
-            }
-        }
-
-        this.context.beginPath();
-        this.context.fillStyle = this.colors[colorIndex];
-        this.context.fillRect(this.right + 50, this.top + 50 + verticalOffset, 10, 10);
-        this.context.stroke();
-
-        this.context.font = "15px Arial";
-        this.context.fillText(name, this.right + 65, this.top + 60 + verticalOffset);
-
-        this.context.fillStyle = "black";
-    }
-}
-
-class HorizontalBarGraph{
-    constructor(canvas){
-        this.canvas = canvas;
-        this.context = canvas.getContext("2d");
-        this.left = 0;
-        this.right = canvas.clientWidth;
-        this.top = canvas.clientHeight - (canvas.clientHeight * 0.99);
-        this.bottom = canvas.clientHeight;
-        this.data = [];
-        this.max = 0;
-    }
-
-    //Adds an array of data points to the chart
-    //All data is removed  and redrawn when called
-    //Must pass in all data points
-    //Inputs: 
-    //  dataArray: array of objects
-    //      num: number for the actual data
-    //      label: text to display on bar
-    addData(dataArray){
-        this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
-
-        for(let i = 0; i < dataArray.length; i++){
-            if(dataArray[i].num > this.max){
-                this.max = dataArray[i].num;
-            }
-
-            this.data.push(dataArray[i]);
-        }
-
-        this.drawGraph();
-    }
-
-    drawGraph(){
-        let barHeight = ((this.bottom - this.top) / this.data.length) - 2;
-
-        for(let i = 0; i < this.data.length; i++){
-            let topLocation = this.top + (i * barHeight) + 5;
-            let width = (this.right - this.left) * (this.data[i].num / this.max);
-
-            if(this.data[i].num >= this.max){
-                this.context.fillStyle = "rgb(255, 99, 107)";
-            }else{
-                this.context.fillStyle = "rgb(179, 191, 209)";
-            }
-
-            this.context.beginPath();
-            this.context.fillRect(this.left, topLocation, width, barHeight - 5);
-            this.context.stroke();
-
-            let textLocation  = 15;
-            this.context.font = "12px Saira";
-            this.context.fillStyle = "black";
-            this.context.fillText(this.data[i].label, textLocation, (this.top) + (i * barHeight) + (barHeight / 1.5));
-        }
-    }
-}
-
-module.exports = {
-    LineGraph: LineGraph,
-    HorizontalBarGraph: HorizontalBarGraph
-}

+ 1 - 1
views/shared/shared.css

@@ -280,7 +280,7 @@ form{
     list-style-type: none;
     font-size: 20px;
     color: white;
-    z-index: 10;
+    z-index: -10;
     position: absolute;
     border-radius: 5px;
 }