Compare commits

..

10 commits

Author SHA1 Message Date
Unknown
f96dd0bfe2 WIP 2018-05-14 16:52:33 +01:00
Unknown
5fb33cffbf amended package lock json 2018-03-13 16:37:57 +00:00
Unknown
dc1e740889 Merge remote-tracking branch 'origin/theslby/pushapi' into theslby/pushapi 2018-03-13 16:37:03 +00:00
Unknown
9997cf7ee5 Merge remote-tracking branch 'origin/development' into theslby/pushapi 2018-03-13 16:33:59 +00:00
piratefinn
aa8763d5a6 Merge branch 'development' into theslby/pushapi 2018-03-07 13:09:14 +00:00
piratefinn
700ee3c155 fixes 2018-03-07 12:27:16 +00:00
Unknown
ff0fe0202f /pushapi: Auto stash before merge of "theslby/pushapi" and "development"
Push Frontend
2018-03-06 15:41:13 +00:00
Unknown
59cbc920c2 Merge branch 'development' into theslby/pushapi 2018-03-06 12:25:58 +00:00
Unknown
175c2decdd Merge remote-tracking branch 'origin/master' into theslby/pushapi 2018-01-19 13:40:17 +00:00
Unknown
6e0b059241 push stuff 2018-01-19 13:39:54 +00:00
108 changed files with 10427 additions and 17310 deletions

60
.angular-cli.json Normal file
View file

@ -0,0 +1,60 @@
{
"$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"
},
"serviceWorker": true
}
],
"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
}
}

7
.gitignore vendored
View file

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

3
.idea/.gitignore vendored
View file

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

View file

@ -1,14 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<TypeScriptCodeStyleSettings version="0">
<option name="SPACES_WITHIN_IMPORTS" value="true" />
<option name="BLANK_LINES_AROUND_FUNCTION" value="0" />
</TypeScriptCodeStyleSettings>
<codeStyleSettings language="TypeScript">
<option name="BLANK_LINES_AROUND_METHOD" value="0" />
<option name="BLANK_LINES_AROUND_METHOD_IN_INTERFACE" value="0" />
<option name="KEEP_SIMPLE_BLOCKS_IN_ONE_LINE" value="true" />
<option name="KEEP_SIMPLE_METHODS_IN_ONE_LINE" value="true" />
</codeStyleSettings>
</code_scheme>
</component>

View file

@ -1,5 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

View file

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

View file

@ -1,8 +0,0 @@
<?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>

View file

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

View file

@ -1,12 +1,16 @@
# Force sudo due to issue in travis
sudo: required
addons:
chrome: stable
language: node_js
node_js:
- node
- 8
before_install:
- openssl aes-256-cbc -K $encrypted_17157b34afc7_key -iv $encrypted_17157b34afc7_iv -in src/environments/environments.tar.enc -out src/environments/environments.tar -d
- openssl aes-256-cbc -K $encrypted_9d2af3734b6c_key -iv $encrypted_9d2af3734b6c_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
@ -17,7 +21,7 @@ before_deploy:
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="
secure: IZKEl1/LnzCN4/uMPKqUktURoNFjDmrybGKwrKbTPS27iXLrC9x4NwrQnOgipLF5h6Jeee4Qf1LhFCEpL/VTBTGMljQU2uOUFqn7TTGV5Ok7wxn2Z4CYeLDHMqDeGEECidAs8pB7I8lQvk5iUqmQT0NbuB0tDgavM9XCAecGUrFHh2tQaqPRGgZ1Q+1QB5d68EItSncMFKgRKja3Jq3u6ArF3uR3qY0dx9UKgcSUkKMnYbkBoEmiuCthdtwsH6WiIp4+K5GnVkGWxFa6KwdUJseHvUCuwS0Jp6lz47fP75xr4pHP45BxL7s549P60Iyr5AMRUge2+LPQV5QdbRH4guQV6qWI2L6Vw64fdC3lK00MhNFNO4M9p1T9eeldXhvUJwBDjzjoHlsrZysUMBy5G9CpQIVJtaPSVMWm/9yfPbu/B3k85cCHLb3fTos3altCWREIUScWIdDAHczJmmITUE9d5KljT8t2gLzCiCqB3BQ6ZzN8Ur68EIJ2ePuCO4644S+1shg5AXYDzXuUd30J57UGnuELQSMQkuZvMUQy8PmNO/iInIqpnuAwOwT4YD0MHgXrjD38a9+KszDjY+9HrIWcQE8P7AuN0iVf2voB5uLTOckkd4mXF9sskwoREVYJM+jz49HViggYyn1MHLsnlY5//vhMo5HNaf2DtT8fhk4=
file_glob: true
file: "../WebApp-Releases/*"
skip_cleanup: true

View file

@ -2,64 +2,6 @@
# 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

View file

@ -1,12 +0,0 @@
<?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>

123
README.md
View file

