Compare commits

...

423 commits

Author SHA1 Message Date
Rumperuu
94ee509b43
Merge pull request #114 from Pear-Trading/ben/update-readme
Update and clean up README
2020-10-19 20:39:21 +01:00
bab6b9f354 Merge branch 'development' into ben/update-readme 2020-10-19 20:27:50 +01:00
a0c0c7e032 Remove old readme section 2020-10-18 14:48:43 +01:00
90f0a91f50 Reformat build status table 2020-10-18 14:46:21 +01:00
067f0543d4 Add table of contents 2020-10-18 14:45:03 +01:00
Rumperuu
792e431fad
Merge pull request #110 from Pear-Trading/ben/fix/sidebar-links
Correct which links are shown for different users
2020-10-18 14:40:40 +01:00
a19ddcb1b7 Add property to component 2020-10-18 14:03:23 +01:00
Rumperuu
49e5951747
Merge branch 'development' into ben/fix/sidebar-links 2020-10-18 13:33:54 +01:00
Rumperuu
e6f7ba4b65
Merge pull request #111 from Pear-Trading/ben/fix/postcode-validation
Capitalise entered postcodes
2020-10-18 13:32:21 +01:00
e839ab28b6 Capitalise entered postcodes 2020-10-17 16:41:10 +01:00
7be68c3fc0 Correct which links are shown for different users 2020-10-17 16:21:05 +01:00
0dd7ab1e34 Clean up README 2020-10-17 14:39:57 +01:00
Tom Bloor
53db04dd9c
Merge pull request #103 from Pear-Trading/finn/fixes
Bring dependencies up to date
2020-08-13 16:52:20 +01:00
Tom Bloor
da30036d4e
Merge branch 'development' into finn/fixes 2020-08-13 16:31:57 +01:00
Tom Bloor
2a5e790e3d
Fix e2e test and actually check login works 2020-08-13 16:24:49 +01:00
Finn Kempers
e4dcc2e664 fixing agm 2020-08-12 13:27:34 +01:00
Finn Kempers
a0e28cb440 Fixes to declaration name of agm markers 2020-08-12 13:12:11 +01:00
Finn Kempers
4312f2ad8b fixed a variable 2020-08-10 10:11:22 +01:00
Finn Kempers
20ec21f8cb npm audit fix run 2020-08-10 09:49:27 +01:00
Finn Kempers
ee88c8c31a package-lock automatic update 2020-08-10 09:48:41 +01:00
Finn Kempers
55eb053b08 package fixes due to missing peer & npm warnings 2020-07-29 20:13:08 +01:00
Finn Kempers
fba44b237a Additional fix needed for clusters 2020-07-29 14:55:20 +01:00
Finn Kempers
20f7e5dc0f angular json fix 2020-07-29 14:50:43 +01:00
Finn Kempers
d4540e7650 Upgrade to Angular 10 2020-07-29 14:40:01 +01:00
Finn Kempers
611dbc4bac changed agm cluster and updated agm dependencies
the changelog didnt mention this properly to do but as seen in change 7982bfbcf0 the module was renamed and needed changing.
2020-07-29 14:32:51 +01:00
Finn Kempers
7c651e1522 package updates to match angular v 2020-07-29 14:08:12 +01:00
Finn Kempers
3ed88a4d5e ng update to 9 2020-07-29 13:31:46 +01:00
Finn Kempers
e9c81dc428 ng update angular 8 done 2020-07-29 13:03:09 +01:00
Finn Kempers
8a4e7872fb reverting updates 2020-07-29 13:01:24 +01:00
Finn Kempers
208a85c5a5 fix to get ng update to work 2020-07-29 12:56:49 +01:00
Finn Kempers
839d322327 rxjs update 2020-07-29 12:54:28 +01:00
Finn Kempers
28b575e819 npm audit fix fixes 2020-07-29 12:44:33 +01:00
Finn
0f190a70d7 package fixes 2020-01-08 20:00:57 +00:00
Finn
326e4ec322 testing stuff 2020-01-08 18:27:54 +00:00
Thomas Bloor
fe125c812f
Merge branch 'master' into development 2019-09-16 13:05:26 +01:00
Thomas Bloor
b91de60175
Merge branch 'Release-v0.1.13' 2019-09-16 13:02:07 +01:00
Thomas Bloor
5abf5d29b3
Updated changelog and package version 2019-09-16 13:01:04 +01:00
Finn
891bb0aee4 fixes quill audit issue 2019-09-11 14:10:53 +01:00
Thomas Bloor
50b9cfdc31
update webdriver-manager 2019-09-11 13:54:22 +01:00
Thomas Bloor
53aae6fc07
Updated api key for deploy build 2019-09-11 13:11:52 +01:00
Thomas Bloor
5e92829a7a
use package config in script 2019-09-09 21:39:58 +01:00
Thomas Bloor
ace6900145
Fix prod configuration name 2019-09-09 21:33:28 +01:00
Tom Bloor
893bdca736
Merge pull request #85 from Pear-Trading/TBSliver/CIFixes
CI Update
2019-09-09 21:26:20 +01:00
Thomas Bloor
75edb86abe
update secret files 2019-09-09 21:19:51 +01:00
Thomas Bloor
66388593e6
note even sure why that was there 2019-09-09 21:09:47 +01:00
Thomas Bloor
f697b2243d
more CI fixes 2019-09-09 21:08:49 +01:00
Thomas Bloor
abdbfda56c
change to use latest node for compilation 2019-09-09 21:00:30 +01:00
Thomas Bloor
bad4f7f83c
fix CI config 2019-09-09 20:55:21 +01:00
Thomas Bloor
87160f5387
remove uneccessary stuff and work on CI 2019-09-09 20:45:24 +01:00
Thomas Bloor
bab670f60b
Merge branch 'finn/wards' into development 2019-09-09 19:56:26 +01:00
Thomas Bloor
7883dbb169
Fix a large amount of things in the ui 2019-09-09 19:52:38 +01:00
Finn
52bce4dd0a Added initial working table for transaction types 2019-09-09 16:13:34 +01:00
Finn
5c85d5aae9 ward import fixed 2019-09-05 17:18:52 +01:00
Finn
db5f982ed3 BROKEN: Added wards table 2019-09-04 18:21:21 +01:00
Tom Slater
1dc46a55e0 Misc chart work
Search feature on both suppliers and the pagination feature on the spend history still not functional
2019-08-30 16:50:19 +01:00
Tom Slater
420174e953 chart work 2019-08-28 10:47:44 +01:00
Tom Slater
5e91665e0b Merge remote-tracking branch 'origin/tyu-tyu/charts' into tyu-tyu/charts 2019-08-23 11:42:39 +01:00
Tom Slater
301f706365 Chart improvements
Minor fixes  + refresh on supplier spend chart
2019-08-22 16:59:51 +01:00
Tom Slater
ade578d78b Merge remote-tracking branch 'origin/tyu-tyu/char' into tyu-tyu/charts 2019-08-21 11:53:53 +01:00
Tom Slater
8859c6977c Colour fixes 2019-08-21 11:51:10 +01:00
Tom Slater
9fbe36e401 Misc chart work 2019-08-20 16:47:20 +01:00
Finn
55e62a6b87
properly displaying essential purchases dataset 2019-08-20 16:43:53 +01:00
Finn
03e9e365fc
removed console logs that show password 2019-08-20 14:33:44 +01:00
Tom Slater
4181a09b96 Merge remote-tracking branch 'origin/felix/customranges' into tyu-tyu/char 2019-08-19 13:48:47 +01:00
Tom Slater
4b42a08af1 Chart work update
working on essential purchase graph
2019-08-19 12:30:36 +01:00
Tom Slater
156a2f3764 Misc Chart work 2019-08-16 16:58:35 +01:00
Felix
7c5eedaa87 removed test graph 2019-08-16 15:55:22 +01:00
Felix
301a6e619a commented out console.logs 2019-08-16 15:24:49 +01:00
Felix
12a72c00eb added loading spinner for when graph is loading 2019-08-16 15:23:18 +01:00
Tom Slater
e9334cec6d Merge branch 'tyu-tyu/chart' into felix/customranges 2019-08-16 13:58:13 +01:00
Tom Slater
1dd17e7a7a Chart work
Misc Chart work
2019-08-16 12:27:05 +01:00
Felix
ab72264521 IT WORKS! (date range picker) 2019-08-16 12:13:43 +01:00
Felix
6f801d20de more debugging 2019-08-16 11:32:45 +01:00
Felix
93b21d2547 debugging caching 2019-08-16 10:51:47 +01:00
Felix
277c058450 caching data from server to increase speed 2019-08-16 10:27:49 +01:00
Felix
ce64a9140e turns out the brackets were the wrong way around 2019-08-15 16:51:06 +01:00
Felix
5ffffbfe08 date pickers work 2019-08-15 16:34:13 +01:00
Felix
a5b32936bd forgot to grab important change from dev server 2019-08-15 15:10:48 +01:00
Felix
5b50803a25 more debugging 2019-08-15 14:51:10 +01:00
Felix
88bd12157b fixed incorrect initial value 2019-08-15 14:33:56 +01:00
Felix
6a338d41a4 removed unnecessary console.log() 2019-08-15 14:23:28 +01:00
Felix
a8cd23cf5c bubble chart updates on new dates entered to field
but is slowwwwwwww
2019-08-15 14:22:33 +01:00
Felix
a2f8f90513 custom dateRange code works 2019-08-15 12:37:49 +01:00
Felix
6e21cf8746 debugging custom range 2019-08-15 12:09:46 +01:00
Felix
e2960a3fcf fetching the right data this time at least 2019-08-14 16:36:02 +01:00
Felix
b6c0e1b6dd debugging options 2019-08-14 16:27:28 +01:00
Felix
00889b2104 fetches correct date ranges 2019-08-14 16:06:09 +01:00
Felix
263d3d15ab yeet 2019-08-14 15:43:41 +01:00
Tom Slater
fe6964fb8d WIP event timepicker 2019-08-14 15:34:08 +01:00
Finn
6c807194bc
fixed errors with ngModel 2019-08-14 14:18:53 +01:00
Tom Slater
a605d66d37 work on date picker bubble chart 2019-08-14 12:07:05 +01:00
Tom Slater
d550bf5e12 Work on date picker function on bubble chart 2019-08-14 09:49:18 +01:00
Tom Slater
c206a394d2 Added time filter options + Chart Labels
Cleaned up graphs that aren't relevant to this particular page, added chart labels and a WIP time filter option
2019-08-09 10:21:40 +01:00
Tom Slater
7ee0b64351 Chart Labels and clean up
Added axis labels and cleaned up charts a little.
2019-08-07 16:21:40 +01:00
Tom Slater
3248caed07 Misc Chart Improvements
Fixed doughnut chart colour loading issues, added WIP dashboard charts, few other misc changes
2019-08-06 16:51:44 +01:00
Tom Bloor
0dcf98fed3
Various updates 2019-07-15 12:38:06 +01:00
Tom Bloor
5cd0db3faf
More graphs with testing data 2019-07-15 01:17:01 +01:00
Finn
b1790a2fe9
Added meta data to transaction list 2019-07-12 20:47:01 +01:00
Finn
75ba91379d
made fixes to imports and supplier list 2019-07-12 20:18:28 +01:00
Finn
8202cdfdb1
Amended supplier list 2019-07-12 20:05:06 +01:00
Finn
40462b7d8f
Added proper supplier table 2019-07-12 18:50:59 +01:00
Finn
6b1e474916
Fixed suppliers 2019-07-12 17:45:43 +01:00
Finn
af04adeb31
Fixed errors when there is no data available 2019-07-12 17:38:19 +01:00
Finn
df631ad793
various fixes to dashboard stuff 2019-07-12 17:12:01 +01:00
Finn
7a6f40e300
fixed bugs 2019-07-12 15:49:06 +01:00
Finn
4b7ffd91b3
Adding new chart and fixing stuff 2019-07-12 13:40:21 +01:00
Finn
c5f6cb2750
FIxing a lot of issues and adding org data 2019-07-11 16:12:46 +01:00
Tom Slater
bd33f79067 Unfished Graph sections
Part 2
2019-07-11 15:18:33 +01:00
Tom Slater
630beeb5d0 Unfinished Company Component 2019-07-11 15:17:50 +01:00
Tom Slater
71162bcce7 Merge remote-tracking branch 'origin/felix/newSection-testing' into tom/minorfixes 2019-07-10 14:00:25 +01:00
Felix
45d52c9cdb Real bubble chart works, just need data now! 2019-07-10 13:04:07 +01:00
Felix
1c11f8e936 Added sample bubble chart 2019-07-10 12:49:36 +01:00
Felix
9f50b3c2ce Updating/removing some old files, preparing to add bubble chart 2019-07-10 12:22:26 +01:00
Felix
45e700675a added a necessary import 2019-07-09 14:24:59 +01:00
Felix
9dcc22bc3c added renaming instructions..? 2019-07-09 14:24:23 +01:00
Felix
46d2ed7ad8 Made a new section as per TomS's request :-) 2019-07-09 14:22:19 +01:00
Felix
c98a098ae9 Corrected grammar lol 2019-07-09 12:59:56 +01:00
Felix
2ded74f63c Merge commit '2998f0b3da' into felix/newSection-testing 2019-07-09 12:50:45 +01:00
Tom Slater
2998f0b3da Minor changes for Felix 2019-07-09 12:41:17 +01:00
Felix
d150441aa1 required packages updated 2019-07-09 12:22:03 +01:00
Felix
ee636b87fa Changed name of new-section to suppliers 2019-07-04 16:36:06 +01:00
Felix
1b56d6e95c Real table added to new section with sample data, dashboard 'total spend' is still broken - I can't figure out how I broke it in the first place :( 2019-07-04 16:10:50 +01:00
Felix
55f3f50576 Removed 'stacked-bar' reference from Dashboard, added table to New Section 2019-07-04 15:49:14 +01:00
Felix
ec1655a5ca package-lock.json and package.json added 2019-07-04 15:37:13 +01:00
Felix
68cf9ee049 New graph types added (not all working unfortunately), new section added for table views and probably a lot of messy code too! 2019-07-04 15:34:42 +01:00
Finn
6649888876
Properly fixed categories showing 2019-07-03 15:11:02 +01:00
Finn
bc8f024d41
fixed showTotalCategoryList 2019-07-03 15:04:03 +01:00
Finn
700287fc32
updated modules to work properly locally 2019-07-03 13:46:28 +01:00
Finn
5bb0669204
updated dependencies 2019-07-03 13:04:17 +01:00
Finn
0c43962f47
Updated versions 2019-07-02 16:46:20 +01:00
Finn
e7d2038041
Merge pull request #84 from Pear-Trading/hotfix-index
Icons and colours properly added for category list
2018-06-18 12:15:38 +01:00
piratefinn
090b3a65cb fixed icon colours and icons 2018-06-18 12:08:50 +01:00
piratefinn
54f2ad832d removed production overrides on local 2018-06-18 12:08:12 +01:00
Finn
f018e72e3a
Merge pull request #83 from Pear-Trading/master
Master
2018-06-06 14:35:12 +01:00
piratefinn
a751cf393e Merge branch 'hotfix-index' 2018-06-06 14:25:11 +01:00
piratefinn
63475b45b4 updated changelog 2018-06-06 14:24:47 +01:00
piratefinn
02be5e78d0 fixed pasta'd app-root 2018-06-06 14:23:38 +01:00
Finn
8510c539f7
Merge pull request #82 from Pear-Trading/v0.1.11
v0.1.11 release
2018-06-06 14:15:53 +01:00
piratefinn
18340a61f9 updated changelog 2018-06-05 14:11:56 +01:00
piratefinn
df30ab88c4 version bumps and proper naming 2018-06-05 14:08:27 +01:00
Finn
fb606016ee
Merge pull request #81 from Pear-Trading/finn/angular-6
Move to Angular 6
2018-06-05 13:58:50 +01:00
piratefinn
ee0daa2d57 removed unused config in ng test and cleaned angular.json 2018-06-05 13:47:02 +01:00
piratefinn
4d34e5c0f2 added local and ci configuration to e2e 2018-06-05 12:51:05 +01:00
piratefinn
478429b3c9 cleaned up code
keep eye on ngx-bootstrap for when it removes rxjs-compat requirement
2018-06-05 12:25:18 +01:00
piratefinn
21e163fda2 package updates 2018-06-04 18:05:59 +01:00
piratefinn
f7d2faea54 Merge remote-tracking branch 'origin/development' into finn/angular-6 2018-06-04 15:50:07 +01:00
Finn
e7e3f90739
Merge pull request #80 from Pear-Trading/finn/dashboard-fixes
Added fixes to dashboard
2018-06-04 15:49:33 +01:00
piratefinn
10622204b7 fixed icon errors 2018-06-04 15:26:46 +01:00
piratefinn
c9f786eaee removed icon list for sectors 2018-06-04 15:26:41 +01:00
piratefinn
e9b659a4bb Removed sector stuff on dashboard
To be moved to organisation dashboard at later time
2018-06-04 15:26:36 +01:00
piratefinn
adc0a1a7db Added category all time purchase list 2018-06-04 15:26:31 +01:00
piratefinn
ca95f8d10d layout fixes 2018-06-04 15:26:10 +01:00
piratefinn
fa2180f603 made submit message on add more logical 2018-06-04 15:25:54 +01:00
piratefinn
a388e2629d extraneous console logs removed 2018-06-04 15:25:24 +01:00
piratefinn
32b6dc810f extraneous login console logs removed 2018-06-04 15:24:59 +01:00
piratefinn
6abe0ec6f6 moved to angular 6 2018-06-04 15:23:16 +01:00
Tom Bloor
ebdd0354f5
Merge pull request #79 from Pear-Trading/master
Master
2018-05-24 13:31:52 +01:00
Tom Bloor
e19102a457
Merge pull request #78 from Pear-Trading/Release-v0.1.10
Release v0.1.10
2018-05-24 13:02:43 +01:00
Thomas Bloor
e6658aeccb
Updated Changelog 2018-05-24 12:39:10 +01:00
Tom Bloor
334ed6b411
Merge pull request #76 from Pear-Trading/THelsby/Analytics
Analytics
2018-05-24 12:35:36 +01:00
Thomas Bloor
a6e5412e40
Fix missing commas in environments files 2018-05-24 12:27:06 +01:00
Thomas Bloor
f05a5816e4
Update environment files 2018-05-24 12:15:39 +01:00
Thomas Bloor
5ef8f9471c
Fix http -> https error 2018-05-24 12:14:26 +01:00
Unknown
6bf111a3be
Analytics 2018-05-24 12:08:27 +01:00
Finn
7811806903
Merge pull request #75 from Pear-Trading/finn/yearlysubmit
added yearly submit
2018-05-15 17:04:28 +01:00
piratefinn
b373d4e63f added yearly submit 2018-05-15 16:48:51 +01:00
piratefinn
233c523353 Merge branch 'master' into development 2018-04-17 14:22:03 +01:00
piratefinn
7215a31262 Merge branch 'Release-0.1.9' 2018-04-17 14:20:46 +01:00
piratefinn
2ac7244605 version bumps and changelog 2018-04-17 14:20:26 +01:00
piratefinn
77972adcbf Merge branch 'finn/fixes' into development 2018-04-17 14:19:07 +01:00
piratefinn
4867a1ecaa made fixes 2018-04-17 14:17:57 +01:00
piratefinn
cc08f622af fixed layout on dashboard 2018-04-17 12:16:22 +01:00
piratefinn
a4e4158d7f Merge branch 'master' into development 2018-04-16 16:46:50 +01:00
Finn
c244eb8735
Merge pull request #74 from Pear-Trading/Release-0.07
0.1.8 Release
2018-04-16 16:27:44 +01:00
piratefinn
1cb3985778 changelog and version bumps 2018-04-16 16:06:00 +01:00
piratefinn
48fd3114b2 Merge branch 'finn/UIfixes' into development 2018-04-16 15:42:02 +01:00
piratefinn
4f3e0aa22d Amended for possible undefined error 2018-04-16 15:41:45 +01:00
Finn
735b2de28a
Merge pull request #73 from Pear-Trading/finn/UIfixes
Amended close button
2018-04-16 15:09:58 +01:00
piratefinn
daff33657f Fixed instruction for close menu button 2018-04-16 15:08:23 +01:00
piratefinn
3d5d20e419 Added hint for closing menu 2018-04-16 14:52:19 +01:00
Finn
7881ae1103
Merge pull request #72 from Pear-Trading/finn/graphs
Adding new graph views and fixes
2018-04-13 18:19:12 +01:00
piratefinn
2709a7893d various fixes 2018-04-13 16:37:26 +01:00
piratefinn
9d17a331fa transferred stuff from pie panel to dashboard 2018-04-13 16:07:26 +01:00
piratefinn
52a2b6716c fixed view of charts to show currency where relevant 2018-04-12 14:12:34 +01:00
piratefinn
61cd0905d1 fixed bar chart 2018-04-11 19:19:34 +01:00
piratefinn
093cdef3cf weekly view fixed, broken category bar chart
Chart needs fixing and currencies should pipe to show currency
2018-04-11 19:10:03 +01:00
piratefinn
511f680800 working essential bar chart 2018-04-09 19:19:06 +01:00
piratefinn
351aa5b2c1 added preliminary pie dashboard 2018-03-26 15:47:21 +01:00
piratefinn
6c202fa84a changed category view method 2018-03-26 14:47:20 +01:00
piratefinn
1f24095285 hotfix and version fixes 2018-03-21 17:41:07 +00:00
piratefinn
4bd3d67323 Merge remote-tracking branch 'origin/master' into development 2018-03-21 17:16:01 +00:00
Finn
085c1b40d5
Merge pull request #71 from Pear-Trading/finn/Release-v0.1.6
v0.1.6 Release
2018-03-21 16:58:48 +00:00
piratefinn
022f0ab2a1 changelog updated 2018-03-20 19:17:02 +00:00
Finn
8b65d8eefb
Merge pull request #70 from Pear-Trading/finn/buttoncategory
Added ability to edit and delete recurring transactions
2018-03-20 19:11:22 +00:00
piratefinn
e8217de9b6 fixed errors 2018-03-20 19:04:05 +00:00
piratefinn
db1e3efbf5 removed console logs 2018-03-20 17:41:35 +00:00
piratefinn
40eef44666 fixed recurring transaction editing 2018-03-20 17:41:03 +00:00
piratefinn
463bbe98a9 fully working transaction edit and delete 2018-03-20 17:29:00 +00:00
piratefinn
2188c1272f added updating transaction 2018-03-20 16:53:29 +00:00
piratefinn
49bd7d2e0c fixed httpclient error logging 2018-03-20 16:53:14 +00:00
piratefinn
3e8767e481 fixed category viewing and editing on several pages 2018-03-19 17:52:54 +00:00
piratefinn
f36ef00478 fixed add data submission and removed unnecessary console logs 2018-03-19 16:28:46 +00:00
piratefinn
b9578da579 Added initial recurring variable viewing 2018-03-14 20:06:21 +00:00
piratefinn
c45790c7e9 fixed upload submissions 2018-03-14 20:05:46 +00:00
piratefinn
e1dfa37f63 removed unnecessary console logs 2018-03-14 16:49:01 +00:00
piratefinn
f3c6370394 Amended purchase validation and submission 2018-03-13 12:49:30 +00:00
piratefinn
40b0a0088c applied new category style to fixed category view 2018-03-12 17:43:15 +00:00
piratefinn
dc27b286cf Placeholder recurring transaction box 2018-03-12 17:41:01 +00:00
piratefinn
144961acb3 Merge branch 'development' into finn/buttoncategory 2018-03-12 17:40:00 +00:00
piratefinn
2965a8d33c Merge branch 'master' into development 2018-03-12 17:39:52 +00:00
piratefinn
7bb8500315 changelog updated 2018-03-12 17:38:39 +00:00
piratefinn
dbf77ca4ee Merge branch 'development' 2018-03-12 17:37:06 +00:00
piratefinn
0b27aa43e4 Merge branch 'master' into development 2018-03-12 17:36:27 +00:00
piratefinn
c8c52d33c4 emergency fix for category viewing on purchase 2018-03-12 17:35:18 +00:00
piratefinn
aaaa7c7a05 Converted category select into buttons, no color yet 2018-03-12 16:36:26 +00:00
Finn
c62e74e07a
Merge pull request #69 from Pear-Trading/master
merge to dev
2018-03-09 17:28:53 +00:00
Finn
675f319993
Merge pull request #68 from Pear-Trading/Release-v0.1.4
0.1.4 Release
2018-03-09 17:11:47 +00:00
piratefinn
eee50b9073 version bumps 2018-03-09 16:51:58 +00:00
piratefinn
c37b30bea5 Updated angular CLI to solve security dependency issue 2018-03-09 15:48:04 +00:00
piratefinn
db8d27a888 Merge branch 'finn/changelog' into development 2018-03-09 14:29:05 +00:00
piratefinn
247e38e680 updated changelog 2018-03-09 14:28:55 +00:00
piratefinn
f7697178fd emergency fix to upload for category 2018-03-08 15:09:35 +00:00
Finn
b4f3f754c2
Merge pull request #67 from Pear-Trading/finn/recurring
fixed views and added recurring purchase input
2018-03-07 13:06:07 +00:00
piratefinn
d97c21caf8 updated moment to fix known vulnerability 2018-03-07 13:02:42 +00:00
piratefinn
f6ef50f686 added fixes to category viewing 2018-03-07 12:58:01 +00:00
piratefinn
450b63620f added fixes for input 2018-03-06 15:19:35 +00:00
piratefinn
6e9f4133d2 amended upload for recurring 2018-03-05 15:35:26 +00:00
piratefinn
f5c0a76032 amended if code and validation 2018-03-05 13:42:28 +00:00
piratefinn
45d016c897 added recurring purchase inputs 2018-03-05 13:33:26 +00:00
Finn
db5a712a5d
Merge pull request #66 from Pear-Trading/finn/essentialpurchase
added essential purchase
2018-03-02 17:00:17 +00:00
piratefinn
8b7b5e50be fixed display with essential and categories 2018-03-02 16:31:14 +00:00
piratefinn
5762b35fbe removed unnecessary console logs and cleaned essential input 2018-03-01 17:07:25 +00:00
piratefinn
f6dd18bfc8 added initial essential branch checkbox 2018-02-28 12:02:01 +00:00
Finn
46e2efeaba
Merge pull request #65 from Pear-Trading/finn/categories
Budget view amended
2018-01-25 16:25:19 +00:00
piratefinn
9bbf7a5c2e updated changelog 2018-01-25 16:21:52 +00:00
piratefinn
bf965448c5 made input two columns if wide enough 2018-01-25 16:19:02 +00:00
piratefinn
d11fbcb4e6 removed unnecessary console logging 2018-01-25 15:56:23 +00:00
piratefinn
5d112ff4a6 Changed percentages on budget view screen 2018-01-25 11:18:31 +00:00
piratefinn
ec1aef6627 Changed to radio buttons on transaction category input 2018-01-25 11:18:13 +00:00
Finn
6c16b7aee0
Merge pull request #64 from Pear-Trading/finn/categories
Categorised purchase list fully added
2018-01-24 16:09:55 +00:00
piratefinn
d674c5b59b list fully finished 2018-01-24 13:18:51 +00:00
piratefinn
ca2cf0fdab No longer throwing errors and proper limit & slice 2018-01-24 12:51:03 +00:00
piratefinn
9446ef11d4 properly viewing category list 2018-01-24 12:38:58 +00:00
piratefinn
5f2eee81ba Placeholder and date math 2018-01-22 17:23:56 +00:00
piratefinn
6cca97cc68 changed category list size 2018-01-19 13:22:18 +00:00
piratefinn
fae6b7ce31 Merge branch 'master' into finn/categories 2018-01-19 13:22:00 +00:00
Finn
2cc087a295
Merge pull request #63 from Pear-Trading/Release-v0.1.3
Release of v0.1.3
2018-01-18 13:08:01 +00:00
piratefinn
43144da9fc updated changelog 2018-01-18 13:04:32 +00:00
Tom Bloor
d27d18e729
Merge pull request #62 from Pear-Trading/TBSliver/Travis-Temp-Fix
Update .travis.yml
2018-01-18 13:00:26 +00:00
Tom Bloor
40973b87ff
Update .travis.yml
Setting sudo mode for the travis builds
2018-01-18 12:57:38 +00:00
Finn
9e122bf4bc
Merge pull request #61 from Pear-Trading/development
Release of categories
2018-01-18 12:41:34 +00:00
Finn
4a388a4c3e
Merge pull request #60 from Pear-Trading/finn/categoryrelease
Amended category change for release
2018-01-18 12:23:20 +00:00
piratefinn
22b3e00eaa updated changelog 2018-01-18 12:20:26 +00:00
piratefinn
a20d94d9cb Changed transaction category input 2018-01-18 12:17:40 +00:00
piratefinn
7284452317 added just in case for font loading file attribute 2018-01-18 12:17:35 +00:00
piratefinn
d285b04119 removed unused button on pie 2018-01-18 12:17:27 +00:00
piratefinn
004c30cc28 Changed transaction category input 2018-01-18 12:16:37 +00:00
piratefinn
45eb7cb7a1 added new category transaction listing page 2018-01-17 17:36:28 +00:00
piratefinn
bd8af1d1bd added just in case for font loading file attribute 2018-01-17 15:02:53 +00:00
piratefinn
b18943c268 removed unused button on pie 2018-01-17 15:02:32 +00:00
Finn
a2ff339a84
Merge pull request #59 from Pear-Trading/finn/categories
Added categories to transactions
2018-01-17 14:51:46 +00:00
piratefinn
93b5fb52cc angular & CLI version bump 2018-01-17 13:23:36 +00:00
piratefinn
511969656e Fixed app, added ability for non category transaction changed reset form 2018-01-16 15:57:38 +00:00
piratefinn
d17a686490 functional receiving, choosing and upload of transaction category 2018-01-15 16:20:51 +00:00
piratefinn
59efe7a650 Retrieving categories from API 2018-01-15 15:33:38 +00:00
piratefinn
e64b6057e8 added category list endpoint 2018-01-15 14:19:16 +00:00
Finn
c4b5f8f91f
Merge pull request #57 from Pear-Trading/Release-v0.1.1
Release v0.1.1
2017-12-21 17:29:43 +00:00
piratefinn
c7020f4283 changelog and versioning updated 2017-12-21 17:27:14 +00:00
Finn
08a8f464f3
Merge pull request #56 from Pear-Trading/finn/pielayoutchange
updated pie css
2017-12-21 17:25:28 +00:00
piratefinn
71eee8f06b updated pie css 2017-12-21 17:20:56 +00:00
Finn
f9711b6686
Merge pull request #55 from Pear-Trading/master
Master merge for v0.1.0
2017-12-21 15:54:46 +00:00
Finn
ef66009baa
Merge pull request #54 from Pear-Trading/Release-v0.1.0
Release v0.1.0
2017-12-21 15:43:55 +00:00
piratefinn
057487db4c versioning updated 2017-12-21 15:31:58 +00:00
Finn
ee43c80041
Merge pull request #53 from Pear-Trading/finn/circlefix
snippet circles fixed
2017-12-20 16:52:26 +00:00
piratefinn
11fcf170ba updated changelog 2017-12-20 16:46:57 +00:00
piratefinn
95a918076a snippet circles fixed 2017-12-20 16:46:25 +00:00
Finn
4f6edf9470
Merge pull request #52 from Pear-Trading/finn/sectorColours
added varying colors based on sector
2017-12-20 13:45:22 +00:00
piratefinn
2784387b12 added varying colors based on sector 2017-12-20 13:41:13 +00:00
Finn
2ed709476c
Merge pull request #51 from Pear-Trading/finn/loopdashboard
Added loops to customer dashboard
2017-12-20 13:25:33 +00:00
piratefinn
2d4a64cef4 clarified a TODO in css 2017-12-20 12:33:04 +00:00
piratefinn
30919e6eb2 added circles to snippets
WARNING: They are a fixed size, therefore will spill content on larger numbers
2017-12-20 12:32:23 +00:00
piratefinn
d8fab7c4df frontpage sectors and week stats amended 2017-12-19 18:01:29 +00:00
piratefinn
f4c9724097 added initial looping 2017-12-18 17:20:38 +00:00
Finn
f882ad64dd
Merge pull request #50 from Pear-Trading/finn/sectorU
added new sector
2017-12-18 16:26:53 +00:00
piratefinn
13d84905b8 added new sector 2017-12-18 16:20:31 +00:00
Finn
b8fcedaf09
Merge pull request #49 from Pear-Trading/finn/endpointchange
changed customer dashboard endpoint
2017-12-18 14:06:33 +00:00
piratefinn
b8f5687ac2 changed customer dashboard endpoint 2017-12-18 12:57:40 +00:00
Finn
99e0a6c5d4
Merge pull request #48 from Pear-Trading/finn/customerdashboard
revamped customer dashboard
2017-12-15 18:03:56 +00:00
piratefinn
b50c65ba3d Changelog updated 2017-12-15 17:59:09 +00:00
piratefinn
46d99d11f9 fixed variable definition 2017-12-15 17:44:50 +00:00
piratefinn
728f9e3d47 fixed es5 compatibility 2017-12-15 17:38:59 +00:00
piratefinn
e16cf3e801 sector list code fully working 2017-12-15 17:20:18 +00:00
piratefinn
7e7bb4ccf3 percent per week data viewing done 2017-12-14 20:14:47 +00:00
piratefinn
93f15ae6de improved doughnut header layout 2017-12-14 17:51:00 +00:00
piratefinn
9545601d01 working graph widgets and local doughnut chart 2017-12-14 16:44:23 +00:00
piratefinn
32bebd7c24 Added working snippets and placeholder pie chart 2017-12-13 12:37:11 +00:00
piratefinn
6eb6425fb8 More placeholder code for customer dashboard 2017-12-12 15:10:44 +00:00
Finn
7e69c111e7
Merge pull request #47 from Pear-Trading/finn/trailchoicechange
Changed from dropdown to modal
2017-12-08 20:39:33 +00:00
piratefinn
ef84c7fa68 changelog updated 2017-12-08 20:35:50 +00:00
piratefinn
44740d5dd4 Changed from dropdown to modal 2017-12-08 20:34:58 +00:00
Tom Bloor
d469bc2c7d
Merge pull request #46 from Pear-Trading/master
Master merge for v0.0.7
2017-12-08 16:30:23 +00:00
Tom Bloor
2092716b95
Merge pull request #45 from Pear-Trading/Release-v0.0.7
Release v0.0.7
2017-12-08 16:17:54 +00:00
Thomas Bloor
dd891486ae
Update Changelog and bump version numbers 2017-12-08 16:13:50 +00:00
Finn
aa7d17522e
Merge pull request #44 from Pear-Trading/finn/trailchange
Changes made to ESTA and trail map
2017-12-08 15:38:11 +00:00
piratefinn
e104b2c4d2 changelog, esta pin and naming updated 2017-12-08 15:30:16 +00:00
Finn
0829f8d71b
Merge pull request #43 from Pear-Trading/finn/trailchange
Added Esta
2017-12-08 14:20:08 +00:00
piratefinn
644c097f0b image url naming sanitised 2017-12-08 14:13:47 +00:00
piratefinn
100a3c71a8 renamed LIS to trail and added esta logo 2017-12-08 14:01:46 +00:00
piratefinn
0cc3a473cf changed code for trail map 2017-12-08 13:26:18 +00:00
Finn
4428cd6fcd
Merge pull request #42 from Pear-Trading/finn/alignmentfix
fixed navbar alignment
2017-11-29 18:12:49 +00:00
piratefinn
03ec811c6c Merge branch 'development' into finn/alignmentfix 2017-11-29 18:10:39 +00:00
Tom Bloor
a44975b879
Merge pull request #41 from Pear-Trading/master
Master merge for v0.0.6
2017-11-29 18:07:38 +00:00
piratefinn
830b4b3c4f fixed navbar alignment 2017-11-29 18:07:37 +00:00
Tom Bloor
f39c192a2b
Merge pull request #40 from Pear-Trading/Release-v0.0.6
Release v0.0.6
2017-11-29 17:30:07 +00:00
Thomas Bloor
d9b8ebfc50
Travis can only manage one file for decryption it seems 2017-11-29 17:24:16 +00:00
Thomas Bloor
0ce452233d
Setup Travis to auto build releases when a tag is created 2017-11-29 17:12:18 +00:00
Thomas Bloor
f5ee21d067
Updated changelog and bumped version number in all packages 2017-11-29 16:52:31 +00:00
Finn
53fb48e819
Merge pull request #39 from Pear-Trading/finn/typocorrection
fixed typo
2017-11-29 16:24:53 +00:00
Finn
614b353686
Merge pull request #38 from Pear-Trading/master
Master merge for v0.0.5
2017-11-29 16:24:42 +00:00
piratefinn
16ef46972e fixed typo 2017-11-29 16:19:38 +00:00
Tom Bloor
cc04b9eb11
Merge pull request #37 from Pear-Trading/Release-v0.0.5
Release v0.0.5
2017-11-29 16:01:21 +00:00
Thomas Bloor
dd63a4ee4a
Updated Changelog and bumped version number in package files 2017-11-29 15:40:00 +00:00
Tom Bloor
bf191a7c06
Merge pull request #35 from Pear-Trading/master
Master merge for v0.0.4
2017-11-29 15:32:14 +00:00
Finn
ae2fc74d2e
Merge pull request #36 from Pear-Trading/finn/liscorrection
Renamed some text for Lancaster Independent Story
2017-11-29 15:28:20 +00:00
piratefinn
2af4cd8adf Renamed some text for Lancaster Independent Story 2017-11-29 15:23:03 +00:00
Thomas Bloor
7968493fe3 Fixed error on building 2017-11-28 18:14:15 +00:00
Tom Bloor
f13546a6fe
Merge pull request #34 from Pear-Trading/Release-v0.0.4
v0.0.4
2017-11-28 18:03:07 +00:00
Thomas Bloor
007ddd294a Bumped version number in package.json and CHANGELOG 2017-11-28 17:59:06 +00:00
Finn
2b92d02346
Merge pull request #33 from Pear-Trading/finn/logocorrection
fixed logo on main map
2017-11-28 12:51:44 +00:00
piratefinn
4670e10c2f fixed logo on main map 2017-11-28 12:44:53 +00:00
Finn
c2ee94f82a
Merge pull request #32 from Pear-Trading/finn/storytrail
story markers and package updates
2017-11-27 17:30:42 +00:00
piratefinn
961d8eafe3 fix made to modaldirective import 2017-11-27 17:20:22 +00:00
piratefinn
0af35cd787 changelog updated 2017-11-27 16:56:58 +00:00
piratefinn
0fb3f51748 Fixes made and info window usage revamped with modals 2017-11-27 16:56:09 +00:00
piratefinn
5ca44cedf3 changelog updated 2017-11-23 17:21:52 +00:00
piratefinn
b7cbbcc23a changelog updated and redundant dependency removed 2017-11-23 17:21:00 +00:00
piratefinn
4a331a3475 fixed modals to use new endpoint and scrapped old pages 2017-11-23 17:16:46 +00:00
piratefinn
7843a87fd6 working modals added 2017-11-15 15:36:45 +00:00
piratefinn
5ed6bfe7f3 fixed HTTP posts 2017-11-15 14:46:04 +00:00
piratefinn
b8775e2e45 fixed packaging and rewrote http 2017-11-15 14:16:05 +00:00
piratefinn
d34fdb58ca removed unused imports and packages 2017-11-15 14:12:28 +00:00
piratefinn
840d0aabb1 currencyPipe fixed 2017-11-15 13:46:34 +00:00
piratefinn
3bde7aed3f updated packages 2017-11-15 13:40:57 +00:00
piratefinn
b46ab1d8a2 locked all of package.json 2017-11-15 13:26:35 +00:00
piratefinn
9fa0c16250 packages updated 2017-11-15 13:20:35 +00:00
piratefinn
694a65f0ee ngx-bootstrap upgraded 2017-11-15 13:05:35 +00:00
piratefinn
a411a0e7fb got clicks logging 2017-11-14 18:31:05 +00:00
piratefinn
4c5074c5fc map page html layout fix 2017-11-14 17:13:34 +00:00
piratefinn
83bc98ece5 test page added 2017-11-14 17:12:19 +00:00
piratefinn
c85602f6e8 snazzy info added, logo added 2017-11-14 17:12:07 +00:00
piratefinn
5af0e1e35c Initial commit with modules and routing 2017-11-14 12:47:28 +00:00
Tom Bloor
cc38618b03
Merge pull request #31 from Pear-Trading/master
Master merge for v0.0.3
2017-11-14 11:33:29 +00:00
Tom Bloor
3f5a52d4f4
Merge pull request #30 from Pear-Trading/Release-v0.0.3
Release v0.0.3
2017-11-14 11:25:12 +00:00
Tom Bloor
663d78e4bf Bump package version number 2017-11-14 11:18:34 +00:00
Tom Bloor
63006e4be1 Bump version number in Changelog 2017-11-14 11:13:25 +00:00
Tom Bloor
c4d1bf5a07 Fix build-release script to put stuff in a folder 2017-11-14 11:12:29 +00:00
Finn
72ceabf43f
Merge pull request #29 from Pear-Trading/finn/moremaps
Added zoom level to map
2017-11-14 11:11:01 +00:00
piratefinn
1c0cd372d5 Changelog updated 2017-11-14 11:08:17 +00:00
piratefinn
52834af3fc Fixed and working zoom level change 2017-11-14 11:03:19 +00:00
Finn
0286887ead Initial agmcluster commit with broken maxZoom 2017-11-13 22:21:59 +00:00
Finn
994dcdfb63
Merge pull request #28 from Pear-Trading/finn/leaderboardfix
Fixes made
2017-11-13 16:25:07 +00:00
piratefinn
0688685676 changed login username input to email and changed test 2017-11-13 15:49:17 +00:00
piratefinn
a5b73c7e59 Transaction fix added 2017-11-13 13:05:42 +00:00
piratefinn
03299fb5a8 Changelog updated 2017-11-13 12:47:32 +00:00
piratefinn
3009365682 Map changed and made available to customers 2017-11-13 12:39:31 +00:00
piratefinn
8f725b93af Fixed authguard 2017-11-13 12:26:24 +00:00
Finn
356aeae974
Merge pull request #27 from Pear-Trading/finn/leaderboard
Web App Leaderboard added
2017-11-13 11:48:42 +00:00
piratefinn
c4a9790eb8 Web app leaderboard done 2017-11-10 18:41:35 +00:00
piratefinn
faea82d2cc route fix 2017-11-10 17:17:04 +00:00
Finn
40e20679e0
Merge pull request #26 from Pear-Trading/finn/typofix
fixes
2017-11-10 17:15:59 +00:00
piratefinn
8a5236ad3a Leaderboard pages added and routing sorted 2017-11-10 17:15:11 +00:00
piratefinn
f5239a3822 Added password reset placeholder 2017-11-10 15:20:48 +00:00
piratefinn
0b2c9c20f8 Unusable password reset button disabled 2017-11-10 14:14:11 +00:00
piratefinn
0a54cdc091 fixed register name in place 2017-11-10 14:13:28 +00:00
Finn
bca2d62fdc Merge pull request #25 from Pear-Trading/finn/transactionFix
Fixed transaction submission
2017-10-20 13:27:11 +01:00
piratefinn
0a9e80fc97 Fixed transaction submission 2017-10-20 13:21:49 +01:00
Tom Bloor
1ce755d2d3 Merge pull request #24 from Pear-Trading/master
Master merge for v0.0.2
2017-09-28 14:47:42 +01:00
Tom Bloor
1171f935a7 Merge pull request #23 from Pear-Trading/Release-v0.0.2
Release v0.0.2
2017-09-28 14:38:34 +01:00
Tom Bloor
ed3a14806b Bumped package version number to 0.0.2 2017-09-28 14:34:33 +01:00
Tom Bloor
aeda2ec027 Updated changelog 2017-09-28 14:32:58 +01:00
Tom Bloor
e82c12d265 Merge pull request #22 from Pear-Trading/TBSliver/Maps
Added map for suppliers to trader portal
2017-09-28 14:23:37 +01:00
Tom Bloor
0cd7cdc96c Added CI environment to angular cli file 2017-09-28 14:15:51 +01:00
Tom Bloor
cea000b90c Added ci specific e2e and test scripts 2017-09-28 14:12:25 +01:00
Tom Bloor
ed738b287a Added placeholder for mapApiKey 2017-09-28 13:28:57 +01:00
Tom Bloor
8520328582 Changed path of dev environment 2017-09-28 13:25:19 +01:00
Tom Bloor
1eb8d71b4a Removed prod environment file from repo 2017-09-28 13:21:28 +01:00
Tom Bloor
d8b92af1a3 Ignore prod and dev env settings 2017-09-28 13:12:34 +01:00
Tom Bloor
2ffe32c6f5 Change to a map icon for the Supplier Map 2017-09-28 12:55:48 +01:00
Tom Bloor
a622b0d871 Fix issue with supplier location api url 2017-09-28 12:47:09 +01:00
piratefinn
f402d46db8 Added API changes 2017-09-27 17:46:25 +01:00
piratefinn
b59351addf made looping markers 2017-09-27 14:34:46 +01:00
piratefinn
1eb79aa047 Added API and fixes 2017-09-27 13:54:10 +01:00
piratefinn
64e028f0c5 Added default zoom 2017-09-27 12:02:46 +01:00
piratefinn
5d3283171c styling improved 2017-09-26 17:51:14 +01:00
piratefinn
40bd7459b5 Map page added with map 2017-09-26 17:31:40 +01:00
piratefinn
67bb904770 Package added & npm updated 2017-09-26 16:22:54 +01:00
Finn
7cf8af3058 Merge pull request #21 from Pear-Trading/finn/PayrollSubmit
Transaction submitting and log added
2017-09-19 17:31:45 +01:00
piratefinn
089e7f7a97 Functionality fully added 2017-09-19 17:22:19 +01:00
piratefinn
7b03f6adff Initial addition of API for payroll log 2017-09-19 15:38:35 +01:00
piratefinn
f1a6b1886f Merge branch 'development' into finn/PayrollSubmit 2017-09-19 15:37:17 +01:00
Tom Bloor
e32d29b310 Merge pull request #19 from Pear-Trading/TBSliver/More-Automation
Added script for automating prod and dev builds
2017-09-19 15:31:29 +01:00
Tom Bloor
f70d3945b6 Merge pull request #20 from Pear-Trading/TBSliver/Lint-Errors
Tb sliver/lint errors
2017-09-19 15:31:03 +01:00
Tom Bloor
e62a46c312 Ignore linter error in transaction-result component 2017-09-19 15:21:03 +01:00
Tom Bloor
8f164ca94a ignore linter errors in org-result items 2017-09-19 15:19:50 +01:00
Tom Bloor
a5b786a749 Linter errors in dashboard-customer component 2017-09-19 15:04:49 +01:00
Tom Bloor
0907134ff7 Fix linter errors for transaction-log component 2017-09-19 15:02:48 +01:00
Tom Bloor
662d6be5c9 Linter error fixes for feedback component 2017-09-19 14:59:19 +01:00
Tom Bloor
921b25718d Linter error fixes for account-edit component 2017-09-19 14:56:07 +01:00
Tom Bloor
f6c0583481 Fix linter errors in add-data component 2017-09-19 14:53:25 +01:00
Tom Bloor
61ac856cc2 fix linter errors on register component 2017-09-19 14:48:22 +01:00
Tom Bloor
dec53b4630 Fix linter errors in full-layout 2017-09-19 11:10:56 +01:00
Tom Bloor
8807ba6cc9 linter errors in auth.guard.ts 2017-09-19 11:09:30 +01:00
Tom Bloor
baf64ff4b7 whitespace changes for linter in pages 500 2017-09-19 11:06:41 +01:00
Tom Bloor
49be004bbc whitespace changes for pages 404 2017-09-19 11:05:21 +01:00
Tom Bloor
10664f0da9 Added script for automating prod and dev builds 2017-09-19 10:59:41 +01:00
Finn
78af694c04 Merge pull request #18 from Pear-Trading/finn/ValidationFix
Fixed Transaction Validation
2017-09-18 14:08:36 +01:00
piratefinn
d1eb79b394 Fixed Transaction Validation 2017-09-18 13:58:38 +01:00
Finn
d2f75af362 Merge pull request #17 from Pear-Trading/finn/RegisterViewFix
made register view fix
2017-09-18 10:50:01 +01:00
piratefinn
7f3e9f4fd0 Added better error messaging 2017-09-15 17:33:34 +01:00
piratefinn
f34c558d41 Fixes to receipt form 2017-09-15 16:50:53 +01:00
piratefinn
70be2cbc2b Amended changelog 2017-09-15 16:36:37 +01:00
piratefinn
7b1771b45f Fixed Register View on mobile 2017-09-15 16:35:04 +01:00
Tom Bloor
bd0c9e4aa5 Merge branch 'Release-v0.0.1' into development 2017-09-15 15:52:37 +01:00
119 changed files with 18846 additions and 7755 deletions

