New formula manager

This commit is contained in:
Zakary Timson 2018-11-04 17:30:43 -05:00
parent b57e3cc2cc
commit 80a59878f1
12 changed files with 243 additions and 226 deletions

View File

@ -1,6 +1,6 @@
{
"name": "fhsons",
"version": "0.0.0",
"version": "1.0.0",
"main": "main.js",
"scripts": {
"ng": "ng",

View File

@ -1,17 +1,19 @@
<div class="container">
<div class="row">
<div class="col-12 py-4">
<img src="/assets/img/Mariamorgis.jpg" class="float-left img-fluid mb-4 mr-4">
<h2 class="mb-4 roboto">40 Years of Excellence</h2>
<hr class="ml-4">
<strong>FH &amp; Sons</strong> has been in business for 40 years. We have been ISO 9001:2000 certified since May 2000. Our
company manufactures plastisol silk screen inks, plastisols for moulding and dipping applications, as well as bulk
pigment dispersions. We also distribute for several top end equipment and chemical manufacturers. We strive to provide
value in everything we sell, coupled with unmatched customer service and guidance.
<div class="container-fluid bg-white">
<div class="container">
<div class="row">
<div class="col-12 py-4">
<img src="/assets/img/Mariamorgis.jpg" class="float-left img-fluid mb-4 mr-4">
<h2 class="mb-4 roboto">40 Years of Excellence</h2>
<hr class="ml-4">
<strong>FH &amp; Sons</strong> has been in business for 40 years. We have been ISO 9001:2000 certified since May 2000. Our
company manufactures plastisol silk screen inks, plastisols for moulding and dipping applications, as well as bulk
pigment dispersions. We also distribute for several top end equipment and chemical manufacturers. We strive to provide
value in everything we sell, coupled with unmatched customer service and guidance.
</div>
</div>
</div>
</div>
<div style="background-color: #53709f;">
<div style="background: #53709f;">
<div class="container">
<div class="row pt-4">
<div class="col-12">

View File

@ -1,54 +1,52 @@
<!-- Nav -->
<div class="wrapper">
<nav class="navbar navbar-expand-lg fixed-top navbar-light bg-light">
<div class="container">
<a class="navbar-brand roboto" [routerLink]="['/']">
<span class="fh-fade">fh</span>
<span class="rainbow"> & </span>
<span class="sons-fade">sons</span>
</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-expanded="false">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbar">
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<a class="nav-link text-dark" [routerLink]="['/about']">About</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" [routerLink]="['/store']">Store</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" [routerLink]="['/formulaManager']">Formula Manager</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" target="_blank" href="/assets/Chromatex_MSDS.pdf">MSDS</a>
</li>
</ul>
<div class="ml-auto">
<button *ngIf="!store.user" mat-stroked-button color="primary" (click)="login()">Login</button>
<div *ngIf="store.user" class="d-inline">
<span class="text-muted mr-3">{{store.user.email}}</span>
<button mat-stroked-button color="accent" (click)="afAuth.auth.signOut()">Logout</button>
</div>
<button *ngIf="cart.length" mat-icon-button class="ml-3" [routerLink]="['/cart']">
<mat-icon style="transform: translateY(6px)" [matBadge]="cartCount()" matBadgeColor="warn">
shopping_cart
</mat-icon>
</button>
<nav class="navbar navbar-expand-lg fixed-top navbar-light bg-light">
<div class="container">
<a class="navbar-brand roboto" [routerLink]="['/']">
<span class="fh-fade">fh</span>
<span class="rainbow"> & </span>
<span class="sons-fade">sons</span>
</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-expanded="false">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbar">
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<a class="nav-link text-dark" [routerLink]="['/about']">About</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" [routerLink]="['/store']">Store</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" [routerLink]="['/formulaManager']">Formula Manager</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" target="_blank" href="/assets/Chromatex_MSDS.pdf">MSDS</a>
</li>
</ul>
<div class="ml-auto">
<button *ngIf="!store.user" mat-stroked-button color="primary" (click)="login()">Login</button>
<div *ngIf="store.user" class="d-inline">
<span class="text-muted mr-3">{{store.user.email}}</span>
<button mat-stroked-button color="accent" (click)="afAuth.auth.signOut()">Logout</button>
</div>
<button *ngIf="cart.length" mat-icon-button class="ml-3" [routerLink]="['/cart']">
<mat-icon style="transform: translateY(6px)" [matBadge]="cartCount()" matBadgeColor="warn">
shopping_cart
</mat-icon>
</button>
</div>
</div>
</nav>
<!-- Content -->
<div>
<div style="height: 56px"></div>
<router-outlet></router-outlet>
</div>
</nav>
<div class="push"></div>
<!-- Push content down from under nav-->
<div style="height: 56px"></div>
<!-- Content -->
<div>
<router-outlet></router-outlet>
</div>
<!-- Footer -->

View File

@ -26,10 +26,10 @@ import { ProductsComponent } from './store/products/products.component';
import { CartComponent } from './store/cart/cart.component';
import { ViewComponents } from './formulaManager/viewComponents/viewComponents.component';
import { NewComponentComponent } from './formulaManager/newComponent/newComponent.component';
import { HttpModule } from '@angular/http';
import { NewFormulaComponent } from './formulaManager/newFormula/newFormula.component';
import { AppStore } from './app.store';
import { SlideshowModule } from 'ng-simple-slideshow';
import {HttpClientModule} from '@angular/common/http';
@NgModule({
declarations: [
@ -60,7 +60,7 @@ import { SlideshowModule } from 'ng-simple-slideshow';
BrowserAnimationsModule,
BrowserModule,
FormsModule,
HttpModule,
HttpClientModule,
NgxElectronModule,
ReactiveFormsModule,
RouterModule.forRoot([

View File

@ -1,129 +1,115 @@
<div class="container">
<div *ngIf="!electron.isElectronApp" class="row mt-3 d-none d-lg-block">
<div class="col-12">
<div class="jumbotron text-white" style="
background-color: #000;
background-image: url('../../assets/img/formula.jpg');
background-size: cover;
">
<h1 class="display-4">Standalone</h1>
<p class="lead text-white-50">Download Formula Manager 2.0</p>
<hr class="my-4">
<p>Tired of using your browser? Try downloading Formula Manager 2.0!</p>
<a class="btn btn-primary btn-lg mb-3 mr-3" href="#" download>
<i class="fab fa-windows"></i> Windows</a>
<a class="btn btn-primary btn-lg mb-3 mr-3" href="#" download>
<i class="fab fa-apple"></i> MacOS</a>
<a class="btn btn-primary btn-lg mb-3 mr-3" href="#" download>
<i class="fab fa-linux"></i> Linux</a>
<button *ngIf="installPrompt" class="btn btn-primary btn-lg mb-3" (click)="prompt()">
<i class="fas fa-mobile-alt"></i> Mobile</button>
</div>
<mat-drawer-container class="flex-grow-1">
<mat-drawer #drawer [mode]="mobile ? 'over' : 'side'" [opened]="!mobile" [disableClose]="!mobile" [autoFocus]="false">
<mat-form-field appearance="outline" class="mx-3 mt-3 pb-0">
<mat-label>Search</mat-label>
<input #search matInput>
<mat-icon matSuffix>search</mat-icon>
</mat-form-field>
<mat-divider></mat-divider>
<mat-list>
<ng-container *ngFor="let f of formulas | async; let i = index">
<mat-divider *ngIf="f.name.toLowerCase().indexOf(search.value.toLowerCase()) != -1 && i > 0"></mat-divider>
<mat-list-item *ngIf="f.name.toLowerCase().indexOf(search.value.toLowerCase()) != -1" (click)="displayFormula(f)" [ngClass]="{'active': f.id == formula?.id}">
<mat-icon *ngIf="!f.approved" class="mr-3 text-danger">remove_circle</mat-icon>
{{f.name}}
</mat-list-item>
</ng-container>
</mat-list>
<div *ngIf="store.user" class="fixed-bottom w-100">
<mat-divider></mat-divider>
<button mat-button class="m-1 float-right" (click)="newFormula()">
<mat-icon>add</mat-icon> New
</button>
</div>
</div>
<div *ngIf="store.user" class="row mt-3">
<div class="col-12">
<div class="float-right">
<button mat-raised-button class="mr-3" (click)="openComponents()">
<mat-icon>list</mat-icon> Components
</button>
<button mat-raised-button (click)="newFormula()">
<mat-icon>add</mat-icon> Formula
</button>
</div>
</mat-drawer>
<mat-drawer-content class="p-3 bg-white" style="min-height: 450px; background: url('assets/img/splatter.jpg') center; background-size: cover;">
<div>
<button *ngIf="mobile" mat-raised-button (click)="drawer.open()">
<mat-icon>opacity</mat-icon> Formulas
</button>
</div>
</div>
<div class="row mt-3">
<div class="col-12 col-lg-3 mb-5">
<mat-form-field class="w-100">
<input #search matInput placeholder="Search">
<mat-icon matSuffix>search</mat-icon>
</mat-form-field>
<mat-list style="max-height: 500px; overflow-y: scroll">
<ng-container *ngFor="let f of formulas | async; let i = index">
<mat-divider *ngIf="f.name.toLowerCase().indexOf(search.value.toLowerCase()) != -1 && i > 0"></mat-divider>
<mat-list-item *ngIf="f.name.toLowerCase().indexOf(search.value.toLowerCase()) != -1" (click)="displayFormula(f)" [ngClass]="{'active': f.id == formula?.id}">
<mat-icon *ngIf="!f.approved" class="mr-3 text-danger">remove_circle</mat-icon>
{{f.name}}
</mat-list-item>
</ng-container>
</mat-list>
<div *ngIf="(electron.isElectronApp || mobile) && !formula">
<img src="assets/img/starthere.png" class="m-4">
</div>
<div *ngIf="!formula" class="d-none d-lg-block col-lg-9">
<div class="w-100">
<div class="col-12 p-5">
<mat-card class="mx-auto text-center">
<mat-card-content>
Pick a formula from the left hand side to see its components!
</mat-card-content>
</mat-card>
<mat-card *ngIf="!electron.isElectronApp && !mobile && !formula" class="mx-auto" style="max-width: 600px">
<mat-card-header>
<div class="d-flex align-items-center" style="color: #53709f">
<mat-icon style="transform: scale(2.5)">opacity</mat-icon>
<h1 class="ml-3 display-4">Formula Manager</h1>
</div>
</div>
</div>
<div *ngIf="formula" class="col-12 col-lg-9 mb-5">
<div>
<div class="float-left">
<h3 class="mb-0">{{formula.name}}</h3>
<span class="text-muted">Created: {{formula.created | date}}</span>
</div>
<div *ngIf="store.user" class="float-right">
<h5>Approved:
</mat-card-header>
<mat-card-content>
<p class="lead text-white-50">Tired of using your browser? Download the standalone version of the Formula Manager.</p>
<button mat-button class="button-fix"><i class="fab fa-windows mr-1 mb-1"></i> Windows</button>
<button mat-button class="button-fix"><i class="fab fa-apple mr-1 mb-1"></i> MacOS</button>
<button mat-button class="button-fix"><i class="fab fa-linux mr-1 mb-1"></i> Linux</button>
</mat-card-content>
</mat-card>
<mat-card *ngIf="formula" class="my-4 mx-auto" style="max-width: 600px">
<mat-card-header>
<mat-card-title class="mb-0"><h4>{{formula.name}}</h4></mat-card-title>
<mat-card-subtitle>{{formula.created | date}}</mat-card-subtitle>
<div *ngIf="store.user" class="ml-auto">
<h4>Approved:
<mat-icon *ngIf="formula.approved" class="text-success" style="transform: translateY(20%)">check_circle</mat-icon>
<mat-icon *ngIf="!formula.approved" class="text-danger" style="transform: translateY(20%)">remove_circle</mat-icon>
</h5>
</h4>
</div>
</div>
<table class="w-100 mt-4 table">
<thead>
</mat-card-header>
<mat-card-content>
<table class="w-100 mt-4 table">
<thead>
<tr>
<th style="width: 80%">Name</th>
<th style="width: 10%">Quantity</th>
<th style="width: 10%">Cost</th>
<th class="text-right" style="width: 10%">Quantity</th>
<th class="text-right" style="width: 10%">Cost</th>
</tr>
</thead>
<tbody>
</thead>
<tbody>
<tr *ngFor="let c of formula.components">
<td style="width: 80%">{{c.component?.name}}</td>
<td style="width: 10%">{{c.quantity | scale: formula.total : _newTotal | convertFromG: unit}} {{unit}}</td>
<td style="width: 10%">{{c.quantity | scale: formula.total : _newTotal / 1000 * c.component.cost | currency}}</td>
</tbody>
</table>
<div class="col-12 d-lg-block mb-3" style="height: 4px; background:black; width: 100%;"></div>
<div class="d-flex">
<div class="d-inline ml-auto">
<div class="d-inline-block mr-3">
<mat-form-field style="width: 75px">
<input matInput type="number" placeholder="Yield" [(ngModel)]="newTotal">
</mat-form-field>
<mat-form-field style="width: 40px">
<mat-select placeholder="Unit" [(ngModel)]="unit">
<mat-option value="g">g</mat-option>
<mat-option value="oz">oz</mat-option>
<mat-option value="kg">kg</mat-option>
<mat-option value="lb">lb</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="d-inline-block">
<mat-form-field id="totalCost" style="width: 100px">
<input matInput placeholder="Total Cost" [value]="cost() | currency" [readonly]="true" style="text-align: right;">
</mat-form-field>
<td class="text-right" style="width: 10%">{{c.quantity | scale: formula.total : _newTotal | convertFromG: unit}} {{unit}}</td>
<td class="text-right" style="width: 10%">{{c.quantity | scale: formula.total : _newTotal / 1000 * c.component.cost | currency}}</td>
</tbody>
</table>
<div class="col-12 d-lg-block mb-3" style="height: 4px; background:black; width: 100%;"></div>
<div class="d-flex">
<div class="d-inline ml-auto">
<div class="d-inline-block mr-3">
<mat-form-field style="width: 75px">
<input matInput type="number" placeholder="Yield" [(ngModel)]="newTotal">
</mat-form-field>
<mat-form-field style="width: 40px">
<mat-select placeholder="Unit" [(ngModel)]="unit">
<mat-option value="g">g</mat-option>
<mat-option value="oz">oz</mat-option>
<mat-option value="kg">kg</mat-option>
<mat-option value="lb">lb</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="d-inline-block">
<mat-form-field id="totalCost" style="width: 100px">
<input matInput placeholder="Total Cost" [value]="cost() | currency" [readonly]="true" style="text-align: right;">
</mat-form-field>
</div>
</div>
</div>
</div>
<div *ngIf="store.user" class="w-100 mt-4">
<div class="float-right">
<button *ngIf="!formula.approved" mat-raised-button class="mr-3" (click)="approve(formula)">
<mat-icon>check</mat-icon>Approve
</button>
<button mat-raised-button class="mr-3" (click)="edit(formula)">
</mat-card-content>
<mat-card-actions *ngIf="store.user" style="overflow: hidden">
<mat-divider></mat-divider>
<button *ngIf="!formula.approved" mat-button class="m-1 mt-2" (click)="approve(formula)">
<mat-icon>check</mat-icon>Approve
</button>
<div class="float-right m-1 mt-2">
<button mat-button class="mr-3" (click)="edit(formula)">
<mat-icon>edit</mat-icon>Edit
</button>
<button mat-raised-button (click)="delete(formula)">
<button mat-button (click)="delete(formula)">
<mat-icon>delete</mat-icon>Delete
</button>
</div>
</div>
</div>
</div>
</div>
</mat-card-actions>
</mat-card>
</mat-drawer-content>
</mat-drawer-container>

View File

@ -1,4 +1,4 @@
import {Component, ElementRef, ViewChildren, HostListener} from '@angular/core';
import {Component} from '@angular/core';
import {ConvertFromGPipe, ConvertToGPipe} from './units.pipe';
import {ElectronService} from 'ngx-electron';
import {LocalStorage} from 'webstorage-decorators';
@ -8,68 +8,74 @@ import {NewFormulaComponent} from './newFormula/newFormula.component';
import {AppStore} from '../app.store';
import {map} from 'rxjs/operators';
import {DeleteComponent} from '../delete/delete.component';
import {BreakpointObserver, Breakpoints} from '@angular/cdk/layout';
@Component({
selector: 'formula-manager',
templateUrl: './formulaManager.component.html',
styles: [
`
.active {
background-color: #f0f0f0;
}
`
]
selector: 'formula-manager',
templateUrl: './formulaManager.component.html',
styles: [
`
.active {
background-color: #f0f0f0;
}
`
]
})
export class FormulaManagerComponent {
formula;
formulas;
@LocalStorage({defaultValue: 'g'})
unit;
formula;
formulas;
@LocalStorage({defaultValue: 'g'})
unit;
mobile = false;
_newTotal: number = 0;
get newTotal() {
return new ConvertFromGPipe().transform(this._newTotal, this.unit);
}
set newTotal(total) {
this._newTotal = new ConvertToGPipe().transform(total, this.unit);
}
_newTotal: number = 0;
get newTotal() {
return new ConvertFromGPipe().transform(this._newTotal, this.unit);
}
constructor(public electron: ElectronService, private dialog: MatDialog, public store: AppStore) {
this.formulas = this.store.formulas.pipe(map(rows => rows.filter(row => this.store.user || row.approved)));
}
set newTotal(total) {
this._newTotal = new ConvertToGPipe().transform(total, this.unit);
}
approve(formula) {
formula.approved = true;
formula.ref.update({approved: true});
}
constructor(public electron: ElectronService, private dialog: MatDialog, private $breakpoint: BreakpointObserver, public store: AppStore) {
this.formulas = this.store.formulas.pipe(map(rows => rows.filter(row => this.store.user || row.approved)));
cost() {
let cost = 0;
this.formula.components.forEach(
row => (cost += (((row.quantity / this.formula.total) * this._newTotal) / 1000) * row.component.cost)
);
return cost;
}
// Handle switching between mobile and desktop
this.$breakpoint.observe(Breakpoints.Handset).subscribe(e => this.mobile = e.matches);
}
edit(formula) {
this.dialog.open(NewFormulaComponent, {data: formula});
}
approve(formula) {
formula.approved = true;
formula.ref.update({approved: true});
}
delete(formula) {
this.dialog.open(DeleteComponent, {data: formula});
}
cost() {
let cost = 0;
this.formula.components.forEach(
row => (cost += (((row.quantity / this.formula.total) * this._newTotal) / 1000) * row.component.cost)
);
return cost;
}
displayFormula(formula) {
formula.total = formula.components.reduce((acc, row) => (acc += row.quantity), 0);
this.newTotal = new ConvertFromGPipe().transform(formula.total, this.unit);
this.formula = formula;
}
edit(formula) {
this.dialog.open(NewFormulaComponent, {data: formula});
}
newFormula() {
this.dialog.open(NewFormulaComponent);
}
delete(formula) {
this.dialog.open(DeleteComponent, {data: formula});
}
openComponents() {
this.dialog.open(ViewComponents, {height: '500px'});
}
displayFormula(formula) {
formula.total = formula.components.reduce((acc, row) => (acc += row.quantity), 0);
this.newTotal = new ConvertFromGPipe().transform(formula.total, this.unit);
this.formula = formula;
}
newFormula() {
this.dialog.open(NewFormulaComponent);
}
openComponents() {
this.dialog.open(ViewComponents, {height: '500px'});
}
}

View File

@ -36,7 +36,7 @@
</div>
</div>
</div>
<div class="container-fluid pb-5">
<div class="container-fluid bg-white pb-5">
<div class="row">
<div class="col d-none d-md-inline p-0" style="position: relative">
<div class="d-inline-block" style="position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%)">

View File

@ -1,8 +1,21 @@
@import '~@angular/material/prebuilt-themes/indigo-pink.css';
@import url('https://use.fontawesome.com/releases/v5.1.0/css/all.css');
@import url('https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css');
@import url('https://fonts.googleapis.com/css?family=Roboto+Condensed');
@import url('https://fonts.googleapis.com/icon?family=Material+Icons');
@import url('https://fonts.googleapis.com/css?family=Material+Icons|Roboto+Condensed');
::-webkit-scrollbar-track {
border-radius: 10px;
background-color: rgba(255, 255, 255, 0);
}
::-webkit-scrollbar {
width: 8px;
background-color: #F5F5F5;
}
::-webkit-scrollbar-thumb {
border-radius: 10px;
background-color: #555;
}
html,
body {
@ -10,6 +23,11 @@ body {
width: 100%;
margin: 0;
padding: 0;
background-color: #292929;
}
.button-fix {
font-size: 1.2rem;
}
a,

7
src/assets/css/bootstrap.min.css vendored Normal file

File diff suppressed because one or more lines are too long

BIN
src/assets/img/splatter.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 703 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

View File

@ -16,7 +16,7 @@
<link rel="icon" type="image/png" href="assets/img/logo.png">
<link rel="manifest" href="manifest.json">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.1.1/css/all.css">
<link rel="stylesheet" href="assets/css/bootstrap.min.css">
<script src="https://www.paypalobjects.com/api/checkout.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>