@ -1,43 +1,30 @@
# LocalSpend - Web Application
# LocalLoop Web Interface
This repository contains the Web application for the LocalSpend system.
## Current Status
This is the repository for the LocalLoop web interface, for traders and
customers to see and submit data to the service.
| Branch | Status |
| --- | --- |
| `master` | [![Build Status](https://travis-ci.org/Pear-Trading/FoodLoop-Web.svg?branch=master)](https://travis-ci.org/Pear-Trading/FoodLoop-Web) |
| `development` | [![Build Status](https://travis-ci.org/Pear-Trading/FoodLoop-Web.svg?branch=development)](https://travis-ci.org/Pear-Trading/FoodLoop-Web) |
## Contents
1. [Getting Started](#getting-started)
1. [Environments](#environments)
1. [Testing](#testing)
1. [Troubleshooting](#troubleshooting)
1. [Licences](#licenses)
| *Master:* | [![Build Status](https://travis-ci.org/Pear-Trading/FoodLoop-Web.svg?branch=master)](https://travis-ci.org/Pear-Trading/FoodLoop-Web) |
| *Development:* | [![Build Status](https://travis-ci.org/Pear-Trading/FoodLoop-Web.svg?branch=development)](https://travis-ci.org/Pear-Trading/FoodLoop-Web) |
## Getting Started
To get started with development, you will need an up-to-date version of
Node.js, git, and access to either the backend dev. server or a local
copy of it from the [LocalSpend Server][LocalLoop-Server] repo.
To get started with development, you will need an up to date version of
node.js, git, and access to either the backend dev server, or a local running
copy of the backend server from [LocalLoop Server][LocalLoop-Server].
For your local Node.js, we recommend using:
- [n][tj/n] for \*nix and Mac; and
- [nodist][marcelklehr/nodist] for Windows.
For your local node.js, We reccomend using [n][tj/n] on \*nix and Mac, for
Windows take a look at [nodist][marcelklehr/nodist] - although other options
exist. We reccomend Node.js version 8.0.0+ and npm version 5.3.0+.
We reccomend Node.js version 8.0.0+ and npm version 5.3.0+.
To get this repository set up, first clone it and then run the following
commands:
To get this repository set up:
1. Clone it
2. Install the dependencies:
- `npm install -g @angular/cli`
- `npm install`
3. Start the application:
- `npm start`
- The app. will automatically reload after source file changes
```
npm install -g @angular/cli
npm install
```
[LocalLoop-Server]:https://github.com/Pear-Trading/Foodloop-Server
[tj/n]:https://github.com/tj/n
@ -45,52 +32,46 @@ To get this repository set up:
## Environments
The app defaults to using the development server. For other options, see
`src/environments/environments.ts`.
## Build
Run `ng build` to build the project; the resulting files will be stored in the `dist/` directory. Use the `-prod` flag for a production build.
## Testing
There are two types of test in this codebase:
- unit tests (using Karma); and
- E2E tests (using Protractor).
### Karma Unit Tests
To run these, you just need to run `npm run test` - this should work without any further configuration. These run the `*.spec.ts` files next to the normal source files, and are for basic tests for each function on their own.
These tests are a long-running process, and will automatically run on file changes. Just run the command above, and then check back to the browser window that appears to see any errors as you work!
### Protractor E2E Tests
To run these, you will need to run the following command:
- `webdriver-manager update`
This will download the correct webdriver for you (we use chromedriver), and
any other dependencies it needs.
After that, you can run the tests with `npm run e2e`.
Note these run once, and will need to be re-run every time you want to run an
e2e test.
These tests are best run regularly, and should show if any issues have emerged
in other parts of the application that you are not aware of, or if some part
of the flow through the app has changed significantly.
## Troubleshooting
### Error: EACCES: permission denied, access '/usr/local/lib' when installing dependencies
Change npm's default directory by following [these steps](https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally#manually-change-npms-default-directory).
The app defaults to using the development server. For other options, see
`src/environments/environments.ts`
## Licences
### CoreUI
The interface itself is based off of [CoreUI][core-ui] which is MIT Licenced.
For information, see `LICENCE.MIT` included in this repo.
For information, see [MIT Licence](./LICENCE.MIT) included in this repo.
[core-ui]: http://coreui.io
# Old README
This is the old README with some minor tips on getting started, and more reading.
## Angular2DevelopmentCLI
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.0.0-beta.32.3.
### Development server
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
### Code scaffolding
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive/pipe/service/class/module`.
### Build
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build.
### Running unit tests
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
### Running end-to-end tests
Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
Before running the tests make sure you are serving the app via `ng serve`.
### Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).

View file

@ -1,182 +0,0 @@
{
"$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",
{"input":
"./node_modules/@google/markerclustererplus/images",
"glob": "*",
"output": "/images"
}
],
"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,5 +1,4 @@
#! /bin/bash
set -e
VERSION=`git describe --tags`
@ -12,13 +11,13 @@ echo "Building releases for $VERSION"
echo "Building Prod Release..."
npm run build:prod
ng build --prod
tar -czf ../WebApp-Releases/LocalLoop-Web-prod-$VERSION.tar.gz dist
echo "Building Dev Release..."
npm run build:dev
ng build --dev
tar -czf ../WebApp-Releases/LocalLoop-Web-dev-$VERSION.tar.gz dist

View file

@ -1,12 +0,0 @@
# 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

@ -1,5 +1,4 @@
import { LoginPageObject } from './login.po';
import { browser } from "protractor";
describe('Login Page', () => {
let page: LoginPageObject;
@ -14,46 +13,12 @@ describe('Login Page', () => {
});
it('should have a username box of type email', () => {
expect(page.isEmailFieldPresent()).toBeTruthy();
expect(page.getEmailFieldType()).toEqual('email');
expect(page.isUsernameFieldPresent()).toBeTruthy();
expect(page.getUsernameFieldType()).toEqual('email');
});
it('should have a password box of type password', () => {
expect(page.isPasswordFieldPresent()).toBeTruthy();
expect(page.getPasswordFieldType()).toBe('password');
});
it('should have a login button of type submit', () => {
expect(page.isLoginButtonPresent()).toBeTruthy();
expect(page.getLoginButtonType()).toBe('submit');
});
it('should have a disabled login button when empty', () => {
expect(page.isLoginButtonEnabled()).toBeFalsy();
});
it('should have a disabled login button when only email', () => {
page.fillEmailFieldWith('test@example.com');
expect(page.isLoginButtonEnabled()).toBeFalsy();
});
it('should have a disabled login button when only password', () => {
page.fillPasswordFieldWith('abc123');
expect(page.isLoginButtonEnabled()).toBeFalsy();
});
it('should have an enabled login button when both inputs filled', () => {
page.fillEmailFieldWith('test@example.com');
page.fillPasswordFieldWith('abc123');
expect(page.isLoginButtonEnabled()).toBeTruthy();
});
it('should submit the filled data when login pressed', () => {
page.fillEmailFieldWith('test@example.com');
page.fillPasswordFieldWith('abc123');
expect(page.isLoginButtonEnabled()).toBeTruthy();
page.getLoginButton().click();
browser.waitForAngular();
expect(browser.getCurrentUrl()).toContain('dashboard');
});
});

View file

@ -9,23 +9,12 @@ export class LoginPageObject {
return element(by.css('app-root h1')).getText();
}
getEmailField() { return element(by.id('username')); }
getUsernameField() { return element(by.id('username')); }
getPasswordField() { return element(by.id('password')); }
getLoginButton() { return element(by.id('login')); }
isEmailFieldPresent() { return this.getEmailField().isPresent(); }
isUsernameFieldPresent() { return this.getUsernameField().isPresent(); }
isPasswordFieldPresent() { return this.getPasswordField().isPresent(); }
isLoginButtonPresent() { return this.getLoginButton().isPresent(); }
getEmailFieldType() { return this.getEmailField().getAttribute('type'); }
getUsernameFieldType() { return this.getUsernameField().getAttribute('type'); }
getPasswordFieldType() { return this.getPasswordField().getAttribute('type'); }
getLoginButtonType() { return this.getLoginButton().getAttribute('type'); }
isLoginButtonEnabled() { return this.getLoginButton().isEnabled(); }
clearEmailField() { return this.getEmailField().clear() };
clearPasswordField() { return this.getPasswordField().clear() };
fillEmailFieldWith(text) { return this.getEmailField().sendKeys(text) };
fillPasswordFieldWith(text) { return this.getPasswordField().sendKeys(text) };
}

View file

@ -4,28 +4,28 @@
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
frameworks: ['jasmine', '@angular/cli'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma')
require('@angular/cli/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: {
dir: require('path').join(__dirname, 'coverage'), reports: [ 'html', 'lcovonly' ],
reports: [ 'html', 'lcovonly' ],
fixWebpackSourcePaths: true
},
angularCli: {

23816
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.1.13",
"version": "0.1.4",
"description": "LocalLoop Web - Web interface for LocalLoop app",
"author": "",
"url": "http://www.peartrade.org",
@ -8,81 +8,68 @@
"scripts": {
"ng": "ng",
"start": "ng serve",
"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",
"build": "ng build",
"test": "ng test",
"test:ci": "ng test --watch=false --browsers=ChromeHeadless",
"test:ci": "ng test --watch=false --env=ci",
"lint": "ng lint",
"e2e": "ng e2e",
"e2e:ci": "ng e2e --configuration=ci",
"e2e:ci": "ng e2e --env=ci",
"ci": "npm run test:ci && npm run e2e:ci"
},
"private": true,
"dependencies": {
"@agm/core": "^3.0.0-beta.0",
"@agm/markerclusterer": "^3.0.0-beta.0",
"@angular/common": "10.0.6",
"@angular/compiler": "10.0.6",
"@angular/core": "10.0.6",
"@angular/forms": "10.0.6",
"@angular/platform-browser": "10.0.6",
"@angular/platform-browser-dynamic": "10.0.6",
"@angular/router": "10.0.6",
"@angular/upgrade": "10.0.6",
"@coreui/coreui-plugin-chartjs-custom-tooltips": "^1.3.1",
"@coreui/icons": "0.3.0",
"@google/markerclustererplus": "^5.0.4",
"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.5.1",
"jszip": "^3.2.2",
"luxon": "^1.16.1",
"moment": "^2.24.0",
"ng2-charts": "^2.3.2",
"@agm/core": "1.0.0-beta.2",
"@agm/js-marker-clusterer": "1.0.0-beta.2",
"@angular/animations": "5.2.1",
"@angular/cdk": "5.2.3",
"@angular/common": "5.2.0",
"@angular/compiler": "5.2.0",
"@angular/core": "5.2.0",
"@angular/forms": "5.2.0",
"@angular/http": "5.2.7",
"@angular/material": "5.2.3",
"@angular/platform-browser": "5.2.0",
"@angular/platform-browser-dynamic": "5.2.0",
"@angular/router": "5.2.0",
"@angular/service-worker": "5.2.4",
"@angular/upgrade": "5.2.0",
"@types/moment": "2.13.0",
"chart.js": "2.7.1",
"core-js": "2.5.1",
"http-server": "0.11.1",
"js-marker-clusterer": "1.0.0",
"moment": "^2.21.0",
"ng2-charts": "1.6.0",
"ng2-validation-manager": "0.5.3",
"ngx-bootstrap": "^5.0.0",
"ngx-filter-pipe": "^2.1.2",
"ngx-pagination": "^4.0.0",
"popper.js": "^1.15.0",
"rxjs": "6.6.0",
"stream": "0.0.2",
"ngx-bootstrap": "2.0.0-beta.8",
"ngx-pagination": "3.0.3",
"node-snackbar": "0.1.9",
"rxjs": "5.5.6",
"ts-helpers": "1.1.2",
"tslib": "^1.10.0",
"web-animations-js": "^2.3.2",
"webpack-dev-server": "^3.11.0",
"zone.js": "~0.10.3"
"web-push": "3.2.5",
"webpack": "3.8.1",
"webpack-dev-server": "3.1.0",
"zone.js": "0.8.18"
},
"devDependencies": {
"@angular-devkit/build-angular": "^0.1000.4",
"@angular/cli": "^10.0.4",
"@angular/compiler-cli": "10.0.6",
"@types/googlemaps": "^3.39.8",
"@types/jasmine": "3.3.13",
"@types/jasminewd2": "2.0.6",
"@types/node": "12.0.10",
"codelyzer": "^6.0.0",
"jasmine-core": "^3.4.0",
"@angular/cli": "1.7.3",
"@angular/compiler-cli": "5.2.0",
"@types/google.analytics": "0.0.39",
"@types/jasmine": "2.8.2",
"@types/jasminewd2": "2.0.3",
"@types/node": "8.0.52",
"codelyzer": "4.0.1",
"jasmine-core": "2.8.0",
"jasmine-spec-reporter": "4.2.1",
"karma": "^5.1.1",
"karma": "1.7.1",
"karma-chrome-launcher": "2.2.0",
"karma-cli": "2.0.0",
"karma-coverage-istanbul-reporter": "^2.0.5",
"karma-jasmine": "^2.0.1",
"karma-jasmine-html-reporter": "^1.4.2",
"protractor": "^7.0.0",
"readable-stream": "latest",
"ts-node": "^8.3.0",
"tslint": "^5.18.0",
"typescript": "~3.9.7"
"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.5.3"
}
}

View file

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

View file

@ -8,11 +8,13 @@ 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,9 +8,11 @@ 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,9 +8,11 @@ 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,10 +1,12 @@
import { HttpModule } from '@angular/http';
import { ServiceWorkerModule } from '@angular/service-worker';
import { environment } from '../environments/environment';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } 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';
@ -28,7 +30,6 @@ 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';
@ -41,30 +42,26 @@ 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: [
BrowserAnimationsModule,
BrowserModule,
HttpClientModule,
FormsModule,
FilterPipeModule,
ReactiveFormsModule,
HttpModule,
NgxPaginationModule,
BsDropdownModule.forRoot(),
TabsModule.forRoot(),
AuthModule,
ChartsModule,
DashboardModule,
ServiceWorkerModule.register('/ngsw-worker.js', {enabled: environment.production}),
// Loaded last to allow for 404 catchall
AppRoutingModule,
],
declarations: [
AppComponent,
// StackedBarChartComponent,
FullLayoutComponent,
SimpleLayoutComponent,
NAV_DROPDOWN_DIRECTIVES,
@ -84,12 +81,14 @@ import { FilterPipeModule } from 'ngx-filter-pipe';
CustGraphsService,
CustSnippetsService,
CustPiesService,
OrgPiesService,
{
provide: LocationStrategy,
useClass: HashLocationStrategy
}
],
schemas: [
CUSTOM_ELEMENTS_SCHEMA
],
bootstrap: [ AppComponent ]
})
export class AppModule {

View file

@ -28,7 +28,7 @@
</div>
<div class="row">
<div class="col-6">
<button id="login" type="submit" [disabled]="!signin.valid" class="btn btn-primary px-4">Login</button>
<button type="submit" [disabled]="!signin.valid" class="btn btn-primary px-4">Login</button>
</div>
<div class="col-6 text-right">
<button type="button" class="btn btn-link px-0" disabled>Forgot password?</button>

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,11 +40,15 @@ 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

@ -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',
@ -70,7 +70,7 @@ export class RegisterComponent {
password: signupForm.password,
display_name: customerForm.display_name,
full_name: customerForm.full_name,
postcode: customerForm.postcode.toUpperCase(),
postcode: customerForm.postcode,
year_of_birth: customerForm.year_of_birth,
};
console.log(data);
@ -78,18 +78,24 @@ 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 {
this.registerStatusError = '"' + error.error.error + '" Error, ' + error.error.message;
console.log(error.error);
const jsonError = error.json();
console.log('boop');
this.registerStatusError = '"' + jsonError.error + '" Error, ' + jsonError.message;
} catch (e) {
this.registerStatusError = 'There was a server error, please try again later.';
}
this.registerStatus = 'send_failed';
console.log(this.registerStatus);
}
);
}
@ -114,19 +120,26 @@ export class RegisterComponent {
sector: organisationForm.sector,
street_name: organisationForm.street_name,
town: organisationForm.town,
postcode: organisationForm.postcode.toUpperCase(),
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 {
this.registerStatusError = '"' + error.error.error + '" Error, ' + error.error.message;
console.log(error.error);
const jsonError = error.json();
console.log('boop');
this.registerStatusError = '"' + jsonError.error + '" Error, ' + jsonError.message;
} catch (e) {
this.registerStatusError = 'There was a server error, please try again later.';
}

16
src/app/config.service.ts Normal file
View file

@ -0,0 +1,16 @@
import { Injectable } from '@angular/core';
import { environment } from './../environments/environment';
@Injectable()
export class ConfigService {
private _config:any = environment.config;
constructor() {
}
get(key: any) {
return this._config[key];
}
}

View file

@ -175,3 +175,16 @@
</div>
</div><!--/.row-->
</div>
<div class="row">
<div class="col-lg-12">
<div class="card">
<div class="card-header">
<strong>Push Notification Settings</strong>
<small>Would you like to recieve notfications from companys about their latests offers?</small>
<div class="card-body">
<app-push></app-push>
</div>
</div>
</div>
</div>
</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',
@ -89,7 +89,7 @@ export class AccountEditComponent implements OnInit {
const submitData = {
email: settingForm.email,
postcode: settingForm.postcode.toUpperCase(),
postcode: settingForm.postcode,
password: settingForm.password,
new_password: settingForm.new_password,
name: settingOrganisationForm.name,
@ -105,17 +105,23 @@ 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 {
this.submitStatusError = '"' + error.error.error + '" Error, ' + error.error.message;
console.log(error.error);
const jsonError = error.json();
console.log('boop');
this.submitStatusError = '"' + jsonError.error + '" Error, ' + jsonError.message;
} catch (e) {
this.submitStatusError = 'There was a server error, please try again later.';
}
this.submitStatus = 'send_failed';
console.log(this.submitStatus);
}
);
}
@ -125,6 +131,7 @@ 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;
}
@ -142,7 +149,7 @@ export class AccountEditComponent implements OnInit {
const submitData = {
email: settingForm.email,
postcode: settingForm.postcode.toUpperCase(),
postcode: settingForm.postcode,
password: settingForm.password,
new_password: settingForm.new_password,
full_name: settingCustomerForm.full_name,

View file

@ -24,14 +24,13 @@
<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>
<span class="help-block">Tick if the purchase is deemed an essential purchase for budgeting purposes.</span>
</div>
</div>
<div class="form-group row">
@ -39,33 +38,37 @@
<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>
<span class="help-block">Tick if the purchase frequently recurs, such as monthly.</span>
</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 class="col-md-6">
<div>
<input value="daily" type="radio" name="daily" [(ngModel)]="recurringType" (ngModelChange)="transactionFormValidate()">
<label>Daily</label>
</div>
<div>
<input value="weekly" type="radio" name="weekly" [(ngModel)]="recurringType" (ngModelChange)="transactionFormValidate()">
<label>Weekly</label>
</div>
<div>
<input value="fortnightly" type="radio" name="fortnightly" [(ngModel)]="recurringType" (ngModelChange)="transactionFormValidate()">
<label>Fortnightly</label>
</div>
</div>
<div class="col-md-6">
<div>
<input value="monthly" type="radio" name="monthly" [(ngModel)]="recurringType" (ngModelChange)="transactionFormValidate()">
<label>Monthly</label>
</div>
<div>
<input value="quarterly" type="radio" name="quarterly" [(ngModel)]="recurringType" (ngModelChange)="transactionFormValidate()">
<label>Quarterly</label>
</div>
</div>
</div>
<span class="help-block">Please give the period of time the purchase will recur from "Time of Transaction".</span>
@ -75,18 +78,21 @@
<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 class="col-md-6">
<div>
<input value="" type="radio" name="radios" [(ngModel)]="categoryId">
<label>Uncategorised</label>
</div>
<div class="radio" *ngFor="let category of leftCategoryIdList, let i=index">
<input [value]="category" type="radio" name="radios" [(ngModel)]="categoryId">
<label>{{ leftCategoryNameList[i] }}</label>
</div>
</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 class="col-md-6">
<div class="radio" *ngFor="let category2 of rightCategoryIdList, let i=index">
<input [value]="category2" type="radio" name="radios" [(ngModel)]="categoryId">
<label>{{ rightCategoryNameList[i] }}</label>
</div>
</div>
</div>
<span class="help-block"><strong>Optional:</strong> Choose the Budget Type for the majority of the purchase.</span>
@ -102,21 +108,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">Organisation Street Name</label>
<label class="col-md-3 form-control-label" for="text-input"><strong>Organisation Street Name</strong></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">Organisation Town</label>
<label class="col-md-3 form-control-label" for="text-input"><strong>Organisation Town</strong></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">Organisation Postcode</label>
<label class="col-md-3 form-control-label" for="text-input"><strong>Organisation Postcode</strong></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>
@ -157,14 +163,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" min="0">
<input type="number" class="form-control" formControlName="employee_amount" placeholder="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" min="0">
<input type="number" class="form-control" formControlName="local_employee_amount" placeholder="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,8 @@ export class AddDataComponent implements OnInit {
organisationTown: string;
organisationPostcode: string;
amount: number;
// Assumes Groceries is 1st category
categoryId: number = 1;
essentialPurchase = false;
recurringPurchase = false;
recurringType: string;
@ -40,11 +42,10 @@ export class AddDataComponent implements OnInit {
transactionFormInvalid = true;
myDate: any;
minDate: any;
categoryList: any;
categoryIdList: any;
leftCategoryList: number[] = [];
rightCategoryList: string[] = [];
categoryId: number;
leftCategoryIdList: number[] = [];
rightCategoryIdList: number[] = [];
leftCategoryNameList: number[] = [];
rightCategoryNameList: number[] = [];
constructor(
private formBuilder: FormBuilder,
@ -74,9 +75,7 @@ export class AddDataComponent implements OnInit {
// 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);
this.setCategoryList(result.categories);
},
error => {
console.log('Retrieval Error');
@ -91,9 +90,13 @@ export class AddDataComponent implements OnInit {
}
private setCategoryList(data: any) {
let halfLength = Math.floor(data.length / 2);
this.leftCategoryList = data.splice(0, halfLength);
this.rightCategoryList = data;
let categoryIdList = Object.keys(data.ids).map(key => data.ids[key]);
let categoryNameList = Object.keys(data.names).map(key => data.names[key]);
let halfLength = Math.floor(categoryIdList.length / 2);
this.leftCategoryIdList = categoryIdList.splice(0, halfLength);
this.leftCategoryNameList = categoryNameList.splice(0, halfLength);
this.rightCategoryIdList = categoryIdList;
this.rightCategoryNameList = categoryNameList;
}
getMinDate() {
@ -174,16 +177,14 @@ export class AddDataComponent implements OnInit {
}
transactionFormValidate() {
this.transactionFormStatus = null;
if (this.submitOrg.name.length &&
this.amount &&
(this.recurringPurchase &&
this.recurringType ||
!this.recurringPurchase &&
!this.recurringType)) {
this.transactionFormInvalid = false;
} else {
if (this.submitOrg.name.length === 0 ||
this.submitOrg.town.length === 0 ||
this.amount === 0 ||
this.recurringPurchase &&
!this.recurringType) {
this.transactionFormInvalid = true;
} else {
this.transactionFormInvalid = false;
}
}
@ -237,22 +238,31 @@ 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);
this.transactionFormStatusError = '"' + error.error.error + '" Error, ' + error.error.message;
const jsonError = error.json();
console.log('boop');
this.transactionFormStatusError = '"' + jsonError.error + '" Error, ' + jsonError.message;
} catch (e) {
this.transactionFormStatusError = 'There was a server error, please try again later.';
}
this.transactionFormStatus = 'send_failed';
console.log(this.transactionFormStatus);
}
);
}
@ -280,11 +290,14 @@ 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);
}
);
}
@ -296,10 +309,14 @@ 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);
}
);
}
@ -311,10 +328,14 @@ 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

@ -1,6 +1,6 @@
<div class="animated fadeIn">
<div class=row>
<div *ngIf="weekList1" class="col-md-6">
<div class="col-md-6">
<div class="card">
<div class="card-block">
<div class="row">
@ -12,8 +12,8 @@
<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>
<span class="value">{{ ( weekEssential1.value || 0 ) | currency:'GBP':'symbol':'1.2-2' }} <span class="text-muted small">
({{ (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"
@ -22,7 +22,7 @@
</div>
</li>
<li *ngFor="let categoryEntry of weekList1 | slice:0:categoryLimit1; let i=index;">
<span class="title">{{ categoryEntry.category || 'Uncategorised' }}</span>
<span class="title">{{ categoryNameList[categoryEntry.category - 1] || '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">
@ -42,7 +42,7 @@
</div>
</div>
</div><!--/.col-->
<div *ngIf="weekList2" class="col-md-6">
<div class="col-md-6">
<div class="card">
<div class="card-block">
<div class="row">
@ -54,8 +54,8 @@
<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>
<span class="value">{{ ( weekEssential2.value || 0 ) | currency:'GBP':'symbol':'1.2-2' }} <span class="text-muted small">
({{ (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"
@ -64,7 +64,7 @@
</div>
</li>
<li *ngFor="let categoryEntry of weekList2 | slice:0:categoryLimit2; let i=index;">
<span class="title">{{ categoryEntry.category || 'Uncategorised' }}</span>
<span class="title">{{ categoryNameList[categoryEntry.category - 1] || '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">
@ -84,7 +84,7 @@
</div>
</div>
</div><!--/.col-->
<div *ngIf="weekList3" class="col-md-6">
<div class="col-md-6">
<div class="card">
<div class="card-block">
<div class="row">
@ -96,8 +96,8 @@
<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>
<span class="value">{{ ( weekEssential1.value || 0 ) | currency:'GBP':'symbol':'1.2-2' }} <span class="text-muted small">
({{ (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"
@ -106,7 +106,7 @@
</div>
</li>
<li *ngFor="let categoryEntry of weekList3 | slice:0:categoryLimit3; let i=index;">
<span class="title">{{ categoryEntry.category || 'Uncategorised' }}</span>
<span class="title">{{ categoryNameList[categoryEntry.category - 1] || '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">
@ -126,7 +126,7 @@
</div>
</div>
</div><!--/.col-->
<div *ngIf="weekList4" class="col-md-6">
<div class="col-md-6">
<div class="card">
<div class="card-block">
<div class="row">
@ -138,8 +138,8 @@
<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>
<span class="value">{{ ( weekEssential4.value || 0 ) | currency:'GBP':'symbol':'1.2-2' }} <span class="text-muted small">
({{ (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"
@ -148,7 +148,7 @@
</div>
</li>
<li *ngFor="let categoryEntry of weekList4 | slice:0:categoryLimit4; let i=index;">
<span class="title">{{ categoryEntry.category || 'Uncategorised' }}</span>
<span class="title">{{ categoryNameList[categoryEntry.category - 1] || '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">

View file

@ -2,7 +2,7 @@ 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';
import 'rxjs/add/operator/map';
@Component({
templateUrl: 'category-month.component.html'
@ -31,12 +31,15 @@ export class CategoryMonthComponent implements OnInit {
weekEssential3: number = 0;
weekEssential4: number = 0;
categoryList: number[] = [];
dayList: any[] = [];
valueList: number[] = [];
myWeek1: any;
myWeek2: any;
myWeek3: any;
myWeek4: any;
categoryIdList: number[] = [];
categoryNameList: string[] = [];
categoryLimit1: number = 6;
categoryLimit2: number = 6;
categoryLimit3: number = 6;
@ -46,6 +49,15 @@ export class CategoryMonthComponent implements OnInit {
private api: ApiService,
) {
this.setDate();
this.api.categoryList().subscribe(
result => {
this.setCategoryList(result.categories);
},
error => {
console.log('Retrieval Error');
console.log( error._body );
}
);
this.api.categoryTransactionList().subscribe(
result => {
this.setData(result);
@ -60,6 +72,11 @@ export class CategoryMonthComponent implements OnInit {
ngOnInit(): void {
}
private setCategoryList(data: any) {
this.categoryIdList = Object.keys(data.ids).map(key => data.ids[key]);
this.categoryNameList = Object.keys(data.names).map(key => data.names[key]);
}
private setDate () {
this.myWeek1 = moment().startOf('isoWeek').format('YYYY-MM-DD');
this.myWeek2 = moment(this.myWeek1).subtract(1, 'weeks').format('YYYY-MM-DD');

View file

@ -11,105 +11,96 @@
</div><!--/.col-->
</div><!--/.row-->
<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 class="col-xl-4 col-md-6">
<panel-pie></panel-pie>
</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="col-xl-4 col-md-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>
<h4 class="card-title float-left mb-0">Purchases by Week</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>
<ul class="horizontal-bars type-2">
<li>
<span class="title">This Week</span>
<span class="value">{{ (weekPurchaseList.first || 0 ) }} <span class="text-muted small">
({{ (weekPurchaseList.first || 0 ) / weekPurchaseList.max | 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]="(weekPurchaseList.first || 0 ) / weekPurchaseList.max | percent:'1.0-0'" aria-valuemin="0" aria-valuemax="100"></div>
</div>
</div>
</li>
<li>
<span class="title">Last Week</span>
<span class="value">{{ weekPurchaseList.second || 0 }} <span class="text-muted small">
({{ (weekPurchaseList.second || 0 ) / weekPurchaseList.max | 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]="(weekPurchaseList.second || 0 ) / weekPurchaseList.max | percent:'1.0-0'" aria-valuemin="0" aria-valuemax="100"></div>
</div>
</div>
</li>
<li>
<span class="title">Week Maximum</span>
<span class="value">{{ weekPurchaseList.max || 0 }} <span class="text-muted small">
(100%)</span></span>
<div class="bars">
<div class="progress" style="height: 6px;">
<div class="progress-bar bg-success" role="progressbar"
style="width: 100%" aria-valuemin="0" aria-valuemax="100"></div>
</div>
</div>
</li>
<li>
<span class="title">Weekly Average</span>
<span class="value">{{ (weekPurchaseList.sum / weekPurchaseList.count) || 0 | number:'1.0-0'}} <span class="text-muted small">
({{ ((weekPurchaseList.sum / weekPurchaseList.count) || 0) / weekPurchaseList.max | 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]="((weekPurchaseList.sum / weekPurchaseList.count) || 0) / weekPurchaseList.max | percent:'1.0-0'" aria-valuemin="0" aria-valuemax="100"></div>
</div>
</div>
</li>
</ul>
</div>
</div>
</div>
</div><!--/.col-->
<div class="col-xl-6">
<div class="col-xl-4">
<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>
<h4 class="card-title float-left mb-0">Purchases by Sector</h4>
</div><!--/.col-->
</div><!--/.row-->
<div class="chart-wrapper">
<ul class="icons-list">
<!-- New loop -->
<li *ngFor="let sector of sectorLetters | slice:0:sectorLimit; let i=index">
<i [ngClass]="[sectorIcons [sector] || 'icon-question', sectorClasses [sector] || 'bg-info']"></i>
<div class="desc">
<div class="title">{{ sectorNames [sector] || 'N/A' }}</div>
</div>
<div class="value">
<div class="small text-muted">Bought</div>
<strong>{{ sectorPurchases[i] || 'N/A' }}</strong>
</div>
</li>
<li *ngIf="sectorLetters.length > sectorLimit && disableSectorButton == false" class="divider text-center">
<button type="button" class="btn btn-sm btn-link text-muted" (click)="loadMore()"><i class="icon-options"></i></button>
</li>
</ul>
</div>
</div><!--/.row-->
</div>
</div>
</div>
</div>
</div><!--/.col-->
</div><!--/.row-->
</div>

View file

@ -1,20 +1,10 @@
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'
@ -30,111 +20,7 @@ export class DashboardCustomerComponent implements OnInit {
username: any;
maxPurchase: number = 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'
]
}];
disableSectorButton: boolean = false;
weekPurchaseList = {
first: 0,
@ -144,10 +30,83 @@ export class DashboardCustomerComponent implements OnInit {
count: 0,
};
showTotalCategoryList: boolean = false;
totalCategoryLimit: number = 10;
totalCategoryList: any[]=[];
sectorNames = {
A: 'Agriculture, Forestry & Fishing',
B: 'Mining & Quarrying',
C: 'Manufacturing',
D: 'Electricity, Gas, Steam & Air Conditioning',
E: 'Water & Waste Management',
F: 'Construction',
G: 'Wholesale & Retail Trade',
H: 'Transportation & Storage',
I: 'Accomodation & Food Services',
J: 'Information & Communication',
K: 'Financial & Insurance Activities',
L: 'Real Estate',
M: 'Professional, Scientfic & Technical',
N: 'Administrative & Support Services',
O: 'Public Administration, Defence & Social Security',
P: 'Education',
Q: 'Human Health & Social Work',
R: 'Arts, Entertainment & Recreation',
S: 'Other Service Activities',
T: 'Household Domestic Business',
U: 'Extraterritorial Organisations and Bodies'
}
sectorIcons = {
A: 'icon-drop',
B: 'icon-diamond',
C: 'icon-settings',
D: 'icon-energy',
E: 'icon-trash',
F: 'icon-wrench',
G: 'icon-tag',
H: 'icon-speedometer',
I: 'icon-cup',
J: 'icon-feed',
K: 'icon-credit-card',
L: 'icon-graph',
M: 'icon-chemistry',
N: 'icon-drawer',
O: 'icon-pie-chart',
P: 'icon-graduation',
Q: 'icon-support',
R: 'icon-film',
S: 'icon-calendar',
T: 'icon-home',
U: 'icon-globe',
}
sectorClasses = {
A: 'bg-primary',
B: 'bg-success',
C: 'bg-danger',
D: 'bg-warning',
E: 'bg-info',
F: 'bg-primary',
G: 'bg-success',
H: 'bg-danger',
I: 'bg-warning',
J: 'bg-info',
K: 'bg-primary',
L: 'bg-success',
M: 'bg-danger',
N: 'bg-warning',
O: 'bg-info',
P: 'bg-primary',
Q: 'bg-success',
R: 'bg-danger',
S: 'bg-warning',
T: 'bg-info',
U: 'bg-primary',
}
sectorLetters: string[] = [];
sectorPurchases: number[] = [];
sectorLimit: number = 10;
sectorList: any;
// Graph widgets
public widgetList = [
@ -181,25 +140,11 @@ export class DashboardCustomerComponent implements OnInit {
constructor(
private api: ApiService,
private currencyPipe: CurrencyPipe,
) {
this.setDate();
this.api.customerStats().subscribe(
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;
this.setSectorList(result.sectors);
},
error => {
console.log('Retrieval Error');
@ -208,35 +153,6 @@ 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,
@ -247,36 +163,14 @@ export class DashboardCustomerComponent implements OnInit {
};
}
private categoryLoadMore () {
this.disableCategoryButton = true;
this.totalCategoryLimit = 30;
public setSectorList (data: any) {
this.sectorLetters = Object.keys(data.sectors).map(key => data.sectors[key]);
this.sectorPurchases = Object.keys(data.purchases).map(key => data.purchases[key]);
}
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 {
private loadMore () {
this.disableSectorButton = true;
this.sectorLimit = 22;
}
ngOnInit(): void {

View file

@ -1,4 +1,3 @@
<div class="animated fadeIn">
<snippet-bar-org></snippet-bar-org>
<div class="row">
@ -12,102 +11,4 @@
</div><!--/.col-->
</div><!--/.row-->
<panel-graph></panel-graph>
<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>
</div>

View file

@ -1,16 +1,10 @@
import { Component } from '@angular/core';
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 {Router, NavigationEnd} from "@angular/router";
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'
@ -44,13 +38,6 @@ 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',
@ -63,194 +50,8 @@ export class DashboardComponent {
title: 'Purchases Last 30 Days',
dataType: DataType.currency,
},
{
type: 'graph',
name: 'purchases_last_quart;',
title: 'Purchases Last Quart',
dataType: DataType.currency,
},
];
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,
) {
constructor(private router: Router) {
if (environment.enableAnalytics) {
this.router.events.subscribe(event => {
if (event instanceof NavigationEnd) {
@ -259,111 +60,5 @@ export class DashboardComponent {
}
});
}
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,11 +1,11 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { ChartsModule } from 'ng2-charts';
import { ChartsModule } from 'ng2-charts/ng2-charts';
import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
import { NgxPaginationModule } from 'ngx-pagination';
import { AgmCoreModule, GoogleMapsAPIWrapper } from '@agm/core';
import { AgmMarkerClustererModule } from '@agm/markerclusterer';
import { AgmJsMarkerClustererModule } from '@agm/js-marker-clusterer';
import { ModalModule } from 'ngx-bootstrap/modal';
import { CurrencyPipe } from '@angular/common';
@ -18,8 +18,8 @@ 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 { OrgPushComponent } from './org-push.component';
import { LeaderboardComponent } from './leaderboard.component';
import { MapComponent } from './map.component';
import { TrailMapComponent } from './trail-map.component';
@ -28,22 +28,19 @@ 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';
import { MatButtonModule } from '@angular/material/button';
import { ConfigService } from '../config.service';
import { PushComponent } from '../push/push.component';
// API key env variable import
import { environment } from '../../environments/environment';
@ -51,13 +48,14 @@ import { environment } from '../../environments/environment';
imports: [
// Angular imports
CommonModule,
MatButtonModule,
FormsModule,
ReactiveFormsModule,
ChartsModule,
AgmCoreModule.forRoot({
apiKey: environment.mapApiKey
}),
AgmMarkerClustererModule,
AgmJsMarkerClustererModule,
BsDropdownModule,
NgxPaginationModule,
DashboardRoutingModule,
@ -65,21 +63,18 @@ import { environment } from '../../environments/environment';
],
declarations: [
DashboardComponent,
PushComponent,
DashboardCustomerComponent,
AccountEditComponent,
AddDataComponent,
OrgResultComponent,
OrgTableComponent,
RecurResultComponent,
RecurTableComponent,
TransactionLogComponent,
CategoryMonthComponent,
TransactionResultComponent,
SupplierResultComponent,
WardResultComponent,
MetaTypeResultComponent,
PayrollLogComponent,
PayrollResultComponent,
OrgPushComponent,
LeaderboardComponent,
LeaderboardResultComponent,
MapComponent,
@ -90,14 +85,11 @@ import { environment } from '../../environments/environment';
CustBarSnippetComponent,
GraphPanel,
PiePanel,
OrgPiePanel,
BubbleChartComponent,
SuppliersComponent,
MoreStuffComponent,
],
providers: [
CurrencyPipe,
GoogleMapsAPIWrapper,
ConfigService
],
})
export class DashboardModule { }

View file

@ -14,11 +14,10 @@ import { FeedbackComponent } from './feedback.component';
import { TransactionLogComponent } from './transaction-log.component';
import { CategoryMonthComponent } from './category-month.component';
import { PayrollLogComponent } from './payroll-log.component';
import { OrgPushComponent } from './org-push.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 = [
@ -82,20 +81,16 @@ const routes: Routes = [
data: { title: 'Payroll Log' },
canActivate: [OrgGuard],
},
{
path: 'org-push',
component: OrgPushComponent,
data: { title: 'Push Notifications' },
canActivate: [OrgGuard],
},
{
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,7 +55,9 @@ 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: '',
});
@ -63,16 +65,22 @@ 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 {
this.feedbackFormStatusError = '"' + error.error.error + '" Error, ' + error.error.message;
console.log(error.error);
const jsonError = error.json();
console.log('boop');
this.feedbackFormStatusError = '"' + jsonError.error + '" Error, ' + jsonError.message;
} catch (e) {
this.feedbackFormStatusError = 'There was a server error, please try again later.';
}
this.feedbackFormStatus = 'send_failed';
console.log(this.feedbackFormStatus);
}
);
}

View file

@ -24,7 +24,7 @@
<tr>
<th>Position</th>
<th>Name</th>
<th class="js-sort-number">Gross amount</th>
<th>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: 20,
itemsPerPage: 10,
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

@ -39,8 +39,9 @@
[latitude]="lat"
[longitude]="lng"
[zoom]="zoom"
[scaleControl]="true"
(idle)="viewBoundsChanged()">
<agm-marker-cluster maxZoom="13">
<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"
[latitude]="m.latitude"

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', { static: true }) myStatusModal: ModalDirective;
@ViewChild('statusModal') myStatusModal: ModalDirective;
lat: number = 54.0466;
lng: number = -2.8007;
zoom: number = 12;

View file

@ -1,136 +0,0 @@
<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

@ -1,335 +0,0 @@
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 }) 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

@ -0,0 +1,47 @@
<div class="row">
<div class="col-lg-12">
<div class="card">
<div class="card-header">
<strong>Push Notifcations</strong>
<small>Here you can send push notifications about sales to users</small>
</div>
<form class="form-horizontal" [formGroup]="pushForm" (ngSubmit)="onSubmit()">
<div class="card-block">
<div class="form-group row">
<label class="col-md-3 form-control-label" for="text-input"><strong>Title</strong></label>
<div class="col-md-9">
<input type="text" class="form-control" formControlName="title">
</div>
</div>
</div>
<div class="card-block">
<div class="form-group row">
<label class="col-md-3 form-control-label" for="text-input"><strong>Body</strong></label>
<div class="col-md-9">
<input type="text" class="form-control" formControlName="body">
</div>
</div>
</div>
<div class="card-block">
<div class="form-group row">
<label class="col-md-3 form-control-label" for="text-input"><strong>Icon</strong></label>
<div class="col-md-9">
<input type="text" class="form-control" formControlName="icon">
<span class="help-block">Enter URL to Icon you would like to use.</span>
</div>
</div>
</div>
<div class="form-group row">
<label class="col-md-3 form-control-label" for="text-input"><strong>Personalise Notifcations</strong></label>
<div class="col-md-9">
<input type="checkbox" class="form-control" formControlName="personalised" (change)="toggleEditable($event)" >
<span class="help-block">Tick if you want to personalise notifications adding the users name.</span>
</div>
</div>
<div class="card-footer">
<button type="submit" class="btn btn-sm btn-primary"><i class="fa fa-dot-circle-o"></i> Submit</button>
</div>
</form>
</div>
</div>
</div>

View file

@ -0,0 +1,47 @@
import { Component} from '@angular/core';
import { ApiService } from '../providers/api-service';
import { Validators, FormBuilder, FormGroup } from '@angular/forms';
@Component({
templateUrl: './org-push.component.html'
})
export class OrgPushComponent{
pushForm: FormGroup;
personalised = false;
constructor(
private formBuilder: FormBuilder,
private api: ApiService,
) {
this.pushForm = this.formBuilder.group({
title: ['', [Validators.required]],
body: ['', [Validators.required]],
icon: ['', [Validators.required]],
personalised: [true, [Validators.required]],
});
}
onSubmit(){
console.log(this.pushForm.value)
console.log(this.personalised)
this.api.sendNotification(this.pushForm.value)
.subscribe(
res => {
console.log('Message Sent', res)
},
err => {
console.log('Message Failed to send', err)
}
)
}
toggleEditable(event) {
if ( event.target.checked ) {
this.personalised = true;
}
}
}

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

@ -1,79 +0,0 @@
<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

@ -1,74 +0,0 @@
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

@ -72,8 +72,9 @@
[latitude]="lat"
[longitude]="lng"
[zoom]="zoom"
[scaleControl]="true"
(idle)="viewBoundsChanged()">
<agm-marker-cluster maxZoom="13">
<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/association/' + assocMap + '-map-pin.png'"

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: 'trail-map.component.html',
})
export class TrailMapComponent implements OnInit, AfterViewInit {
@ViewChild('statusModal', { static: true }) myStatusModal: ModalDirective;
@ViewChild('statusModal') myStatusModal: ModalDirective;
lat: number = 54.0466;
lng: number = -2.8007;
zoom: number = 12;
@ -51,6 +51,7 @@ export class TrailMapComponent implements OnInit, AfterViewInit {
}
public onMarkerClick(clickedMarker, template: TemplateRef<any>) {
console.log(clickedMarker);
this.clickedMarker = clickedMarker;
this.assocLogo = 'assets/img/association/' + this.assocMap + '-logo.png';
this.openModal(template);

View file

@ -1,124 +1,22 @@
<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 *ngIf="accountType == 'organisation'" 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 *ngIf="!showMeta">Value</th>
<th *ngIf="showMeta">Net Value</th>
<th *ngIf="showMeta">Sales Tax Value</th>
<th *ngIf="showMeta">Gross Value</th>
<th>Value</th>
<th>Purchase Time</th>
</tr>
</thead>
<tbody>
<tr transaction-result *ngFor="let transaction of transactionList | paginate: paginateConfig" [transaction]="transaction" [showMeta]="showMeta"></tr>
<tr transaction-result *ngFor="let transaction of transactionList | paginate: paginateConfig" [transaction]="transaction"></tr>
</tbody>
</table>
<pagination-template #p="paginationApi"

View file

@ -1,68 +1,58 @@
import { Component, OnInit, EventEmitter, TemplateRef } from '@angular/core';
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { ApiService } from '../providers/api-service';
import { BsModalService, ModalDirective } from 'ngx-bootstrap/modal';
import { BsModalRef } from 'ngx-bootstrap/modal/bs-modal-ref.service';
import { PaginationInstance } from 'ngx-pagination';
// 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 * as moment from 'moment';
import 'rxjs/add/operator/map';
@Component({
templateUrl: 'transaction-log.component.html',
})
export class TransactionLogComponent implements OnInit {
transactionList: any;
recurringTransactionList: any;
transactionList;
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;
accountType: any;
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);
this.accountType = localStorage.getItem('usertype');
}
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) {
@ -76,13 +66,6 @@ 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);
@ -90,82 +73,4 @@ 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

@ -20,16 +20,10 @@
</li>
</ul>
</header>
<div class="app-body">
<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']">
<div class="row no-gutters align-items-center">
@ -38,14 +32,6 @@
</div>
</a>
</li>
<li *ngIf="accountType == 'organisation'" 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']">
<div class="row no-gutters align-items-center">
@ -94,7 +80,7 @@
</div>
</a>
</li>
<li *ngIf="accountType == 'customer'" class="nav-item">
<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>
@ -102,22 +88,22 @@
</div>
</a>
</li>
<li *ngIf="accountType == 'organisation'" 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>
<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>
<li *ngIf="accountType == 'organisation'" class="nav-item">
<a class="nav-link" routerLinkActive="active" [routerLink]="['/org-push']">
<div class="row no-gutters align-items-center">
<div class="col-2"><i class="icon-envelope-letter"></i></div>
<div class="col-10">Push Notifications</div>
</div>
</a>
</li>
</ul>
</nav>
</div>
@ -137,7 +123,6 @@
</main>
</div>
<footer class="app-footer">
<a href="http://www.peartrade.org" target="_blank">&copy; 2017 Pear Trading Ltd.</a>
<span class="float-right">Powered by <a href="http://coreui.io">CoreUI</a></span>

View file

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

View file

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

View file

@ -1,97 +0,0 @@
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,9 +1,18 @@
<div class="card">
<div class="card-block">
<div class="row">
<div class="col-sm-12">
<div class="col-sm-5">
<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: this.convertHex(this.brandInfo, 10),
backgroundColor: 'transparent',
borderColor: this.brandSuccess,
pointHoverBackgroundColor: '#fff'
},
{ // brandDanger
backgroundColor: this.convertHex(this.brandDanger, 10),
backgroundColor: 'transparent',
borderColor: this.brandDanger,
pointHoverBackgroundColor: '#fff',
borderWidth: 1,

View file

@ -1,19 +0,0 @@
<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

@ -1,86 +0,0 @@
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

@ -1,19 +1,18 @@
<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 class="card">
<div class="card-block">
<div class="row">
<div class="col-sm-8">
<h4 class="card-title mb-0">All Purchases</h4>
</div><!--/.col-->
</div><!--/.row-->
<div class="chart-wrapper">
<canvas baseChart class="chart"
[data]="doughnutChartData"
[labels]="doughnutChartLabels"
[legend]="chartLegend"
[chartType]="chartType"
(chartHover)="chartHovered($event)"
(chartClick)="chartClicked($event)"></canvas>
</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

@ -1,76 +1,37 @@
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 chartType = 'doughnut';
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]
}
];
public doughnutChartLabels: string[] = [];
public doughnutChartData: number[] = [];
//Old
// public mainChartElements = 7;
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 {
this.pieService.getPie()
.subscribe( result => this.setData(result.pie) );
}
private setChartData(dataLocal: any) {
this.doughnutChartDataLocal = Object.keys(dataLocal).map(key => dataLocal[key]);
private setData(data: any) {
this.doughnutChartData = Object.keys(data).map(key => data[key]);
// setTimeout is currently a workaround for ng2-charts labels
setTimeout(() => this.doughnutChartLabelsLocal = Object.keys(dataLocal), 0);
setTimeout(() => this.doughnutChartLabels = Object.keys(data), 0);
}
// convert Hex to RGBA
@ -86,9 +47,11 @@ export class PiePanel implements OnInit {
// events
public chartClicked(e: any): void {
console.log(e);
}
public chartHovered(e: any): void {
console.log(e);
}
}

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,15 +29,18 @@ 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');
}
@ -54,8 +57,8 @@ export class ApiService {
.post<any>(
this.apiUrl + '/login',
data
).pipe(
map(
)
.map(
result => {
const json = result;
this.setSessionKey(json.session_key);
@ -66,23 +69,24 @@ 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 },
).pipe(
map(
)
.map(
response => {
localStorage.clear();
this.sessionKey = null;
return response;
}
));
);
}
// Submits feedback
@ -92,6 +96,7 @@ 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
@ -133,43 +138,6 @@ export class ApiService {
);
}
// 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) {
@ -190,26 +158,6 @@ 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) {
@ -262,6 +210,7 @@ export class ApiService {
public setUserInfo(
email: string,
display_name: string) {
console.log('set UserInfo');
localStorage.setItem('email', email);
localStorage.setItem('displayname', display_name);
}
@ -269,6 +218,7 @@ export class ApiService {
// Sets usertype
public setUserType(user_type: string) {
console.log('set UserType');
localStorage.setItem('usertype', user_type);
}
@ -290,33 +240,73 @@ export class ApiService {
);
}
public sendNotification(data) {
data.session_key = this.sessionKey;
return this.http.post<any>(
this.apiUrl + '/push/org_send_notification',
data
);
}
// 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');
}
//Push Api
public addSubscriber(data) {
const key = this.sessionKey;
console.log(data)
return this.http.post<any>(
this.apiUrl + '/subscription/add',
{
session_key : key,
data : data
}
);
}
public deleteSubscriber(data) {
const key = this.sessionKey;
console.log(data)
return this.http.post<any>(
this.apiUrl + '/subscription/delete',
{
session_key : key,
data : data
}
);
}
// Leaderboard Api
public leaderboard_fetch(
@ -361,15 +351,4 @@ export class ApiService {
}
);
}
// 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

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

View file

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

View file

@ -3,7 +3,7 @@ import { ApiService } from './api-service';
@Injectable()
export class OrgGraphsService {
private orgGraphUrl = '/v1/organisation/graphs';
private orgGraphUrl = '/v1/customer/graphs';
constructor(private api: ApiService) { }

View file

@ -1,14 +0,0 @@
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';
import { Observable } from 'rxjs/Rx';
@Injectable()
export class OrgSnippetsService {

View file

@ -0,0 +1,3 @@
.button{
padding: 15px 32px;
}

View file

@ -0,0 +1,2 @@
<button mat-raised-button (click)="subscribeToPush()" [disabled]=subscribeButton>Subscribe To Pushs</button>
<button mat-raised-button (click)="unsubscribeFromPush()" [disabled]=unsubscribeButton>Unsubscribe From Push</button>

View file

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { PushComponent } from './push.component';
describe('PushComponent', () => {
let component: PushComponent;
let fixture: ComponentFixture<PushComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ PushComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(PushComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -0,0 +1,91 @@
import { Component, OnInit } from '@angular/core';
import { ConfigService } from './../config.service';
import { SwPush } from '@angular/service-worker';
import { ApiService } from '../providers/api-service';
import "rxjs/Rx";
@Component({
selector: 'app-push',
templateUrl: './push.component.html',
styleUrls: ['./push.component.css']
})
export class PushComponent implements OnInit {
subscribeButton = false;
unsubscribeButton = true;
private VAPID_PUBLIC_KEY: string;
tweets = []
constructor(private api: ApiService, private configService: ConfigService, private swPush: SwPush) {
}
ngOnInit() {
this.VAPID_PUBLIC_KEY = this.configService.get('VAPID_PUBLIC_KEY')
}
subscribeToPush() {
// Requesting messaging service to subscribe current client (browser)
this.swPush.requestSubscription({
serverPublicKey: this.VAPID_PUBLIC_KEY
})
.then(pushSubscription => {
// Passing subscription object to our backend
console.log(pushSubscription.endpoint)
console.log(pushSubscription.getKey)
this.api.addSubscriber(pushSubscription)
.subscribe(
res => {
console.log('[App] Add subscriber request answer', res)
this.subscribeButton = true;
this.unsubscribeButton = false;
},
err => {
console.log('[App] Add subscriber request failed', err)
}
)
})
.catch(err => {
console.error(err);
})
}
unsubscribeFromPush(){
this.subscribeButton = false;
this.unsubscribeButton = true;
// Get active subscription
this.swPush.subscription
.take(1)
.subscribe(pushSubscription => {
console.log('[App] pushSubscription', pushSubscription)
// Delete the subscription from the backend
this.api.deleteSubscriber(pushSubscription)
.subscribe(
res => {
console.log('[App] Delete subscriber request answer', res)
// Unsubscribe current client (browser)
pushSubscription.unsubscribe()
.then(success => {
console.log('[App] Unsubscription successful', success)
})
.catch(err => {
console.log('[App] Unsubscription failed', err)
})
},
err => {
console.log('[App] Delete subscription request failed', err)
}
)
})
}
}

View file

@ -1,8 +1,6 @@
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',
@ -20,7 +18,7 @@ export class BreadcrumbsComponent implements OnInit {
breadcrumbs: Array<Object>;
constructor(private router: Router, private route: ActivatedRoute) {}
ngOnInit(): void {
this.router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe(event => {
this.router.events.filter(event => event instanceof NavigationEnd).subscribe(event => {
this.breadcrumbs = [];
let currentRoute = this.route.root,
url = '';

View file

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

View file

@ -1,19 +0,0 @@
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

@ -1,6 +0,0 @@
<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

@ -1,40 +0,0 @@
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

@ -1,17 +0,0 @@
<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

@ -1,30 +0,0 @@
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

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

View file

@ -1,19 +0,0 @@
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,6 +1,3 @@
<td>{{transaction.seller}}</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>{{transaction.value | currency:'GBP':'symbol':'1.2-2' }}</td>
<td>{{transactionDate}}</td>

View file

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

View file

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

View file

@ -1,19 +0,0 @@
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

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

View file

@ -7,8 +7,6 @@ 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;
@ -16,8 +14,6 @@ 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;
@ -33,8 +29,6 @@ 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;
@ -42,8 +36,6 @@ 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]="lineGraphChartData"
[datasets]="lineChartData"
[labels]="lineChartLabels"
[options]="lineChartOptions"
[colors]="lineChartColours"

View file

@ -12,7 +12,6 @@ interface ChartData {
selector: 'widget-graph',
templateUrl: 'graph-widget.component.html',
})
export class GraphWidget implements OnInit {
@Input() public graphName: string;
@Input() public graphTitle = 'Graph';
@ -25,7 +24,7 @@ export class GraphWidget implements OnInit {
public graphSum: Number = 0;
public availableDataTypes = DataType;
public lineGraphChartData: Array<ChartData> = [
public lineChartData: Array<ChartData> = [
{
data: [],
label: 'Series A'
@ -64,7 +63,7 @@ export class GraphWidget implements OnInit {
},
elements: {
line: {
borderWidth: 2
borderWidth: 1
},
point: {
radius: 4,
@ -125,7 +124,7 @@ export class GraphWidget implements OnInit {
}
private setChartData(data: Array<number>) {
this.lineGraphChartData[0].data = data;
this.lineChartData[0].data = data;
this.graphSum = data.reduce((a, b) => a + b, 0);
// Set point size based on data
if ( data.length < 15 ) {

View file

@ -5,8 +5,11 @@
export const environment = {
production: false,
apiUrl: 'https://dev.localspend.co.uk/api',
mapApiKey: 'CHANGEME',
apiUrl: 'http://localhost:3000/api',
mapApiKey: 'AIzaSyBhm0iaIGG0Ko5IsfZx-CpLt01YHkp4Y1w',
config: {
"VAPID_PUBLIC_KEY": "BMDZ6FANqsYRF9iGo3Ki0LdltGZZksgIFbgxBr_otO0H7jTFgcm3v2bGSgnVGJ5bidvLvuKStirfDNl4khVBiok"
},
enableAnalytics: false,
analyticsKey: 'CHANGEME',
analyticsKey: ''
};

View file

@ -122,11 +122,14 @@
<div class="sk-cube3 sk-cube"></div>
</div>
</app-root>
<app-root></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');
ga('create', 'UA-119198369-1', 'auto');
</script>
</body>

31
src/ngsw-config.json Normal file
View file

@ -0,0 +1,31 @@
{
"index": "/index.html",
"assetGroups": [{
"name": "app",
"installMode": "prefetch",
"resources": {
"files": [
"/favicon.ico",
"/index.html"
],
"versionedFiles": [
"/*.bundle.css",
"/*.bundle.js",
"/*.chunk.js"
]
}
}, {
"name": "assets",
"installMode": "lazy",
"updateMode": "prefetch",
"resources": {
"files": [
"/assets/**"
]
}
}],
"push": {
"showNotifications": true,
"backgroundOnly": false
}
}

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/guide/browser-support
* Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
*/
/***************************************************************************************************
* BROWSER POLYFILLS
* BROWSER POLYFILLS
*/
/** IE9, IE10 and IE11 requires all of the following polyfills. **/
@ -31,37 +31,23 @@ 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/es7/array';
import 'core-js/es7/object';
import 'core-js/es6/reflect';
/** 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 for the Reflect API. */
/** 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. **/
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`.
/**
* 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;
/** ALL Firefox browsers require the following to support `@angular/animation`. **/
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.

View file

@ -48,7 +48,7 @@ $navbar-border: (
);
$navbar-brand-width: 155px;
$navbar-brand-bg: #fff;
$navbar-brand-logo: url('../../assets/img/logo.png');
$navbar-brand-logo: url('../assets/img/logo.png');
$navbar-brand-logo-size: 70px auto;
$navbar-brand-border: (
bottom: (

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