View file

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

6
.gitattributes vendored
View file

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

8
.gitignore vendored
View file

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

3
.idea/.gitignore generated vendored Normal file
View file

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

14
.idea/codeStyles/Project.xml generated Normal file
View file

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

5
.idea/codeStyles/codeStyleConfig.xml generated Normal file
View file

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

6
.idea/misc.xml generated Normal file
View file

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

8
.idea/modules.xml generated Normal file
View file

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

6
.idea/vcs.xml generated Normal file
View file

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

View file

@ -2,12 +2,24 @@ addons:
chrome: stable
language: node_js
node_js:
- 8
- node
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
- tar xf src/environments/environments.tar -C src/environments
before_script:
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start
- npm config set spin false
install:
- npm install
- npm install
script:
- npm run ci
- npm run ci
before_deploy:
- ./bin/build-releases
deploy:
provider: releases
api_key:
secure: "Ke27Qm16uRgfX+ZVAq92Suu0ZpLOG9TGXDD8ndKNQZ4zl80DOmePoKvVpk/5Ip3H57Me2seqtCwgjeUCfW0dX3KayPoNmGVxWXCsZCH3MZWuaMnGk/Zc1Ef8P3L1sTEG4LD6+59RaOiMwIrMtLPjSlzvV2gc+002O7MHoudx/qZl47L+T01B0Ovh1AueSVN224Q79NrBnbgTtMqaS3x2avLkJmdZpneafqeO5OusOFcvsHvBr7ca0qKv5yIpn4eotK2bo6TFuaC9e9i7gUgPKHb1/GXAK1DcteUDF3AzK/b7T+dqTS+1vowuNKjMZ+ecyB8VDXQlWnBcv/IGn/C3nBmtp2oN97BFQtHguCY42Qk9LZxIu9o0mpOt6aMRiIsfbWstgONKaLzgt4Ce3DFlJ7YR5BFRaoKDdGHoCW+tcucQ/o9vFCbBVZ8sol2aYJOiNHYxlC8A7NLs6YEjyckVAa3q1l6CddnSjrFA5oe9fsLdzUDGhJ57Nv7kF9v6jjsTlZtucrzf8ix9+vNKNfWLQ6K86UIeBT40pPYHLBmWEsiGai+s4IWrYjTscT4zQDHcfQCMuQbbb3NTEfy9Fwv0VQdIR/cKsQgUCZwwlv3RBImryuDFqY2pNOqvnGIcr/OJ/MmY9bbCYEp55dxrZ50dNBbtR4O8IyUDte/ycU4OKbE="
file_glob: true
file: "../WebApp-Releases/*"
skip_cleanup: true
on:
tags: true

