Merge branch 'Release-v0.1.13'

This commit is contained in:
Thomas Bloor 2019-09-16 13:02:07 +01:00
commit b91de60175
No known key found for this signature in database
GPG Key ID: 4657C7EBE42CC5CC
101 changed files with 12772 additions and 6505 deletions

View File

@ -1,59 +0,0 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"project": {
"version": "1.0.0-alpha.4",
"name": "coreui-angular"
},
"apps": [
{
"root": "src",
"outDir": "dist",
"assets": ["assets"],
"index": "index.html",
"main": "main.ts",
"polyfills": "polyfills.ts",
"test": "test.ts",
"tsconfig": "tsconfig.app.json",
"testTsconfig": "tsconfig.spec.json",
"prefix": "app",
"scripts": [
"../node_modules/moment/min/moment.min.js"
],
"styles": [
"scss/style.scss"
],
"environmentSource": "environments/environment.ts",
"environments": {
"dev": "environments/environment.dev.ts",
"prod": "environments/environment.prod.ts",
"local": "environments/environment.local.ts",
"ci": "environments/environment.ci.ts"
}
}
],
"e2e": {
"protractor": {
"config": "./protractor.conf.js"
}
},
"lint": [
{
"project": "src/tsconfig.app.json"
},
{
"project": "src/tsconfig.spec.json"
},
{
"project": "e2e/tsconfig.e2e.json"
}
],
"test": {
"karma": {
"config": "./karma.conf.js"
}
},
"defaults": {
"styleExt": "scss",
"prefixInterfaces": false
}
}

6
.gitattributes vendored
View File

@ -15,3 +15,9 @@
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain
# ensures font files are loaded as binary not text
*.ttf binary
*.eot binary
*.woff binary
*.woff2 binary

5
.gitignore vendored
View File

@ -22,7 +22,6 @@ $RECYCLE.BIN/
/bower_components
# IDEs and editors
/.idea
.project
.classpath
*.launch
@ -41,11 +40,15 @@ testem.log
/e2e/*.js
/e2e/*.map
# build
/dist
# local env variable
/src/environments/environment.local.ts
/src/environments/environment.prod.ts
/src/environments/environment.dev.ts
/src/environments/environment.ci.ts
/src/environments/environments.tar
# =========================
# Operating System Files

3
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
# Default ignored files
/workspace.xml

6
.idea/misc.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

8
.idea/modules.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/Foodloop-Web.iml" filepath="$PROJECT_DIR$/Foodloop-Web.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@ -2,14 +2,24 @@ addons:
chrome: stable
language: node_js
node_js:
- 8
- node
before_install:
- openssl aes-256-cbc -K $encrypted_9d2af3734b6c_key -iv $encrypted_9d2af3734b6c_iv -in src/environments/environment.ci.ts.enc -out src/environments/environment.ci.ts -d
- openssl aes-256-cbc -K $encrypted_17157b34afc7_key -iv $encrypted_17157b34afc7_iv -in src/environments/environments.tar.enc -out src/environments/environments.tar -d
- tar xf src/environments/environments.tar -C src/environments
before_script:
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start
- npm config set spin false
install:
- npm install
- npm install
script:
- npm run ci
- npm run ci
before_deploy:
- ./bin/build-releases
deploy:
provider: releases
api_key:
secure: "Ke27Qm16uRgfX+ZVAq92Suu0ZpLOG9TGXDD8ndKNQZ4zl80DOmePoKvVpk/5Ip3H57Me2seqtCwgjeUCfW0dX3KayPoNmGVxWXCsZCH3MZWuaMnGk/Zc1Ef8P3L1sTEG4LD6+59RaOiMwIrMtLPjSlzvV2gc+002O7MHoudx/qZl47L+T01B0Ovh1AueSVN224Q79NrBnbgTtMqaS3x2avLkJmdZpneafqeO5OusOFcvsHvBr7ca0qKv5yIpn4eotK2bo6TFuaC9e9i7gUgPKHb1/GXAK1DcteUDF3AzK/b7T+dqTS+1vowuNKjMZ+ecyB8VDXQlWnBcv/IGn/C3nBmtp2oN97BFQtHguCY42Qk9LZxIu9o0mpOt6aMRiIsfbWstgONKaLzgt4Ce3DFlJ7YR5BFRaoKDdGHoCW+tcucQ/o9vFCbBVZ8sol2aYJOiNHYxlC8A7NLs6YEjyckVAa3q1l6CddnSjrFA5oe9fsLdzUDGhJ57Nv7kF9v6jjsTlZtucrzf8ix9+vNKNfWLQ6K86UIeBT40pPYHLBmWEsiGai+s4IWrYjTscT4zQDHcfQCMuQbbb3NTEfy9Fwv0VQdIR/cKsQgUCZwwlv3RBImryuDFqY2pNOqvnGIcr/OJ/MmY9bbCYEp55dxrZ50dNBbtR4O8IyUDte/ycU4OKbE="
file_glob: true
file: "../WebApp-Releases/*"
skip_cleanup: true
on:
tags: true

View File

@ -2,6 +2,109 @@
# Next Release
# 0.1.13
* Added new graphs for organisation view, for better breakdown of spending
* Added Suppliers spend search page
* Updated and fixes numerous graphs on dashboard
## Minor
* *Dev Fixes* Updated Travis config
# 0.1.12
* Fixed accidentally added app-root
# 0.1.11
* Bumped Angular to version 6 and upgraded packages to match
* Converted RxJS 5 syntax to RxJS 6
* Removed extraneous console logs
* Changed dashboard sector list to category all time list & tweaked layout
# 0.1.10
* Allowed for creation of yearly recurring transactions
* Added google analytics
# 0.1.9
* Made layout change to make it neater when chart doesn't show
* Made hotfix
# 0.1.8
* Amended how category is pulled from server
* Added chart on essential purchase numbers as a whole
* Added bar chart of category purchases in the month
* Added pie chart of purchases by category in the week
* Added hint for closing the burger menu while in mobile view
# v0.1.7
* Fixed category on upload highlighting
# v0.1.6
* Changed layout of category choosing on upload
* Added ability to edit and remove recurring transactions
* Fixed HttpClient error log viewing
* Made transaction validation more lenient
# v0.1.5
* Fixed category viewing on purchase
* Changed category view from radio buttons to full label buttons
* Amended local validation of submit
* Changed recurring purchase selection view
# v0.1.4
* Amended category list on transaction submission
* Added budget view for weekly breakdown of spend by category
* Added flag to make purchases essential
* Fixed budget view issues and amended it to show essential purchases that week
* Added ability to make purchases recurring
* Updated Moment dependency due to security issue
* Fixed category uploading in upload
# v0.1.3
* Made fix to Travis config
# v0.1.2
* Removed unused button
* Added ability to choose category for transaction
* Added version bump on Angular
# v0.1.1
* Redid layout on circle customer view
* Renamed customer dashboard headers
# v0.1.0
* Changed Story Trail choosing to modals
* Revamped snippets and graph widgets on customer dashboard
* Added local purchase pie chart for customer dashboard
* Added week by week purchase list for customer dashboard
* Added sector purchase amount list for customer dashboard
* Added sector U to available ones
* Fixed snippet layout
# v0.0.7
* Added ESTA to Story Trail
* Reverted Story Trail naming to LIS
* Changed Trail map code to make it flexible based on association
# v0.0.6
* Fix typo in Map Titles
# v0.0.5
* Change page name from Story Trail to Lancaster Independent Story

12
Foodloop-Web.iml Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/dist" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

177
angular.json Normal file
View File

@ -0,0 +1,177 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"localloop-web": {
"root": "",
"sourceRoot": "src",
"projectType": "application",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist",
"index": "src/index.html",
"main": "src/main.ts",
"tsConfig": "src/tsconfig.app.json",
"polyfills": "src/polyfills.ts",
"assets": [
"src/assets"
],
"styles": [
"src/scss/style.scss"
],
"scripts": [
"node_modules/moment/min/moment.min.js"
]
},
"configurations": {
"dev": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.dev.ts"
}
]
},
"production": {
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
]
},
"local": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.local.ts"
}
]
},
"ci": {
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.ci.ts"
}
]
}
}
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "localloop-web:build"
},
"configurations": {
"dev": {
"browserTarget": "localloop-web:build:dev"
},
"production": {
"browserTarget": "localloop-web:build:production"
},
"local": {
"browserTarget": "localloop-web:build:local"
},
"ci": {
"browserTarget": "localloop-web:build:ci"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "localloop-web:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"karmaConfig": "./karma.conf.js",
"polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.spec.json",
"styles": [
"src/scss/style.scss"
],
"assets": [
"src/assets"
]
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"src/tsconfig.app.json",
"src/tsconfig.spec.json"
],
"exclude": []
}
}
}
},
"localloop-web-e2e": {
"root": "",
"sourceRoot": "",
"projectType": "application",
"architect": {
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "./protractor.conf.js",
"devServerTarget": "localloop-web:serve"
},
"configurations": {
"local": {
"devServerTarget": "localloop-web:serve:local"
},
"ci": {
"devServerTarget": "localloop-web:serve:ci"
}
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"e2e/tsconfig.e2e.json"
],
"exclude": []
}
}
}
}
},
"defaultProject": "localloop-web",
"schematics": {
"@schematics/angular:component": {
"prefix": "app",
"styleext": "scss"
},
"@schematics/angular:directive": {
"prefix": "app"
}
}
}

View File

@ -1,4 +1,5 @@
#! /bin/bash
set -e
VERSION=`git describe --tags`
@ -11,13 +12,13 @@ echo "Building releases for $VERSION"
echo "Building Prod Release..."
ng build --prod
npm run build:prod
tar -czf ../WebApp-Releases/LocalLoop-Web-prod-$VERSION.tar.gz dist
echo "Building Dev Release..."
ng build --dev
npm run build:dev
tar -czf ../WebApp-Releases/LocalLoop-Web-dev-$VERSION.tar.gz dist

12
browserslist Normal file
View File

@ -0,0 +1,12 @@
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
# You can see what browsers were selected by your queries by running:
# npx browserslist
> 0.5%
last 2 versions
Firefox ESR
not dead
not IE 9-11 # For IE 9-11 support, remove 'not'.

View File

@ -4,28 +4,28 @@
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular/cli'],
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular/cli/plugins/karma')
require('@angular-devkit/build-angular/plugins/karma')
],
client:{
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
files: [
{ pattern: './src/test.ts', watched: false }
],
preprocessors: {
'./src/test.ts': ['@angular/cli']
},
mime: {
'text/x-typescript': ['ts','tsx']
},
coverageIstanbulReporter: {
reports: [ 'html', 'lcovonly' ],
dir: require('path').join(__dirname, 'coverage'), reports: [ 'html', 'lcovonly' ],
fixWebpackSourcePaths: true
},
angularCli: {

15253
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "localloop-web",
"version": "0.0.5",
"version": "0.1.13",
"description": "LocalLoop Web - Web interface for LocalLoop app",
"author": "",
"url": "http://www.peartrade.org",
@ -8,59 +8,80 @@
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"start:dev": "ng serve --optimization=false --configuration=dev",
"start:prod": "ng serve --optimization=false --configuration=production",
"start:local": "ng serve --optimization=false --configuration=local",
"build:dev": "ng build --configuration=dev",
"build:prod": "ng build --configuration=production",
"test": "ng test",
"test:ci": "ng test --watch=false --env=ci",
"test:ci": "ng test --watch=false --browsers=ChromeHeadless",
"lint": "ng lint",
"e2e": "ng e2e",
"e2e:ci": "ng e2e --env=ci",
"e2e:ci": "ng e2e --configuration=ci",
"ci": "npm run test:ci && npm run e2e:ci"
},
"private": true,
"dependencies": {
"@agm/core": "1.0.0-beta.2",
"@agm/js-marker-clusterer": "1.0.0-beta.2",
"@angular/common": "5.0.1",
"@angular/compiler": "5.0.1",
"@angular/core": "5.0.1",
"@angular/forms": "5.0.1",
"@angular/platform-browser": "5.0.1",
"@angular/platform-browser-dynamic": "5.0.1",
"@angular/router": "5.0.1",
"@angular/upgrade": "5.0.1",
"@types/moment": "2.13.0",
"chart.js": "2.7.1",
"core-js": "2.5.1",
"@agm/core": "1.0.0-beta.6",
"@agm/js-marker-clusterer": "1.0.0-beta.6",
"@angular/common": "8.1.0",
"@angular/compiler": "8.1.0",
"@angular/core": "8.1.0",
"@angular/forms": "8.1.0",
"@angular/platform-browser": "8.1.0",
"@angular/platform-browser-dynamic": "8.1.0",
"@angular/router": "8.1.0",
"@angular/upgrade": "8.1.0",
"@coreui/coreui-plugin-chartjs-custom-tooltips": "^1.3.1",
"@coreui/icons": "0.3.0",
"ajv": "^6.10.0",
"ajv-keywords": "^3.4.0",
"angular2-datetimepicker": "^1.1.1",
"chart.js": "^2.8.0",
"chartjs-adapter-luxon": "^0.2.0",
"core-js": "^2.6.9",
"devextreme": "^19.1.4",
"devextreme-angular": "^19.1.4",
"jasmine": "^3.4.0",
"jquery": "^3.3.1",
"js-marker-clusterer": "1.0.0",
"moment": "^2.19.2",
"ng2-charts": "1.6.0",
"jszip": "^3.2.2",
"luxon": "^1.16.1",
"moment": "^2.24.0",
"ng2-charts": "^2.3.0",
"ng2-validation-manager": "0.5.3",
"ngx-bootstrap": "2.0.0-beta.8",
"ngx-pagination": "3.0.3",
"rxjs": "5.5.2",
"ngx-bootstrap": "^5.0.0",
"ngx-filter-pipe": "^2.1.2",
"ngx-pagination": "^4.0.0",
"popper.js": "^1.15.0",
"rxjs": "6.5.2",
"stream": "0.0.2",
"ts-helpers": "1.1.2",
"webpack": "3.8.1",
"webpack-dev-server": "2.9.4",
"zone.js": "0.8.18"
"tslib": "^1.10.0",
"web-animations-js": "^2.3.2",
"webpack-dev-server": "^3.7.2",
"zone.js": "~0.9.1"
},
"devDependencies": {
"@angular/cli": "1.5.0",
"@angular/compiler-cli": "5.0.1",
"@types/jasmine": "2.8.2",
"@types/jasminewd2": "2.0.3",
"@types/node": "8.0.52",
"codelyzer": "4.0.1",
"jasmine-core": "2.8.0",
"@angular-devkit/build-angular": "~0.801.0",
"@angular/cli": "^8.1.0",
"@angular/compiler-cli": "8.1.0",
"@types/jasmine": "3.3.13",
"@types/jasminewd2": "2.0.6",
"@types/node": "12.0.10",
"codelyzer": "^5.1.0",
"jasmine-core": "^3.4.0",
"jasmine-spec-reporter": "4.2.1",
"karma": "1.7.1",
"karma": "^4.1.0",
"karma-chrome-launcher": "2.2.0",
"karma-cli": "1.0.1",
"karma-coverage-istanbul-reporter": "1.3.0",
"karma-jasmine": "1.1.0",
"karma-jasmine-html-reporter": "0.2.2",
"protractor": "5.2.0",
"ts-node": "3.3.0",
"tslint": "5.8.0",
"typescript": "2.4.2"
"karma-cli": "2.0.0",
"karma-coverage-istanbul-reporter": "^2.0.5",
"karma-jasmine": "^2.0.1",
"readable-stream": "latest",
"karma-jasmine-html-reporter": "^1.4.2",
"protractor": "^5.4.2",
"ts-node": "^8.3.0",
"tslint": "^5.18.0",
"typescript": "~3.4.5"
}
}

View File

@ -9,7 +9,10 @@ exports.config = {
'./e2e/**/*.e2e-spec.ts'
],
capabilities: {
'browserName': 'chrome'
'browserName': 'chrome',
chromeOptions: {
args: [ "--headless" ]
}
},
directConnect: true,
baseUrl: 'http://localhost:4200/',

View File

@ -8,13 +8,11 @@ export class AuthGuard implements CanActivate {
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
if (localStorage.getItem('sessionKey')) {
console.log('session key found');
// logged in so return true
return true;
}
// not logged in so redirect to login page with the return url
console.log('no session key found');
this.router.navigate(['/login'], { queryParams: { returnUrl: state.url }});
return false;
}

View File

