Source: frontend/dashboard/Dashboard.js

//Brennan Wilkes

//Imports
import React from "react";
import "../bootstrap-import.js";
import axios from "axios";

import "./dashboard.css";
import DrinkDetails from "../detailedViews/DrinkDetails.js";
import DrinkIcon from "../iconViews/DrinkIcon.js";
import IngredientDetails from "../detailedViews/IngredientDetails.js";
import IngredientIcon from "../iconViews/IngredientIcon.js";
import DetailedViewController from "../detailedViews/DetailedViewController.js";

import computerImage from "../../../assets/computer-chip-stock.jpg";

//capitalization one liner using regex
const capitalize = s => String(s).toLowerCase().replace(/(?:^|\s|["'([{])+\S/g, l => l.toUpperCase());

/**
	List of transaction tuple headers
	@type {string[]}
	@memberof frontend
*/
const TRANSACTION_HEADERS = [
	"Date",
	"Customer",
	"Drink",
	"Price"
]

/**
	Trims a string based on a max length and appends with ...
	@param {string} str String to trim
	@param {number} max Max length to trim by
	@returns {string} Trimmed string
*/
const cutoffString = (str,max) => str.length > max ? `${str.substring(0,max-3).trim()}...` : str;

/**
	Renders an HTML table row with details of a transaction
	@class
	@memberof frontend
	@extends React.Component
*/
class Transaction extends React.Component{

	/**
		Binds methods
		@param {any[]} props Should contain a click callback, and a transaction information object
	*/
	constructor(props){
		super(props);

		this.state = this.props.transactionInfo;
	}

	/**
		Renders the information out as a row
	*/
	render(){
		return <>
			<tr onClick={event=>{
				this.props.clickCallback(this.state.drinkId);
			}}>
				<td>{this.state.date.substring(0,10)}</td>
				<td>{cutoffString(this.state.customerName,10)}</td>
				<td>{cutoffString(capitalize(this.state.name),14)}</td>
				<td>{`\$${this.state.price}`}</td>
			</tr>
		</>;
	}
}

/**
	A styled label
	@class
	@memberof frontend
	@extends React.Component
*/
class FloatingSectionLabel extends React.Component{
	render(){
		return <>
			<div className="FloatingSectionLabel p-1 text-light bg-secondary">
				{this.props.text}
			</div>
		</>
	}
}

/**
	Admin dashboard component. Performs all function required for the admin,
	and links all admin related components together through callbacks.
	@class
	@memberof frontend
	@extends {@link DetailedViewController}
*/
class Dashboard extends DetailedViewController {

	/**
		Initializes state and binds methods
		@param {any[]} props Must contain a user name and password
	*/
	constructor(props){
		super(props);

		this.orderIngredient = this.orderIngredient.bind(this);
		this.deleteIngredient = this.deleteIngredient.bind(this);
		this.deleteDrink = this.deleteDrink.bind(this);
		this.updateRefresh = this.updateRefresh.bind(this);

		this.state = {
			orders : [],
			popularDrinks : [],
			popularIngr : [],
			lowStock: [],
			detailedDrink: undefined,
			detailedIngredient: undefined,
			userName: this.props.userName,
			userPass: this.props.userPass
		}

		this.updateRefresh(true);
	}

	/**
		Updates admin dashboard data by querying the database
		@param {boolean} firstTime Flag to reset state. Defaults to false
	*/
	updateRefresh(firstTime=false){
		if(!firstTime){
			this.setState({
				orders : [],
				popularDrinks : [],
				popularIngr : [],
				lowStock: []
			});
		}

		//Database calls
		axios.get("/orders").then(res => this.setState({orders:res.data}));
		axios.get("/popular/drinks").then(res => this.setState({popularDrinks:res.data}));
		axios.get("/popular/ingredients").then(res => this.setState({popularIngr:res.data}));
		axios.get("/ingredients").then(res => this.setState({lowStock:res.data}));
	}

	/**
		Triggers a elegent scroll animation to show the user that there is horizontal scroll within pannel sections
	*/
	componentDidMount(){

		//Update background image
		$("main").css("backgroundImage",`url(${computerImage})`);
		$("main").css("background-color","#00000080");
		$("main").css("background-blend-mode","overlay");

		//Scroll each component with animations
		setTimeout(event=>{
			$(".scrollable-x").each((i,component) => {
				component = $(component);
				component.scrollLeft(component.width());
				component.animate({ scrollLeft: "0" },1000);
			});
			$(".scrollable-y").each((i,component) => {
				component = $(component);
				component.scrollTop(component.height());
				component.animate({ scrollTop: "0" },1000);
			});
		},100);
	}

	/**
		Callback to query database to order an ingredient.
	*/
	orderIngredient(){
		return axios.post('/order',{
			id: `${this.state.detailedIngredient}`,
			userName: this.state.userName,
			userPass: this.state.userPass
		});
	}

	/**
		Callback to query database to delete an ingredient.
	*/
	deleteIngredient(){
		return axios.post('/delete/ingredient',{
			id: `${this.state.detailedIngredient}`,
			userName: this.state.userName,
			userPass: this.state.userPass
		});
	}

	/**
		Callback to query database to delete a drink.
	*/
	deleteDrink(){
		return axios.post('/delete/drink',{
			id: `${this.state.detailedDrink}`,
			userName: this.state.userName,
			userPass: this.state.userPass
		});
	}

	/**
		Renders out responsive pannels containing lists of drinks and ingredients
		based on specific criteria, as well as hidden detailed views.
	*/
	render(){
		return <>
			<div className="fixed-top" id="nav-wrapper">
				<nav className="adminNavBar navbar navbar-dark bg-dark py-0">
					<a className="navbar-brand ml-3 nav-brand-item" href=".">Every Last Drop</a>
					<ul className="mr-3 ml-3 navbar-nav mr-auto">
						<li className="nav-item active">
							<a className="nav-link" href=".">Home</a>
						</li>
					</ul>
				</nav>
			</div>
			<div className="container-fluid p-3" id="dashboard">
				<div className="row">
					<div className="col-md-4 p-3 dashboardComponent bg-dark scrollable-y">
						<FloatingSectionLabel text="Orders" />
						<table id="transactions"><thead className="text-light">
							<tr>{
								TRANSACTION_HEADERS.map(h => {
									return <th>{h}</th>
								})
							}</tr></thead><tbody className="text-muted">
							{
								this.state.orders.map(t => {
									return <Transaction transactionInfo={t} clickCallback={this.updateDetailedDrink} />
								})
							}
						</tbody></table>
					</div>
					<div className="col-md-8 px-3">
						<div className="p-3 iconContainer dashboardComponent text-light bg-dark scrollable-x" id="popularDrinks">
							<FloatingSectionLabel text="Popular Drinks" />
							<div>
							{
								this.state.popularDrinks.map(d => {
									return <DrinkIcon drinkInfo={d} clickCallback={this.updateDetailedDrink} />
								})
							}
						</div></div>
						<div className="p-3 iconContainer dashboardComponent text-light bg-dark scrollable-x" id="popularIngr">
							<FloatingSectionLabel text="Popular Ingredients" />
							<div>
							{
								this.state.popularIngr.map(i => {
									return <IngredientIcon ingrInfo={i} clickCallback={this.updateDetailedIngrident} />
								})
							}
						</div></div>
					</div>
				</div>
				<div className="row">
					<div className="col-md-12 p-3 dashboardComponent bg-dark text-light iconContainer scrollable-x" id="inventory">
						<FloatingSectionLabel text="Out of Stock" />
						<div>
						{
							this.state.lowStock.map(i => {
								return <IngredientIcon ingrInfo={i} clickCallback={this.updateDetailedIngrident} />
							})
						}
					</div></div>
				</div>
				<DrinkDetails
					drinkId={this.state.detailedDrink}
					changeIngredient={this.updateDetailedIngrident}
					deleteCallback={this.deleteDrink}
					changeDrink={this.updateDetailedDrink}
					parentUpdate={this.updateRefresh} />
				<IngredientDetails
					ingredientId={this.state.detailedIngredient}
					changeIngredient={this.updateDetailedIngrident}
					orderCallback={this.orderIngredient}
					deleteCallback={this.deleteIngredient}
					parentUpdate={this.updateRefresh} />
			</div>
		</>;
	}
}
export default Dashboard;