View file

@ -2,6 +2,141 @@
# Next Release
# 0.1.13
* Added new graphs for organisation view, for better breakdown of spending
* Added Suppliers spend search page
* Updated and fixes numerous graphs on dashboard
## Minor
* *Dev Fixes* Updated Travis config
# 0.1.12
* Fixed accidentally added app-root
# 0.1.11
* Bumped Angular to version 6 and upgraded packages to match
* Converted RxJS 5 syntax to RxJS 6
* Removed extraneous console logs
* Changed dashboard sector list to category all time list & tweaked layout
# 0.1.10
* Allowed for creation of yearly recurring transactions
* Added google analytics
# 0.1.9
* Made layout change to make it neater when chart doesn't show
* Made hotfix
# 0.1.8
* Amended how category is pulled from server
* Added chart on essential purchase numbers as a whole
* Added bar chart of category purchases in the month
* Added pie chart of purchases by category in the week
* Added hint for closing the burger menu while in mobile view
# v0.1.7
* Fixed category on upload highlighting
# v0.1.6
* Changed layout of category choosing on upload
* Added ability to edit and remove recurring transactions
* Fixed HttpClient error log viewing
* Made transaction validation more lenient
# v0.1.5
* Fixed category viewing on purchase
* Changed category view from radio buttons to full label buttons
* Amended local validation of submit
* Changed recurring purchase selection view
# v0.1.4
* Amended category list on transaction submission
* Added budget view for weekly breakdown of spend by category
* Added flag to make purchases essential
* Fixed budget view issues and amended it to show essential purchases that week
* Added ability to make purchases recurring
* Updated Moment dependency due to security issue
* Fixed category uploading in upload
# v0.1.3
* Made fix to Travis config
# v0.1.2
* Removed unused button
* Added ability to choose category for transaction
* Added version bump on Angular
# v0.1.1
* Redid layout on circle customer view
* Renamed customer dashboard headers
# v0.1.0
* Changed Story Trail choosing to modals
* Revamped snippets and graph widgets on customer dashboard
* Added local purchase pie chart for customer dashboard
* Added week by week purchase list for customer dashboard
* Added sector purchase amount list for customer dashboard
* Added sector U to available ones
* Fixed snippet layout
# v0.0.7
* Added ESTA to Story Trail
* Reverted Story Trail naming to LIS
* Changed Trail map code to make it flexible based on association
# v0.0.6
* Fix typo in Map Titles
# v0.0.5
* Change page name from Story Trail to Lancaster Independent Story
# v0.0.4
* Info window on map replaced with modals
* Loading modals added to maps
* Lancaster Independent Story markers added
* Code dependencies updated
* Angular code updated to Angular 5 and made fixes to account for it
# v0.0.3
* Customer Leaderboards added
* Map made available to Customers
* Clustering added to Map
* Map renamed from "Supplier Map" to "Purchase Map" to fall in line with
previous change
* Added placeholder while password reset is not present
* Fixed error on store list during a transaction
# v0.0.2
* Supplier map added to organisation view
* Fix various linter errors
* Payroll Log Data added
* Fixed register box view on login page
* Made the form reset properly if adding a new one
* Added better error messages on register and account edit
# v0.0.1
* Added Travis Testing