@ -8,11 +8,9 @@ export class CustomerGuard implements CanActivate {
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
if (localStorage.getItem('usertype') === 'customer') {
console.log('Customer logged in');
// customer logged in so return true
return true;
} else if (localStorage.getItem('usertype') === 'organisation') {
console.log('not an customer');
this.router.navigate(['/dashboard']);
return false;
}

View File

@ -8,11 +8,9 @@ export class OrgGuard implements CanActivate {
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
if (localStorage.getItem('usertype') === 'organisation') {
console.log('Organisation logged in');
// org logged in so return true
return true;
} else if (localStorage.getItem('usertype') === 'customer') {
console.log('not an organisation');
this.router.navigate(['/dashboard-customer']);
return false;
}

View File

@ -1,7 +1,10 @@
import { environment } from '../environments/environment';
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { LocationStrategy, HashLocationStrategy } from '@angular/common';
import { HttpClientModule } from '@angular/common/http';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
@ -21,7 +24,11 @@ import { CustomerGuard } from './_guards/customer.guard';
import { ApiService } from './providers/api-service';
import { OrgGraphsService } from './providers/org-graphs.service';
import { CustGraphsService } from './providers/cust-graphs.service';
import { OrgSnippetsService } from './providers/org-snippets.service';
import { CustSnippetsService } from './providers/cust-snippets.service';
import { CustPiesService } from './providers/cust-pies.service';
import { OrgPiesService } from './providers/org-pies.service';
// Layouts
import { FullLayoutComponent } from './layouts/full-layout.component';
@ -34,21 +41,30 @@ import { P500Component } from './pages/500.component';
// Submodules
import { AuthModule } from './auth/auth.module';
import { DashboardModule } from './dashboard/dashboard.module';
import { ChartsModule } from 'ng2-charts';
// import { StackedBarChartComponent } from './panels/stacked-bar.component';
import { FilterPipeModule } from 'ngx-filter-pipe';
@NgModule({
imports: [
BrowserModule,
HttpClientModule,
FormsModule,
FilterPipeModule,
ReactiveFormsModule,
NgxPaginationModule,
BsDropdownModule.forRoot(),
TabsModule.forRoot(),
AuthModule,
ChartsModule,
DashboardModule,
// Loaded last to allow for 404 catchall
AppRoutingModule,
],
declarations: [
AppComponent,
// StackedBarChartComponent,
FullLayoutComponent,
SimpleLayoutComponent,
NAV_DROPDOWN_DIRECTIVES,
@ -65,6 +81,10 @@ import { DashboardModule } from './dashboard/dashboard.module';
ApiService,
OrgGraphsService,
OrgSnippetsService,
CustGraphsService,
CustSnippetsService,
CustPiesService,
OrgPiesService,
{
provide: LocationStrategy,
useClass: HashLocationStrategy
@ -72,4 +92,10 @@ import { DashboardModule } from './dashboard/dashboard.module';
],
bootstrap: [ AppComponent ]
})
export class AppModule { }
export class AppModule {
constructor () {
if (environment.enableAnalytics) {
(<any>window).ga('create', environment.analyticsKey, 'auto');
}
}
}

View File

@ -2,7 +2,7 @@ import { Component, OnInit } from '@angular/core';
import { Validators, FormBuilder, FormGroup } from '@angular/forms';
import { ApiService } from '../providers/api-service';
import { Router, ActivatedRoute } from '@angular/router';
import 'rxjs/add/operator/map';
@Component({
templateUrl: 'login.component.html',
@ -40,15 +40,11 @@ export class LoginComponent implements OnInit {
}
onSubmit() {
console.log(this.signin.value);
this.api
.login(this.signin.value)
.subscribe(
result => {
console.log('logged in!');
this.loginStatus = 'success';
console.log(this.loginStatus);
this.router.navigate([this.returnUrl]);
},
error => {

View File

@ -106,6 +106,7 @@
<option value='R'>Arts, Entertainment & Recreation</option>
<option value='S'>Other Service Activities</option>
<option value='T'>Household Domestic Business</option>
<option value='U'>Extraterritorial Organisations and Bodies</option>
</select>
</div>

View File

@ -3,7 +3,7 @@ import { Validators, FormBuilder, FormGroup } from '@angular/forms';
import { ValidationManager } from 'ng2-validation-manager';
import { ApiService } from '../providers/api-service';
import {Router } from '@angular/router';
import 'rxjs/add/operator/map';
@Component({
templateUrl: 'register.component.html',
@ -78,24 +78,18 @@ export class RegisterComponent {
.register(data)
.subscribe(
result => {
console.log('registered!');
this.registerStatus = 'success';
console.log(this.registerStatus);
this.router.navigate(['/dashboard']);
},
error => {
console.log('Register Error');
console.log(error);
try {
console.log(error.error);
const jsonError = error.json();
console.log('boop');
this.registerStatusError = '"' + jsonError.error + '" Error, ' + jsonError.message;
this.registerStatusError = '"' + error.error.error + '" Error, ' + error.error.message;
} catch (e) {
this.registerStatusError = 'There was a server error, please try again later.';
}
this.registerStatus = 'send_failed';
console.log(this.registerStatus);
}
);
}
@ -122,24 +116,17 @@ export class RegisterComponent {
town: organisationForm.town,
postcode: organisationForm.postcode,
};
console.log(data);
this.api
.register(data)
.subscribe(
result => {
console.log('registered!');
this.registerStatus = 'success';
console.log(this.registerStatus);
this.router.navigate(['/dashboard']);
},
error => {
console.log('Register Error');
console.log(error);
try {
console.log(error.error);
const jsonError = error.json();
console.log('boop');
this.registerStatusError = '"' + jsonError.error + '" Error, ' + jsonError.message;
this.registerStatusError = '"' + error.error.error + '" Error, ' + error.error.message;
} catch (e) {
this.registerStatusError = 'There was a server error, please try again later.';
}

View File

@ -99,6 +99,7 @@
<option value='R'>Arts, Entertainment & Recreation</option>
<option value='S'>Other Service Activities</option>
<option value='T'>Household Domestic Business</option>
<option value='U'>Extraterritorial Organisations and Bodies</option>
</select>
<span class="help-block">Alter this if your business sector has changed.</span>
</div>

View File

@ -1,7 +1,7 @@
import { Component, OnInit, ViewChild } from '@angular/core';
import { Validators, FormBuilder, FormGroup } from '@angular/forms';
import { ApiService } from '../providers/api-service';
import 'rxjs/add/operator/map';
@Component({
templateUrl: 'account-edit.component.html',
@ -105,23 +105,17 @@ export class AccountEditComponent implements OnInit {
.accountEditUpdate(submitData)
.subscribe(
result => {
console.log('data submitted!');
this.submitStatus = 'success';
console.log(this.submitStatus);
},
error => {
console.log('Edit Error');
console.log(error);
try {
console.log(error.error);
const jsonError = error.json();
console.log('boop');
this.submitStatusError = '"' + jsonError.error + '" Error, ' + jsonError.message;
this.submitStatusError = '"' + error.error.error + '" Error, ' + error.error.message;
} catch (e) {
this.submitStatusError = 'There was a server error, please try again later.';
}
this.submitStatus = 'send_failed';
console.log(this.submitStatus);
}
);
}
@ -131,7 +125,6 @@ export class AccountEditComponent implements OnInit {
if (!this.settingForm.valid && !this.settingCustomerForm.valid) {
console.log('Not Valid!');
this.submitStatus = 'validation_failed';
console.log(this.submitStatus);
return;
}

View File

@ -24,6 +24,74 @@
<span class="help-block">Enter the amount spent, such as 5.35 for £5.35.</span>
</div>
</div>
<div class="form-group row">
<label class="col-md-3 form-control-label" for="text-input">Essential Purchase</label>
<div class="col-md-9">
<div class="input-group">
<input type="checkbox" class="mr-auto" [(ngModel)]="essentialPurchase" (ngModelChange)="transactionFormValidate()">
<span class="help-block">Tick if the purchase is deemed an essential purchase for budgeting purposes.</span>
</div>
</div>
</div>
<div class="form-group row">
<label class="col-md-3 form-control-label" for="text-input">Recurring Purchase</label>
<div class="col-md-9">
<div class="input-group">
<input type="checkbox" class="mr-auto" [(ngModel)]="recurringPurchase" (ngModelChange)="transactionFormValidate()">
<span class="help-block">Tick if the purchase frequently recurs, such as monthly.</span>
</div>
</div>
</div>
<div *ngIf="recurringPurchase" class="form-group row">
<label class="col-md-3 form-control-label" for="text-input"><strong>Recurring Period</strong></label>
<div class="col-md-9">
<div class="row">
<div class="col-md-6 btn-group-vertical">
<label class="btn btn-secondary mb-0" [class.active]="recurringType == 'daily'">
<input value="daily" type="radio" name="radios" style="display:none;" [(ngModel)]="recurringType" (ngModelChange)="transactionFormValidate()">Daily
</label>
<label class="btn btn-secondary mb-0" [class.active]="recurringType == 'weekly'">
<input value="weekly" type="radio" name="radios" style="display:none;" [(ngModel)]="recurringType" (ngModelChange)="transactionFormValidate()">Weekly
</label>
<label class="btn btn-secondary mb-0" [class.active]="recurringType == 'fortnightly'">
<input value="fortnightly" type="radio" name="radios" style="display:none;" [(ngModel)]="recurringType" (ngModelChange)="transactionFormValidate()">Fortnightly
</label>
<label class="btn btn-secondary mb-0" [class.active]="recurringType == 'monthly'">
<input value="monthly" type="radio" name="radios" style="display:none;" [(ngModel)]="recurringType" (ngModelChange)="transactionFormValidate()">Monthly
</label>
<label class="btn btn-secondary mb-0" [class.active]="recurringType == 'quarterly'">
<input value="quarterly" type="radio" name="radios" style="display:none;" [(ngModel)]="recurringType" (ngModelChange)="transactionFormValidate()">Quarterly
</label>
<label class="btn btn-secondary mb-0" [class.active]="recurringType == 'yearly'">
<input value="yearly" type="radio" name="radios" style="display:none;" [(ngModel)]="recurringType" (ngModelChange)="transactionFormValidate()">Yearly
</label>
</div>
</div>
<span class="help-block">Please give the period of time the purchase will recur from "Time of Transaction".</span>
</div>
</div>
<div class="form-group row">
<label class="col-md-3 form-control-label" for="text-input">Budget Type</label>
<div class="col-md-9">
<div class="row">
<div class="col-md-6 btn-group-vertical">
<label class="btn btn-secondary mb-0" [class.active]="categoryId == null">
<input value="" type="radio" name="radios" style="display:none;" [(ngModel)]="categoryId">Uncategorised
</label>
<label *ngFor="let category of leftCategoryList" class="btn btn-secondary mb-0" [class.active]="categoryId == category">
<input [value]="category" type="radio" name="radios" style="display:none;" [(ngModel)]="categoryId">{{ categoryList[category] }}
</label>
</div>
<div class="col-md-6 btn-group-vertical">
<label *ngFor="let category2 of rightCategoryList" class="btn btn-secondary mb-0" [class.active]=" categoryId == category2">
<input [value]="category2" type="radio" name="radios" style="display:none;" [(ngModel)]="categoryId">{{ categoryList[category2] }}
</label>
</div>
</div>
<span class="help-block"><strong>Optional:</strong> Choose the Budget Type for the majority of the purchase.</span>
</div>
</div>
<div class="form-group row">
<label class="col-md-3 form-control-label" for="text-input"><strong>Organisation Name</strong></label>
<div class="col-md-9">
@ -34,21 +102,21 @@
<org-table *ngIf="storeList.length > 0" [orgList]="storeList" (onClick)="addStore($event)"></org-table>
<div *ngIf="showAddStore">
<div class="form-group row">
<label class="col-md-3 form-control-label" for="text-input"><strong>Organisation Street Name</strong></label>
<label class="col-md-3 form-control-label" for="text-input">Organisation Street Name</label>
<div class="col-md-9">
<input type="text" class="form-control" placeholder="Which Street?" [(ngModel)]="submitOrg.street_name" (ngModelChange)="transactionFormValidate()">
<span class="help-block">Enter the street name where the organisation is located at.</span>
</div>
</div>
<div class="form-group row">
<label class="col-md-3 form-control-label" for="text-input"><strong>Organisation Town</strong></label>
<label class="col-md-3 form-control-label" for="text-input">Organisation Town</label>
<div class="col-md-9">
<input type="text" class="form-control" placeholder="Which Town?" [(ngModel)]="submitOrg.town" (ngModelChange)="transactionFormValidate()">
<span class="help-block">Enter the name of the town where the organisation is located at.</span>
</div>
</div>
<div class="form-group row">
<label class="col-md-3 form-control-label" for="text-input"><strong>Organisation Postcode</strong></label>
<label class="col-md-3 form-control-label" for="text-input">Organisation Postcode</label>
<div class="col-md-9">
<input type="text" class="form-control" placeholder="Postcode if known" [(ngModel)]="submitOrg.postcode" (ngModelChange)="transactionFormValidate()">
<span class="help-block">Enter the postcode where the organisation is located at.</span>
@ -89,14 +157,14 @@
<div class="form-group row">
<label class="col-md-3 form-control-label" for="text-input"><strong>Total amount of Employees</strong></label>
<div class="col-md-9">
<input type="number" class="form-control" formControlName="employee_amount" placeholder="0">
<input type="number" class="form-control" formControlName="employee_amount" placeholder="0" min="0">
<span class="help-block">Enter the amount of employees the organisation has for the entry month.</span>
</div>
</div>
<div class="form-group row">
<label class="col-md-3 form-control-label" for="text-input"><strong>Total amount of local Employees</strong></label>
<div class="col-md-9">
<input type="number" class="form-control" formControlName="local_employee_amount" placeholder="0">
<input type="number" class="form-control" formControlName="local_employee_amount" placeholder="0" min="0">
<span class="help-block">Enter the amount of employees that live locally to the organisation for the entry month.</span>
</div>
</div>

View File

@ -3,7 +3,7 @@ import { Validators, FormBuilder, FormGroup } from '@angular/forms';
import { ApiService } from '../providers/api-service';
import { OrgTableComponent } from '../shared/org-table.component';
import * as moment from 'moment';
import 'rxjs/add/operator/map';
@Component({
templateUrl: 'add-data.component.html',
@ -30,6 +30,9 @@ export class AddDataComponent implements OnInit {
organisationTown: string;
organisationPostcode: string;
amount: number;
essentialPurchase = false;
recurringPurchase = false;
recurringType: string;
transactionAdditionType = 1;
storeList = [];
showAddStore = false;
@ -37,6 +40,11 @@ export class AddDataComponent implements OnInit {
transactionFormInvalid = true;
myDate: any;
minDate: any;
categoryList: any;
categoryIdList: any;
leftCategoryList: number[] = [];
rightCategoryList: string[] = [];
categoryId: number;
constructor(
private formBuilder: FormBuilder,
@ -64,6 +72,17 @@ export class AddDataComponent implements OnInit {
});
this.myDate = moment().format('YYYY-MM-DD[T]HH:mm');
// this.myDate = new Date().toISOString().slice(0, 16);
this.api.categoryList().subscribe(
result => {
this.categoryList = result.categories;
this.categoryIdList = Object.keys(this.categoryList);
this.setCategoryList(this.categoryIdList);
},
error => {
console.log('Retrieval Error');
console.log( error._body );
}
);
}
ngOnInit(): void {
@ -71,6 +90,12 @@ export class AddDataComponent implements OnInit {
this.accountType = localStorage.getItem('usertype');
}
private setCategoryList(data: any) {
let halfLength = Math.floor(data.length / 2);
this.leftCategoryList = data.splice(0, halfLength);
this.rightCategoryList = data;
}
getMinDate() {
// gets the April 1st date of the current year
const aprilDate = moment().month(3).date(1);
@ -149,12 +174,16 @@ export class AddDataComponent implements OnInit {
}
transactionFormValidate() {
if (this.submitOrg.name.length === 0 ||
this.submitOrg.town.length === 0 ||
this.amount === 0 ) {
this.transactionFormInvalid = true;
} else {
this.transactionFormStatus = null;
if (this.submitOrg.name.length &&
this.amount &&
(this.recurringPurchase &&
this.recurringType ||
!this.recurringPurchase &&
!this.recurringType)) {
this.transactionFormInvalid = false;
} else {
this.transactionFormInvalid = true;
}
}
@ -170,6 +199,9 @@ export class AddDataComponent implements OnInit {
transaction_value : this.amount,
purchase_time : purchaseTime,
organisation_id : this.organisationId,
category : this.categoryId,
essential : this.essentialPurchase,
recurring : this.recurringType,
};
break;
case 2:
@ -178,6 +210,9 @@ export class AddDataComponent implements OnInit {
transaction_value : this.amount,
purchase_time : purchaseTime,
organisation_id : this.organisationId,
category : this.categoryId,
essential : this.essentialPurchase,
recurring : this.recurringType,
};
break;
case 3:
@ -189,6 +224,9 @@ export class AddDataComponent implements OnInit {
street_name : this.submitOrg.street_name,
town : this.submitOrg.town,
postcode : this.submitOrg.postcode,
category : this.categoryId,
essential : this.essentialPurchase,
recurring : this.recurringType,
};
break;
}
@ -199,31 +237,22 @@ export class AddDataComponent implements OnInit {
.subscribe(
result => {
if ( result.success === true ) {
console.log('Successful Upload');
console.log(result);
this.transactionFormStatus = 'success';
console.log(this.transactionFormStatus);
this.resetForm();
} else {
console.log('Upload Error');
this.transactionFormStatusError = JSON.stringify(result.status) + 'Error, ' + JSON.stringify(result.message);
this.transactionFormStatus = 'send_failed';
console.log(this.transactionFormStatus);
}
},
error => {
console.log('Upload Error');
console.log(error);
try {
console.log(error.error);
const jsonError = error.json();
console.log('boop');
this.transactionFormStatusError = '"' + jsonError.error + '" Error, ' + jsonError.message;
this.transactionFormStatusError = '"' + error.error.error + '" Error, ' + error.error.message;
} catch (e) {
this.transactionFormStatusError = 'There was a server error, please try again later.';
}
this.transactionFormStatus = 'send_failed';
console.log(this.transactionFormStatus);
}
);
}
@ -239,6 +268,9 @@ export class AddDataComponent implements OnInit {
this.amount = null;
this.transactionFormInvalid = true;
this.showAddStore = false;
this.essentialPurchase = false;
this.recurringPurchase = false;
this.recurringType = null;
}
onSubmitPayroll() {
@ -248,14 +280,11 @@ export class AddDataComponent implements OnInit {
.orgPayroll(this.payrollForm.value)
.subscribe(
result => {
console.log('data submitted!');
this.payrollFormStatus = 'success';
console.log(this.payrollFormStatus);
},
error => {
console.log( error._body );
this.payrollFormStatus = 'send_failed';
console.log(this.payrollFormStatus);
}
);
}
@ -267,14 +296,10 @@ export class AddDataComponent implements OnInit {
.orgSupplier(this.singleSupplierForm.value)
.subscribe(
result => {
console.log('data submitted!');
this.singleSupplierFormStatus = 'success';
console.log(this.singleSupplierFormStatus);
},
error => {
console.log( error._body );
this.singleSupplierFormStatus = 'send_failed';
console.log(this.singleSupplierFormStatus);
}
);
}
@ -286,14 +311,10 @@ export class AddDataComponent implements OnInit {
.orgEmployee(this.employeeForm.value)
.subscribe(
result => {
console.log('data submitted!');
this.employeeFormStatus = 'success';
console.log(this.employeeFormStatus);
},
error => {
console.log( error._body );
this.employeeFormStatus = 'send_failed';
console.log(this.employeeFormStatus);
}
);
}

View File

@ -0,0 +1,172 @@
<div class="animated fadeIn">
<div class=row>
<div *ngIf="weekList1" class="col-md-6">
<div class="card">
<div class="card-block">
<div class="row">
<div class="col-12">
<h4 class="card-title float-left mb-0">Purchases Last Week</h4>
</div><!--/.col-->
</div><!--/.row-->
<div class="chart-wrapper">
<ul *ngIf="weekList1" class="horizontal-bars type-2">
<li *ngIf="weekEssential1">
<span class="title">Essential Purchases</span>
<span class="value">{{ ( weekEssential1 ? weekEssential1.value : 0 ) | currency:'GBP':'symbol':'1.2-2' }} <span class="text-muted small">
({{ ( weekEssential1 ? weekEssential1.value : 0 ) / weekListValueSum1 | percent:'1.0-0' }})</span></span>
<div class="bars">
<div class="progress" style="height: 6px;">
<div class="progress-bar bg-success" role="progressbar"
[style.width]="(weekEssential1.value || 0 ) / weekListValueSum1 | percent:'1.0-0'" aria-valuemin="0" aria-valuemax="100"></div>
</div>
</div>
</li>
<li *ngFor="let categoryEntry of weekList1 | slice:0:categoryLimit1; let i=index;">
<span class="title">{{ categoryEntry.category || 'Uncategorised' }}</span>
<span class="value">{{ ( categoryEntry.value || 0 ) | currency:'GBP':'symbol':'1.2-2' }} <span class="text-muted small">
({{ (categoryEntry.value || 0 ) / weekListValueSum1 | percent:'1.0-0' }})</span></span>
<div class="bars">
<div class="progress" style="height: 6px;">
<div class="progress-bar bg-success" role="progressbar"
[style.width]="(categoryEntry.value || 0 ) / weekListValueSum1 | percent:'1.0-0'" aria-valuemin="0" aria-valuemax="100"></div>
</div>
</div>
</li>
<div *ngIf="weekList1">
<li *ngIf="weekList1.length > categoryLimit1 && disableCategoryButton1 == false" class="divider text-center">
<button type="button" class="btn btn-sm btn-link text-muted" (click)="loadMore1()"><i class="icon-options"></i></button>
</li>
</div>
</ul>
</div>
</div>
</div>
</div><!--/.col-->
<div *ngIf="weekList2" class="col-md-6">
<div class="card">
<div class="card-block">
<div class="row">
<div class="col-12">
<h4 class="card-title float-left mb-0">Purchases 1 Week Ago</h4>
</div><!--/.col-->
</div><!--/.row-->
<div class="chart-wrapper">
<ul *ngIf="weekList2" class="horizontal-bars type-2">
<li *ngIf="weekEssential2">
<span class="title">Essential Purchases</span>
<span class="value">{{ ( weekEssential2 ? weekEssential2.value : 0 ) | currency:'GBP':'symbol':'1.2-2' }} <span class="text-muted small">
({{ ( weekEssential2 ? weekEssential2.value : 0 ) / weekListValueSum2 | percent:'1.0-0' }})</span></span>
<div class="bars">
<div class="progress" style="height: 6px;">
<div class="progress-bar bg-success" role="progressbar"
[style.width]="(weekEssential2.value || 0 ) / weekListValueSum2 | percent:'1.0-0'" aria-valuemin="0" aria-valuemax="100"></div>
</div>
</div>
</li>
<li *ngFor="let categoryEntry of weekList2 | slice:0:categoryLimit2; let i=index;">
<span class="title">{{ categoryEntry.category || 'Uncategorised' }}</span>
<span class="value">{{ ( categoryEntry.value || 0 ) | currency:'GBP':'symbol':'1.2-2' }} <span class="text-muted small">
({{ (categoryEntry.value || 0 ) / weekListValueSum2 | percent:'1.0-0' }})</span></span>
<div class="bars">
<div class="progress" style="height: 6px;">
<div class="progress-bar bg-success" role="progressbar"
[style.width]="(categoryEntry.value || 0 ) / weekListValueSum2 | percent:'1.0-0'" aria-valuemin="0" aria-valuemax="100"></div>
</div>
</div>
</li>
<div *ngIf="weekList2">
<li *ngIf="weekList2.length > categoryLimit2 && disableCategoryButtonFirst == false" class="divider text-center">
<button type="button" class="btn btn-sm btn-link text-muted" (click)="loadMore2()"><i class="icon-options"></i></button>
</li>
</div>
</ul>
</div>
</div>
</div>
</div><!--/.col-->
<div *ngIf="weekList3" class="col-md-6">
<div class="card">
<div class="card-block">
<div class="row">
<div class="col-12">
<h4 class="card-title float-left mb-0">Purchases 2 Weeks Ago</h4>
</div><!--/.col-->
</div><!--/.row-->
<div class="chart-wrapper">
<ul *ngIf="weekList3" class="horizontal-bars type-2">
<li *ngIf="weekEssential3">
<span class="title">Essential Purchases</span>
<span class="value">{{ ( weekEssential3 ? weekEssential3.value : 0 ) | currency:'GBP':'symbol':'1.2-2' }} <span class="text-muted small">
({{ ( weekEssential3 ? weekEssential3.value : 0 ) / weekListValueSum3 | percent:'1.0-0' }})</span></span>
<div class="bars">
<div class="progress" style="height: 6px;">
<div class="progress-bar bg-success" role="progressbar"
[style.width]="(weekEssential3.value || 0 ) / weekListValueSum3 | percent:'1.0-0'" aria-valuemin="0" aria-valuemax="100"></div>
</div>
</div>
</li>
<li *ngFor="let categoryEntry of weekList3 | slice:0:categoryLimit3; let i=index;">
<span class="title">{{ categoryEntry.category || 'Uncategorised' }}</span>
<span class="value">{{ ( categoryEntry.value || 0 ) | currency:'GBP':'symbol':'1.2-2' }} <span class="text-muted small">
({{ (categoryEntry.value || 0 ) / weekListValueSum3 | percent:'1.0-0' }})</span></span>
<div class="bars">
<div class="progress" style="height: 6px;">
<div class="progress-bar bg-success" role="progressbar"
[style.width]="(categoryEntry.value || 0 ) / weekListValueSum3 | percent:'1.0-0'" aria-valuemin="0" aria-valuemax="100"></div>
</div>
</div>
</li>
<div *ngIf="weekList3">
<li *ngIf="weekList3.length > categoryLimit3 && disableCategoryButtonFirst == false" class="divider text-center">
<button type="button" class="btn btn-sm btn-link text-muted" (click)="loadMore3()"><i class="icon-options"></i></button>
</li>
</div>
</ul>
</div>
</div>
</div>
</div><!--/.col-->
<div *ngIf="weekList4" class="col-md-6">
<div class="card">
<div class="card-block">
<div class="row">
<div class="col-12">
<h4 class="card-title float-left mb-0">Purchases 3 Weeks Ago</h4>
</div><!--/.col-->
</div><!--/.row-->
<div class="chart-wrapper">
<ul *ngIf="weekList4" class="horizontal-bars type-2">
<li *ngIf="weekEssential4">
<span class="title">Essential Purchases</span>
<span class="value">{{ ( weekEssential4 ? weekEssential4.value : 0 ) | currency:'GBP':'symbol':'1.2-2' }} <span class="text-muted small">
({{ ( weekEssential4 ? weekEssential4.value : 0 ) / weekListValueSum4 | percent:'1.0-0' }})</span></span>
<div class="bars">
<div class="progress" style="height: 6px;">
<div class="progress-bar bg-success" role="progressbar"
[style.width]="(weekEssential4.value || 0 ) / weekListValueSum4 | percent:'1.0-0'" aria-valuemin="0" aria-valuemax="100"></div>
</div>
</div>
</li>
<li *ngFor="let categoryEntry of weekList4 | slice:0:categoryLimit4; let i=index;">
<span class="title">{{ categoryEntry.category || 'Uncategorised' }}</span>
<span class="value">{{ ( categoryEntry.value || 0 ) | currency:'GBP':'symbol':'1.2-2' }} <span class="text-muted small">
({{ (categoryEntry.value || 0 ) / weekListValueSum4 | percent:'1.0-0' }})</span></span>
<div class="bars">
<div class="progress" style="height: 6px;">
<div class="progress-bar bg-success" role="progressbar"
[style.width]="(categoryEntry.value || 0 ) / weekListValueSum4 | percent:'1.0-0'" aria-valuemin="0" aria-valuemax="100"></div>
</div>
</div>
</li>
<div *ngIf="weekList4">
<li *ngIf="weekList4.length > categoryLimit4 && disableCategoryButtonFirst == false" class="divider text-center">
<button type="button" class="btn btn-sm btn-link text-muted" (click)="loadMore4()"><i class="icon-options"></i></button>
</li>
</div>
</ul>
</div>
</div>
</div>
</div><!--/.col-->
</div><!--/.row-->
</div>

View File

@ -0,0 +1,119 @@
import { Directive, Component, OnInit } from '@angular/core';
import { ApiService } from '../providers/api-service';
import { DataType } from '../shared/data-types.enum';
import * as moment from 'moment';
@Component({
templateUrl: 'category-month.component.html'
})
export class CategoryMonthComponent implements OnInit {
disableCategoryButton1: boolean = false;
disableCategoryButton2: boolean = false;
disableCategoryButton3: boolean = false;
disableCategoryButton4: boolean = false;
weekPurchaseList = {
first: 0,
};
weekList1 = [];
weekList2 = [];
weekList3 = [];
weekList4 = [];
weekListValueSum1: number = 0;
weekListValueSum2: number = 0;
weekListValueSum3: number = 0;
weekListValueSum4: number = 0;
weekEssential1: number = 0;
weekEssential2: number = 0;
weekEssential3: number = 0;
weekEssential4: number = 0;
dayList: any[] = [];
valueList: number[] = [];
myWeek1: any;
myWeek2: any;
myWeek3: any;
myWeek4: any;
categoryLimit1: number = 6;
categoryLimit2: number = 6;
categoryLimit3: number = 6;
categoryLimit4: number = 6;
constructor(
private api: ApiService,
) {
this.setDate();
this.api.categoryTransactionList().subscribe(
result => {
this.setData(result);
},
error => {
console.log('Retrieval Error');
console.log( error._body );
}
);
}
ngOnInit(): void {
}
private setDate () {
this.myWeek1 = moment().startOf('isoWeek').format('YYYY-MM-DD');
this.myWeek2 = moment(this.myWeek1).subtract(1, 'weeks').format('YYYY-MM-DD');
this.myWeek3 = moment(this.myWeek2).subtract(1, 'weeks').format('YYYY-MM-DD');
this.myWeek4 = moment(this.myWeek3).subtract(1, 'weeks').format('YYYY-MM-DD');
}
private setData (data: any) {
function prop<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
this.weekList1 = prop(data.data.categories, this.myWeek1);
this.weekList2 = prop(data.data.categories, this.myWeek2);
this.weekList3 = prop(data.data.categories, this.myWeek3);
this.weekList4 = prop(data.data.categories, this.myWeek4);
this.getMaxValue(this.weekList1, this.weekList2, this.weekList3, this.weekList4);
this.weekEssential1 = prop(data.data.essentials, this.myWeek1);
this.weekEssential2 = prop(data.data.essentials, this.myWeek2);
this.weekEssential3 = prop(data.data.essentials, this.myWeek3);
this.weekEssential4 = prop(data.data.essentials, this.myWeek4);
}
private getMaxValue (data1: any,
data2: any,
data3: any,
data4: any) {
if (data1) {
this.weekListValueSum1 = data1.reduce(function (s, a) {return s + a.value;}, 0);
}
if (data2) {
this.weekListValueSum2 = data2.reduce(function (s, a) {return s + a.value;}, 0);
}
if (data3) {
this.weekListValueSum3 = data3.reduce(function (s, a) {return s + a.value;}, 0);
}
if (data4) {
this.weekListValueSum4 = data4.reduce(function (s, a) {return s + a.value;}, 0);
}
}
private loadMore1 () {
this.disableCategoryButton1 = true;
this.categoryLimit1 = 20;
}
private loadMore2 () {
this.disableCategoryButton2 = true;
this.categoryLimit2 = 20;
}
private loadMore3 () {
this.disableCategoryButton3 = true;
this.categoryLimit3 = 20;
}
private loadMore4 () {
this.disableCategoryButton4 = true;
this.categoryLimit4 = 20;
}
}

View File

@ -1,131 +1,115 @@
<div class="animated fadeIn">
<div class="card">
<div class="card-footer">
<ul>
<li>
<div class="text-muted">My Points</div>
<strong>{{ basicStats.user_sum / 10 | number:'1.0-0' }}</strong>
</li>
<li>
<div class="text-muted">My Rank</div>
<div *ngIf="basicStats.user_position == 0" class="statuscontent">
<strong>Unranked</strong>
</div>
<div *ngIf="basicStats.user_position != 0" class="statuscontent">
<strong>{{ basicStats.user_position }}</strong>
</div>
</li>
</ul>
</div>
<div class="card-footer">
<ul>
<li>
<div class="text-muted">My Total Spend</div>
<strong>{{ basicStats.user_sum | currency:'GBP':'symbol':'1.2-2' }}</strong>
</li>
<li>
<div class="text-muted">Value to Local Economy</div>
<strong>{{ basicStats.user_sum * 2.3 | currency:'GBP':'symbol':'1.2-2' }}</strong>
</li>
</ul>
</div>
</div>
<snippet-bar-cust></snippet-bar-cust>
<div class="row">
<div class="col-sm-6 col-lg-3">
<div class="card card-inverse card-primary">
<div class="card-block">
<div class="h4 mb-0">{{ basicStats.today_sum | currency:'GBP':'symbol':'1.2-2' }}</div>
<div>Total Today</div>
<!-- <div class="progress progress-white progress-xs mt-3">
<div class="progress-bar" role="progressbar" style="width: 100%" attr.aria-valuenow="100" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<small class="text-muted">Lorem ipsum dolor sit amet enim.</small> -->
</div>
</div>
</div><!--/.col-->
<div class="col-sm-6 col-lg-3">
<div class="card card-inverse card-primary">
<div class="card-block">
<div class="h4 mb-0">{{ basicStats.today_sum / (basicStats.today_count ? basicStats.today_count : 1) | currency:'GBP':'symbol':'1.2-2' }}</div>
<div>Avg. Spend Today</div>
<!-- <div class="progress progress-white progress-xs mt-3">
<div class="progress-bar" role="progressbar" style="width: 100%" attr.aria-valuenow="100" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<small class="text-muted">Lorem ipsum dolor sit amet enim.</small> -->
</div>
</div>
</div><!--/.col-->
<div class="col-sm-6 col-lg-3">
<div class="card card-inverse card-primary">
<div class="card-block">
<div class="h4 mb-0">{{ basicStats.week_sum | currency:'GBP':'symbol':'1.2-2' }}</div>
<div>Last Week Total</div>
<!-- <div class="progress progress-white progress-xs mt-3">
<div class="progress-bar" role="progressbar" style="width: 100%" attr.aria-valuenow="100" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<small class="text-muted">Lorem ipsum dolor sit amet enim.</small> -->
</div>
</div>
</div><!--/.col-->
<div class="col-sm-6 col-lg-3">
<div class="card card-inverse card-primary">
<div class="card-block">
<div class="h4 mb-0">{{ basicStats.week_sum / (basicStats.week_count ? basicStats.week_count : 1) | currency:'GBP':'symbol':'1.2-2' }}</div>
<div>Last Week Avg. Spend</div>
<!-- <div class="progress progress-white progress-xs mt-3">
<div class="progress-bar" role="progressbar" style="width: 100%" attr.aria-valuenow="100" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<small class="text-muted">Lorem ipsum dolor sit amet enim.</small> -->
</div>
</div>
</div><!--/.col-->
<div class="col-sm-6 col-lg-3">
<div class="card card-inverse card-primary">
<div class="card-block">
<div class="h4 mb-0">{{ basicStats.month_sum | currency:'GBP':'symbol':'1.2-2' }}</div>
<div>Last Month Total</div>
<!-- <div class="progress progress-white progress-xs mt-3">
<div class="progress-bar" role="progressbar" style="width: 100%" attr.aria-valuenow="100" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<small class="text-muted">Lorem ipsum dolor sit amet enim.</small> -->
</div>
</div>
</div><!--/.col-->
<div class="col-sm-6 col-lg-3">
<div class="card card-inverse card-primary">
<div class="card-block">
<div class="h4 mb-0">{{ basicStats.month_sum / (basicStats.month_count ? basicStats.month_count : 1) | currency:'GBP':'symbol':'1.2-2' }}</div>
<div>Last Month Avg. Spend</div>
<!-- <div class="progress progress-white progress-xs mt-3">
<div class="progress-bar" role="progressbar" style="width: 100%" attr.aria-valuenow="100" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<small class="text-muted">Lorem ipsum dolor sit amet enim.</small> -->
</div>
</div>
</div><!--/.col-->
<div class="col-sm-6 col-lg-3">
<div class="card card-inverse card-primary">
<div class="card-block">
<div class="h4 mb-0">{{ basicStats.user_sum | currency:'GBP':'symbol':'1.2-2' }}</div>
<div>User Total</div>
<!-- <div class="progress progress-white progress-xs mt-3">
<div class="progress-bar" role="progressbar" style="width: 100%" attr.aria-valuenow="100" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<small class="text-muted">Lorem ipsum dolor sit amet enim.</small> -->
</div>
</div>
</div><!--/.col-->
<div class="col-sm-6 col-lg-3">
<div class="card card-inverse card-primary">
<div class="card-block">
<div class="h4 mb-0">{{ basicStats.user_sum / (basicStats.user_count ? basicStats.user_count : 1) | currency:'GBP':'symbol':'1.2-2' }}</div>
<div>User Avg. Spend</div>
<!-- <div class="progress progress-white progress-xs mt-3">
<div class="progress-bar" role="progressbar" style="width: 100%" attr.aria-valuenow="100" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<small class="text-muted">Lorem ipsum dolor sit amet enim.</small> -->
</div>
</div>
<div *ngFor="let widget of widgetList" class="col-sm-6 col-lg-3">
<widget-graph *ngIf="widget.type == 'graph'"
[graphName]="widget.name"
[graphTitle]="widget.title"
[graphIcon]="widget.icon"
[dataType]="widget.dataType">
</widget-graph>
</div><!--/.col-->
</div><!--/.row-->
</div>
<div class=row>
<div class="col-xl-6">
<panel-pie></panel-pie><!--All Purchases -->
<!-- <div class="demo-container" ng-app="stacked-bar" ng-controller="stacked-bar">
<div id="stacked-bar" dx-chart="chartOptions"></div>
</div> -->
</div><!--/.col-->
<!--<div *ngIf="showCategoryDoughnutChart" class="col-xl-6">
<div class="card"> -->
<!-- <body style="background-color:rgb(0,0,0);"> -->
<!-- <div class="card-block">
<div class="row">
<div class="col-12">
<h4 class="card-title mb-0">Spending by Category</h4>
</div>
</div>
<div class="chart-wrapper">
<canvas baseChart class="chart"
[datasets]="doughnutChartDataCategory"
[labels]="doughnutChartLabelsCategory"
[options]="doughnutChartOptionsCategory"
[colors]= "doughnutChartColoursCategory"
[legend]="chartLegend"
[chartType]="chartType"
(chartHover)="chartHovered($event)"
(chartClick)="chartClicked($event)"></canvas>
</div>
</div> -->
<!-- </body> -->
<!-- </div> --><!--/.col-->
<div *ngIf="showEssentialBarChart" class="col-xl-6">
<div class="card">
<div class="card-block">
<div class="row">
<div class="col-12">
<h4 class="card-title mb-0">No. of Essential Purchases</h4>
</div>
</div>
<div class="chart-wrapper">
<canvas baseChart class="chart"
[datasets]="barChartDataEssential"
[labels]="barChartLabelsEssential"
[options]="barChartOptionsEssential"
[chartType]="barChartTypeEssential"
(chartHover)="chartHovered($event)"
(chartClick)="chartClicked($event)"></canvas>
</div>
</div>
</div>
</div><!--/.col-->
<div class="col-xl-6">
<div class="card">
<div class="card-block">
<div class="row">
<div class="col-12">
<h4 class="card-title float-left mb-0">Your Purchases by Category</h4>
</div><!--/.col-->
</div><!--/.row-->
<div class="chart-wrapper">
<canvas baseChart class="chart"
[datasets]="barChartDataCategory"
[labels]="barChartLabelsCategory"
[options]="barChartOptionsCategory"
[colors]="barChartColoursCategory"
[legend]="barChartLegendCategory"
[chartType]="barChartTypeCategory"
(chartHover)="chartHovered($event)"
(chartClick)="chartClicked($event)"></canvas>
</div>
</div>
</div>
</div><!--/.col-->
<div class="col-xl-6">
<div class="card">
<div class="card-block">
<div class="row">
<div class="col-12">
<h4 class="card-title float-left mb-0"> Global Puchases by Category</h4>
</div>
<div class="col-12">
<div *ngIf="showTotalCategoryList" class="chart-wrapper">
<ul class="icons-list">
<!-- New loop -->
<li *ngFor="let category of totalCategoryList | slice:0:totalCategoryLimit; let i=index">
<i [ngClass]="['icon-' + category.icon, getBootstrapColour(i)]"></i>
<div class="desc">
<div class="title">{{ category.category || 'N/A' }}</div>
</div>
<div class="value">
<div class="small text-muted">Bought</div>
<strong>{{ category.value || 'N/A' }}</strong>
</div>
</li>
<li *ngIf="totalCategoryList.length > totalCategoryLimit && disableCategoryButton == false" class="divider text-center">
<button type="button" class="btn btn-sm btn-link text-muted" (click)="categoryLoadMore()"><i class="icon-options"></i></button>
</li>
</ul>
</div>
</div>
</div><!--/.row-->
</div>
</div>
</div>

View File

@ -1,16 +1,25 @@
import { Directive, Component, OnInit } from '@angular/core';
import { CurrencyPipe } from '@angular/common';
import { ApiService } from '../providers/api-service';
import { Router } from '@angular/router';
import { ChartOptions, ChartType, ChartDataSets } from 'chart.js';
import { GraphWidget } from '../widgets/graph-widget.component';
import { Color, Label } from 'ng2-charts';
import { CustBarSnippetComponent } from '../snippets/cust-snippet-bar.component';
import { PiePanel } from '../panels/pie-panel.component';
import { DataType } from '../shared/data-types.enum';
import * as moment from 'moment';
import { MoreStuffComponent } from '../dashboard/more-graphs-and-tables.component';
// import { StackedBarChartComponent } from '../panels/stacked-bar.component';
interface SuppliersComponent {
name : string;
}
@Component({
templateUrl: 'dashboard-customer.component.html'
})
export class DashboardCustomerComponent implements OnInit {
customersThisMonth: any;
moneySpentThisMonth: any;
pointsTotal: any;
averageTransactionToday: any;
/* Setting up dashboard's main variables*/
name: any;
@ -19,27 +28,178 @@ export class DashboardCustomerComponent implements OnInit {
trends: any;
myRank: any;
username: any;
maxPurchase: number = 0;
basicStats = {
today_sum: 0,
today_count: 0,
week_sum: 0,
week_count: 0,
month_sum: 0,
month_count: 0,
user_sum: 0,
user_count: 0,
global_sum: 0,
global_count: 0,
user_position: 0,
disableCategoryButton: boolean = false;
public bootstrapColours: string[] = ['bg-primary', 'bg-secondary', 'bg-success',
'bg-danger', 'bg-warning', 'bg-info'];
public chartType = 'doughnut';
public chartLegend = true;
public doughnutChartDataCategory: any[] = [];
public doughnutChartLabelsCategory: string[] = [];
public doughnutChartColoursCategory: any[] = [
{
backgroundColor:[
'#ffa1b5',
'#3cde52',
'#52afed',
'#c133e3',
'#f7fa08',
'#75152d',
'#ee12ee',
'#15eaea',
'#eaa015',
'#ea1515',
'#2d4fcc'
]
}];
public doughnutChartOptionsCategory:any = {
tooltips: {
callbacks: {
label: (tooltip, data) => {
return this.tooltipLabelCallback(tooltip, data);
},
},
},
}
myWeek1: any;
weekList1 = [];
public purchaseNotEssential: number;
public purchaseEssential: number;
public showEssentialBarChart = false;
public showCategoryBarChart = false;
public showCategoryDoughnutChart = false;
public barChartDataEssential:any[]=[
{data: 0, label: 'Essential', stack: '1'},
{data: 0, label: 'Non-Essential', stack: '1'},
];
public barChartLabelsEssential:string[] = ['All Purchases'];
public barChartOptionsEssential:any = {
responsive: true,
scales:{
xAxes:[{
scaleLabel: {
display:true,
},
stacked:true,
}],
}
};
public barChartTypeEssential:string = 'horizontalBar';
public barChartOptionsCategory:any = {
scaleShowVerticalLines: false,
responsive: true,
scales: {
yAxes: [{
ticks: {
beginAtZero: true,
callback: label => `£${label}`
}
}]
},
tooltips: {
callbacks: {
label: (tooltip, data) => {
return this.tooltipLabelCallback(tooltip, data);
},
},
},
};
public barChartTypeCategory:string = 'bar';
public barChartLegendCategory:boolean = false;
public barChartDataCategory:any[]=[];
public barChartLabelsCategory:string[] = [];
public barChartColoursCategory: any[] = [
{
backgroundColor:[
'#ffa1b5',
'#3cde52',
'#52afed',
'#c133e3',
'#f7fa08',
'#75152d',
'#ee12ee',
'#15eaea',
'#eaa015',
'#ea1515',
'#2d4fcc'
]
}];
weekPurchaseList = {
first: 0,
second: 0,
max: 0,
sum: 0,
count: 0,
};
showTotalCategoryList: boolean = false;
totalCategoryLimit: number = 10;
totalCategoryList: any[]=[];
// Graph widgets
public widgetList = [
{
type: 'graph',
name: 'total_last_week',
icon: 'icon-diamond',
title: 'Last Week Total',
dataType: DataType.currency,
},
{
type: 'graph',
name: 'avg_spend_last_week',
icon: 'icon-diamond',
title: 'Last Week Avg. Spend',
dataType: DataType.currency,
},
{
type: 'graph',
name: 'total_last_month',
title: 'Last Month Total',
dataType: DataType.currency,
},
{
type: 'graph',
name: 'avg_spend_last_month',
title: 'Last Month Avg. Spend',
dataType: DataType.currency,
},
];
constructor(
private api: ApiService,
private currencyPipe: CurrencyPipe,
) {
this.api.basicStats().subscribe(
this.setDate();
this.api.customerStats().subscribe(
result => {
this.basicStats = result;
this.setWeekPurchaseList(result.weeks);
this.setWeekData(result);
this.setChartData(result.data.cat_total);
this.totalCategoryList = result.data.cat_list;
if (this.totalCategoryList) {
this.showTotalCategoryList = true;
}
this.purchaseEssential = result.data.essentials.purchase_no_essential_total;
this.purchaseNotEssential = result.data.essentials.purchase_no_total - this.purchaseEssential;
this.barChartDataEssential = [
{data: [this.purchaseEssential], label: 'Essential', stack: '1'},
{data: [this.purchaseNotEssential], label: 'Non-Essential', stack: '1'},
];
this.showEssentialBarChart = true;
},
error => {
console.log('Retrieval Error');
@ -48,6 +208,77 @@ export class DashboardCustomerComponent implements OnInit {
);
}
private setChartData(dataCat: any) {
this.barChartLabelsCategory = Object.keys(dataCat);
let barChartDataCategoryInitial = Object.keys(dataCat).map(key => dataCat[key]);
this.barChartDataCategory = [
{data: barChartDataCategoryInitial, label: 'Series A'},
];
this.showCategoryBarChart = true;
if (this.weekList1) {
let doughnutChartDataCategoryInitial = this.weekList1.map(function(a) {return a.value;});
this.doughnutChartDataCategory = [
{data: doughnutChartDataCategoryInitial, label: 'Series A'},
];
// setTimeout is currently a workaround for ng2-charts labels
setTimeout(() => this.doughnutChartLabelsCategory = this.weekList1.map(function(a) {return a.category;}), 0);
this.showCategoryDoughnutChart = true;
}
}
private setDate () {
this.myWeek1 = moment().startOf('isoWeek').format('YYYY-MM-DD');
}
private setWeekData (data: any) {
function prop<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
this.weekList1 = prop(data.data.categories, this.myWeek1);
}
public setWeekPurchaseList (data: any) {
this.weekPurchaseList = {
first: data.first,
second: data.second,
max: data.max,
sum: data.sum,
count: data.count,
};
}
private categoryLoadMore () {
this.disableCategoryButton = true;
this.totalCategoryLimit = 30;
}
public getBootstrapColour(index: number) {
return this.bootstrapColours[index % this.bootstrapColours.length];
}
public convertHex(hex: string, opacity: number) {
hex = hex.replace('#', '');
const r = parseInt(hex.substring(0, 2), 16);
const g = parseInt(hex.substring(2, 4), 16);
const b = parseInt(hex.substring(4, 6), 16);
const rgba = 'rgba(' + r + ', ' + g + ', ' + b + ', ' + opacity / 100 + ')';
return rgba;
}
private tooltipLabelCallback(tooltipItem: any, data: any) {
var dataset = data.datasets[tooltipItem.datasetIndex];
var value = dataset.data[tooltipItem.index];
return this.currencyPipe.transform(value, 'GBP', 'symbol', '1.2-2');
}
// events
public chartClicked(e: any): void {
}
public chartHovered(e: any): void {
}
ngOnInit(): void {
}
}

View File

@ -1,3 +1,4 @@
<div class="animated fadeIn">
<snippet-bar-org></snippet-bar-org>
<div class="row">
@ -6,8 +7,107 @@
[graphName]="widget.name"
[graphTitle]="widget.title"
[graphIcon]="widget.icon"
[dataType]="widget.dataType"></widget-graph>
[dataType]="widget.dataType">
</widget-graph>
</div><!--/.col-->
</div><!--/.row-->
<panel-graph></panel-graph>
</div>
<div class=row>
<div class="col-xl-6">
<div class="card">
<div class="card-block">
<div class="row">
<div class="col-12">
<h4 class="card-title mb-0">Number of Essential Purchases</h4>
</div>
</div>
<div *ngIf="showEssentialBarChart" class="chart-wrapper">
<canvas baseChart class="chart"
[datasets]="barChartDataEssential"
[labels]="barChartLabelsEssential"
[options]="barChartOptionsEssential"
[chartType]="barChartTypeEssential"
(chartHover)="chartHovered($event)"
(chartClick)="chartClicked($event)"></canvas>
</div>
</div>
</div>
</div><!--/.col-->
<div class="col-xl-6">
<div class="card">
<div class="card-block">
<div class="row">
<div class="col-12">
<h4 class="card-title float-left mb-0">All Organisation Purchases by Category</h4>
</div><!--/.col-->
</div><!--/.row-->
<div class="chart-wrapper">
<canvas baseChart class="chart"
[datasets]="barChartDataCategory"
[labels]="barChartLabelsCategory"
[options]="barChartOptionsCategory"
[colors]="barChartColoursCategory"
[legend]="barChartLegendCategory"
[chartType]="barChartTypeCategory"
(chartHover)="chartHovered($event)"
(chartClick)="chartClicked($event)"></canvas>
</div>
</div>
</div>
</div><!--/.col-->
<div *ngIf="showCategoryDoughnutChart" class="col-xl-6">
<div class="card">
<div class="card-block">
<div class="row">
<div class="col-12">
<h4 class="card-title mb-0">This weeks' spending by Category</h4>
</div>
</div>
<div class="chart-wrapper">
<canvas baseChart class="chart"
[datasets]="doughnutChartDataCategory"
[labels]="doughnutChartLabelsCategory"
[options]="doughnutChartOptionsCategory"
[colors]="doughnutChartColoursCategory"
[legend]="chartLegend"
[chartType]="chartType"
(chartHover)="chartHovered($event)"
(chartClick)="chartClicked($event)"></canvas>
</div>
</div>
</div>
</div>
<div class="col-xl-6">
<org-pie-panel></org-pie-panel>
</div>
<div class="col-xl-6">
<div class="card">
<div class="card-block">
<div class="row">
<div class="col-12">
<h4 class="card-title float-left mb-0"> Global Puchases by Category</h4>
</div>
<div class="col-12">
<div *ngIf="showTotalCategoryList" class="chart-wrapper">
<ul class="icons-list">
<!-- New loop -->
<li *ngFor="let category of totalCategoryList | slice:0:totalCategoryLimit; let i=index">
<i [ngClass]="['icon-' + category.icon, getBootstrapColour(i)]"></i>
<div class="desc">
<div class="title">{{ category.category || 'N/A' }}</div>
</div>
<div class="value">
<div class="small text-muted">Bought</div>
<strong>{{ category.value || 'N/A' }}</strong>
</div>
</li>
<li *ngIf="totalCategoryList.length > totalCategoryLimit && disableCategoryButton == false" class="divider text-center">
<button type="button" class="btn btn-sm btn-link text-muted" (click)="categoryLoadMore()"><i class="icon-options"></i></button>
</li>
</ul>
</div>
</div>
</div><!--/.row-->
</div>
</div>
</div>

View File

@ -1,9 +1,16 @@
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { Router, NavigationEnd } from "@angular/router";
import { CurrencyPipe } from '@angular/common';
import { ChartOptions, ChartType, ChartDataSets } from 'chart.js';
import { Color, Label } from 'ng2-charts';
import { GraphWidget } from '../widgets/graph-widget.component';
import { OrgBarSnippetComponent } from '../snippets/org-snippet-bar.component';
import { GraphPanel } from '../panels/graph-panel.component';
import { OrgPiePanel } from '../panels/org-pie-panel.component';
import { DataType } from '../shared/data-types.enum';
import { ApiService } from '../providers/api-service';
import { environment } from '../../environments/environment';
import * as moment from 'moment';
@Component({
templateUrl: 'dashboard.component.html'
@ -37,6 +44,13 @@ export class DashboardComponent {
title: 'Sales Last 30 Days',
dataType: DataType.currency,
},
{
type: 'graph',
name: 'sales_last_quart',
icon: 'icon-diamond',
title: 'Sales Last Quart',
dataType: DataType.currency,
},
{
type: 'graph',
name: 'purchases_last_7_days',
@ -49,7 +63,307 @@ export class DashboardComponent {
title: 'Purchases Last 30 Days',
dataType: DataType.currency,
},
{
type: 'graph',
name: 'purchases_last_quart;',
title: 'Purchases Last Quart',
dataType: DataType.currency,
},
];
constructor() { }
disableCategoryButton: boolean = false;
public bootstrapColours: string[] = ['bg-primary', 'bg-secondary', 'bg-success',
'bg-danger', 'bg-warning', 'bg-info'];
public chartType = 'doughnut';
public chartLegend = true;
public doughnutChartDataCategory: any[] = [];
public doughnutChartLabelsCategory: string[] = [];
public doughnutChartColoursCategory: any[] = [
{
backgroundColor:[
'#ffa1b5',
'#3cde52',
'#52afed',
'#c133e3',
'#f7fa08',
'#75152d',
'#ee12ee',
'#15eaea',
'#eaa015',
'#ea1515',
'#2d4fcc'
]
}];
public doughnutChartOptionsCategory:any = {
tooltips: {
callbacks: {
label: (tooltip, data) => {
return this.tooltipLabelCallback(tooltip, data);
},
},
},
}
myWeek1: any;
weekList1 = [];
public purchaseNotEssential: number;
public purchaseEssential: number;
public showEssentialBarChart:boolean = false;
public showCategoryBarChart = false;
public showCategoryDoughnutChart = false;
public barChartDataEssential: ChartDataSets[] = [
{data: [0], label: 'Essential', stack: '1'},
{data: [0], label: 'Non-Essential', stack: '1'},
];
public barChartLabelsEssential:string[] = ['All Purchases'];
public barChartOptionsEssential:any = {
responsive: true,
scales:{
xAxes:[{
stacked:true
}],
yAxes:[{
stacked:true
}]
}
};
public barChartTypeEssential:string = 'horizontalBar';
public barChartColoursCategory: any[] = [
{
backgroundColor:[
'#ffa1b5',
'#3cde52',
'#52afed',
'#c133e3',
'#f7fa08',
'#75152d',
'#ee12ee',
'#15eaea',
'#eaa015',
'#ea1515',
'#2d4fcc'
]
}];
public barChartOptionsCategory:any = {
scaleShowVerticalLines: false,
responsive: true,
scales: {
yAxes: [{
ticks: {
beginAtZero: true,
callback: label => `£${label}`
}
}]
},
tooltips: {
callbacks: {
label: (tooltip, data) => {
return this.tooltipLabelCallback(tooltip, data);
},
},
},
};
public barChartTypeCategory:string = 'bar';
public barChartLegendCategory:boolean = false;
public barChartDataCategory:any[]=[];
public barChartLabelsCategory:string[] = [];
public lineChartDataSector: ChartDataSets[] = [
{ data: [], label: '' },
];
public lineChartLabelsSector: Label[] = ['January', 'February', 'March', 'April', 'May', 'June', 'July','August','September','October','November','December'];
public lineChartOptionsSector: (ChartOptions & { annotation: any }) = {
responsive: true,
scales: {
// We use this empty structure as a placeholder for dynamic theming.
xAxes: [{}],
yAxes: [{}]
},
annotation: {
annotations: [
{
type: 'line',
mode: 'vertical',
scaleID: 'x-axis-0',
value: 'March',
borderColor: 'orange',
borderWidth: 2,
label: {
enabled: true,
fontColor: 'orange',
content: 'LineAnno'
}
},
],
},
};
public lineChartColorsSector: Color[] = [
{ // grey
backgroundColor: 'rgba(148,159,177,0.2)',
borderColor: 'rgba(148,159,177,1)',
pointBackgroundColor: 'rgba(148,159,177,1)',
pointBorderColor: '#fff',
pointHoverBackgroundColor: '#fff',
pointHoverBorderColor: 'rgba(148,159,177,0.8)'
},
{ // dark grey
backgroundColor: 'rgba(77,83,96,0.2)',
borderColor: 'rgba(77,83,96,1)',
pointBackgroundColor: 'rgba(77,83,96,1)',
pointBorderColor: '#fff',
pointHoverBackgroundColor: '#fff',
pointHoverBorderColor: 'rgba(77,83,96,1)'
},
{ // red
backgroundColor: 'rgba(255,0,0,0.3)',
borderColor: 'red',
pointBackgroundColor: 'rgba(148,159,177,1)',
pointBorderColor: '#fff',
pointHoverBackgroundColor: '#fff',
pointHoverBorderColor: 'rgba(148,159,177,0.8)'
}
];
public lineChartLegendSector = true;
public lineChartTypeSector = 'line';
weekPurchaseList = {
first: 0,
second: 0,
max: 0,
sum: 0,
count: 0,
};
showTotalCategoryList: boolean = false;
totalCategoryLimit: number = 10;
totalCategoryList: any[]=[];
constructor(
private router: Router,
private api: ApiService,
private currencyPipe: CurrencyPipe,
) {
if (environment.enableAnalytics) {
this.router.events.subscribe(event => {
if (event instanceof NavigationEnd) {
(<any>window).ga('set', 'page', event.urlAfterRedirects);
(<any>window).ga('send', 'pageview');
}
});
}
this.setDate();
this.api.orgStats().subscribe(
result => {
this.setWeekPurchaseList(result.weeks);
this.setWeekData(result);
this.setChartDataCat(result.data.cat_total);
this.setChartDataEssential(result.data.essentials);
this.totalCategoryList = result.data.cat_list;
if (this.totalCategoryList) {
this.showTotalCategoryList = true;
}
},
error => {
console.log('Retrieval Error');
console.log( error._body );
}
);
}
private setChartDataEssential (dataEs: any) {
this.purchaseEssential = dataEs.purchase_no_essential_total;
this.purchaseNotEssential = dataEs.purchase_no_total - this.purchaseEssential;
this.barChartDataEssential = [
{data: [this.purchaseEssential], label: 'Essential', stack: '1'},
{data: [this.purchaseNotEssential], label: 'Non-Essential', stack: '1'},
];
this.showEssentialBarChart = true;
console.log(this.barChartDataEssential);
}
private setChartDataCat(dataCat: any) {
this.barChartLabelsCategory = Object.keys(dataCat);
let barChartDataCategoryInitial = Object.keys(dataCat).map(key => dataCat[key]);
this.barChartDataCategory = [
{data: barChartDataCategoryInitial, label: 'Series A'},
];
this.showCategoryBarChart = true;
if (this.weekList1) {
let doughnutChartDataCategoryInitial = this.weekList1.map(function(a) {return a.value;});
this.doughnutChartDataCategory = [
{data: doughnutChartDataCategoryInitial, label: 'Series A'},
];
// setTimeout is currently a workaround for ng2-charts labels
setTimeout(() => this.doughnutChartLabelsCategory = this.weekList1.map(function(a) {return a.category;}), 0);
this.showCategoryDoughnutChart = true;
}
}
private setChartDataSector(dataSec: any) {
}
private setDate () {
this.myWeek1 = moment().startOf('isoWeek').format('YYYY-MM-DD');
}
private setWeekData (data: any) {
function prop<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
this.weekList1 = prop(data.data.categories, this.myWeek1);
}
public setWeekPurchaseList (data: any) {
this.weekPurchaseList = {
first: data.first,
second: data.second,
max: data.max,
sum: data.sum,
count: data.count,
};
}
private categoryLoadMore () {
this.disableCategoryButton = true;
this.totalCategoryLimit = 30;
}
public getBootstrapColour(index: number) {
return this.bootstrapColours[index % this.bootstrapColours.length];
}
public convertHex(hex: string, opacity: number) {
hex = hex.replace('#', '');
const r = parseInt(hex.substring(0, 2), 16);
const g = parseInt(hex.substring(2, 4), 16);
const b = parseInt(hex.substring(4, 6), 16);
const rgba = 'rgba(' + r + ', ' + g + ', ' + b + ', ' + opacity / 100 + ')';
return rgba;
}
private tooltipLabelCallback(tooltipItem: any, data: any) {
var dataset = data.datasets[tooltipItem.datasetIndex];
var value = dataset.data[tooltipItem.index];
return this.currencyPipe.transform(value, 'GBP', 'symbol', '1.2-2');
}
// events
public chartClicked(e: any): void {
}
public chartHovered(e: any): void {
}
ngOnInit(): void {
}
}

View File

@ -1,7 +1,7 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { ChartsModule } from 'ng2-charts/ng2-charts';
import { ChartsModule } from 'ng2-charts';
import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
import { NgxPaginationModule } from 'ngx-pagination';
import { AgmCoreModule, GoogleMapsAPIWrapper } from '@agm/core';
@ -16,19 +16,31 @@ import { AccountEditComponent } from './account-edit.component';
import { AddDataComponent } from './add-data.component';
import { FeedbackComponent } from './feedback.component';
import { TransactionLogComponent } from './transaction-log.component';
import { CategoryMonthComponent } from './category-month.component';
import { PayrollLogComponent } from './payroll-log.component';
import { SuppliersComponent } from './suppliers.component';
import { MoreStuffComponent } from './more-graphs-and-tables.component';
import { LeaderboardComponent } from './leaderboard.component';
import { MapComponent } from './map.component';
import { TrailMapComponent } from './trail-map.component';
import { GraphWidget } from '../widgets/graph-widget.component';
import { OrgBarSnippetComponent } from '../snippets/org-snippet-bar.component';
import { CustBarSnippetComponent } from '../snippets/cust-snippet-bar.component';
import { GraphPanel } from '../panels/graph-panel.component';
import { BubbleChartComponent } from '../panels/bubble-panel.component';
import { PiePanel } from '../panels/pie-panel.component';
import { OrgPiePanel } from '../panels/org-pie-panel.component';
import { DashboardRoutingModule } from './dashboard.routing';
import { OrgResultComponent } from '../shared/org-result.component';
import { OrgTableComponent } from '../shared/org-table.component';
import { RecurResultComponent } from '../shared/recur-result.component';
import { RecurTableComponent } from '../shared/recur-table.component';
import { TransactionResultComponent } from '../shared/transaction-result.component';
import { SupplierResultComponent } from '../shared/supplier-result.component';
import { WardResultComponent } from '../shared/ward-result.component';
import { MetaTypeResultComponent } from '../shared/meta-type-result.component';
import { PayrollResultComponent } from '../shared/payroll-result.component';
import { LeaderboardResultComponent } from '../shared/leaderboard-result.component';
@ -58,8 +70,14 @@ import { environment } from '../../environments/environment';
AddDataComponent,
OrgResultComponent,
OrgTableComponent,
RecurResultComponent,
RecurTableComponent,
TransactionLogComponent,
CategoryMonthComponent,
TransactionResultComponent,
SupplierResultComponent,
WardResultComponent,
MetaTypeResultComponent,
PayrollLogComponent,
PayrollResultComponent,
LeaderboardComponent,
@ -69,7 +87,13 @@ import { environment } from '../../environments/environment';
FeedbackComponent,
GraphWidget,
OrgBarSnippetComponent,
CustBarSnippetComponent,
GraphPanel,
PiePanel,
OrgPiePanel,
BubbleChartComponent,
SuppliersComponent,
MoreStuffComponent,
],
providers: [
CurrencyPipe,

View File

@ -12,10 +12,13 @@ import { AccountEditComponent } from './account-edit.component';
import { AddDataComponent } from './add-data.component';
import { FeedbackComponent } from './feedback.component';
import { TransactionLogComponent } from './transaction-log.component';
import { CategoryMonthComponent } from './category-month.component';
import { PayrollLogComponent } from './payroll-log.component';
import { LeaderboardComponent } from './leaderboard.component';
import { MapComponent } from './map.component';
import { TrailMapComponent } from './trail-map.component';
import { MoreStuffComponent } from './more-graphs-and-tables.component';
import { SuppliersComponent } from './suppliers.component';
// Using child path to allow for FullLayout theming
const routes: Routes = [
@ -58,6 +61,11 @@ const routes: Routes = [
component: TransactionLogComponent,
data: { title: 'Transaction Log' },
},
{
path: 'category-month',
component: CategoryMonthComponent,
data: { title: 'Budget' },
},
{
path: 'map',
component: MapComponent,
@ -66,7 +74,7 @@ const routes: Routes = [
{
path: 'story-trail',
component: TrailMapComponent,
data: { title: 'Lancaster Independent Story' },
data: { title: 'Story Trail' },
},
{
path: 'payroll-log',
@ -78,6 +86,16 @@ const routes: Routes = [
path: 'feedback',
component: FeedbackComponent,
data: { title: 'Give Feedback' },
},
{
path: 'suppliers',
component: SuppliersComponent,
data: { title: 'Suppliers' }
},
{
path: 'more-graphs-and-tables',
component: MoreStuffComponent,
data: { title: 'Infographics'}
}
],
}

View File

@ -1,7 +1,7 @@
import { Component, OnInit } from '@angular/core';
import { Validators, FormBuilder, FormGroup } from '@angular/forms';
import { ApiService } from '../providers/api-service';
import 'rxjs/add/operator/map';
@Component({
templateUrl: 'feedback.component.html',
@ -55,9 +55,7 @@ export class FeedbackComponent implements OnInit {
result => {
if ( result.success === true ) {
console.log('Successful Upload');
console.log(result);
this.feedbackFormStatus = 'success';
console.log(this.feedbackFormStatus);
this.feedbackForm.patchValue({
feedbacktext: '',
});
@ -65,22 +63,16 @@ export class FeedbackComponent implements OnInit {
console.log('Upload Error');
this.feedbackFormStatusError = JSON.stringify(result.status) + 'Error, ' + JSON.stringify(result.message);
this.feedbackFormStatus = 'send_failed';
console.log(this.feedbackFormStatus);
}
},
error => {
console.log('Upload Error');
console.log(error);
try {
console.log(error.error);
const jsonError = error.json();
console.log('boop');
this.feedbackFormStatusError = '"' + jsonError.error + '" Error, ' + jsonError.message;
this.feedbackFormStatusError = '"' + error.error.error + '" Error, ' + error.error.message;
} catch (e) {
this.feedbackFormStatusError = 'There was a server error, please try again later.';
}
this.feedbackFormStatus = 'send_failed';
console.log(this.feedbackFormStatus);
}
);
}

View File

@ -23,8 +23,8 @@
<thead>
<tr>
<th>Position</th>
<th>Value</th>
<th>Purchase Time</th>
<th>Name</th>
<th class="js-sort-number">Gross amount</th>
</tr>
</thead>
<tbody>

View File

@ -5,7 +5,7 @@ import {PaginationInstance} from 'ngx-pagination';
// import { PaginationControlsComponent } from 'ngx-pagination';
// import { PaginationControlsDirective } from 'ngx-pagination';
// import { TransactionResultComponent } from '../shared/transaction-result.component';
import 'rxjs/add/operator/map';
@Component({
templateUrl: 'leaderboard.component.html',
@ -22,7 +22,7 @@ export class LeaderboardComponent implements OnInit {
public paginateConfig: PaginationInstance = {
id: 'leadpaginate',
itemsPerPage: 10,
itemsPerPage: 20,
currentPage: 1,
totalItems: 0
};
@ -70,7 +70,7 @@ export class LeaderboardComponent implements OnInit {
console.log(error);
}
);
}
}org
// // dynamically changes the row style based on player's position
// // for instance, top three player and the player him/herself should

View File

@ -3,8 +3,8 @@
<div class="col-lg-12">
<div class="card">
<div class="card-header">
<strong>Lancaster Independent Story</strong>
<small>Required Data marked in <strong>bold</strong>.</small>
<strong>Purchase Map</strong>
<small>Click a marker to get location details.</small>
</div>
<div class="modal fade" bsModal #statusModal="bs-modal" [config]="{backdrop: false, animated: false}"
tabindex="-1" role="dialog" aria-labelledby="mySmallModalLabel" aria-hidden="true">

View File

@ -3,13 +3,13 @@ import { ApiService } from '../providers/api-service';
import { AgmCoreModule } from '@agm/core';
import { BsModalService, ModalDirective } from 'ngx-bootstrap/modal';
import { BsModalRef } from 'ngx-bootstrap/modal/bs-modal-ref.service';
import 'rxjs/add/operator/map';
@Component({
templateUrl: 'map.component.html',
})
export class MapComponent implements OnInit, AfterViewInit {
@ViewChild('statusModal') myStatusModal: ModalDirective;
@ViewChild('statusModal', { static: true }) myStatusModal: ModalDirective;
lat: number = 54.0466;
lng: number = -2.8007;
zoom: number = 12;

View File

@ -0,0 +1,136 @@
<div class="animated fadeIn">
<div class="row">
<div class="col-lg-12">
<div class="card">
<div class="card-header">
<h4>Filter</h4>
</div>
<div class="card-block">
<form class="form-inline">
<label class="mr-2" for="filter-from">From</label>
<input id="filter-from" class="form-control" type="date" [(ngModel)]="filterFrom" name="from">
<label class="mx-2" for="filter-to">To</label>
<input class="form-control" id="filter-to" type="date" [(ngModel)]="filterTo" name="to">
<button type="submit" class="btn btn-primary ml-2" (click)="loadData()">Filter</button>
</form>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<div class="card">
<div class="card-header">
<strong>Transaction Types</strong>
</div>
<div *ngIf="metaTypeListAvailable" class="card-block">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Ward</th>
<th>Amount of Transactions</th>
<th>Sum of Transactions</th>
</tr>
</thead>
<tbody>
<tr meta-type-result *ngFor="let type of metaTypeList" [type]="type"></tr>
</tbody>
</table>
</div>
<div *ngIf="!metaTypeListAvailable" class="card-block">
No Data available.
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card">
<div class="card-header">
<strong>Ward Spending</strong>
</div>
<div *ngIf="wardListAvailable" class="card-block">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Ward</th>
<th>Amount of Transactions</th>
<th>Sum of Transactions</th>
</tr>
</thead>
<tbody>
<tr ward-result *ngFor="let ward of wardList" [ward]="ward"></tr>
</tbody>
</table>
</div>
<div *ngIf="!wardListAvailable" class="card-block">
No Data available.
</div>
</div>
</div>
</div>
</div>
<div class="animated fadeIn">
<div class="card">
<div class="card-block">
<div class="row">
<div class="col-sm-8">
<h4 class="card-title mb-0">Supplier spend amount and number of purchases</h4>
</div>
</div>
<small>vertical shows number of purchases, size of bubble shows the total spend amount, horizontal shows date</small>
<div class="col-sm-12" *ngIf="!isBubbleChartLoaded">
<div class="spinner"></div>
</div>
<div *ngIf="isBubbleChartLoaded">
<canvas baseChart
[datasets]="supplierBubbleChartData"
[options]="supplierBubbleChartOptions"
[labels]="supplierBubbleChartLabels"
[legend]="showLegend"
[chartType]="supplierBubbleChartType">
</canvas>
</div>
</div>
</div>
<div class="card">
<div class="card-block">
<div class="row">
<div class="col-sm-12">
<h4 class="card-title mb-0">Spend & Number of Transactions</h4>
<small>Date against Value and Number of Transactions</small>
</div>
</div>
<div>
<canvas baseChart
[datasets]="yearSpendChartData"
[options]="yearSpendChartOptions"
[labels]="yearSpendChartLabels"
[legend]="showLegend"
[chartType]="yearSpendChartType">
</canvas>
</div>
</div>
</div>
<div class="card">
<div class="card-block">
<div class="row">
<div class="col-sm-6">
<h4 class="card-title mb-0">Supplier Spend History</h4>
</div>
<div class="col-sm-6 hidden-sm-down">
<button type="button" class="btn btn-danger" (click)="previousSupplierHistoryPage()">Previous Page</button>
<button type="button" class="btn btn-info" (click)="nextSupplierHistoryPage()">Next Page</button>
<span class="ml-2">Page {{ _supplierHistoryPage }} of {{ _supplierHistoryPages }}</span>
</div>
</div>
<div *ngIf="isSupplierChartLoaded">
<canvas baseChart #supplierChart
[datasets]="supplierMonthChartData"
[options]="supplierMonthChartOptions"
[labels]="supplierMonthChartLabels"
[legend]="showLegend"
[chartType]="supplierMonthChartType">
</canvas>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,335 @@
import {Component, OnInit, Input, Output, EventEmitter, ViewChild} from '@angular/core';
import {ApiService} from '../providers/api-service';
import {BaseChartDirective} from 'ng2-charts';
import {CurrencyPipe} from '@angular/common';
import {ChartType} from "chart.js";
import * as moment from 'moment';
@Component({
templateUrl: 'more-graphs-and-tables.component.html',
})
export class MoreStuffComponent implements OnInit {
@Output() public onClick = new EventEmitter();
@Input() public categories: any;
// Global Filter Setup
filterFrom: any;
filterTo: any;
isBubbleChartLoaded: boolean = false;
isSupplierChartLoaded: boolean = false;
wardList: any;
wardListAvailable = false;
metaTypeList: any;
metaTypeListAvailable = false;
constructor(
private api: ApiService,
private currencyPipe: CurrencyPipe,
) {
let now = moment();
this.filterTo = now.format('YYYY-MM-DD');
now.subtract(1, 'months');
this.filterFrom = now.format('YYYY-MM-DD');
this.tableSummary();
}
ngOnInit(): void {
this.loadData();
}
public loadData() {
this.tableSummary();
this.loadYearSpend();
this.loadSupplierBubble();
this.loadSupplierHistory();
}
public showLegend = true;
/*
* Supplier Bubble Chart Setup
*/
private formatGraphData(data: any): any[] {
let graph_data = [];
data.data.map(item => {
graph_data.push({
t: item.date,
r: item.value > 1000000 ? (item.value / 200000) : (item.value / 100000) + 5,
supplier: item.seller,
y: item.count,
value: item.value,
count: item.count,
});
});
return graph_data;
}
private loadSupplierBubble() {
this.api.loadMiscUrl('organisation/external/supplier_count', {
from: this.filterFrom,
to: this.filterTo,
}).subscribe(
result => {
this.supplierBubbleChartData[0].data = this.formatGraphData(result);
this.isBubbleChartLoaded = true;
}
)
}
public supplierBubbleChartType: ChartType = 'bubble';
public supplierBubbleChartData: any[] = [
{
data: [],
label: ["Spend"],
borderColor: 'blue',
hoverBorderColor: 'black',
radius: 5,
},
];
public supplierBubbleChartLabels: string[] = [];
public supplierBubbleChartOptions: any = {
responsive: true,
scales: {
xAxes: [{
type: 'time',
time: {
unit: 'month'
},
scaleLabel: {
display: true,
labelString: 'Date'
}
}],
yAxes: [{
scaleLabel: {
display: true,
labelString: 'Number of purchases'
}
}]
},
tooltips: {
callbacks: {
label: (tooltip, data) => {
return this.bubbleTooltipCallback(tooltip, data);
},
},
},
};
private bubbleTooltipCallback(tooltipItem: any, data: any) {
let dataset = data.datasets[tooltipItem.datasetIndex];
let value = dataset.data[tooltipItem.index];
return `${value.supplier}: ${this.currencyPipe.transform(value.value, 'GBP', 'symbol', '1.2-2')} over ${value.count} purchases`;
}
private tableSummary() {
this.api.loadMiscUrl('organisation/external/lcc_tables', {
from: this.filterFrom,
to: this.filterTo,
}).subscribe(
result => {
this.wardList = result.wards;
this.metaTypeList = Object.keys(result.types).map(key => result.types[key]);
if (this.wardList) {
this.wardListAvailable = true;
}
if (this.metaTypeList) {
this.metaTypeListAvailable = true;
}
},
error => {
console.log('Retrieval Error');
console.log(error._body);
}
)
}
private loadYearSpend() {
this.api.loadMiscUrl('organisation/external/year_spend', {
from: this.filterFrom,
to: this.filterTo,
}).subscribe(
result => {
let value_data = [];
let count_data = [];
result.data.map(item => {
value_data.push({
t: item.date,
y: item.value,
});
count_data.push({
t: item.date,
y: item.count,
});
});
this.yearSpendChartData[0].data = value_data;
this.yearSpendChartData[1].data = count_data;
}
)
}
public yearSpendChartData: any[] = [
{
data: [],
label: ["Value £"],
fill: false,
borderColor: 'red',
hoverBackgroundColor: '#ffa1b5',
hoverBorderColor: 'red',
yAxisID: 'y-value',
},
{
data: [],
label: ["Count"],
fill: false,
borderColor: 'blue',
hoverBackgroundColor: '#52afed',
hoverBorderColor: 'blue',
yAxisID: 'y-count',
},
];
public yearSpendChartOptions: any = {
elements: {line: {tension: 0}},
responsive: true,
scales: {
xAxes: [{
type: 'time',
time: {
unit: 'month'
},
scaleLabel: {
display: true,
labelString: 'Date'
}
}],
yAxes: [
{id: 'y-value', position: 'left', beginAtZero: true, type: 'linear'},
{id: 'y-count', position: 'right', beginAtZero: true, type: 'linear'},
]
},
};
public yearSpendChartLabels: string[] = [];
public yearSpendChartType: ChartType = 'line';
randomData() {
return Math.random();
}
lineChartUpdate() {
this.loadYearSpend();
}
@ViewChild('supplierChart', {read: BaseChartDirective, static: false}) supplierChart: BaseChartDirective;
private loadSupplierHistory() {
this.api.loadMiscUrl('organisation/external/supplier_history').subscribe(
result => {
this._supplierHistoryData = result.data;
this._supplierHistoryPage = 1;
this._supplierHistoryPages = Math.ceil(this._supplierHistoryData.length / this._supplierHistoryPerPage);
this.updateSupplierHistoryData();
this.isSupplierChartLoaded = true;
}
);
}
private updateSupplierHistoryData() {
const lastResult = this._supplierHistoryPerPage * this._supplierHistoryPage;
console.log(this._supplierHistoryPage);
const firstResult = lastResult - this._supplierHistoryPerPage;
const pageData = this._supplierHistoryData.slice(firstResult, lastResult);
console.log(pageData);
let labels = [];
let year = [];
let half = [];
let quarter = [];
pageData.map(item => {
labels.push(item.name);
year.push(item.year_total);
half.push(item.half_total);
quarter.push(item.quarter_total);
});
this.supplierMonthChartData[0].data = quarter;
this.supplierMonthChartData[1].data = half;
this.supplierMonthChartData[2].data = year;
this.supplierMonthChartLabels = labels;
}
public nextSupplierHistoryPage() {
if (this._supplierHistoryPage < this._supplierHistoryPages) {
this._supplierHistoryPage++;
}
this.updateSupplierHistoryData();
}
public previousSupplierHistoryPage() {
if (this._supplierHistoryPage > 1) {
this._supplierHistoryPage--;
}
this.updateSupplierHistoryData();
}
private _supplierHistoryData: any[];
private _supplierHistoryPerPage: number = 15;
public _supplierHistoryPage: number = 1;
public _supplierHistoryPages: number = 1;
public supplierMonthChartData: any[] = [
{
data: [],
label: ["3 Month"],
fill: false,
borderColor: 'red',
hoverBorderColor: 'red',
hoverBackgroundColor: 'red',
},
{
data: [],
label: ["6 Month"],
fill: false,
borderColor: 'blue',
hoverBorderColor: 'blue',
hoverBackgroundColor: 'blue',
},
{
data: [],
label: ["12 Month"],
fill: false,
borderColor: 'orange',
hoverBorderColor: 'orange',
hoverBackgroundColor: 'orange',
},
];
public supplierMonthChartOptions: any = {
//maintainAspectRatio: false,
responsive: true,
scales: {
xAxes: [{
scaleLabel: {
display: true,
labelString: 'Spend amount £'
}
}],
yAxes: [{
scaleLabel: {
display: true,
labelString: 'Supplier Names'
}
}]
},
};
public supplierMonthChartLabels: string[] = [];
public supplierMonthChartType: ChartType = 'horizontalBar';
}

View File

@ -6,7 +6,7 @@ import {PaginationInstance} from 'ngx-pagination';
// import { PaginationControlsDirective } from 'ngx-pagination';
// import { TransactionResultComponent } from '../shared/transaction-result.component';
import * as moment from 'moment';
import 'rxjs/add/operator/map';
@Component({
templateUrl: 'payroll-log.component.html',

View File

@ -0,0 +1,79 @@
<div class="animated fadeIn">
<div class="row">
<div class="col-lg-12">
<div class="card">
<div class="card-header">
<h4>Search Suppliers</h4>
</div>
<div *ngIf="supplierListAvailable" class="card-block">
<div class="input-group">
<input class="form-control" type="text" name="search" [(ngModel)]="searchText" autocomplete="off"
placeholder="Search by Name or Postcode" (keydown.enter)="searchSuppliers()">
<div class="input-group-append">
<button class="btn btn-primary" (click)="searchSuppliers()">Search</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="animated fadeIn">
<div class="row">
<div class="col-lg-12">
<div class="card">
<div class="card-header">
<h4>List of Suppliers</h4>
<div class="small">Click on Column Headers to change Sort Order</div>
</div>
<div *ngIf="supplierListAvailable" class="card-block">
<table class="table table-striped table-hover">
<thead>
<tr>
<th (click)="sortName()">Name <span class="fa-stack">
<i *ngIf="sortBy !== 'name' || sortDir == 'asc'" class="fa fa-sort-up fa-stack-1x"></i>
<i *ngIf="sortBy !== 'name' || sortDir == 'desc'" class="fa fa-sort-down fa-stack-1x"></i>
</span></th>
<th (click)="sortPostcode()">Postcode <span class="fa-stack">
<i *ngIf="sortBy !== 'postcode' || sortDir == 'asc'" class="fa fa-sort-up fa-stack-1x"></i>
<i *ngIf="sortBy !== 'postcode' || sortDir == 'desc'" class="fa fa-sort-down fa-stack-1x"></i>
</span></th>
<th (click)="sortSpend()">Spend <span class="fa-stack">
<i *ngIf="sortBy !== 'spend' || sortDir == 'asc'" class="fa fa-sort-up fa-stack-1x"></i>
<i *ngIf="sortBy !== 'spend' || sortDir == 'desc'" class="fa fa-sort-down fa-stack-1x"></i>
</span></th>
</tr>
</thead>
<tbody>
<tr supplier-result *ngFor="let supplier of supplierList | paginate: paginateConfig"
[supplier]="supplier"></tr>
</tbody>
</table>
<pagination-template #p="paginationApi"
[id]="paginateConfig.id"
(pageChange)="loadSuppliers($event)">
<ul class="pagination">
<li class="page-item" [class.disabled]="p.isFirstPage()">
<a class="page-link clickable" *ngIf="!p.isFirstPage()" (click)="p.previous()">Prev</a>
</li>
<li *ngFor="let page of p.pages" class="page-item" [class.active]="p.getCurrent() === page.value">
<a class="page-link clickable" (click)="p.setCurrent(page.value)" *ngIf="p.getCurrent() !== page.value">
<span>{{ page.label }}</span>
</a>
<div class="page-link" *ngIf="p.getCurrent() === page.value">
<span>{{ page.label }}</span>
</div>
</li>
<li class="page-item" [class.disabled]="p.isLastPage()">
<a class="page-link clickable" *ngIf="!p.isLastPage()" (click)="p.next()">Next</a>
</li>
</ul>
</pagination-template>
</div>
<div *ngIf="!supplierListAvailable" class="card-block">
No Suppliers available.
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,74 @@
import { Component, OnInit, AfterViewInit, Input, Output, EventEmitter, ViewChild, TemplateRef } from '@angular/core';
import { ApiService } from '../providers/api-service';
import { AgmCoreModule } from '@agm/core';
import { BsModalService, ModalDirective } from 'ngx-bootstrap/modal';
import { BsModalRef } from 'ngx-bootstrap/modal/bs-modal-ref.service';
import { PaginationInstance } from 'ngx-pagination';
import { FilterPipeModule } from 'ngx-filter-pipe';
@Component({
templateUrl: 'suppliers.component.html',
})
export class SuppliersComponent implements OnInit, AfterViewInit {
@Output() public onClick = new EventEmitter();
@Input() public categories: any;
public perPage: number = 10;
searchText: string;
supplierList: any;
supplierListAvailable = false;
sortBy = 'name';
sortDir = 'asc';
public paginateConfig: PaginationInstance = {
id: 'transpaginate',
itemsPerPage: this.perPage,
currentPage: 1,
totalItems: 0
};
constructor(
private api: ApiService,
) { }
ngOnInit(): void {
this.loadSuppliers(1);
}
loadSuppliers(logPage: number) {
this.api.externalSuppliers(logPage, this.sortBy, this.sortDir, this.perPage, this.searchText).subscribe(
result => {
this.supplierList = result.suppliers;
if (this.supplierList) {
this.supplierListAvailable = true;
}
this.paginateConfig.totalItems = result.page_no;
this.paginateConfig.currentPage = logPage;
},
error => {
console.log('Retrieval Error');
console.log( error._body );
}
);
}
sortName() { this.sortByColumn('name'); }
sortPostcode() { this.sortByColumn('postcode'); }
sortSpend() { this.sortByColumn('spend'); }
sortByColumn(name) {
this.sortBy = name;
this.sortDir = this.sortDir === 'asc' ? 'desc' : 'asc';
this.loadSuppliers(1);
}
searchSuppliers() {
// Go back to page 1 when searching
this.loadSuppliers(1);
}
ngAfterViewInit() {
}
}

View File

@ -3,8 +3,40 @@
<div class="col-lg-12">
<div class="card">
<div class="card-header">
<strong>Purchase Map</strong>
<small>Required Data marked in <strong>bold</strong>.</small>
<strong>Story Trail</strong>
<div class="row">
<div class="col-12 col-sm-6 mb-3">
<small>Select a Story Trail to see all the businesses in that story on the map.<br>
Click an icon on the map to get more information.</small>
</div>
<div class="col-12 col-sm-6 order-12">
<button type="button" class="btn btn-outline-info btn-lg float-right" (click)="openModalAssoc(templateAssoc)">Select Story Trail</button>
</div>
</div>
<ng-template #templateAssoc>
<div class="modal-header">
<h4 class="modal-title pull-left">Select View</h4>
<button type="button" class="close pull-right" aria-label="Close" (click)="modalRef2.hide()">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="row">
<div class="col-12 col-sm-6 text-center">
<img src="assets/img/association/lis-logo.png" class="w-50" alt="lis logo"><br>
<button type="button" class="btn btn-success mt-3" (click)="modalRef2.hide(); assocMap = 'lis'">Lancaster Independent Story</button>
</div>
<div class="col-12 col-sm-6 text-center">
<img src="assets/img/association/esta-logo.png" class="w-50" alt="lis logo"><br>
<button type="button" class="btn btn-success mt-3" (click)="modalRef2.hide(); assocMap = 'esta'">ESTA Association</button>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger" (click)="modalRef2.hide()">Close</button>
</div>
</ng-template>
</div>
<div class="modal fade" bsModal #statusModal="bs-modal" [config]="{backdrop: false, animated: false}"
tabindex="-1" role="dialog" aria-labelledby="mySmallModalLabel" aria-hidden="true">
@ -45,7 +77,7 @@
<agm-marker-cluster maxZoom="13" imagePath="https://raw.githubusercontent.com/googlemaps/v3-utility-library/master/markerclustererplus/images/m">
<agm-marker
*ngFor="let m of markers"
[iconUrl]="'/assets/img/map-pin-lis.png'"
[iconUrl]="'/assets/img/association/' + assocMap + '-map-pin.png'"
[latitude]="m.latitude"
[longitude]="m.longitude"
[openInfoWindow]="false"
@ -55,7 +87,7 @@
</agm-map>
<ng-template #template>
<div class="modal-header d-flex justify-content-between">
<img src="assets/img/lis_logo.png" class="w-15" alt="lis logo"><h4 class="modal-title">{{clickedMarker.name}}</h4>
<img src="{{assocLogo}}" class="w-15" alt="lis logo"><h4 class="modal-title">{{clickedMarker.name}}</h4>
<button type="button" class="close pull-right" aria-label="Close" (click)="modalRef.hide()">
<span aria-hidden="true">&times;</span>
</button>

View File

@ -3,18 +3,21 @@ import { ApiService } from '../providers/api-service';
import { AgmCoreModule } from '@agm/core';
import { BsModalService, ModalDirective } from 'ngx-bootstrap/modal';
import { BsModalRef } from 'ngx-bootstrap/modal/bs-modal-ref.service';
import 'rxjs/add/operator/map';
@Component({
templateUrl: 'trail-map.component.html',
})
export class TrailMapComponent implements OnInit, AfterViewInit {
@ViewChild('statusModal') myStatusModal: ModalDirective;
@ViewChild('statusModal', { static: true }) myStatusModal: ModalDirective;
lat: number = 54.0466;
lng: number = -2.8007;
zoom: number = 12;
public modalRef: BsModalRef;
public modalRef2: BsModalRef;
clickedMarker: any;
assocMap = 'lis';
assocLogo: string;
dataReceived: string = 'loading';
@ -25,7 +28,9 @@ export class TrailMapComponent implements OnInit, AfterViewInit {
constructor(
private api: ApiService,
private modalService: BsModalService,
) {}
) {
this.assocLogo = 'assets/img/association/' + this.assocMap + '-logo.png';
}
ngOnInit(): void { }
@ -41,9 +46,13 @@ export class TrailMapComponent implements OnInit, AfterViewInit {
this.modalRef = this.modalService.show(template);
}
openModalAssoc(templateAssoc: TemplateRef<any>) {
this.modalRef2 = this.modalService.show(templateAssoc);
}
public onMarkerClick(clickedMarker, template: TemplateRef<any>) {
console.log(clickedMarker);
this.clickedMarker = clickedMarker;
this.assocLogo = 'assets/img/association/' + this.assocMap + '-logo.png';
this.openModal(template);
}
@ -64,8 +73,9 @@ export class TrailMapComponent implements OnInit, AfterViewInit {
latitude: resp.getSouthWest().lat(),
longitude: resp.getSouthWest().lng()
},
association: this.assocMap,
}
this.api.getLisData(mapData).subscribe(
this.api.getAssocData(mapData).subscribe(
result => {
this.myStatusModal.hide();
this.markers = result.locations;

View File

@ -1,22 +1,124 @@
<div class="animated fadeIn">
<div class="row">
<div class="col-lg-12">
<div class="card">
<div class="card-header">
<strong>Recurring Transactions</strong>
<small>Select a Recurring Transaction below to edit it.</small>
</div>
<div *ngIf="!noRecurringList" class="card-block">
<recur-table [recurList]="recurringTransactionList" [categories]="categoryList" (onClick)="recurringTransactionDetails($event, template)"></recur-table>
<ng-template #template>
<div class="modal-header d-flex justify-content-between">
<h4 class="modal-title">Edit Recurring Transaction</h4>
<button type="button" class="close pull-right" aria-label="Close" (click)="modalRef.hide()">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="form-group row">
<label class="col-md-3 form-control-label" for="text-input"><strong>Time of Transaction</strong></label>
<div class="col-md-9">
<input type="datetime-local" class="form-control" [(ngModel)]="updatedTime">
<span class="help-block">Enter the date and time the transaction occurred.</span>
</div>
</div>
<div class="form-group row">
<label class="col-md-3 form-control-label" for="text-input"><strong>Amount</strong></label>
<div class="col-md-9">
<div class="input-group">
<span class="input-group-addon"><i class="fa fa-gbp"></i></span>
<input type="number" min="0.00" step="0.01" class="form-control" placeholder="0.00" [(ngModel)]="clickedRecur.value">
</div>
<span class="help-block">Enter the amount spent, such as 5.35 for £5.35.</span>
</div>
</div>
<div class="form-group row">
<label class="col-md-3 form-control-label" for="text-input">Essential Purchase</label>
<div class="col-md-9">
<div class="input-group">
<input type="checkbox" class="mr-auto" [(ngModel)]="clickedRecur.essential">
</div>
<span class="help-block">Tick if the purchase is deemed an essential purchase for budgeting purposes.</span>
</div>
</div>
<div class="form-group row">
<label class="col-md-3 form-control-label" for="text-input"><strong>Recurring Period</strong></label>
<div class="col-md-9">
<div class="input-group">
<select type="text" class="form-control" [(ngModel)]="clickedRecur.recurring_period">
<option value="daily">Daily</option>
<option value="weekly">Weekly</option>
<option value="fortnightly">Fortnightly</option>
<option value="monthly">Monthly</option>
<option value="quarterly">Quarterly</option>
</select>
</div>
<span class="help-block">Please give the period of time the purchase will recur from "Time of Transaction".</span>
</div>
</div>
<div class="form-group row">
<label class="col-md-3 form-control-label" for="text-input">Budget Type</label>
<div class="col-md-9">
<div class="input-group">
<select type="text" class="form-control" [(ngModel)]="clickedRecur.category">
<option value="0">Uncategorised</option>
<option *ngFor="let category of categoryIdList" [ngValue]="category">
{{ categoryList[category] }}
</option>
</select>
</div>
<span class="help-block"><strong>Optional:</strong> Choose the Budget Type for the majority of the purchase.</span>
</div>
</div>
<div class="form-group row">
<div class="input-group">
<span class="col-12"><strong>WARNING: Clicking "Delete" will completely remove the Recurring Transaction.</strong></span>
</div>
</div>
<div class="col-md-12">
<div [ngSwitch]="transactionFormStatus">
<div *ngSwitchCase="'success'" class="alert alert-success" role="alert">
{{transactionFormStatusSuccess}}
</div>
<div *ngSwitchCase="'send_failed'" class="alert alert-danger" role="alert">
{{transactionFormStatusError}}
</div>
</div>
</div>
</div>
<div class="modal-footer d-flex justify-content-between">
<button type="submit" (click)="deleteRecurringTransaction()" class="btn btn-sm btn-danger"><i class="fa fa-times"></i> Delete</button>
<button type="submit" (click)="editRecurringTransaction()" class="btn btn-sm btn-primary"><i class="fa fa-dot-circle-o"></i> Save</button>
</div>
</ng-template>
</div>
<div *ngIf="noRecurringList" class="card-block">
No Recurring Transactions.
</div>
</div>
<div class="card">
<div class="card-header">
<strong>Log of Outgoing Transactions</strong>
<small>This lists all purchases that have been submitted.</small>
<button class="btn pull-right btn-sm" (click)="toggleShowMeta()">
<span *ngIf="!showMeta">Show</span><span *ngIf="showMeta">Hide</span> Details
</button>
</div>
<div *ngIf="!noTransactionList" class="card-block">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Seller</th>
<th>Value</th>
<th *ngIf="!showMeta">Value</th>
<th *ngIf="showMeta">Net Value</th>
<th *ngIf="showMeta">Sales Tax Value</th>
<th *ngIf="showMeta">Gross Value</th>
<th>Purchase Time</th>
</tr>
</thead>
<tbody>
<tr transaction-result *ngFor="let transaction of transactionList | paginate: paginateConfig" [transaction]="transaction"></tr>
<tr transaction-result *ngFor="let transaction of transactionList | paginate: paginateConfig" [transaction]="transaction" [showMeta]="showMeta"></tr>
</tbody>
</table>
<pagination-template #p="paginationApi"

View File

@ -1,58 +1,66 @@
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { Component, OnInit, EventEmitter, TemplateRef } from '@angular/core';
import { ApiService } from '../providers/api-service';
// import { PaginatePipe } from 'ngx-pagination';
import {PaginationInstance} from 'ngx-pagination';
// import { PaginationControlsComponent } from 'ngx-pagination';
// import { PaginationControlsDirective } from 'ngx-pagination';
// import { TransactionResultComponent } from '../shared/transaction-result.component';
import { BsModalService, ModalDirective } from 'ngx-bootstrap/modal';
import { BsModalRef } from 'ngx-bootstrap/modal/bs-modal-ref.service';
import { PaginationInstance } from 'ngx-pagination';
import * as moment from 'moment';
import 'rxjs/add/operator/map';
@Component({
templateUrl: 'transaction-log.component.html',
})
export class TransactionLogComponent implements OnInit {
transactionList;
transactionList: any;
recurringTransactionList: any;
noTransactionList = true;
noRecurringList = true;
myDate: any;
minDate: any;
public p: any;
public modalRef: BsModalRef;
clickedRecur: any;
public updatedDate: string;
public startTime: string;
categoryIdList: any;
categoryList: any;
categoryNameList: string[] = [];
transactionFormStatus: string;
transactionFormStatusSuccess: string;
transactionFormStatusError = 'Error received, please try again.';
updatedTime: string;
showMeta = false;
public paginateConfig: PaginationInstance = {
id: 'transpaginate',
itemsPerPage: 10,
currentPage: 1,
totalItems: 0
};
id: 'transpaginate',
itemsPerPage: 10,
currentPage: 1,
totalItems: 0
};
constructor(
private api: ApiService,
private modalService: BsModalService,
) {
this.myDate = moment().format('YYYY-MM-DD[T]HH:mm');
this.api.categoryList().subscribe(
result => {
this.categoryList = result.categories;
this.categoryIdList = Object.keys(this.categoryList);
},
error => {
console.log('Retrieval Error');
console.log( error._body );
}
);
// this.myDate = new Date().toISOString().slice(0, 16);
}
ngOnInit(): void {
this.getMinDate();
this.loadTransactions(1);
}
getMinDate() {
// gets the April 1st date of the current year
const aprilDate = moment().month(3).date(1);
const now = moment();
// Checks if current time is before April 1st, if so returns true
const beforeApril = now.isBefore(aprilDate);
if ( beforeApril === true ) {
this.minDate = aprilDate.subtract(2, 'years').format('YYYY-MM-DD');
} else {
this.minDate = aprilDate.subtract(1, 'years').format('YYYY-MM-DD');
}
}
loadTransactions(logPage: number) {
console.log(logPage);
this.api.transList(logPage).subscribe(
result => {
if (result.transactions.length > 0) {
@ -66,6 +74,13 @@ export class TransactionLogComponent implements OnInit {
this.transactionList = null;
this.noTransactionList = true;
}
if (result.recurring_transactions) {
this.recurringTransactionList = result.recurring_transactions;
this.noRecurringList = false;
} else {
this.recurringTransactionList = null;
this.noRecurringList = true;
}
},
error => {
console.log(error);
@ -73,4 +88,82 @@ export class TransactionLogComponent implements OnInit {
);
}
recurringTransactionDetails(clicked, template: TemplateRef<any>) {
this.clickedRecur = clicked;
this.updatedTime = moment(this.clickedRecur.display_time, 'llll').format('YYYY-MM-DD[T]HH:mm');
this.openModal(template);
}
openModal(template: TemplateRef<any>) {
this.modalRef = this.modalService.show(template);
}
editRecurringTransaction() {
let updatedTimeSubmit = moment(this.updatedTime, 'YYYY-MM-DD[T]HH:mm').local().format('YYYY-MM-DD[T]HH:mm:ss.SSSZ');
this.clickedRecur.display_time = moment(this.updatedTime).format('llll');
let myParams = {
category: (this.clickedRecur.category == 0 ? undefined : this.clickedRecur.category),
essential: this.clickedRecur.essential,
id: this.clickedRecur.id,
apply_time: updatedTimeSubmit,
recurring_period: this.clickedRecur.recurring_period,
seller: this.clickedRecur.seller,
value: this.clickedRecur.value,
};
this.api
.recurUpdate(myParams)
.subscribe(
result => {
if ( result.success === true ) {
this.transactionFormStatus = 'success';
this.transactionFormStatusSuccess = 'Edit Succeeded.';
} else {
this.transactionFormStatusError = JSON.stringify(result.status) + 'Error, ' + JSON.stringify(result.message);
this.transactionFormStatus = 'send_failed';
}
},
error => {
console.log(error);
try {
this.transactionFormStatusError = '"' + error.error.error + '" Error, ' + error.error.message;
} catch (e) {
this.transactionFormStatusError = 'There was a server error, please try again later.';
}
this.transactionFormStatus = 'send_failed';
}
);
}
deleteRecurringTransaction() {
let myParams = {
id: this.clickedRecur.id,
};
this.api
.recurDelete(myParams)
.subscribe(
result => {
if ( result.success === true ) {
this.transactionFormStatus = 'success';
this.transactionFormStatusSuccess = 'Delete Succeeded.';
} else {
this.transactionFormStatusError = JSON.stringify(result.status) + 'Error, ' + JSON.stringify(result.message);
this.transactionFormStatus = 'send_failed';
}
},
error => {
console.log(error);
try {
this.transactionFormStatusError = '"' + error.error.error + '" Error, ' + error.error.message;
} catch (e) {
this.transactionFormStatusError = 'There was a server error, please try again later.';
}
this.transactionFormStatus = 'send_failed';
}
);
}
toggleShowMeta() {
this.showMeta = !this.showMeta;
}
}

View File

@ -25,46 +25,99 @@
<div class="sidebar">
<nav class="sidebar-nav">
<ul class="nav">
<li class="nav-item">
<div class="row no-gutters mt-1 d-lg-none small text-center text-muted">
<div class="col-12">Click &#9776; to Close Menu</div>
</div>
</li>
<li class="nav-item">
<a class="nav-link" routerLinkActive="active" [routerLink]="['/dashboard']">
<i class="icon-speedometer"></i> Dashboard
<div class="row no-gutters align-items-center">
<div class="col-2"><i class="icon-speedometer"></i></div>
<div class="col-10">Dashboard</div>
</div>
</a>
</li>
<li class="nav-item">
<a class="nav-link" routerLinkActive="active" [routerLink]="['/more-graphs-and-tables']">
<div class="row no-gutters align-items-center">
<div class="col-2"><i class="icon-map"></i></div>
<div class="col-10">Infographics</div>
</div>
</a>
</li>
<li class="nav-item">
<a class="nav-link" routerLinkActive="active" [routerLink]="['/add-data']">
<i class="icon-basket"></i> Add Transaction
<div class="row no-gutters align-items-center">
<div class="col-2"><i class="icon-basket"></i></div>
<div class="col-10">Add Transaction</div>
</div>
</a>
</li>
<li class="nav-item">
<a class="nav-link" routerLinkActive="active" [routerLink]="['/feedback']">
<i class="icon-envelope-letter"></i> Enter Feedback
<div class="row no-gutters align-items-center">
<div class="col-2"><i class="icon-envelope-letter"></i></div>
<div class="col-10">Enter Feedback</div>
</div>
</a>
</li>
<li class="nav-item">
<a class="nav-link" routerLinkActive="active" [routerLink]="['/map']">
<i class="icon-map"></i> Purchase Map
<div class="row no-gutters align-items-center">
<div class="col-2"><i class="icon-map"></i></div>
<div class="col-10">Purchase Map</div>
</div>
</a>
</li>
<li class="nav-item">
<a class="nav-link" routerLinkActive="active" [routerLink]="['/story-trail']">
<i class="icon-map"></i> Lancaster Independent Story
<div class="row no-gutters align-items-center">
<div class="col-2"><i class="icon-map"></i></div>
<div class="col-10">Story Trail</div>
</div>
</a>
</li>
<li *ngIf="accountType == 'customer'" class="nav-item">
<a class="nav-link" routerLinkActive="active" [routerLink]="['/leaderboard']">
<i class="icon-basket"></i> Leaderboard
<div class="row no-gutters align-items-center">
<div class="col-2"><i class="icon-basket"></i></div>
<div class="col-10">Leaderboard</div>
</div>
</a>
</li>
<li class="nav-item">
<a class="nav-link" routerLinkActive="active" [routerLink]="['/transaction-log']">
<i class="icon-basket"></i> Transaction Log
<div class="row no-gutters align-items-center">
<div class="col-2"><i class="icon-basket"></i></div>
<div class="col-10">Transaction Log</div>
</div>
</a>
</li>
<li *ngIf="accountType == 'organisation'" class="nav-item">
<a class="nav-link" routerLinkActive="active" [routerLink]="['/payroll-log']">
<i class="icon-basket"></i> Payroll Log
<li class="nav-item">
<a class="nav-link" routerLinkActive="active" [routerLink]="['/category-month']">
<div class="row no-gutters align-items-center">
<div class="col-2"><i class="icon-basket"></i></div>
<div class="col-10">Budget</div>
</div>
</a>
</li>
<li class="nav-item">
<a class="nav-link" routerLinkActive="active" [routerLink]="['/suppliers']">
<div class="row no-gutters align-items-center">
<div class="col-2"><i class="icon-speedometer"></i></div>
<div class="col-10">Suppliers</div>
</div>
</a>
</li>
<li *ngIf="accountType == 'organisation'" class="nav-item">
<a class="nav-link" routerLinkActive="active" [routerLink]="['/payroll-log']">
<div class="row no-gutters align-items-center">
<div class="col-2"><i class="icon-basket"></i></div>
<div class="col-10">Payroll Log</div>
</div>
</a>
</li>
</ul>
</nav>
</div>

View File

@ -39,7 +39,6 @@ export class FullLayoutComponent implements OnInit {
.logout()
.subscribe(
result => {
console.log('Logged out!');
localStorage.clear();
this.router.navigate(['/login']);
}

View File

@ -0,0 +1,13 @@
<div>
<div>
<div style="display: block">
<canvas baseChart
[datasets]="bubbleChartData"
[options]="bubbleChartOptions"
[colors]="bubbleChartColors"
[legend]="bubbleChartLegend"
[chartType]="bubbleChartType">
</canvas>
</div>
</div>
</div>

View File

@ -0,0 +1,97 @@
import { Component, OnInit } from '@angular/core';
import { ChartOptions, ChartType, ChartDataSets } from 'chart.js';
import { Color } from 'ng2-charts';
@Component({
selector: 'app-bubble-chart',
templateUrl: './bubble-panel.component.html',
})
export class BubbleChartComponent implements OnInit {
public bubbleChartOptions: ChartOptions = {
responsive: true,
scales: {
xAxes: [
{
ticks: {
min: 0,
max: 30,
}
}
],
yAxes: [
{
ticks: {
min: 0,
max: 30,
}
}
]
}
};
public bubbleChartType: ChartType = 'bubble';
public bubbleChartLegend = true;
public bubbleChartData: ChartDataSets[] = [
{
data: [
{ x: 10, y: 10, r: 10 },
{ x: 15, y: 5, r: 15 },
{ x: 26, y: 12, r: 23 },
{ x: 7, y: 8, r: 8 },
],
label: 'Series A',
backgroundColor: 'green',
borderColor: 'blue',
hoverBackgroundColor: 'purple',
hoverBorderColor: 'red',
},
];
public bubbleChartColors: Color[] = [
{
backgroundColor: [
'red',
'green',
'blue',
'purple',
'yellow',
'brown',
'magenta',
'cyan',
'orange',
'pink'
]
}
];
constructor() { }
ngOnInit() {
}
// events
public chartClicked({ event, active }: { event: MouseEvent, active: {}[] }): void {
console.log(event, active);
}
public chartHovered({ event, active }: { event: MouseEvent, active: {}[] }): void {
console.log(event, active);
}
private rand(max: number) {
return Math.trunc(Math.random() * max);
}
private randomPoint(maxCoordinate: number) {
const x = this.rand(maxCoordinate);
const y = this.rand(maxCoordinate);
const r = this.rand(30) + 5;
return { x, y, r };
}
public randomize(): void {
const numberOfPoints = this.rand(5) + 5;
const data = Array.apply(null, { length: numberOfPoints }).map(r => this.randomPoint(30));
this.bubbleChartData[0].data = data;
}
}

View File

@ -1,18 +1,9 @@
<div class="card">
<div class="card-block">
<div class="row">
<div class="col-sm-5">
<div class="col-sm-12">
<h4 class="card-title mb-0">Customers</h4>
</div><!--/.col-->
<div class="col-sm-7 hidden-sm-down">
<div class="btn-toolbar float-right" role="toolbar" aria-label="Toolbar with button groups">
<div class="btn-group mr-3" data-toggle="buttons" aria-label="First group">
<label class="btn btn-outline-secondary active">
<input type="radio" name="options" id="option2" checked> Week
</label>
</div>
</div>
</div><!--/.col-->
</div><!--/.row-->
<div class="chart-wrapper" style="height:300px;margin-top:40px;">
<canvas baseChart class="chart"

View File

@ -82,12 +82,12 @@ export class GraphPanel implements OnInit {
pointHoverBackgroundColor: '#fff'
},
{ // brandSuccess
backgroundColor: 'transparent',
backgroundColor: this.convertHex(this.brandInfo, 10),
borderColor: this.brandSuccess,
pointHoverBackgroundColor: '#fff'
},
{ // brandDanger
backgroundColor: 'transparent',
backgroundColor: this.convertHex(this.brandDanger, 10),
borderColor: this.brandDanger,
pointHoverBackgroundColor: '#fff',
borderWidth: 1,

View File

@ -0,0 +1,19 @@
<div class="card">
<div class="card-block">
<div class="row">
<div class="col-12">
<h4 class="card-title mb-0">Global Purchases by Type</h4>
</div>
</div>
<div class="chart-wrapper">
<canvas baseChart class="chart"
[data]="doughnutChartDataLocal"
[labels]="doughnutChartLabelsLocal"
[colors]="doughnutChartColors"
[legend]="chartLegend"
[chartType]="chartType"
(chartHover)="chartHovered($event)"
(chartClick)="chartClicked($event)"></canvas>
</div>
</div>
</div>

View File

@ -0,0 +1,86 @@
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { ApiService } from '../providers/api-service';
import { OrgPiesService } from '../providers/org-pies.service';
import { DataType } from '../shared/data-types.enum';
import { ChartData } from '../_interfaces/chart-data';
@Component({
selector: 'org-pie-panel',
templateUrl: 'org-pie-panel.component.html',
})
export class OrgPiePanel implements OnInit {
public chartType = 'pie';
public chartLegend = true;
public doughnutChartDataLocal: number[] = [];
public doughnutChartColors: any[] = [
{
backgroundColor:[
'#ffa1b5',
'#3cde52',
'#52afed',
'#c133e3',
'#f7fa08',
'#75152d',
'#ee12ee',
'#15eaea',
'#eaa015',
'#ea1515',
'#2d4fcc'
]
},
{
borderColor: [
'red',
'green',
'blue',
'purple',
'yellow',
'brown',
'magenta',
'cyan',
'orange',
'pink'
]
},
{ borderWidth: [100]
},
];
public doughnutChartLabelsLocal: string[] = [];
constructor(
private api: ApiService,
private pieService: OrgPiesService,
) {
this.pieService.getOrgPie().subscribe(
result => {
this.setChartData(result.local_all);
},
error => {
console.log('Retrieval Error');
console.log( error._body );
}
);
}
public ngOnInit(): void {
}
private setChartData(dataLocal: any) {
this.doughnutChartDataLocal = Object.keys(dataLocal).map(key => dataLocal[key]);
// setTimeout is currently a workaround for ng2-charts labels
setTimeout(() => this.doughnutChartLabelsLocal = Object.keys(dataLocal), 0);
}
// events
public chartClicked(e: any): void {
}
public chartHovered(e: any): void {
}
}

View File

@ -0,0 +1,19 @@
<div class="card">
<div class="card-block">
<div class="row">
<div class="col-12">
<h4 class="card-title mb-0">All Purchases by Category</h4>
</div>
</div>
<div class="chart-wrapper">
<canvas baseChart class="chart"
[data]="doughnutChartDataLocal"
[labels]="doughnutChartLabelsLocal"
[colors]="doughnutChartColors"
[legend]="chartLegend"
[chartType]="chartType"
(chartHover)="chartHovered($event)"
(chartClick)="chartClicked($event)"></canvas>
</div>
</div>
</div>

View File

@ -0,0 +1,94 @@
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { ApiService } from '../providers/api-service';
import { CustPiesService } from '../providers/cust-pies.service';
import { DataType } from '../shared/data-types.enum';
import { ChartData } from '../_interfaces/chart-data';
@Component({
selector: 'panel-pie',
templateUrl: 'pie-panel.component.html',
})
export class PiePanel implements OnInit {
public chartType = 'pie';
public chartLegend = true;
public doughnutChartDataLocal: number[] = [];
public doughnutChartLabelsLocal: string[] = [];
public doughnutChartColors: any[] = [
{ backgroundColor: [
'#ffa1b5',
'#3cde52',
'#52afed',
'#c133e3',
'#f7fa08',
'#75152d',
'#ee12ee',
'#15eaea',
'#eaa015',
'#ea1515',
'#2d4fcc'
]
},
{ borderColor:[
'red',
'green',
'blue',
'purple',
'yellow',
'brown',
'magenta',
'cyan',
'orange',
'pink'
]
},
{ borderWidth: [10]
}
];
constructor(
private api: ApiService,
private pieService: CustPiesService,
) {
this.pieService.getPie().subscribe(
result => {
this.setChartData(result.local_all);
},
error => {
console.log('Retrieval Error');
console.log( error._body );
}
);
}
public ngOnInit(): void {
}
private setChartData(dataLocal: any) {
this.doughnutChartDataLocal = Object.keys(dataLocal).map(key => dataLocal[key]);
// setTimeout is currently a workaround for ng2-charts labels
setTimeout(() => this.doughnutChartLabelsLocal = Object.keys(dataLocal), 0);
}
// convert Hex to RGBA
public convertHex(hex: string, opacity: number) {
hex = hex.replace('#', '');
const r = parseInt(hex.substring(0, 2), 16);
const g = parseInt(hex.substring(2, 4), 16);
const b = parseInt(hex.substring(4, 6), 16);
const rgba = 'rgba(' + r + ', ' + g + ', ' + b + ', ' + opacity / 100 + ')';
return rgba;
}
// events
public chartClicked(e: any): void {
}
public chartHovered(e: any): void {
}
}

View File

@ -1,8 +1,8 @@
import { map } from 'rxjs/operators';
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs/Rx';
import { environment } from '../../environments/environment';
import 'rxjs/add/operator/map';
/* this provider handles the interaction between server and client */
@ -29,18 +29,15 @@ export class ApiService {
// Login API
public getSessionKey() {
console.log('get key');
return this.sessionKey;
}
public setSessionKey(key) {
console.log('set key');
this.sessionKey = key;
localStorage.setItem('sessionKey', this.sessionKey);
}
public removeSessionKey() {
console.log('remove key');
this.sessionKey = null;
localStorage.removeItem('sessionKey');
}
@ -57,8 +54,8 @@ export class ApiService {
.post<any>(
this.apiUrl + '/login',
data
)
.map(
).pipe(
map(
result => {
const json = result;
this.setSessionKey(json.session_key);
@ -69,24 +66,23 @@ export class ApiService {
this.setUserType(json.user_type);
return json;
}
);
));
}
public logout() {
console.log(this.sessionKey);
const key = this.sessionKey;
return this.http
.post<any>(
this.apiUrl + '/logout',
{ session_key : key },
)
.map(
).pipe(
map(
response => {
localStorage.clear();
this.sessionKey = null;
return response;
}
);
));
}
// Submits feedback
@ -96,7 +92,6 @@ export class ApiService {
data.package_name = 'Foodloop Web';
data.version_code = 'dev';
data.version_number = 'dev';
console.log(data);
return this.http.post<any>(
this.apiUrl + '/feedback',
data
@ -116,6 +111,65 @@ export class ApiService {
);
}
// Basic Customer User stats API
public categoryList() {
const key = this.sessionKey;
return this.http.post<any>(
this.apiUrl + '/search/category',
{
session_key : key,
}
);
}
// Basic Customer User stats API
public categoryTransactionList() {
const key = this.sessionKey;
return this.http.post<any>(
this.apiUrl + '/stats/category',
{
session_key : key,
}
);
}
// LCC data
public externalTransactions() {
const key = this.sessionKey;
return this.http.post<any>(
this.apiUrl + '/v1/organisation/external/transactions',
{
session_key : key,
}
);
}
public loadMiscUrl(extra_url, extraArgs = {}) {
const key = this.sessionKey;
return this.http.post<any>(
this.apiUrl + '/v1/' + extra_url,
{
session_key : key,
...extraArgs,
}
);
}
public externalSuppliers(page, sortBy, sortDir, perPage, search) {
const key = this.sessionKey;
return this.http.post<any>(
this.apiUrl + '/v1/organisation/external/suppliers',
{
session_key : key,
page : page,
sort_by : sortBy,
sort_dir : sortDir,
per_page : perPage,
search : search,
}
);
}
// Searches organisations used for transaction submission
public search(data) {
@ -136,6 +190,26 @@ export class ApiService {
);
}
// Edits a recurring transaction
public recurUpdate(data) {
data.session_key = this.sessionKey;
return this.http.post<any>(
this.apiUrl + '/recurring-transactions',
data
);
}
// Edits a recurring transaction
public recurDelete(data) {
data.session_key = this.sessionKey;
return this.http.post<any>(
this.apiUrl + '/recurring-transactions/delete',
data
);
}
// gets payroll list for log
public payrollList(data) {
@ -188,7 +262,6 @@ export class ApiService {
public setUserInfo(
email: string,
display_name: string) {
console.log('set UserInfo');
localStorage.setItem('email', email);
localStorage.setItem('displayname', display_name);
}
@ -196,7 +269,6 @@ export class ApiService {
// Sets usertype
public setUserType(user_type: string) {
console.log('set UserType');
localStorage.setItem('usertype', user_type);
}
@ -221,33 +293,27 @@ export class ApiService {
// Deletes account details on logout
public removeUserInfo() {
console.log('remove UserInfo');
localStorage.removeItem('email');
localStorage.removeItem('displayname');
}
public getFullName() {
console.log('get Full Name');
localStorage.getItem('fullname');
}
public getDisplayName() {
console.log('get Display Name');
localStorage.getItem('displayname');
}
public getPostcode() {
console.log('get Postcode');
localStorage.getItem('postcode');
}
public getYearOfBirth() {
console.log('get Year of Birth');
localStorage.getItem('yearofbirth');
}
public getEmail() {
console.log('get email');
localStorage.getItem('email');
}
@ -276,20 +342,31 @@ export class ApiService {
);
}
// Load LIS Data
public getLisData(data) {
// Load Association Data
public getAssocData(data) {
data.session_key = this.sessionKey;
return this.http.post<any>(
this.apiUrl + '/v1/supplier/location/lis',
this.apiUrl + '/v1/supplier/location/trail',
data
);
}
// Basic Customer User stats API
public basicStats() {
public customerStats() {
const key = this.sessionKey;
return this.http.post<any>(
this.apiUrl + '/stats',
this.apiUrl + '/stats/customer',
{
session_key : key,
}
);
}
// Basic Customer User stats API
public orgStats() {
const key = this.sessionKey;
return this.http.post<any>(
this.apiUrl + '/stats/organisation',
{
session_key : key,
}

View File

@ -0,0 +1,14 @@
import { Injectable } from '@angular/core';
import { ApiService } from './api-service';
@Injectable()
export class CustGraphsService {
private custGraphUrl = '/v1/customer/graphs';
constructor(private api: ApiService) { }
public getGraph(name: string, data: any = {}) {
data.graph = name;
return this.api.post(this.custGraphUrl, data);
}
}

View File

@ -0,0 +1,14 @@
import { Injectable } from '@angular/core';
import { ApiService } from './api-service';
import { Observable } from 'rxjs';
@Injectable()
export class CustPiesService {
private custPieUrl = '/v1/customer/pies';
constructor(private api: ApiService) { }
public getPie(): Observable<any> {
return this.api.post(this.custPieUrl);
}
}

View File

@ -0,0 +1,15 @@
import { Injectable } from '@angular/core';
import { ApiService } from './api-service';
import { Observable } from 'rxjs';
@Injectable()
export class CustSnippetsService {
private custSnippetsUrl = '/v1/customer/snippets';
constructor(private api: ApiService) { }
// This endpoint should mimic basicStats
public getData(): Observable<any> {
return this.api.post(this.custSnippetsUrl);
}
}

View File

@ -0,0 +1,14 @@
import { Injectable } from '@angular/core';
import { ApiService } from './api-service';
import { Observable } from 'rxjs';
@Injectable()
export class OrgPiesService {
private orgPieUrl = '/v1/organisation/pies';
constructor(private api: ApiService) { }
public getOrgPie(): Observable<any> {
return this.api.post(this.orgPieUrl);
}
}

View File

@ -1,6 +1,6 @@
import { Injectable } from '@angular/core';
import { ApiService } from './api-service';
import { Observable } from 'rxjs/Rx';
import { Observable } from 'rxjs';
@Injectable()
export class OrgSnippetsService {

View File

@ -1,6 +1,8 @@
import {filter} from 'rxjs/operators';
import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute, NavigationEnd } from '@angular/router';
import 'rxjs/add/operator/filter';
@Component({
selector: 'app-breadcrumbs',
@ -18,7 +20,7 @@ export class BreadcrumbsComponent implements OnInit {
breadcrumbs: Array<Object>;
constructor(private router: Router, private route: ActivatedRoute) {}
ngOnInit(): void {
this.router.events.filter(event => event instanceof NavigationEnd).subscribe(event => {
this.router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe(event => {
this.breadcrumbs = [];
let currentRoute = this.route.root,
url = '';

View File

@ -0,0 +1,3 @@
<td>{{type.type}}</td>
<td>{{type.count}}</td>
<td>{{type.sum | currency:'GBP':'symbol':'1.2-2' }}</td>

View File

@ -0,0 +1,19 @@
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
interface MetaTypeData {
type: string;
sum: number;
count: number;
}
@Component({
// tslint:disable-next-line
selector: '[meta-type-result]',
templateUrl: 'meta-type-result.component.html',
})
export class MetaTypeResultComponent implements OnInit {
@Input() public type: MetaTypeData;
ngOnInit(): void {
}
}

View File

@ -0,0 +1,6 @@
<td (click)="recurClick()">{{recur.seller}}</td>
<td (click)="recurClick()">{{categories[recur.category] || 'Uncategorised'}}</td>
<td (click)="recurClick()">{{recur.essential == 1 ? 'true' : 'false'}}</td>
<td (click)="recurClick()">{{recur.display_time}}</td>
<td (click)="recurClick()">{{recur.recurring_period}}</td>
<td (click)="recurClick()">{{recur.value}}</td>

View File

@ -0,0 +1,40 @@
import { Component, Input, Output, EventEmitter } from '@angular/core';
import * as moment from 'moment';
interface RecurData {
category: number;
essential: number;
id: number;
last_updated: string;
recurring_period: string;
seller: string;
start_time: string;
value: number;
display_time: any;
}
@Component({
// tslint:disable-next-line
selector: '[recur-result]',
templateUrl: 'recur-result.component.html',
})
export class RecurResultComponent {
@Input() public recur: RecurData;
@Output() public onClick = new EventEmitter();
@Input() public categories: any;
public updatedDate: string;
ngOnInit(): void {
if (this.recur.last_updated) {
this.recur.display_time = moment(this.recur.last_updated).format('llll');
} else {
this.recur.display_time = moment(this.recur.start_time).format('llll');
}
}
public recurClick(): void {
this.onClick.emit(
this.recur
);
}
}

View File

@ -0,0 +1,17 @@
<div class="form-group row">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Seller</th>
<th>Category</th>
<th>Essential</th>
<th>Last Applied</th>
<th>Recurring Period</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr recur-result *ngFor="let recur of recurList" [categories]="categories" [recur]="recur" (onClick)="recurClick($event, template)"></tr>
</tbody>
</table>
</div>

View File

@ -0,0 +1,30 @@
import { Component, Input, Output, EventEmitter } from '@angular/core';
import { RecurResultComponent } from '../shared/recur-result.component';
interface RecurData {
category: string;
essential: number;
id: number;
last_updated: string;
recurring_period: string;
seller: string;
start_time: string;
value: number;
display_time: any;
}
@Component({
// tslint:disable-next-line
selector: 'recur-table',
templateUrl: 'recur-table.component.html',
})
export class RecurTableComponent {
@Input() public recurList: Array<RecurData>;
@Output() public onClick = new EventEmitter();
@Input() public categories: any;
public recurClick(event: any): void {
this.onClick.emit( event );
}
}

View File

@ -0,0 +1,3 @@
<td>{{supplier.name}}</td>
<td>{{supplier.postcode}}</td>
<td>{{supplier.spend | currency:'GBP':'symbol':'1.2-2' }}</td>

View File

@ -0,0 +1,19 @@
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
interface SupplierData {
name: string;
postcode: string;
spend: number;
}
@Component({
// tslint:disable-next-line
selector: '[supplier-result]',
templateUrl: 'supplier-result.component.html',
})
export class SupplierResultComponent implements OnInit {
@Input() public supplier: SupplierData;
ngOnInit(): void {
}
}

View File

@ -1,3 +1,6 @@
<td>{{transaction.seller}}</td>
<td>{{transaction.value | currency:'GBP':'symbol':'1.2-2' }}</td>
<td *ngIf="!showMeta">{{transaction.value | currency:'GBP':'symbol':'1.2-2' }}</td>
<td *ngIf="showMeta">{{transaction.net_value | currency:'GBP':'symbol':'1.2-2' }}</td>
<td *ngIf="showMeta">{{transaction.sales_tax_value | currency:'GBP':'symbol':'1.2-2' }}</td>
<td *ngIf="showMeta">{{transaction.gross_value | currency:'GBP':'symbol':'1.2-2' }}</td>
<td>{{transactionDate}}</td>

View File

@ -14,6 +14,7 @@ interface TransactionData {
})
export class TransactionResultComponent implements OnInit {
@Input() public transaction: TransactionData;
@Input() public showMeta: boolean;
public transactionDate: string;
ngOnInit(): void {

View File

@ -0,0 +1,3 @@
<td>{{ward.ward}}</td>
<td>{{ward.count}}</td>
<td>{{ward.sum | currency:'GBP':'symbol':'1.2-2' }}</td>

View File

@ -0,0 +1,19 @@
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
interface WardData {
ward: string;
sum: number;
count: number;
}
@Component({
// tslint:disable-next-line
selector: '[ward-result]',
templateUrl: 'ward-result.component.html',
})
export class WardResultComponent implements OnInit {
@Input() public ward: WardData;
ngOnInit(): void {
}
}

View File

@ -0,0 +1,55 @@
<div class="row">
<div class="col-md-6 col-xl-3">
<div class="card text-center" style="background-color: transparent; border: none">
<div class="card-block">
<ul style="list-style: none; padding-left: 0;">
<li class="hidden-sm-down">
<div><h5 class="text-dark-green">My Points</h5></div>
<div class="number-circle mx-auto"><strong>{{ userSum / 10 | number:'1.0-0' }}</strong></div>
</li>
</ul>
</div>
</div>
</div>
<div class="col-md-6 col-xl-3">
<div class="card text-center" style="background-color: transparent; border: none">
<div class="card-block">
<ul style="list-style: none; padding-left: 0;">
<li class="hidden-sm-down">
<div><h5 class="text-dark-green">My Rank</h5></div>
<div *ngIf="userPosition == 0" class="statuscontent">
<div class="number-circle mx-auto"><strong>Unranked</strong></div>
</div>
<div *ngIf="userPosition != 0" class="statuscontent">
<div class="number-circle mx-auto"><strong>{{ userPosition }}</strong></div>
</div>
</li>
</ul>
</div>
</div>
</div>
<div class="col-md-6 col-xl-3">
<div class="card text-center" style="background-color: transparent; border: none">
<div class="card-block">
<ul style="list-style: none; padding-left: 0;">
<li class="hidden-sm-down">
<div><h5 class="text-dark-green">My Total Spend</h5></div>
<div class="number-circle mx-auto"><strong>{{ userSum | currency:'GBP':'symbol':'1.2-2' }}</strong></div>
</li>
</ul>
</div>
</div>
</div>
<div class="col-md-6 col-xl-3">
<div class="card text-center" style="background-color: transparent; border: none">
<div class="card-block">
<ul style="list-style: none; padding-left: 0;">
<li class="hidden-sm-down">
<div><h5 class="text-dark-green">Value to Local Economy</h5></div>
<div class="number-circle mx-auto"><strong>{{ userSum * 2.3 | currency:'GBP':'symbol':'1.2-2' }}</strong></div>
</li>
</ul>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,26 @@
import { Component, OnInit } from '@angular/core';
import { CustSnippetsService } from '../providers/cust-snippets.service';
@Component({
selector: 'snippet-bar-cust',
templateUrl: 'cust-snippet-bar.component.html',
})
export class CustBarSnippetComponent implements OnInit {
public userSum = 0;
public userPosition = 0;
constructor(
private snippetsService: CustSnippetsService,
) { }
public ngOnInit(): void {
this.snippetsService.getData()
.subscribe(
result => {
this.userSum = result.snippets.user_sum;
this.userPosition = result.snippets.user_position;
}
);
}
}

View File

@ -2,7 +2,15 @@
<div class="card-footer">
<ul>
<li class="hidden-sm-down">
<div class="text-muted">Customers This Month</div>
<div class="text-muted">Sales Total</div>
<strong>{{ allSalesCount }}</strong>
</li>
<li class="hidden-sm-down">
<div class="text-muted">Money Spent Total</div>
<strong>{{ allPurchasesTotal | currency:'GBP':'symbol':'1.2-2'}}</strong>
</li>
<li class="hidden-sm-down">
<div class="text-muted">Sales This Month</div>
<strong>{{ thisMonthSalesCount }}</strong>
</li>
<li class="hidden-sm-down">
@ -10,7 +18,7 @@
<strong>{{ thisMonthPurchasesTotal | currency:'GBP':'symbol':'1.2-2'}}</strong>
</li>
<li class="hidden-sm-down">
<div class="text-muted">Customers Today</div>
<div class="text-muted">Sales Today</div>
<strong>{{ todaySalesCount }}</strong>
</li>
<li class="hidden-sm-down">

View File

@ -7,6 +7,8 @@ import { OrgSnippetsService } from '../providers/org-snippets.service';
})
export class OrgBarSnippetComponent implements OnInit {
public allSalesCount = 0;
public allSalesTotal = 0;
public thisMonthSalesCount = 0;
public thisMonthSalesTotal = 0;
public thisWeekSalesCount = 0;
@ -14,6 +16,8 @@ export class OrgBarSnippetComponent implements OnInit {
public todaySalesCount = 0;
public todaySalesTotal = 0;
public allPurchasesCount = 0;
public allPurchasesTotal = 0;
public thisMonthPurchasesCount = 0;
public thisMonthPurchasesTotal = 0;
public thisWeekPurchasesCount = 0;
@ -29,6 +33,8 @@ export class OrgBarSnippetComponent implements OnInit {
this.snippetsService.getData()
.subscribe(
result => {
this.allSalesCount = result.snippets.all_sales_count;
this.allSalesTotal = result.snippets.all_sales_total;
this.thisMonthSalesCount = result.snippets.this_month_sales_count;
this.thisMonthSalesTotal = result.snippets.this_month_sales_total;
this.thisWeekSalesCount = result.snippets.this_week_sales_count;
@ -36,6 +42,8 @@ export class OrgBarSnippetComponent implements OnInit {
this.todaySalesCount = result.snippets.today_sales_count;
this.todaySalesTotal = result.snippets.today_sales_total;
this.allPurchasesCount = result.snippets.all_purchases_count;
this.allPurchasesTotal = result.snippets.all_purchases_total;
this.thisMonthPurchasesCount = result.snippets.this_week_purchases_count;
this.thisMonthPurchasesTotal = result.snippets.this_week_purchases_total;
this.thisWeekPurchasesCount = result.snippets.this_month_purchases_count;

View File

@ -10,7 +10,7 @@
<div class="chart-wrapper px-3" style="height:70px;">
<canvas baseChart
class="chart"
[datasets]="lineChartData"
[datasets]="lineGraphChartData"
[labels]="lineChartLabels"
[options]="lineChartOptions"
[colors]="lineChartColours"

View File

@ -12,6 +12,7 @@ interface ChartData {
selector: 'widget-graph',
templateUrl: 'graph-widget.component.html',
})
export class GraphWidget implements OnInit {
@Input() public graphName: string;
@Input() public graphTitle = 'Graph';
@ -24,7 +25,7 @@ export class GraphWidget implements OnInit {
public graphSum: Number = 0;
public availableDataTypes = DataType;
public lineChartData: Array<ChartData> = [
public lineGraphChartData: Array<ChartData> = [
{
data: [],
label: 'Series A'
@ -35,12 +36,21 @@ export class GraphWidget implements OnInit {
maintainAspectRatio: false,
scales: {
xAxes: [{
type: 'time',
time: {
unit: 'day',
displayFormats: {
day: 'MMM D',
},
tooltipFormat: 'MMM D',
},
gridLines: {
color: 'transparent',
zeroLineColor: 'transparent'
},
ticks: {
fontSize: 2,
source: 'data',
fontColor: 'transparent',
}
@ -54,7 +64,7 @@ export class GraphWidget implements OnInit {
},
elements: {
line: {
borderWidth: 1
borderWidth: 2
},
point: {
radius: 4,
@ -106,10 +116,16 @@ export class GraphWidget implements OnInit {
private setData(data: any) {
this.setChartData(data.data);
this.setChartLabels(data.labels);
this.setChartBounds(data.bounds);
}
private setChartBounds(data) {
this.lineChartOptions.scales.xAxes[0].time.max = data.max;
this.lineChartOptions.scales.xAxes[0].time.min = data.min;
}
private setChartData(data: Array<number>) {
this.lineChartData[0].data = data;
this.lineGraphChartData[0].data = data;
this.graphSum = data.reduce((a, b) => a + b, 0);
// Set point size based on data
if ( data.length < 15 ) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

Before

Width:  |  Height:  |  Size: 260 KiB

After

Width:  |  Height:  |  Size: 260 KiB

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -1,2 +0,0 @@
ײֶ<67><D7B0>^f^ױ<>jA<6A>Fמ£†ֳ₪י$<24>אQםhֿ0<D6BF>L•+l™<6C>Dבג)9;
@¾&;q<>ƒט ©<C2A0>׀ iEֻײץֱװ0ו#ׂ¡<D782>ְְֵ<D6B0>׳bK<62>װס<D7B0>7<EFBFBD>U¦%¸x<C2B8>_x/6m‡=P <0C>q‡¶F™A$|װײ'Aט£½b1ױ<31><32>~הג/,אd¡×זLֲ”P<05>¢>S L

View File

@ -5,6 +5,8 @@
export const environment = {
production: false,
apiUrl: 'https://dev.peartrade.org/api',
apiUrl: 'https://dev.localspend.co.uk/api',
mapApiKey: 'CHANGEME',
enableAnalytics: false,
analyticsKey: 'CHANGEME',
};

Binary file not shown.

View File

@ -18,7 +18,6 @@
<link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet">
</head>
<body class="app header-fixed sidebar-fixed">
<!-- Enable bootstrap 4 theme -->
<script>window.__theme = 'bs4';</script>
@ -123,6 +122,12 @@
<div class="sk-cube3 sk-cube"></div>
</div>
</app-root>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
</script>
</body>
</html>

View File

@ -11,11 +11,11 @@
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
*
* Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
* Learn more in https://angular.io/guide/browser-support
*/
/***************************************************************************************************
* BROWSER POLYFILLS
* BROWSER POLYFILLS
*/
/** IE9, IE10 and IE11 requires all of the following polyfills. **/
@ -31,23 +31,37 @@ import 'core-js/es6/date';
import 'core-js/es6/array';
import 'core-js/es6/regexp';
import 'core-js/es6/map';
import 'core-js/es6/weak-map';
import 'core-js/es6/set';
import 'core-js/es6/reflect';
import 'core-js/es7/array';
import 'core-js/es7/object';
/** IE10 and IE11 requires the following for NgClass support on SVG elements */
// import 'classlist.js'; // Run `npm install --save classlist.js`.
/** IE10 and IE11 requires the following to support `@angular/animation`. */
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
/** Evergreen browsers require these. **/
/** IE10 and IE11 requires the following for the Reflect API. */
import 'core-js/es6/reflect';
import 'core-js/es7/reflect';
/**
* Required to support Web Animations `@angular/platform-browser/animations`.
* Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation
**/
import 'web-animations-js'; // Run `npm install --save web-animations-js`.
/** ALL Firefox browsers require the following to support `@angular/animation`. **/
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
/**
* By default, zone.js will patch all possible macroTask and DomEvents
* user can disable parts of macroTask/DomEvents patch by setting following flags
*/
(window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
(window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
(window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
/*
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
* with the following flag, it will bypass `zone.js` patch for IE/Edge
*/
(window as any).__Zone_enable_cross_context_check = true;

View File

@ -24,6 +24,25 @@ agm-map {
width: 15%;
}
// circle for text
// TODO: Make these resize based on inside content
.number-circle {
height: 10rem;
border-radius:50%;
text-align:center;
width: 10rem;
padding: 5rem 5%;
line-height: 0;
position: relative;
background: #4dbd74;
color: white;
font-size: 0.875rem;
}
.text-dark-green {
color: #10602c;
}
// white title font variant on type-2 as defined in _widgets.css
.horizontal-bars {
padding: 0;
@ -54,7 +73,8 @@ agm-map {
margin-bottom: 2px;
}
}
.small {
}
&.legend {
text-align: center;

View File

@ -0,0 +1,28 @@
.spinner {
width: 40px;
height: 40px;
background-color: #333;
margin: 100px auto;
-webkit-animation: sk-rotateplane 1.2s infinite ease-in-out;
animation: sk-rotateplane 1.2s infinite ease-in-out;
}
@-webkit-keyframes sk-rotateplane {
0% { -webkit-transform: perspective(120px) }
50% { -webkit-transform: perspective(120px) rotateY(180deg) }
100% { -webkit-transform: perspective(120px) rotateY(180deg) rotateX(180deg) }
}
@keyframes sk-rotateplane {
0% {
transform: perspective(120px) rotateX(0deg) rotateY(0deg);
-webkit-transform: perspective(120px) rotateX(0deg) rotateY(0deg)
} 50% {
transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg);
-webkit-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg)
} 100% {
transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg);
-webkit-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg);
}
}

View File

@ -76,3 +76,4 @@
// Custom styles
@import "custom";
@import "bootstrap/spinner";

View File

@ -2,7 +2,6 @@
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/app",
"module": "es2015",
"baseUrl": "",
"types": []
},

View File

@ -2,8 +2,6 @@
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/spec",
"module": "commonjs",
"target": "es5",
"baseUrl": "",
"types": [
"jasmine",
@ -11,7 +9,8 @@
]
},
"files": [
"test.ts"
"test.ts",
"polyfills.ts"
],
"include": [
"**/*.spec.ts",

View File

@ -1,6 +1,9 @@
{
"compileOnSave": false,
"compilerOptions": {
"downlevelIteration": true,
"importHelpers": true,
"module": "esnext",
"outDir": "./dist/out-tsc",
"baseUrl": "src",
"sourceMap": true,
@ -8,7 +11,7 @@
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es5",
"target": "es2015",
"typeRoots": [
"node_modules/@types"
],

Some files were not shown because too many files have changed in this diff Show More