12
Foodloop-Web.iml Normal file
View file

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

121
README.md
View file

@ -1,30 +1,43 @@
# LocalLoop Web Interface
# LocalSpend - Web Application
This is the repository for the LocalLoop web interface, for traders and
customers to see and submit data to the service.
This repository contains the Web application for the LocalSpend system.
## Current Status
| 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) |
| `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)
## 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 running
copy of the backend server from [LocalLoop Server][LocalLoop-Server].
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.
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+.
For your local Node.js, we recommend using:
- [n][tj/n] for \*nix and Mac; and
- [nodist][marcelklehr/nodist] for Windows.
To get this repository set up, first clone it and then run the following
commands:
We reccomend Node.js version 8.0.0+ and npm version 5.3.0+.
```
npm install -g @angular/cli
npm install
```
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
[LocalLoop-Server]:https://github.com/Pear-Trading/Foodloop-Server
[tj/n]:https://github.com/tj/n
@ -33,45 +46,51 @@ npm install
## Environments
The app defaults to using the development server. For other options, see
`src/environments/environments.ts`
`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).
## Licences
### CoreUI
The interface itself is based off of [CoreUI][core-ui] which is MIT Licenced.
For information, see [MIT Licence](./LICENCE.MIT) included in this repo.
For information, see `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).

182
angular.json Normal file
View file

@ -0,0 +1,182 @@
{
"$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"
}
}
}

29
bin/build-releases Executable file
View file

@ -0,0 +1,29 @@
#! /bin/bash
set -e
VERSION=`git describe --tags`
if [ ! -d ../WebApp-Releases ]; then
echo "Making new Releases Directory"
mkdir ../WebApp-Releases
fi
echo "Building releases for $VERSION"
echo "Building Prod Release..."
npm run build:prod
tar -czf ../WebApp-Releases/LocalLoop-Web-prod-$VERSION.tar.gz dist
echo "Building Dev Release..."
npm run build:dev
tar -czf ../WebApp-Releases/LocalLoop-Web-dev-$VERSION.tar.gz dist
echo "Cleaning up build artefacts..."
rm -rf dist
echo "Done"

12
browserslist Normal file
View file

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

View file

@ -1,4 +1,5 @@
import { LoginPageObject } from './login.po';
import { browser } from "protractor";
describe('Login Page', () => {
let page: LoginPageObject;
@ -12,13 +13,47 @@ describe('Login Page', () => {
expect(page.getLoginHeaderText()).toEqual('Login');
});
it('should have a username box of type text', () => {
expect(page.isUsernameFieldPresent()).toBeTruthy();
expect(page.getUsernameFieldType()).toBe('text');
it('should have a username box of type email', () => {
expect(page.isEmailFieldPresent()).toBeTruthy();
expect(page.getEmailFieldType()).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,12 +9,23 @@ export class LoginPageObject {
return element(by.css('app-root h1')).getText();
}
getUsernameField() { return element(by.id('username')); }
getEmailField() { return element(by.id('username')); }
getPasswordField() { return element(by.id('password')); }
getLoginButton() { return element(by.id('login')); }
isUsernameFieldPresent() { return this.getUsernameField().isPresent(); }
isEmailFieldPresent() { return this.getEmailField().isPresent(); }
isPasswordFieldPresent() { return this.getPasswordField().isPresent(); }
isLoginButtonPresent() { return this.getLoginButton().isPresent(); }
getUsernameFieldType() { return this.getUsernameField().getAttribute('type'); }
getEmailFieldType() { return this.getEmailField().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/cli'],
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular/cli/plugins/karma')
require('@angular-devkit/build-angular/plugins/karma')
],
client:{
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
files: [
{ pattern: './src/test.ts', watched: false }
],
preprocessors: {
'./src/test.ts': ['@angular/cli']
},
mime: {
'text/x-typescript': ['ts','tsx']
},
coverageIstanbulReporter: {
reports: [ 'html', 'lcovonly' ],
dir: require('path').join(__dirname, 'coverage'), reports: [ 'html', 'lcovonly' ],
fixWebpackSourcePaths: true
},
angularCli: {

21070
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
{
"name": "localloop-web",
"version": "0.0.1",
"version": "0.1.13",
"description": "LocalLoop Web - Web interface for LocalLoop app",
"author": "",
"url": "http://www.peartrade.org",
@ -8,57 +8,81 @@
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"start:dev": "ng serve --optimization=false --configuration=dev",
"start:prod": "ng serve --optimization=false --configuration=production",
"start:local": "ng serve --optimization=false --configuration=local",
"build:dev": "ng build --configuration=dev",
"build:prod": "ng build --configuration=production",
"test": "ng test",
"test:once": "ng test --watch=false",
"test:ci": "ng test --watch=false --browsers=ChromeHeadless",
"lint": "ng lint",
"e2e": "ng e2e",
"ci": "npm run test:once && npm run e2e"
"e2e:ci": "ng e2e --configuration=ci",
"ci": "npm run test:ci && npm run e2e:ci"
},
"private": true,
"dependencies": {
"@angular/common": "4.0.3",
"@angular/compiler": "4.0.3",
"@angular/core": "4.0.3",
"@angular/forms": "4.0.3",
"@angular/http": "4.0.3",
"@angular/platform-browser": "4.0.3",
"@angular/platform-browser-dynamic": "4.0.3",
"@angular/router": "4.0.3",
"@angular/upgrade": "4.0.3",
"@types/moment": "^2.13.0",
"angular-in-memory-web-api": "^0.3.1",
"chart.js": "2.5.0",
"core-js": "2.4.1",
"moment": "^2.18.1",
"ng2-charts": "1.6.0",
"ng2-validation-manager": "^0.3.1",
"ngx-bootstrap": "1.6.6",
"ngx-pagination": "^3.0.1",
"rxjs": "5.4.2",
"@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",
"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",
"ts-helpers": "1.1.2",
"webpack": "3.5.4",
"webpack-dev-server": "2.7.1",
"zone.js": "0.8.9"
"tslib": "^1.10.0",
"web-animations-js": "^2.3.2",
"webpack-dev-server": "^3.11.0",
"zone.js": "~0.10.3"
},
"devDependencies": {
"@angular/cli": "1.3.0",
"@angular/compiler-cli": "4.0.3",
"@types/jasmine": "2.5.54",
"@types/jasminewd2": "2.0.2",
"@types/node": "8.0.24",
"codelyzer": "2.1.1",
"jasmine-core": "2.7.0",
"@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",
"jasmine-spec-reporter": "4.2.1",
"karma": "1.7.0",
"karma": "^5.1.1",
"karma-chrome-launcher": "2.2.0",
"karma-cli": "1.0.1",
"karma-coverage-istanbul-reporter": "1.3.0",
"karma-jasmine": "1.1.0",
"karma-jasmine-html-reporter": "0.2.2",
"protractor": "5.1.2",
"ts-node": "3.3.0",
"tslint": "4.5.1",
"typescript": "2.4.2"
"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"
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -10,7 +10,7 @@
<form [formGroup]="signin" (ngSubmit)="onSubmit()">
<div class="input-group mb-3">
<span class="input-group-addon">@</span>
<input id="username" type="text" class="form-control" formControlName="email" placeholder="Email">
<input id="username" type="email" class="form-control" formControlName="email" placeholder="Email">
</div>
<div class="input-group mb-4">
<span class="input-group-addon"><i class="icon-lock"></i></span>
@ -28,16 +28,17 @@
</div>
<div class="row">
<div class="col-6">
<button type="submit" [disabled]="!signin.valid" class="btn btn-primary px-4">Login</button>
<button id="login" 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">Forgot password?</button>
<button type="button" class="btn btn-link px-0" disabled>Forgot password?</button>
</div>
<label class="col-12"><strong>Password reset is currently in development and disabled in testing, please contact an administrator to reset your password.</strong></label>
</div>
</form>
</div>
</div>
<div class="card card-inverse card-primary py-5 d-md-down-none" style="width:44%">
<div class="card card-inverse card-primary py-5">
<div class="card-block text-center">
<div>
<h2>Sign up</h2>

View file

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

View file

@ -40,7 +40,7 @@
<span class="input-group-addon"><i class="icon-people"></i></span>
<select required class="form-control" type="text" formControlName="usertype">
<option value=''>Please select</option>
<option value='organisation'>Organisation</option>
<option value='organisation'>Business/Organisation</option>
<option value='customer'>Customer</option>
</select>
</div>
@ -106,6 +106,7 @@
<option value='R'>Arts, Entertainment & Recreation</option>
<option value='S'>Other Service Activities</option>
<option value='T'>Household Domestic Business</option>
<option value='U'>Extraterritorial Organisations and Bodies</option>
</select>
</div>
@ -137,7 +138,7 @@
Form validation failed, please ensure the form is filled correctly.
</div>
<div *ngSwitchCase="'send_failed'" class="alert alert-danger" role="alert">
Failed to send to server, please try again later.
{{registerStatusError}}
</div>
</div>
</div>

View file

@ -1,10 +1,9 @@
import { Component } from '@angular/core';
import { Validators, FormBuilder, FormGroup } from '@angular/forms';
import { ValidationManager } from "ng2-validation-manager";
import { Http, Response } from '@angular/http';
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',
@ -16,117 +15,122 @@ export class RegisterComponent {
organisationForm: ValidationManager;
years: Object[];
registerStatus: any;
registerStatusError = 'Error received, please try again.';
constructor(
private http: Http,
private formBuilder: FormBuilder,
private router: Router,
private api: ApiService,
) {
this.years = [];
let max = new Date().getFullYear() - 10,
min = max - 140;
private formBuilder: FormBuilder,
private router: Router,
private api: ApiService,
) {
this.years = [];
const max = new Date().getFullYear() - 10,
min = max - 140;
for (let i = max; i>=min; i--){
this.years.push(i);
}
this.signupForm = new ValidationManager({
token: 'required',
usertype: 'required',
email: 'required|email',
password: 'required',
confirmpassword: 'required|equalTo:password'
});
this.customerForm = new ValidationManager({
display_name: 'required',
full_name: 'required',
postcode: 'required',
year_of_birth:'required',
});
this.organisationForm = new ValidationManager({
name: 'required',
for (let i = max; i >= min; i--) {
this.years.push(i);
}
this.signupForm = new ValidationManager({
token: 'required',
usertype: 'required',
email: 'required|email',
password: 'required',
confirmpassword: 'required|equalTo:password'
});
this.customerForm = new ValidationManager({
display_name: 'required',
full_name: 'required',
postcode: 'required',
year_of_birth: 'required',
});
this.organisationForm = new ValidationManager({
name: 'required',
sector: 'required',
street_name: 'required',
town: 'required',
postcode: 'required',
});
street_name: 'required',
town: 'required',
postcode: 'required',
});
}
onSubmitCustomer() {
console.log(this.signupForm.isValid());
if (!this.signupForm.isValid() && !this.customerForm.isValid()) {
console.log("Not Valid!");
this.registerStatus = "validation_failed";
if (!this.signupForm.isValid() && !this.customerForm.isValid()) {
console.log('Not Valid!');
this.registerStatus = 'validation_failed';
console.log(this.registerStatus);
return;
}
let signupForm = this.signupForm.getForm().value;
let customerForm = this.customerForm.getForm().value;
return;
}
const signupForm = this.signupForm.getForm().value;
const customerForm = this.customerForm.getForm().value;
let data = {
token: signupForm.token,
usertype: signupForm.usertype,
email: signupForm.email,
password: signupForm.password,
display_name: customerForm.display_name,
full_name: customerForm.full_name,
postcode: customerForm.postcode,
year_of_birth:customerForm.year_of_birth,
};
console.log(data);
this.api
const data = {
token: signupForm.token,
usertype: signupForm.usertype,
email: signupForm.email,
password: signupForm.password,
display_name: customerForm.display_name,
full_name: customerForm.full_name,
postcode: customerForm.postcode.toUpperCase(),
year_of_birth: customerForm.year_of_birth,
};
console.log(data);
this.api
.register(data)
.subscribe(
result => {
console.log('registered!');
this.registerStatus = "success";
console.log(this.registerStatus);
this.router.navigate(['/dashboard']);
this.registerStatus = 'success';
this.router.navigate(['/dashboard']);
},
error => {
console.log( error._body );
this.registerStatus = "send_failed";
console.log(this.registerStatus)
console.log('Register Error');
console.log(error);
try {
this.registerStatusError = '"' + error.error.error + '" Error, ' + error.error.message;
} catch (e) {
this.registerStatusError = 'There was a server error, please try again later.';
}
this.registerStatus = 'send_failed';
}
);
}
onSubmitOrganisation() {
console.log(this.signupForm.isValid());
if (!this.signupForm.isValid() || !this.organisationForm.isValid()) {
console.log("Not Valid!");
this.registerStatus = "validation_failed";
if (!this.signupForm.isValid() || !this.organisationForm.isValid()) {
console.log('Not Valid!');
this.registerStatus = 'validation_failed';
console.log(this.registerStatus);
return;
}
let signupForm = this.signupForm.getForm().value;
let organisationForm = this.organisationForm.getForm().value;
return;
}
const signupForm = this.signupForm.getForm().value;
const organisationForm = this.organisationForm.getForm().value;
let data = {
token: signupForm.token,
usertype: signupForm.usertype,
email: signupForm.email,
password: signupForm.password,
name: organisationForm.name,
const data = {
token: signupForm.token,
usertype: signupForm.usertype,
email: signupForm.email,
password: signupForm.password,
name: organisationForm.name,
sector: organisationForm.sector,
street_name: organisationForm.street_name,
town: organisationForm.town,
postcode: organisationForm.postcode,
};
console.log(data);
this.api
street_name: organisationForm.street_name,
town: organisationForm.town,
postcode: organisationForm.postcode.toUpperCase(),
};
this.api
.register(data)
.subscribe(
result => {
console.log('registered!');
this.registerStatus = "success";
console.log(this.registerStatus);
this.registerStatus = 'success';
this.router.navigate(['/dashboard']);
},
error => {
console.log( error._body );
this.registerStatus = "send_failed";
console.log(error);
try {
this.registerStatusError = '"' + error.error.error + '" Error, ' + error.error.message;
} catch (e) {
this.registerStatusError = 'There was a server error, please try again later.';
}
this.registerStatus = 'send_failed';
console.log(this.registerStatus);
}
);

View file

@ -99,6 +99,7 @@
<option value='R'>Arts, Entertainment & Recreation</option>
<option value='S'>Other Service Activities</option>
<option value='T'>Household Domestic Business</option>
<option value='U'>Extraterritorial Organisations and Bodies</option>
</select>
<span class="help-block">Alter this if your business sector has changed.</span>
</div>
@ -122,7 +123,7 @@
Form validation failed, please ensure the form is filled correctly.
</div>
<div *ngSwitchCase="'send_failed'" class="alert alert-danger" role="alert">
Failed to send to server, please try again later.
{{submitStatusError}}
</div>
</div>
</div>
@ -160,7 +161,7 @@
Form validation failed, please ensure the form is filled correctly.
</div>
<div *ngSwitchCase="'send_failed'" class="alert alert-danger" role="alert">
Failed to send to server, please try again later.
{{submitStatusError}}
</div>
</div>
</div>

View file

@ -1,8 +1,7 @@
import { Component, OnInit, ViewChild } from '@angular/core';
import { Validators, FormBuilder, FormGroup } from '@angular/forms';
import { Http, Response } from '@angular/http';
import { ApiService } from '../providers/api-service';
import 'rxjs/add/operator/map';
@Component({
templateUrl: 'account-edit.component.html',
@ -14,9 +13,9 @@ export class AccountEditComponent implements OnInit {
accountType: any;
// @ViewChild('fileInput') fileInput;
submitStatus: any;
submitStatusError = 'Error received, please try again.';
constructor(
private http: Http,
private formBuilder: FormBuilder,
private api: ApiService,
) {
@ -71,7 +70,7 @@ export class AccountEditComponent implements OnInit {
console.log(this.settingForm.valid);
if (!this.settingForm.valid && !this.settingOrganisationForm.valid) {
console.log('Not Valid!');
this.submitStatus = "validation_failed";
this.submitStatus = 'validation_failed';
console.log(this.submitStatus);
return;
}
@ -90,7 +89,7 @@ export class AccountEditComponent implements OnInit {
const submitData = {
email: settingForm.email,
postcode: settingForm.postcode,
postcode: settingForm.postcode.toUpperCase(),
password: settingForm.password,
new_password: settingForm.new_password,
name: settingOrganisationForm.name,
@ -106,14 +105,17 @@ export class AccountEditComponent implements OnInit {
.accountEditUpdate(submitData)
.subscribe(
result => {
console.log('data submitted!');
this.submitStatus = "success";
console.log(this.submitStatus);
this.submitStatus = 'success';
},
error => {
console.log( error._body );
this.submitStatus = "send_failed";
console.log(this.submitStatus);
console.log('Edit Error');
console.log(error);
try {
this.submitStatusError = '"' + error.error.error + '" Error, ' + error.error.message;
} catch (e) {
this.submitStatusError = 'There was a server error, please try again later.';
}
this.submitStatus = 'send_failed';
}
);
}
@ -122,8 +124,7 @@ export class AccountEditComponent implements OnInit {
console.log(this.settingForm.valid);
if (!this.settingForm.valid && !this.settingCustomerForm.valid) {
console.log('Not Valid!');
this.submitStatus = "validation_failed";
console.log(this.submitStatus);
this.submitStatus = 'validation_failed';
return;
}
@ -141,7 +142,7 @@ export class AccountEditComponent implements OnInit {
const submitData = {
email: settingForm.email,
postcode: settingForm.postcode,
postcode: settingForm.postcode.toUpperCase(),
password: settingForm.password,
new_password: settingForm.new_password,
full_name: settingCustomerForm.full_name,
@ -155,12 +156,21 @@ export class AccountEditComponent implements OnInit {
.subscribe(
result => {
console.log('data submitted!');
this.submitStatus = "success";
this.submitStatus = 'success';
console.log(this.submitStatus);
},
error => {
console.log( error._body );
this.submitStatus = "send_failed";
console.log('Edit Error');
console.log(error);
try {
console.log(error.error);
const jsonError = error.json();
console.log('boop');
this.submitStatusError = '"' + jsonError.error + '" Error, ' + jsonError.message;
} catch (e) {
this.submitStatusError = 'There was a server error, please try again later.';
}
this.submitStatus = 'send_failed';
console.log(this.submitStatus);
}
);

View file

@ -24,6 +24,74 @@
<span class="help-block">Enter the amount spent, such as 5.35 for £5.35.</span>
</div>
</div>
<div class="form-group row">
<label class="col-md-3 form-control-label" for="text-input">Essential Purchase</label>
<div class="col-md-9">
<div class="input-group">
<input type="checkbox" class="mr-auto" [(ngModel)]="essentialPurchase" (ngModelChange)="transactionFormValidate()">
<span class="help-block">Tick if the purchase is deemed an essential purchase for budgeting purposes.</span>
</div>
</div>
</div>
<div class="form-group row">
<label class="col-md-3 form-control-label" for="text-input">Recurring Purchase</label>
<div class="col-md-9">
<div class="input-group">
<input type="checkbox" class="mr-auto" [(ngModel)]="recurringPurchase" (ngModelChange)="transactionFormValidate()">
<span class="help-block">Tick if the purchase frequently recurs, such as monthly.</span>
</div>
</div>
</div>
<div *ngIf="recurringPurchase" class="form-group row">
<label class="col-md-3 form-control-label" for="text-input"><strong>Recurring Period</strong></label>
<div class="col-md-9">
<div class="row">
<div class="col-md-6 btn-group-vertical">
<label class="btn btn-secondary mb-0" [class.active]="recurringType == 'daily'">
<input value="daily" type="radio" name="radios" style="display:none;" [(ngModel)]="recurringType" (ngModelChange)="transactionFormValidate()">Daily
</label>
<label class="btn btn-secondary mb-0" [class.active]="recurringType == 'weekly'">
<input value="weekly" type="radio" name="radios" style="display:none;" [(ngModel)]="recurringType" (ngModelChange)="transactionFormValidate()">Weekly
</label>
<label class="btn btn-secondary mb-0" [class.active]="recurringType == 'fortnightly'">
<input value="fortnightly" type="radio" name="radios" style="display:none;" [(ngModel)]="recurringType" (ngModelChange)="transactionFormValidate()">Fortnightly
</label>
<label class="btn btn-secondary mb-0" [class.active]="recurringType == 'monthly'">
<input value="monthly" type="radio" name="radios" style="display:none;" [(ngModel)]="recurringType" (ngModelChange)="transactionFormValidate()">Monthly
</label>
<label class="btn btn-secondary mb-0" [class.active]="recurringType == 'quarterly'">
<input value="quarterly" type="radio" name="radios" style="display:none;" [(ngModel)]="recurringType" (ngModelChange)="transactionFormValidate()">Quarterly
</label>
<label class="btn btn-secondary mb-0" [class.active]="recurringType == 'yearly'">
<input value="yearly" type="radio" name="radios" style="display:none;" [(ngModel)]="recurringType" (ngModelChange)="transactionFormValidate()">Yearly
</label>
</div>
</div>
<span class="help-block">Please give the period of time the purchase will recur from "Time of Transaction".</span>
</div>
</div>
<div class="form-group row">
<label class="col-md-3 form-control-label" for="text-input">Budget Type</label>
<div class="col-md-9">
<div class="row">
<div class="col-md-6 btn-group-vertical">
<label class="btn btn-secondary mb-0" [class.active]="categoryId == null">
<input value="" type="radio" name="radios" style="display:none;" [(ngModel)]="categoryId">Uncategorised
</label>
<label *ngFor="let category of leftCategoryList" class="btn btn-secondary mb-0" [class.active]="categoryId == category">
<input [value]="category" type="radio" name="radios" style="display:none;" [(ngModel)]="categoryId">{{ categoryList[category] }}
</label>
</div>
<div class="col-md-6 btn-group-vertical">
<label *ngFor="let category2 of rightCategoryList" class="btn btn-secondary mb-0" [class.active]=" categoryId == category2">
<input [value]="category2" type="radio" name="radios" style="display:none;" [(ngModel)]="categoryId">{{ categoryList[category2] }}
</label>
</div>
</div>
<span class="help-block"><strong>Optional:</strong> Choose the Budget Type for the majority of the purchase.</span>
</div>
</div>
<div class="form-group row">
<label class="col-md-3 form-control-label" for="text-input"><strong>Organisation Name</strong></label>
<div class="col-md-9">
@ -31,24 +99,24 @@
<span class="help-block">Enter the name of the organisation money was spent. Choose existing ones from below or if not found, enter the details below.</span>
</div>
</div>
<org-table *ngIf="storeList != null" [orgList]="storeList" (onClick)="addStore($event)"></org-table>
<org-table *ngIf="storeList.length > 0" [orgList]="storeList" (onClick)="addStore($event)"></org-table>
<div *ngIf="showAddStore">
<div class="form-group row">
<label class="col-md-3 form-control-label" for="text-input"><strong>Organisation Street Name</strong></label>
<label class="col-md-3 form-control-label" for="text-input">Organisation Street Name</label>
<div class="col-md-9">
<input type="text" class="form-control" placeholder="Which Street?" [(ngModel)]="submitOrg.street_name" (ngModelChange)="transactionFormValidate()">
<span class="help-block">Enter the street name where the organisation is located at.</span>
</div>
</div>
<div class="form-group row">
<label class="col-md-3 form-control-label" for="text-input"><strong>Organisation Town</strong></label>
<label class="col-md-3 form-control-label" for="text-input">Organisation Town</label>
<div class="col-md-9">
<input type="text" class="form-control" placeholder="Which Town?" [(ngModel)]="submitOrg.town" (ngModelChange)="transactionFormValidate()">
<span class="help-block">Enter the name of the town where the organisation is located at.</span>
</div>
</div>
<div class="form-group row">
<label class="col-md-3 form-control-label" for="text-input"><strong>Organisation Postcode</strong></label>
<label class="col-md-3 form-control-label" for="text-input">Organisation Postcode</label>
<div class="col-md-9">
<input type="text" class="form-control" placeholder="Postcode if known" [(ngModel)]="submitOrg.postcode" (ngModelChange)="transactionFormValidate()">
<span class="help-block">Enter the postcode where the organisation is located at.</span>
@ -72,7 +140,7 @@
<button type="submit" (click)="postTransaction()" [disabled]="transactionFormInvalid" class="btn btn-sm btn-primary"><i class="fa fa-dot-circle-o"></i> Submit</button>
</div>
</div>
<div *ngIf="false" class="card">
<div *ngIf="accountType == 'organisation'" class="card">
<div class="card-header">
<strong>Profile & Payroll Data</strong>
<small>Required Data marked in <strong>bold</strong>.</small>
@ -89,14 +157,14 @@
<div class="form-group row">
<label class="col-md-3 form-control-label" for="text-input"><strong>Total amount of Employees</strong></label>
<div class="col-md-9">
<input type="number" class="form-control" formControlName="employee_amount" placeholder="0">
<input type="number" class="form-control" formControlName="employee_amount" placeholder="0" min="0">
<span class="help-block">Enter the amount of employees the organisation has for the entry month.</span>
</div>
</div>
<div class="form-group row">
<label class="col-md-3 form-control-label" for="text-input"><strong>Total amount of local Employees</strong></label>
<div class="col-md-9">
<input type="number" class="form-control" formControlName="local_employee_amount" placeholder="0">
<input type="number" class="form-control" formControlName="local_employee_amount" placeholder="0" min="0">
<span class="help-block">Enter the amount of employees that live locally to the organisation for the entry month.</span>
</div>
</div>

View file

@ -1,15 +1,14 @@
import { Component, OnInit } from '@angular/core';
import { Validators, FormBuilder, FormGroup } from '@angular/forms';
import { Http, Response } from '@angular/http';
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',
})
export class AddDataComponent {
export class AddDataComponent implements OnInit {
payrollForm: FormGroup;
singleSupplierForm: FormGroup;
employeeForm: FormGroup;
@ -18,7 +17,7 @@ export class AddDataComponent {
singleSupplierFormStatus: any;
employeeFormStatus: any;
transactionFormStatus: any;
transactionFormStatusError: string = 'Error received, please try again.';
transactionFormStatusError = 'Error received, please try again.';
accountType: any;
submitOrg = {
@ -31,30 +30,37 @@ export class AddDataComponent {
organisationTown: string;
organisationPostcode: string;
amount: number;
essentialPurchase = false;
recurringPurchase = false;
recurringType: string;
transactionAdditionType = 1;
storeList;
storeList = [];
showAddStore = false;
submitReceipt = false;
transactionFormInvalid = true;
myDate: any;
minDate: any;
categoryList: any;
categoryIdList: any;
leftCategoryList: number[] = [];
rightCategoryList: string[] = [];
categoryId: number;
constructor(
private http: Http,
private formBuilder: FormBuilder,
private api: ApiService,
) {
private formBuilder: FormBuilder,
private api: ApiService,
) {
this.payrollForm = this.formBuilder.group({
entry_period: ['', [Validators.required]],
employee_amount: ['', [Validators.required]],
local_employee_amount: ['', [Validators.required]],
gross_payroll: ['', [Validators.required]],
entry_period: ['', [Validators.required]],
employee_amount: ['', [Validators.required]],
local_employee_amount: ['', [Validators.required]],
gross_payroll: ['', [Validators.required]],
payroll_income_tax: ['', [Validators.required]],
payroll_employee_ni: ['', [Validators.required]],
payroll_employer_ni: ['', [Validators.required]],
payroll_total_pension: ['', [Validators.required]],
payroll_other_benefit: ['', [Validators.required]],
});
});
this.employeeForm = this.formBuilder.group({
entry_period: ['', [Validators.required]],
employee_no: ['', [Validators.required]],
@ -63,9 +69,20 @@ export class AddDataComponent {
employee_ni: ['', [Validators.required]],
employee_pension: ['', [Validators.required]],
employee_other_benefit: ['', [Validators.required]],
});
});
this.myDate = moment().format('YYYY-MM-DD[T]HH:mm');
// this.myDate = new Date().toISOString().slice(0, 16);
this.api.categoryList().subscribe(
result => {
this.categoryList = result.categories;
this.categoryIdList = Object.keys(this.categoryList);
this.setCategoryList(this.categoryIdList);
},
error => {
console.log('Retrieval Error');
console.log( error._body );
}
);
}
ngOnInit(): void {
@ -73,13 +90,19 @@ export class AddDataComponent {
this.accountType = localStorage.getItem('usertype');
}
getMinDate(){
private setCategoryList(data: any) {
let halfLength = Math.floor(data.length / 2);
this.leftCategoryList = data.splice(0, halfLength);
this.rightCategoryList = data;
}
getMinDate() {
// gets the April 1st date of the current year
let aprilDate = moment().month(3).date(1);
let now = moment();
const aprilDate = moment().month(3).date(1);
const now = moment();
// Checks if current time is before April 1st, if so returns true
let beforeApril = now.isBefore(aprilDate);
if ( beforeApril == 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');
@ -88,16 +111,16 @@ export class AddDataComponent {
initializeItems() {
// Dont bother searching for an empty or undefined string
if ( this.submitOrg.name == '' ) {
if ( this.submitOrg.name === '' ) {
return;
}
var searchData = {
const searchData = {
search_name: this.submitOrg.name,
};
this.api.search(searchData).subscribe(
data => {
if(data.validated.length > 0) {
if (data.validated.length > 0) {
this.storeList = data.validated;
this.showAddStore = false;
this.transactionAdditionType = 1;
@ -107,8 +130,8 @@ export class AddDataComponent {
this.transactionAdditionType = 2;
}
// handle the case when the storelist is empty
if(this.storeList.length < 1 ) {
this.storeList = null;
if (this.storeList.length < 1) {
this.storeList = [];
this.showAddStore = true;
this.transactionAdditionType = 3;
}
@ -120,7 +143,7 @@ export class AddDataComponent {
}
// if user select a item from the list
addStore(store){
addStore(store) {
this.submitOrg = store;
this.transactionFormValidate();
this.organisationId = store.id;
@ -132,45 +155,53 @@ export class AddDataComponent {
this.initializeItems();
// set val to the value of the searchbar
let val = ev.target.value;
const val = ev.target.value;
// Filter the store list so search seems quicker
if (val && val.trim() != '' && this.storeList != null) {
if (val && val.trim() !== '' && this.storeList.length > 0) {
this.storeList = this.storeList.filter(
(item) => {
return ( item.name.toLowerCase().indexOf( val.toLowerCase() ) > -1 );
}
)
);
}
// if nothing is found
if(!this.storeList === null){
if (!this.storeList === null) {
// display add new store button
this.showAddStore = true;
}
}
transactionFormValidate() {
if( this.submitOrg.name.length == 0 &&
this.amount == 0 ) {
this.transactionFormInvalid = true;
}else{
this.transactionFormStatus = null;
if (this.submitOrg.name.length &&
this.amount &&
(this.recurringPurchase &&
this.recurringType ||
!this.recurringPurchase &&
!this.recurringType)) {
this.transactionFormInvalid = false;
} else {
this.transactionFormInvalid = true;
}
}
public postTransaction() {
var myParams: any;
let myParams: any;
let purchaseTime: string;
purchaseTime = moment(this.myDate, 'YYYY-MM-DD[T]HH:mm').local().format('YYYY-MM-DD[T]HH:mm:ss.SSSZ');
switch(this.transactionAdditionType){
switch (this.transactionAdditionType) {
case 1:
myParams = {
transaction_type : this.transactionAdditionType,
transaction_value : this.amount,
purchase_time : purchaseTime,
organisation_id : this.organisationId,
category : this.categoryId,
essential : this.essentialPurchase,
recurring : this.recurringType,
};
break;
case 2:
@ -179,6 +210,9 @@ export class AddDataComponent {
transaction_value : this.amount,
purchase_time : purchaseTime,
organisation_id : this.organisationId,
category : this.categoryId,
essential : this.essentialPurchase,
recurring : this.recurringType,
};
break;
case 3:
@ -190,6 +224,9 @@ export class AddDataComponent {
street_name : this.submitOrg.street_name,
town : this.submitOrg.town,
postcode : this.submitOrg.postcode,
category : this.categoryId,
essential : this.essentialPurchase,
recurring : this.recurringType,
};
break;
}
@ -199,32 +236,23 @@ export class AddDataComponent {
.upload(myParams)
.subscribe(
result => {
if ( result.success == true ) {
console.log('Successful Upload');
console.log(result);
this.transactionFormStatus = "success";
console.log(this.transactionFormStatus);
if ( result.success === true ) {
this.transactionFormStatus = 'success';
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);
this.transactionFormStatus = 'send_failed';
}
},
error => {
console.log('Upload Error');
console.log(error);
try {
console.log(error.error);
let jsonError = error.json();
console.log("boop");
this.transactionFormStatusError = '"' + jsonError.error + '" Error, ' + jsonError.message;
} catch(e) {
this.transactionFormStatusError = '"' + error.error.error + '" Error, ' + error.error.message;
} catch (e) {
this.transactionFormStatusError = 'There was a server error, please try again later.';
}
this.transactionFormStatus = "send_failed";
console.log(this.transactionFormStatus);
this.transactionFormStatus = 'send_failed';
}
);
}
@ -236,64 +264,57 @@ export class AddDataComponent {
town: '',
postcode: '',
};
this.storeList = null;
this.storeList = [];
this.amount = null;
this.transactionFormInvalid = true;
this.showAddStore = false;
this.essentialPurchase = false;
this.recurringPurchase = false;
this.recurringType = null;
}
onSubmitPayroll() {
console.log(this.payrollForm.value);
console.log(this.payrollForm.value);
this.api
this.api
.orgPayroll(this.payrollForm.value)
.subscribe(
result => {
console.log('data submitted!');
this.payrollFormStatus = "success";
console.log(this.payrollFormStatus);
this.payrollFormStatus = 'success';
},
error => {
console.log( error._body );
this.payrollFormStatus = "send_failed";
console.log(this.payrollFormStatus);
this.payrollFormStatus = 'send_failed';
}
);
}
onSubmitSingleSupplier() {
console.log(this.singleSupplierForm.value);
console.log(this.singleSupplierForm.value);
this.api
this.api
.orgSupplier(this.singleSupplierForm.value)
.subscribe(
result => {
console.log('data submitted!');
this.singleSupplierFormStatus = "success";
console.log(this.singleSupplierFormStatus);
this.singleSupplierFormStatus = 'success';
},
error => {
console.log( error._body );
this.singleSupplierFormStatus = "send_failed";
console.log(this.singleSupplierFormStatus);
this.singleSupplierFormStatus = 'send_failed';
}
);
}
onSubmitEmployee() {
console.log(this.employeeForm.value);
console.log(this.employeeForm.value);
this.api
this.api
.orgEmployee(this.employeeForm.value)
.subscribe(
result => {
console.log('data submitted!');
this.employeeFormStatus = "success";
console.log(this.employeeFormStatus);
this.employeeFormStatus = 'success';
},
error => {
console.log( error._body );
this.employeeFormStatus = "send_failed";
console.log(this.employeeFormStatus);
this.employeeFormStatus = 'send_failed';
}
);
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,9 +1,12 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { ChartsModule } from 'ng2-charts/ng2-charts';
import { ChartsModule } from 'ng2-charts';
import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
import { NgxPaginationModule } from 'ngx-pagination';
import { AgmCoreModule, GoogleMapsAPIWrapper } from '@agm/core';
import { AgmMarkerClustererModule } from '@agm/markerclusterer';
import { ModalModule } from 'ngx-bootstrap/modal';
import { CurrencyPipe } from '@angular/common';
@ -13,15 +16,36 @@ import { AccountEditComponent } from './account-edit.component';
import { AddDataComponent } from './add-data.component';
import { FeedbackComponent } from './feedback.component';
import { TransactionLogComponent } from './transaction-log.component';
import { CategoryMonthComponent } from './category-month.component';
import { PayrollLogComponent } from './payroll-log.component';
import { SuppliersComponent } from './suppliers.component';
import { MoreStuffComponent } from './more-graphs-and-tables.component';
import { LeaderboardComponent } from './leaderboard.component';
import { MapComponent } from './map.component';
import { TrailMapComponent } from './trail-map.component';
import { GraphWidget } from '../widgets/graph-widget.component';
import { OrgBarSnippetComponent } from '../snippets/org-snippet-bar.component';
import { CustBarSnippetComponent } from '../snippets/cust-snippet-bar.component';
import { GraphPanel } from '../panels/graph-panel.component';
import { BubbleChartComponent } from '../panels/bubble-panel.component';
import { PiePanel } from '../panels/pie-panel.component';
import { OrgPiePanel } from '../panels/org-pie-panel.component';
import { DashboardRoutingModule } from './dashboard.routing';
import { OrgResultComponent } from '../shared/org-result.component';
import { OrgTableComponent } from '../shared/org-table.component';
import { RecurResultComponent } from '../shared/recur-result.component';
import { RecurTableComponent } from '../shared/recur-table.component';
import { TransactionResultComponent } from '../shared/transaction-result.component';
import { SupplierResultComponent } from '../shared/supplier-result.component';
import { WardResultComponent } from '../shared/ward-result.component';
import { MetaTypeResultComponent } from '../shared/meta-type-result.component';
import { PayrollResultComponent } from '../shared/payroll-result.component';
import { LeaderboardResultComponent } from '../shared/leaderboard-result.component';
// API key env variable import
import { environment } from '../../environments/environment';
@NgModule({
imports: [
@ -30,9 +54,14 @@ import { TransactionResultComponent } from '../shared/transaction-result.compone
FormsModule,
ReactiveFormsModule,
ChartsModule,
AgmCoreModule.forRoot({
apiKey: environment.mapApiKey
}),
AgmMarkerClustererModule,
BsDropdownModule,
NgxPaginationModule,
DashboardRoutingModule,
ModalModule.forRoot(),
],
declarations: [
DashboardComponent,
@ -41,15 +70,34 @@ import { TransactionResultComponent } from '../shared/transaction-result.compone
AddDataComponent,
OrgResultComponent,
OrgTableComponent,
RecurResultComponent,
RecurTableComponent,
TransactionLogComponent,
CategoryMonthComponent,
TransactionResultComponent,
SupplierResultComponent,
WardResultComponent,
MetaTypeResultComponent,
PayrollLogComponent,
PayrollResultComponent,
LeaderboardComponent,
LeaderboardResultComponent,
MapComponent,
TrailMapComponent,
FeedbackComponent,
GraphWidget,
OrgBarSnippetComponent,
CustBarSnippetComponent,
GraphPanel,
PiePanel,
OrgPiePanel,
BubbleChartComponent,
SuppliersComponent,
MoreStuffComponent,
],
providers: [
CurrencyPipe
CurrencyPipe,
GoogleMapsAPIWrapper,
],
})
export class DashboardModule { }

View file

@ -12,6 +12,13 @@ import { AccountEditComponent } from './account-edit.component';
import { AddDataComponent } from './add-data.component';
import { FeedbackComponent } from './feedback.component';
import { TransactionLogComponent } from './transaction-log.component';
import { CategoryMonthComponent } from './category-month.component';
import { PayrollLogComponent } from './payroll-log.component';
import { LeaderboardComponent } from './leaderboard.component';
import { MapComponent } from './map.component';
import { TrailMapComponent } from './trail-map.component';
import { MoreStuffComponent } from './more-graphs-and-tables.component';
import { SuppliersComponent } from './suppliers.component';
// Using child path to allow for FullLayout theming
const routes: Routes = [
@ -36,22 +43,59 @@ const routes: Routes = [
{
path: 'account-edit',
component: AccountEditComponent,
data: { title: 'Leaderboards' },
data: { title: 'Edit Account' },
},
{
path: 'add-data',
component: AddDataComponent,
data: { title: 'Add Transaction' },
},
{
path: 'leaderboard',
component: LeaderboardComponent,
data: { title: 'Leaderboards' },
canActivate: [CustomerGuard],
},
{
path: 'transaction-log',
component: TransactionLogComponent,
data: { title: 'Transaction Log' },
},
{
path: 'category-month',
component: CategoryMonthComponent,
data: { title: 'Budget' },
},
{
path: 'map',
component: MapComponent,
data: { title: 'Purchase Map' },
},
{
path: 'story-trail',
component: TrailMapComponent,
data: { title: 'Story Trail' },
},
{
path: 'payroll-log',
component: PayrollLogComponent,
data: { title: 'Payroll Log' },
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,24 +1,22 @@
import { Component, OnInit } from '@angular/core';
import { Validators, FormBuilder, FormGroup } from '@angular/forms';
import { Http, Response } from '@angular/http';
import { ApiService } from '../providers/api-service';
import 'rxjs/add/operator/map';
@Component({
templateUrl: 'feedback.component.html',
})
export class FeedbackComponent {
export class FeedbackComponent implements OnInit {
feedbackForm: FormGroup;
loggedInEmail: string;
noEmail: boolean = false;
noEmail = false;
username: any;
feedbackFormStatus: any;
feedbackFormStatusError: string = 'Error received, please try again.';
feedbackFormStatusError = 'Error received, please try again.';
constructor(
private http: Http,
private formBuilder: FormBuilder,
private api: ApiService,
private formBuilder: FormBuilder,
private api: ApiService,
) {
this.feedbackForm = this.formBuilder.group({
email: ['', [Validators.required]],
@ -28,7 +26,7 @@ export class FeedbackComponent {
ngOnInit(): void {
if(localStorage.getItem('email')) {
if (localStorage.getItem('email')) {
this.loggedInEmail = localStorage.getItem('email');
}
console.log('loggedInEmail: ' + this.loggedInEmail);
@ -55,34 +53,26 @@ export class FeedbackComponent {
.feedback(this.feedbackForm.value)
.subscribe(
result => {
if ( result.success == true ) {
if ( result.success === true ) {
console.log('Successful Upload');
console.log(result);
this.feedbackFormStatus = "success";
console.log(this.feedbackFormStatus);
this.feedbackFormStatus = 'success';
this.feedbackForm.patchValue({
feedbacktext: '',
});
} else {
console.log('Upload Error');
this.feedbackFormStatusError = JSON.stringify(result.status) + 'Error, ' + JSON.stringify(result.message);
this.feedbackFormStatus = "send_failed";
console.log(this.feedbackFormStatus);
this.feedbackFormStatus = 'send_failed';
}
},
error => {
console.log('Upload Error');
console.log(error);
try {
console.log(error.error);
let jsonError = error.json();
console.log("boop");
this.feedbackFormStatusError = '"' + jsonError.error + '" Error, ' + jsonError.message;
} catch(e) {
this.feedbackFormStatusError = '"' + error.error.error + '" Error, ' + error.error.message;
} catch (e) {
this.feedbackFormStatusError = 'There was a server error, please try again later.';
}
this.feedbackFormStatus = "send_failed";
console.log(this.feedbackFormStatus);
this.feedbackFormStatus = 'send_failed';
}
);
}

View file

@ -0,0 +1,61 @@
<div class="animated fadeIn">
<div class="row">
<div class="col-lg-12">
<div class="card">
<div class="card-header">
<strong>Leaderboard</strong>
<small>By default this loads the page with your position.</small>
</div>
<div *ngIf="!noLeaderboardList" class="card-block">
<div class="input-group mb-3">
<select type="text" [(ngModel)]="listType" (ngModelChange)="changeLeaderboard($event)">
<option value="daily_total">Yesterday Total</option>
<option value="daily_count">Yesterday Count</option>
<option value="weekly_total" selected>Last Week Total</option>
<option value="weekly_count">Last Week Count</option>
<option value="monthly_total">Last Month Total</option>
<option value="monthly_count">Last Month Count</option>
<option value="all_time_total">All Time Total</option>
<option value="all_time_count">All Time Count</option>
</select>
</div>
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Position</th>
<th>Name</th>
<th class="js-sort-number">Gross amount</th>
</tr>
</thead>
<tbody>
<tr leaderboard-result *ngFor="let leaderboard of leaderboardList | paginate: paginateConfig" [leaderboard]="leaderboard" [listType]="listType"></tr>
</tbody>
</table>
<pagination-template #p="paginationApi"
[id]="paginateConfig.id"
(pageChange)="loadLeaderboard($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="noLeaderboardList" class="card-block">
No Leaderboard available.
</div>
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,108 @@
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { ApiService } from '../providers/api-service';
// import { PaginatePipe } from 'ngx-pagination';
import {PaginationInstance} from 'ngx-pagination';
// import { PaginationControlsComponent } from 'ngx-pagination';
// import { PaginationControlsDirective } from 'ngx-pagination';
// import { TransactionResultComponent } from '../shared/transaction-result.component';
@Component({
templateUrl: 'leaderboard.component.html',
})
export class LeaderboardComponent implements OnInit {
leaderboardList;
noLeaderboardList = false;
public p: any;
leaderboardData: Array<any>;
currentPos: number;
listType: any = 'weekly_total';
public paginateConfig: PaginationInstance = {
id: 'leadpaginate',
itemsPerPage: 20,
currentPage: 1,
totalItems: 0
};
constructor(
private api: ApiService,
) { }
ngOnInit(): void {
this.loadLeaderboard(0);
}
// private fetchLeaderboard() {
// this.peopleService.leaderboard(this.listType)
// .subscribe(
// result => {
// this.leaderboardData = result.leaderboard;
// this.currentPos = result.user_position;
// }
// );
// }
public changeLeaderboard(event) {
this.loadLeaderboard(0);
}
loadLeaderboard(leadPage: number) {
console.log(leadPage, this.listType);
this.api.leaderboard_fetch(this.listType,leadPage).subscribe(
result => {
if (result.leaderboard.length > 0) {
this.leaderboardList = result.leaderboard;
// TODO Rename in server
this.paginateConfig.totalItems = result.count;
this.paginateConfig.currentPage = result.page;
this.noLeaderboardList = false;
} else {
// handle the case when the leaderboardList is empty
this.leaderboardList = null;
this.noLeaderboardList = true;
}
},
error => {
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
// // be hightlighted
// public getClass(item) {
// if( item.position < 4 ) {
// return "topThree";
// } else if( item.position == this.currentPos ) {
// return "user";
// }
// return "otherUsers";
// }
//
// // show changes by using icon, trending up and trending down or no trend.
// public getTrendIcon(item){
// if( item.trend < 0 ){
// return "md-trending-up";
// } else if( item.trend > 0 ){
// return "md-trending-down";
// }
// return "md-remove";
// }
//
// // need to merge this function with getIcon
// // this function shows different icon color based on the direction of the position shifted
// public getTrendIconColor(item){
// if( item.trend < 0 ) {
// return "secondary";
// } else if( item.trend > 0 ){
// return "danger";
// }
// return "dark";
// }
}

View file

@ -0,0 +1,71 @@
<div class="animated fadeIn">
<div class="row">
<div class="col-lg-12">
<div class="card">
<div class="card-header">
<strong>Purchase Map</strong>
<small>Click a marker to get location details.</small>
</div>
<div class="modal fade" bsModal #statusModal="bs-modal" [config]="{backdrop: false, animated: false}"
tabindex="-1" role="dialog" aria-labelledby="mySmallModalLabel" aria-hidden="true">
<div class="modal-dialog modal-sm h-100 d-flex flex-column justify-content-center my-0">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title pull-left">Status</h4>
<button type="button" class="close pull-right" aria-label="Close" (click)="statusModal.hide()">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div [ngSwitch]="dataReceived">
<div *ngSwitchCase="'no'">
<div class="alert alert-danger" role="alert">
No map data received, check your connection.
</div>
</div>
<div *ngSwitchCase="'loading'">
<div class="alert alert-warning" role="alert">
Map loading, please wait.
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- this creates a google map on the page with the given lat/lng from -->
<!-- the component as the initial center of the map: -->
<agm-map (mapReady)="onMapReady($event)"
[latitude]="lat"
[longitude]="lng"
[zoom]="zoom"
(idle)="viewBoundsChanged()">
<agm-marker-cluster maxZoom="13">
<agm-marker
*ngFor="let m of markers"
[latitude]="m.latitude"
[longitude]="m.longitude"
[label]="m.name[0]"
[openInfoWindow]="false"
(markerClick)="onMarkerClick(m, template)">
</agm-marker>
</agm-marker-cluster>
</agm-map>
<ng-template #template>
<div class="modal-header d-flex justify-content-between">
<img src="assets/img/logo.png" class="w-15" alt="lis logo"><h4 class="modal-title">{{clickedMarker.name}}</h4>
<button type="button" class="close pull-right" aria-label="Close" (click)="modalRef.hide()">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body text-right">
<h5>Located at:</h5>
<h6>{{clickedMarker.street_name}}</h6>
<h6>{{clickedMarker.town}}</h6>
<h6>{{clickedMarker.postcode}}</h6>
</div>
</ng-template>
</div>
</div>
</div><!--/.row-->
</div>

View file

@ -0,0 +1,83 @@
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';
@Component({
templateUrl: 'map.component.html',
})
export class MapComponent implements OnInit, AfterViewInit {
@ViewChild('statusModal', { static: true }) myStatusModal: ModalDirective;
lat: number = 54.0466;
lng: number = -2.8007;
zoom: number = 12;
public modalRef: BsModalRef;
clickedMarker: any;
dataReceived: string = 'loading';
markers: Array<{latitude: number, longitude: number, name: string}>;
map: any;
constructor(
private api: ApiService,
private modalService: BsModalService,
) { }
ngOnInit(): void { }
ngAfterViewInit() {
this.myStatusModal.show();
}
public onMapReady(map: any) {
this.map = map;
}
openModal(template: TemplateRef<any>) {
this.modalRef = this.modalService.show(template);
}
public onMarkerClick(clickedMarker, template: TemplateRef<any>) {
console.log(clickedMarker);
this.clickedMarker = clickedMarker;
this.openModal(template);
}
public viewBoundsChanged() {
console.log("finding bounds");
const resp = this.map.getBounds();
console.log("found bounds");
console.log(resp.getNorthEast().lat());
console.log(resp.getNorthEast().lng());
console.log(resp.getSouthWest().lat());
console.log(resp.getSouthWest().lng());
const mapData = {
north_east: {
latitude: resp.getNorthEast().lat(),
longitude: resp.getNorthEast().lng()
},
south_west: {
latitude: resp.getSouthWest().lat(),
longitude: resp.getSouthWest().lng()
},
}
this.api.getMapData(mapData).subscribe(
result => {
this.myStatusModal.hide();
this.markers = result.suppliers;
},
error => {
this.dataReceived = 'no';
console.log('Retrieval Error');
console.log( error._body );
}
);
}
}

View file

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

View file

@ -0,0 +1,335 @@
import {Component, OnInit, Input, Output, EventEmitter, ViewChild} from '@angular/core';
import {ApiService} from '../providers/api-service';
import {BaseChartDirective} from 'ng2-charts';
import {CurrencyPipe} from '@angular/common';
import {ChartType} from "chart.js";
import * as moment from 'moment';
@Component({
templateUrl: 'more-graphs-and-tables.component.html',
})
export class MoreStuffComponent implements OnInit {
@Output() public onClick = new EventEmitter();
@Input() public categories: any;
// Global Filter Setup
filterFrom: any;
filterTo: any;
isBubbleChartLoaded: boolean = false;
isSupplierChartLoaded: boolean = false;
wardList: any;
wardListAvailable = false;
metaTypeList: any;
metaTypeListAvailable = false;
constructor(
private api: ApiService,
private currencyPipe: CurrencyPipe,
) {
let now = moment();
this.filterTo = now.format('YYYY-MM-DD');
now.subtract(1, 'months');
this.filterFrom = now.format('YYYY-MM-DD');
this.tableSummary();
}
ngOnInit(): void {
this.loadData();
}
public loadData() {
this.tableSummary();
this.loadYearSpend();
this.loadSupplierBubble();
this.loadSupplierHistory();
}
public showLegend = true;
/*
* Supplier Bubble Chart Setup
*/
private formatGraphData(data: any): any[] {
let graph_data = [];
data.data.map(item => {
graph_data.push({
t: item.date,
r: item.value > 1000000 ? (item.value / 200000) : (item.value / 100000) + 5,
supplier: item.seller,
y: item.count,
value: item.value,
count: item.count,
});
});
return graph_data;
}
private loadSupplierBubble() {
this.api.loadMiscUrl('organisation/external/supplier_count', {
from: this.filterFrom,
to: this.filterTo,
}).subscribe(
result => {
this.supplierBubbleChartData[0].data = this.formatGraphData(result);
this.isBubbleChartLoaded = true;
}
)
}
public supplierBubbleChartType: ChartType = 'bubble';
public supplierBubbleChartData: any[] = [
{
data: [],
label: ["Spend"],
borderColor: 'blue',
hoverBorderColor: 'black',
radius: 5,
},
];
public supplierBubbleChartLabels: string[] = [];
public supplierBubbleChartOptions: any = {
responsive: true,
scales: {
xAxes: [{
type: 'time',
time: {
unit: 'month'
},
scaleLabel: {
display: true,
labelString: 'Date'
}
}],
yAxes: [{
scaleLabel: {
display: true,
labelString: 'Number of purchases'
}
}]
},
tooltips: {
callbacks: {
label: (tooltip, data) => {
return this.bubbleTooltipCallback(tooltip, data);
},
},
},
};
private bubbleTooltipCallback(tooltipItem: any, data: any) {
let dataset = data.datasets[tooltipItem.datasetIndex];
let value = dataset.data[tooltipItem.index];
return `${value.supplier}: ${this.currencyPipe.transform(value.value, 'GBP', 'symbol', '1.2-2')} over ${value.count} purchases`;
}
private tableSummary() {
this.api.loadMiscUrl('organisation/external/lcc_tables', {
from: this.filterFrom,
to: this.filterTo,
}).subscribe(
result => {
this.wardList = result.wards;
this.metaTypeList = Object.keys(result.types).map(key => result.types[key]);
if (this.wardList) {
this.wardListAvailable = true;
}
if (this.metaTypeList) {
this.metaTypeListAvailable = true;
}
},
error => {
console.log('Retrieval Error');
console.log(error._body);
}
)
}
private loadYearSpend() {
this.api.loadMiscUrl('organisation/external/year_spend', {
from: this.filterFrom,
to: this.filterTo,
}).subscribe(
result => {
let value_data = [];
let count_data = [];
result.data.map(item => {
value_data.push({
t: item.date,
y: item.value,
});
count_data.push({
t: item.date,
y: item.count,
});
});
this.yearSpendChartData[0].data = value_data;
this.yearSpendChartData[1].data = count_data;
}
)
}
public yearSpendChartData: any[] = [
{
data: [],
label: ["Value £"],
fill: false,
borderColor: 'red',
hoverBackgroundColor: '#ffa1b5',
hoverBorderColor: 'red',
yAxisID: 'y-value',
},
{
data: [],
label: ["Count"],
fill: false,
borderColor: 'blue',
hoverBackgroundColor: '#52afed',
hoverBorderColor: 'blue',
yAxisID: 'y-count',
},
];
public yearSpendChartOptions: any = {
elements: {line: {tension: 0}},
responsive: true,
scales: {
xAxes: [{
type: 'time',
time: {
unit: 'month'
},
scaleLabel: {
display: true,
labelString: 'Date'
}
}],
yAxes: [
{id: 'y-value', position: 'left', beginAtZero: true, type: 'linear'},
{id: 'y-count', position: 'right', beginAtZero: true, type: 'linear'},
]
},
};
public yearSpendChartLabels: string[] = [];
public yearSpendChartType: ChartType = 'line';
randomData() {
return Math.random();
}
lineChartUpdate() {
this.loadYearSpend();
}
@ViewChild('supplierChart', { read: BaseChartDirective }) 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,49 @@
<div class="animated fadeIn">
<div class="row">
<div class="col-lg-12">
<div class="card">
<div class="card-header">
<strong>Log of Payroll submissions</strong>
<small>Sorted descending from submission date.</small>
</div>
<div *ngIf="!noPayrollList" class="card-block">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Entry Period</th>
<th>Gross Payroll</th>
<th>Employees</th>
</tr>
</thead>
<tbody>
<tr payroll-result *ngFor="let payroll of payrollList | paginate: paginateConfig" [payroll]="payroll"></tr>
</tbody>
</table>
<pagination-template #p="paginationApi"
[id]="paginateConfig.id"
(pageChange)="loadPayrolls($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="noPayrollList" class="card-block">
No Payroll data available.
</div>
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,75 @@
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { ApiService } from '../providers/api-service';
// import { PaginatePipe } from 'ngx-pagination';
import {PaginationInstance} from 'ngx-pagination';
// import { PaginationControlsComponent } from 'ngx-pagination';
// import { PaginationControlsDirective } from 'ngx-pagination';
// import { TransactionResultComponent } from '../shared/transaction-result.component';
import * as moment from 'moment';
@Component({
templateUrl: 'payroll-log.component.html',
})
export class PayrollLogComponent implements OnInit {
payrollList;
noPayrollList = true;
myDate: any;
minDate: any;
public paginateConfig: PaginationInstance = {
id: 'transpaginate',
itemsPerPage: 10,
currentPage: 1,
totalItems: 0
};
constructor(
private api: ApiService,
) {
this.myDate = moment().format('YYYY-MM-DD[T]HH:mm');
// this.myDate = new Date().toISOString().slice(0, 16);
}
ngOnInit(): void {
this.getMinDate();
this.loadPayrolls(1);
}
getMinDate(){
// gets the April 1st date of the current year
let aprilDate = moment().month(3).date(1);
let now = moment();
// Checks if current time is before April 1st, if so returns true
let 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');
}
}
loadPayrolls(logPage: number) {
console.log(logPage);
this.api.payrollList(logPage).subscribe(
result => {
if(result.payrolls.length > 0) {
this.payrollList = result.payrolls;
//TODO Rename in server
this.paginateConfig.totalItems = result.page_no;
this.paginateConfig.currentPage = logPage;
this.noPayrollList = false;
} else {
// handle the case when the payrollList is empty
this.payrollList = null;
this.noPayrollList = true;
}
},
error => {
console.log(error);
}
);
}
}

View file

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

View file

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

View file

@ -0,0 +1,104 @@
<div class="animated fadeIn">
<div class="row">
<div class="col-lg-12">
<div class="card">
<div class="card-header">
<strong>Story Trail</strong>
<div class="row">
<div class="col-12 col-sm-6 mb-3">
<small>Select a Story Trail to see all the businesses in that story on the map.<br>
Click an icon on the map to get more information.</small>
</div>
<div class="col-12 col-sm-6 order-12">
<button type="button" class="btn btn-outline-info btn-lg float-right" (click)="openModalAssoc(templateAssoc)">Select Story Trail</button>
</div>
</div>
<ng-template #templateAssoc>
<div class="modal-header">
<h4 class="modal-title pull-left">Select View</h4>
<button type="button" class="close pull-right" aria-label="Close" (click)="modalRef2.hide()">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="row">
<div class="col-12 col-sm-6 text-center">
<img src="assets/img/association/lis-logo.png" class="w-50" alt="lis logo"><br>
<button type="button" class="btn btn-success mt-3" (click)="modalRef2.hide(); assocMap = 'lis'">Lancaster Independent Story</button>
</div>
<div class="col-12 col-sm-6 text-center">
<img src="assets/img/association/esta-logo.png" class="w-50" alt="lis logo"><br>
<button type="button" class="btn btn-success mt-3" (click)="modalRef2.hide(); assocMap = 'esta'">ESTA Association</button>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger" (click)="modalRef2.hide()">Close</button>
</div>
</ng-template>
</div>
<div class="modal fade" bsModal #statusModal="bs-modal" [config]="{backdrop: false, animated: false}"
tabindex="-1" role="dialog" aria-labelledby="mySmallModalLabel" aria-hidden="true">
<div class="modal-dialog modal-sm h-100 d-flex flex-column justify-content-center my-0">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title pull-left">Status</h4>
<button type="button" class="close pull-right" aria-label="Close" (click)="statusModal.hide()">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div [ngSwitch]="dataReceived">
<div *ngSwitchCase="'no'">
<div class="alert alert-danger" role="alert">
No map data received, check your connection.
</div>
</div>
<div *ngSwitchCase="'loading'">
<div class="alert alert-warning" role="alert">
Map loading, please wait.
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- this creates a google map on the page with the given lat/lng from -->
<!-- the component as the initial center of the map: -->
<agm-map
(mapReady)="onMapReady($event)"
[latitude]="lat"
[longitude]="lng"
[zoom]="zoom"
(idle)="viewBoundsChanged()">
<agm-marker-cluster maxZoom="13">
<agm-marker
*ngFor="let m of markers"
[iconUrl]="'/assets/img/association/' + assocMap + '-map-pin.png'"
[latitude]="m.latitude"
[longitude]="m.longitude"
[openInfoWindow]="false"
(markerClick)="onMarkerClick(m, template)">
</agm-marker>
</agm-marker-cluster>
</agm-map>
<ng-template #template>
<div class="modal-header d-flex justify-content-between">
<img src="{{assocLogo}}" class="w-15" alt="lis logo"><h4 class="modal-title">{{clickedMarker.name}}</h4>
<button type="button" class="close pull-right" aria-label="Close" (click)="modalRef.hide()">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body text-right">
<h5>Located at:</h5>
<h6>{{clickedMarker.street_name}}</h6>
<h6>{{clickedMarker.town}}</h6>
<h6>{{clickedMarker.postcode}}</h6>
</div>
</ng-template>
</div>
</div>
</div><!--/.row-->
</div>

View file

@ -0,0 +1,93 @@
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';
@Component({
templateUrl: 'trail-map.component.html',
})
export class TrailMapComponent implements OnInit, AfterViewInit {
@ViewChild('statusModal', { static: true }) myStatusModal: ModalDirective;
lat: number = 54.0466;
lng: number = -2.8007;
zoom: number = 12;
public modalRef: BsModalRef;
public modalRef2: BsModalRef;
clickedMarker: any;
assocMap = 'lis';
assocLogo: string;
dataReceived: string = 'loading';
markers: Array<{latitude: number, longitude: number, name: string}>;
map: any;
constructor(
private api: ApiService,
private modalService: BsModalService,
) {
this.assocLogo = 'assets/img/association/' + this.assocMap + '-logo.png';
}
ngOnInit(): void { }
ngAfterViewInit() {
this.myStatusModal.show();
}
public onMapReady(map: any) {
this.map = map;
}
openModal(template: TemplateRef<any>) {
this.modalRef = this.modalService.show(template);
}
openModalAssoc(templateAssoc: TemplateRef<any>) {
this.modalRef2 = this.modalService.show(templateAssoc);
}
public onMarkerClick(clickedMarker, template: TemplateRef<any>) {
this.clickedMarker = clickedMarker;
this.assocLogo = 'assets/img/association/' + this.assocMap + '-logo.png';
this.openModal(template);
}
public viewBoundsChanged() {
console.log("finding bounds");
const resp = this.map.getBounds();
console.log("found bounds");
console.log(resp.getNorthEast().lat());
console.log(resp.getNorthEast().lng());
console.log(resp.getSouthWest().lat());
console.log(resp.getSouthWest().lng());
const mapData = {
north_east: {
latitude: resp.getNorthEast().lat(),
longitude: resp.getNorthEast().lng()
},
south_west: {
latitude: resp.getSouthWest().lat(),
longitude: resp.getSouthWest().lng()
},
association: this.assocMap,
}
this.api.getAssocData(mapData).subscribe(
result => {
this.myStatusModal.hide();
this.markers = result.locations;
},
error => {
this.dataReceived = 'no';
console.log('Retrieval Error');
console.log( error._body );
}
);
}
}

View file

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

View file

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

View file

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

View file

@ -7,16 +7,16 @@ import { Router } from '@angular/router';
templateUrl: './full-layout.component.html',
})
export class FullLayoutComponent implements OnInit {
displayName: any;
public displayName: string;
public accountType: any;
public disabled = false;
public status: {isopen: boolean} = {isopen: false};
constructor(
private api: ApiService,
private router: Router,
) {}
public disabled = false;
public status: {isopen: boolean} = {isopen: false};
public toggled(open: boolean): void {
console.log('Dropdown is now: ', open);
}
@ -30,6 +30,7 @@ export class FullLayoutComponent implements OnInit {
// getDisplayName function from api didnt work
ngOnInit(): void {
this.displayName = localStorage.getItem('displayname') || 'User';
this.accountType = localStorage.getItem('usertype');
}
userLogout() {
@ -38,7 +39,6 @@ export class FullLayoutComponent implements OnInit {
.logout()
.subscribe(
result => {
console.log('Logged out!');
localStorage.clear();
this.router.navigate(['/login']);
}

View file

@ -7,9 +7,9 @@
<h4 class="pt-3">Oops! You're lost.</h4>
<p class="text-muted">The page you are looking for was not found.</p>
</div>
<span class="input-group-btn">
<button class="btn btn-primary btn-lg" type="button"(click)="goBack()">Go Back</button>
</span>
<span class="input-group-btn">
<button class="btn btn-primary btn-lg" type="button"(click)="goBack()">Go Back</button>
</span>
</div>
</div>
</div>

View file

@ -7,11 +7,10 @@ import { Location } from '@angular/common';
export class P404Component {
constructor(
private location: Location
) {
}
private location: Location,
) { }
goBack(): void {
this.location.back();
}
goBack(): void {
this.location.back();
}
}

View file

@ -8,8 +8,8 @@
<p class="text-muted">The page you are looking for is temporarily unavailable.</p>
</div>
<span class="input-group-btn">
<button class="btn btn-primary btn-lg" type="button"(click)="goBack()">Go Back</button>
</span>
<button class="btn btn-primary btn-lg" type="button"(click)="goBack()">Go Back</button>
</span>
</div>
</div>
</div>

View file

@ -7,11 +7,10 @@ import { Location } from '@angular/common';
export class P500Component {
constructor(
private location: Location
) {
}
private location: Location,
) { }
goBack(): void {
this.location.back();
}
goBack(): void {
this.location.back();
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,8 +1,8 @@
import { map } from 'rxjs/operators';
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import { Observable } from 'rxjs/Rx';
import { HttpClient } from '@angular/common/http';
import { environment } from '../../environments/environment';
import 'rxjs/add/operator/map';
/* this provider handles the interaction between server and client */
@ -11,7 +11,7 @@ export class ApiService {
private apiUrl = environment.apiUrl;
private sessionKey: string = null;
constructor(
private http: Http,
private http: HttpClient,
) {
if (localStorage.getItem('sessionKey') ) {
this.sessionKey = localStorage.getItem('sessionKey');
@ -20,47 +20,44 @@ export class ApiService {
public post(url: string, data: any = {}) {
data.session_key = this.sessionKey;
return this.http.post(
return this.http.post<any>(
this.apiUrl + url,
data
).map( response => response.json() );
);
}
// 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');
}
public register(data) {
return this.http.post(
return this.http.post<any>(
this.apiUrl + '/register',
data
).map( response => response.json() );
);
}
public login(data) {
return this.http
.post(
.post<any>(
this.apiUrl + '/login',
data
)
.map(
).pipe(
map(
result => {
const json = result.json();
const json = result;
this.setSessionKey(json.session_key);
this.setUserInfo(
json.email,
@ -69,24 +66,23 @@ export class ApiService {
this.setUserType(json.user_type);
return json;
}
);
));
}
public logout() {
console.log(this.sessionKey);
const key = this.sessionKey;
return this.http
.post(
.post<any>(
this.apiUrl + '/logout',
{ session_key : key },
)
.map(
).pipe(
map(
response => {
localStorage.clear();
this.sessionKey = null;
return response.json();
return response;
}
);
));
}
// Submits feedback
@ -96,70 +92,161 @@ export class ApiService {
data.package_name = 'Foodloop Web';
data.version_code = 'dev';
data.version_number = 'dev';
console.log(data);
return this.http.post(
return this.http.post<any>(
this.apiUrl + '/feedback',
data
).map( response => response.json() );
);
}
// gets transaction list for log
public transList(data) {
const key = this.sessionKey;
return this.http.post(
return this.http.post<any>(
this.apiUrl + '/outgoing-transactions',
{
session_key : key,
page : data
}
).map( response => response.json() );
);
}
// Basic Customer User stats API
public categoryList() {
const key = this.sessionKey;
return this.http.post<any>(
this.apiUrl + '/search/category',
{
session_key : key,
}
);
}
// Basic Customer User stats API
public categoryTransactionList() {
const key = this.sessionKey;
return this.http.post<any>(
this.apiUrl + '/stats/category',
{
session_key : key,
}
);
}
// LCC data
public externalTransactions() {
const key = this.sessionKey;
return this.http.post<any>(
this.apiUrl + '/v1/organisation/external/transactions',
{
session_key : key,
}
);
}
public loadMiscUrl(extra_url, extraArgs = {}) {
const key = this.sessionKey;
return this.http.post<any>(
this.apiUrl + '/v1/' + extra_url,
{
session_key : key,
...extraArgs,
}
);
}
public externalSuppliers(page, sortBy, sortDir, perPage, search) {
const key = this.sessionKey;
return this.http.post<any>(
this.apiUrl + '/v1/organisation/external/suppliers',
{
session_key : key,
page : page,
sort_by : sortBy,
sort_dir : sortDir,
per_page : perPage,
search : search,
}
);
}
// Searches organisations used for transaction submission
public search(data) {
data.session_key = this.sessionKey;
return this.http.post(
return this.http.post<any>(
this.apiUrl + '/search',
data
).map( response => response.json() );
);
}
// Uploads a transaction
public upload(data) {
data.session_key = this.sessionKey;
return this.http.post(
return this.http.post<any>(
this.apiUrl + '/upload',
data
).map( response => response.json() );
);
}
// 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) {
const key = this.sessionKey;
return this.http.post<any>(
this.apiUrl + '/v1/organisation/payroll',
{
session_key : key,
page : data
}
);
}
// handles Org data added
public orgPayroll(data) {
data.session_key = this.sessionKey;
return this.http.post(
this.apiUrl + '/org/payroll',
return this.http.post<any>(
this.apiUrl + '/v1/organisation/payroll/add',
data
).map( response => response.json() );
);
}
public orgSupplier(data) {
data.session_key = this.sessionKey;
return this.http.post(
this.apiUrl + '/org/supplier',
return this.http.post<any>(
this.apiUrl + '/v1/organisation/supplier/add',
data
).map( response => response.json() );
);
}
public orgEmployee(data) {
data.session_key = this.sessionKey;
return this.http.post(
this.apiUrl + '/org/employee',
return this.http.post<any>(
this.apiUrl + '/v1/organisation/employee/add',
data
).map( response => response.json() );
);
}
// Handles user data interaction
@ -175,7 +262,6 @@ export class ApiService {
public setUserInfo(
email: string,
display_name: string) {
console.log('set UserInfo');
localStorage.setItem('email', email);
localStorage.setItem('displayname', display_name);
}
@ -183,7 +269,6 @@ export class ApiService {
// Sets usertype
public setUserType(user_type: string) {
console.log('set UserType');
localStorage.setItem('usertype', user_type);
}
@ -191,74 +276,100 @@ export class ApiService {
public accountFullLoad() {
const key = this.sessionKey;
return this.http.post(
return this.http.post<any>(
this.apiUrl + '/user',
{ session_key : key },
).map( response => response.json() );
);
}
public accountEditUpdate(data) {
data.session_key = this.sessionKey;
return this.http.post(
return this.http.post<any>(
this.apiUrl + '/user/account',
data
).map( response => response.json() );
);
}
// 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');
}
// Leaderboard Api
public leaderboard_fetch(data) {
public leaderboard_fetch(
type: string,
page: number) {
const key = this.sessionKey;
return this.http.post(
this.apiUrl + '/stats/leaderboard',
return this.http.post<any>(
this.apiUrl + '/stats/leaderboard/paged',
{
session_key : key,
type : data
type : type,
page: page,
}
).map( response => response.json() );
);
}
// Initial Map Data
public getMapData(data) {
data.session_key = this.sessionKey;
return this.http.post<any>(
this.apiUrl + '/v1/supplier/location',
data
);
}
// Load Association Data
public getAssocData(data) {
data.session_key = this.sessionKey;
return this.http.post<any>(
this.apiUrl + '/v1/supplier/location/trail',
data
);
}
// Basic Customer User stats API
public basicStats() {
public customerStats() {
const key = this.sessionKey;
return this.http.post(
this.apiUrl + '/stats',
return this.http.post<any>(
this.apiUrl + '/stats/customer',
{
session_key : key,
}
).map( response => response.json() );
);
}
// Basic Customer User stats API
public orgStats() {
const key = this.sessionKey;
return this.http.post<any>(
this.apiUrl + '/stats/organisation',
{
session_key : key,
}
);
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,3 @@
<td>{{ leaderboard.position }}</td>
<td class="text-truncate">{{ leaderboard.display_name }}</td>
<td>{{ listType.includes('total') ? (leaderboard.value | currency:'GBP':'symbol':'1.2-2') : (leaderboard.value | number:'1.0-0') }}</td>

View file

@ -0,0 +1,20 @@
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
interface LeaderboardData {
position: number;
display_name: number,
value: number;
}
@Component({
// tslint:disable-next-line
selector: '[leaderboard-result]',
templateUrl: 'leaderboard-result.component.html',
})
export class LeaderboardResultComponent implements OnInit {
@Input() public leaderboard: LeaderboardData;
@Input() public listType: string;
ngOnInit(): void {
}
}

View file

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

View file

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

View file

@ -9,6 +9,7 @@ interface OrgData {
}
@Component({
// tslint:disable-next-line
selector: '[org-result]',
templateUrl: 'org-result.component.html',
})
@ -19,6 +20,6 @@ export class OrgResultComponent {
public orgClick(): void {
this.onClick.emit(
this.org
)
);
}
}

View file

@ -10,6 +10,7 @@ interface OrgData {
}
@Component({
// tslint:disable-next-line
selector: 'org-table',
templateUrl: 'org-table.component.html',
})

View file

@ -0,0 +1,3 @@
<td>{{payrollDate}}</td>
<td>{{payroll.gross_payroll | currency:'GBP':'symbol':'1.2-2' }}</td>
<td>{{payroll.employee_amount}}</td>

View file

@ -0,0 +1,22 @@
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import * as moment from 'moment';
interface PayrollData {
entry_period: string;
gross_payroll: number;
employee_amount: number;
}
@Component({
// tslint:disable-next-line
selector: '[payroll-result]',
templateUrl: 'payroll-result.component.html',
})
export class PayrollResultComponent implements OnInit {
@Input() public payroll: PayrollData;
public payrollDate: string;
ngOnInit(): void {
this.payrollDate = moment(this.payroll.entry_period).format('MMMM YYYY');
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -3,16 +3,18 @@ import * as moment from 'moment';
interface TransactionData {
seller: number;
value: string;
value: number;
purchase_time: string;
}
@Component({
// tslint:disable-next-line
selector: '[transaction-result]',
templateUrl: 'transaction-result.component.html',
})
export class TransactionResultComponent implements OnInit {
@Input() public transaction: TransactionData;
@Input() public showMeta: boolean;
public transactionDate: string;
ngOnInit(): void {

View file

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

View file

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

View file

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

View file

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

View file

@ -2,20 +2,28 @@
<div class="card-footer">
<ul>
<li class="hidden-sm-down">
<div class="text-muted">Customers This Month</div>
<div class="text-muted">Sales Total</div>
<strong>{{ allSalesCount }}</strong>
</li>
<li class="hidden-sm-down">
<div class="text-muted">Money Spent Total</div>
<strong>{{ allPurchasesTotal | currency:'GBP':'symbol':'1.2-2'}}</strong>
</li>
<li class="hidden-sm-down">
<div class="text-muted">Sales This Month</div>
<strong>{{ thisMonthSalesCount }}</strong>
</li>
<li class="hidden-sm-down">
<div class="text-muted">Money Spent This Month</div>
<strong>{{ thisMonthPurchasesTotal | currency:'GBP':true:'1.2-2'}}</strong>
<strong>{{ thisMonthPurchasesTotal | currency:'GBP':'symbol':'1.2-2'}}</strong>
</li>
<li class="hidden-sm-down">
<div class="text-muted">Customers Today</div>
<div class="text-muted">Sales Today</div>
<strong>{{ todaySalesCount }}</strong>
</li>
<li class="hidden-sm-down">
<div class="text-muted">Average Transaction Today</div>
<strong>{{ ( todaySalesTotal / todaySalesCount ) || 0 | currency:'GBP':true:'1.2-2'}}</strong>
<strong>{{ ( todaySalesTotal / todaySalesCount ) || 0 | currency:'GBP':'symbol':'1.2-2'}}</strong>
</li>
</ul>
</div>

View file

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

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