├── .editorconfig
├── .gitignore
├── .vscode
└── settings.json
├── LICENSE.md
├── README.md
├── angular.json
├── e2e
├── app.e2e-spec.ts
├── app.po.ts
└── tsconfig.json
├── karma.conf.js
├── logo-clear-dark.png
├── logo-clear-dark.svg
├── logo-clear.png
├── logo-clear.svg
├── logo-ideas.ai
├── logo-ideas.jpg
├── logo-ideas.svg
├── package-lock.json
├── package.json
├── pluralsight-colours.png
├── protractor.conf.js
├── src
├── app
│ ├── alltimes
│ │ ├── alltimes.component.css
│ │ ├── alltimes.component.html
│ │ └── alltimes.component.ts
│ ├── app.component.css
│ ├── app.component.html
│ ├── app.component.ts
│ ├── app.module.ts
│ ├── dashboard
│ │ ├── dashboard.component.css
│ │ ├── dashboard.component.html
│ │ └── dashboard.component.ts
│ ├── fielderrors
│ │ ├── fielderrors.component.css
│ │ ├── fielderrors.component.html
│ │ └── fielderrors.component.ts
│ ├── profile
│ │ ├── profile.component.css
│ │ ├── profile.component.html
│ │ ├── profile.component.spec.ts
│ │ └── profile.component.ts
│ ├── projects
│ │ ├── projects.component.css
│ │ ├── projects.component.html
│ │ └── projects.component.ts
│ ├── rxjs-operators.ts
│ ├── settings
│ │ ├── settings.component.css
│ │ ├── settings.component.html
│ │ └── settings.component.ts
│ ├── statistic
│ │ ├── statistic.component.css
│ │ ├── statistic.component.html
│ │ └── statistic.component.ts
│ └── timesheet
│ │ ├── sample.people.data.ts
│ │ ├── sample.projects.data.ts
│ │ ├── timesheet.component.css
│ │ ├── timesheet.component.html
│ │ └── timesheet.component.ts
├── assets
│ ├── .gitkeep
│ ├── data
│ │ ├── hoursByTeam.json
│ │ ├── people.json
│ │ ├── projects.json
│ │ └── teams.json
│ └── img
│ │ └── logo-clear.svg
├── environments
│ ├── environment.prod.ts
│ └── environment.ts
├── favicon.ico
├── index.html
├── main.ts
├── polyfills.ts
├── styles.css
├── test.ts
└── tsconfig.json
├── tslint.json
└── xample-scripts
├── 1.intro.md
├── 2.charts.md
├── 3.forms.md
├── 4.grids.md
├── 5.tabs.md
└── 6.advanced.md
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | max_line_length = off
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /tmp
6 |
7 | # dependencies
8 | /node_modules
9 |
10 | # IDEs and editors
11 | /.idea
12 | .project
13 | .classpath
14 | .c9/
15 | *.launch
16 | .settings/
17 |
18 | # IDE - VSCode
19 | .vscode/*
20 | !.vscode/settings.json
21 | !.vscode/tasks.json
22 | !.vscode/launch.json
23 | !.vscode/extensions.json
24 |
25 | # misc
26 | /.sass-cache
27 | /connect.lock
28 | /coverage/*
29 | /libpeerconnection.log
30 | npm-debug.log
31 | testem.log
32 | /typings
33 |
34 | # e2e
35 | /e2e/*.js
36 | /e2e/*.map
37 |
38 | #System Files
39 | .DS_Store
40 | Thumbs.db
41 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | // Place your settings in this file to overwrite default and user settings.
2 | {
3 | }
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright (c) 2017 Glen Smith
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AgileTimes
2 |
3 | Welcome to the Sample app for my Pluralsight Course [Building Beautiful Angular Apps with PrimeNG](https://app.pluralsight.com/courses/angular-apps-prime-ng).
4 |
5 | The course was officially released on Thursday, Aug 24, 2017.
6 |
7 | I'll keep this page updated with breaking changes as the course ages.
8 |
9 | ## Take it for a spin
10 |
11 | I deploy the latest version of the application directly to github pages.
12 |
13 | [Try it out now](https://glenasmith.github.io/pluralsight-primeng/)
14 |
15 |
16 | ## Starter Kit
17 |
18 | Keen to start from scratch? You can (well, almost). There's a [starter-kit](https://github.com/glenasmith/pluralsight-primeng/tree/starter-kit) branch which you can clone/download which contains:
19 | * A basic menu template routing to blank components
20 | * Styling for the shell, but none of the component CSS - which you insert as you go
21 | * Blank component code shells for you to insert code as you follow along
22 |
23 | Remember: we don't cover every bit of CSS styling you need but I do refer when you need to copy and paste. So you might need to switch branches every now and again to grab some CSS styling.
24 |
25 | Go for it!
26 |
27 |
28 | ## Errata & Updates
29 |
30 | - 2019-03-12 - Updated to PrimeNG 7. Rebuilt starter kit branch to match new build.
31 | - 2018-11-15 - Major Update! Updated to PrimeNG 6.1.6 and Angular 7.0 using [update.angular.io](https://update.angular.io)
32 | - 2017-12-05 - Updated to PrimeNG 5.0.2 and Angular 5.0.5. And some layout fixes for new version.
33 | - 2017-10-26 - Changed base layout to absolute positioning for better resize screen experience.
34 | - 2017-09-17 - Introduced "starter-kit" branch for initial project template
35 | - 2017-08-28 - Fixed some CSS Dialog footer layout issues (my old 2.x CSS causing 4.x issues)
36 | - 2017-08-24 - Course officially released [here](https://app.pluralsight.com/courses/angular-apps-prime-ng)
37 | - 2017-08-24 - Bumped Deps to latest version (PrimeNG 4.1.3, Angular 4.3.6)
38 |
39 |
--------------------------------------------------------------------------------
/angular.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "version": 1,
4 | "newProjectRoot": "projects",
5 | "projects": {
6 | "agile-times": {
7 | "root": "",
8 | "sourceRoot": "src",
9 | "projectType": "application",
10 | "architect": {
11 | "build": {
12 | "builder": "@angular-devkit/build-angular:browser",
13 | "options": {
14 | "outputPath": "dist",
15 | "index": "src/index.html",
16 | "main": "src/main.ts",
17 | "tsConfig": "src/tsconfig.json",
18 | "polyfills": "src/polyfills.ts",
19 | "assets": [
20 | "src/assets",
21 | "src/favicon.ico"
22 | ],
23 | "styles": [
24 | "node_modules/roboto-fontface/css/roboto/roboto-fontface.css",
25 | "node_modules/font-awesome/css/font-awesome.css",
26 | "node_modules/primeng/resources/primeng.css",
27 | "node_modules/primeng/resources/themes/nova-light/theme.css",
28 | "node_modules/quill/dist/quill.core.css",
29 | "node_modules/quill/dist/quill.snow.css",
30 | "node_modules/fullcalendar/dist/fullcalendar.css",
31 | "src/styles.css"
32 | ],
33 | "scripts": [
34 | "node_modules/chart.js/dist/Chart.js",
35 | "node_modules/jquery/dist/jquery.js",
36 | "node_modules/quill/dist/quill.js",
37 | "node_modules/moment/min/moment.min.js",
38 | "node_modules/fullcalendar/dist/fullcalendar.js"
39 | ]
40 | },
41 | "configurations": {
42 | "production": {
43 | "optimization": true,
44 | "outputHashing": "all",
45 | "sourceMap": false,
46 | "extractCss": true,
47 | "namedChunks": false,
48 | "aot": true,
49 | "extractLicenses": true,
50 | "vendorChunk": false,
51 | "buildOptimizer": true,
52 | "fileReplacements": [
53 | {
54 | "replace": "src/environments/environment.ts",
55 | "with": "src/environments/environment.prod.ts"
56 | }
57 | ]
58 | }
59 | }
60 | },
61 | "serve": {
62 | "builder": "@angular-devkit/build-angular:dev-server",
63 | "options": {
64 | "browserTarget": "agile-times:build"
65 | },
66 | "configurations": {
67 | "production": {
68 | "browserTarget": "agile-times:build:production"
69 | }
70 | }
71 | },
72 | "extract-i18n": {
73 | "builder": "@angular-devkit/build-angular:extract-i18n",
74 | "options": {
75 | "browserTarget": "agile-times:build"
76 | }
77 | },
78 | "test": {
79 | "builder": "@angular-devkit/build-angular:karma",
80 | "options": {
81 | "main": "src/test.ts",
82 | "karmaConfig": "./karma.conf.js",
83 | "polyfills": "src/polyfills.ts",
84 | "scripts": [
85 | "node_modules/chart.js/dist/Chart.js",
86 | "node_modules/jquery/dist/jquery.js",
87 | "node_modules/quill/dist/quill.js",
88 | "node_modules/moment/min/moment.min.js",
89 | "node_modules/fullcalendar/dist/fullcalendar.js"
90 | ],
91 | "styles": [
92 | "node_modules/roboto-fontface/css/roboto/roboto-fontface.css",
93 | "node_modules/font-awesome/css/font-awesome.css",
94 | "node_modules/primeng/resources/primeng.css",
95 | "node_modules/primeng/resources/themes/bootstrap/theme.css",
96 | "node_modules/quill/dist/quill.core.css",
97 | "node_modules/quill/dist/quill.snow.css",
98 | "node_modules/fullcalendar/dist/fullcalendar.css",
99 | "src/styles.css"
100 | ],
101 | "assets": [
102 | "src/assets",
103 | "src/favicon.ico"
104 | ]
105 | }
106 | },
107 | "lint": {
108 | "builder": "@angular-devkit/build-angular:tslint",
109 | "options": {
110 | "tsConfig": [
111 | "src/tsconfig.json"
112 | ],
113 | "exclude": []
114 | }
115 | }
116 | }
117 | },
118 | "agile-times-e2e": {
119 | "root": "e2e",
120 | "sourceRoot": "e2e",
121 | "projectType": "application",
122 | "architect": {
123 | "e2e": {
124 | "builder": "@angular-devkit/build-angular:protractor",
125 | "options": {
126 | "protractorConfig": "./protractor.conf.js",
127 | "devServerTarget": "agile-times:serve"
128 | }
129 | },
130 | "lint": {
131 | "builder": "@angular-devkit/build-angular:tslint",
132 | "options": {
133 | "tsConfig": [
134 | "e2e/tsconfig.json"
135 | ],
136 | "exclude": []
137 | }
138 | }
139 | }
140 | }
141 | },
142 | "defaultProject": "agile-times",
143 | "schematics": {
144 | "@schematics/angular:component": {
145 | "prefix": "at",
146 | "styleext": "css"
147 | },
148 | "@schematics/angular:directive": {
149 | "prefix": "at"
150 | }
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/e2e/app.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import { AgileTimesPage } from './app.po';
2 |
3 | describe('agile-times App', function() {
4 | let page: AgileTimesPage;
5 |
6 | beforeEach(() => {
7 | page = new AgileTimesPage();
8 | });
9 |
10 | it('should display message saying app works', () => {
11 | page.navigateTo();
12 | expect(page.getParagraphText()).toEqual('app works!');
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/e2e/app.po.ts:
--------------------------------------------------------------------------------
1 | import { browser, element, by } from 'protractor';
2 |
3 | export class AgileTimesPage {
4 | navigateTo() {
5 | return browser.get('/');
6 | }
7 |
8 | getParagraphText() {
9 | return element(by.css('app-root h1')).getText();
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/e2e/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "declaration": false,
5 | "emitDecoratorMetadata": true,
6 | "experimentalDecorators": true,
7 | "lib": [
8 | "es2016"
9 | ],
10 | "module": "commonjs",
11 | "moduleResolution": "node",
12 | "outDir": "../dist/out-tsc-e2e",
13 | "sourceMap": true,
14 | "target": "es6",
15 | "typeRoots": [
16 | "../node_modules/@types"
17 | ]
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/0.13/config/configuration-file.html
3 |
4 | module.exports = function (config) {
5 | config.set({
6 | basePath: '',
7 | frameworks: ['jasmine', '@angular-devkit/build-angular'],
8 | plugins: [
9 | require('karma-jasmine'),
10 | require('karma-chrome-launcher'),
11 | require('karma-coverage-istanbul-reporter'),
12 | require('@angular-devkit/build-angular/plugins/karma')
13 | ],
14 | files: [
15 |
16 | ],
17 | preprocessors: {
18 |
19 | },
20 | mime: {
21 | 'text/x-typescript': ['ts','tsx']
22 | },
23 | coverageIstanbulReporter: {
24 | dir: require('path').join(__dirname, 'coverage'), reports: [ 'html', 'lcovonly' ],
25 | fixWebpackSourcePaths: true
26 | },
27 |
28 | reporters: config.angularCli && config.angularCli.codeCoverage
29 | ? ['progress', 'coverage-istanbul']
30 | : ['progress'],
31 | port: 9876,
32 | colors: true,
33 | logLevel: config.LOG_INFO,
34 | autoWatch: true,
35 | browsers: ['Chrome'],
36 | singleRun: false
37 | });
38 | };
39 |
--------------------------------------------------------------------------------
/logo-clear-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glenasmith/pluralsight-primeng/8e98e519a71cf70f8927fffe2289bbbaf4dac3f8/logo-clear-dark.png
--------------------------------------------------------------------------------
/logo-clear-dark.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | Agile Times
27 |
28 |
29 |
--------------------------------------------------------------------------------
/logo-clear.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glenasmith/pluralsight-primeng/8e98e519a71cf70f8927fffe2289bbbaf4dac3f8/logo-clear.png
--------------------------------------------------------------------------------
/logo-clear.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | Agile Times
53 |
54 |
55 |
--------------------------------------------------------------------------------
/logo-ideas.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glenasmith/pluralsight-primeng/8e98e519a71cf70f8927fffe2289bbbaf4dac3f8/logo-ideas.ai
--------------------------------------------------------------------------------
/logo-ideas.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glenasmith/pluralsight-primeng/8e98e519a71cf70f8927fffe2289bbbaf4dac3f8/logo-ideas.jpg
--------------------------------------------------------------------------------
/logo-ideas.svg:
--------------------------------------------------------------------------------
1 |
2 | image/svg+xml Agile Times
76 | Agile Times
111 | Agile Times
146 | Agile Times
181 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "agile-times",
3 | "version": "1.0.0",
4 | "license": "MIT",
5 | "angular-cli": {},
6 | "scripts": {
7 | "404": "shx cp dist/index.html dist/404.html",
8 | "ng": "ng",
9 | "start": "ng serve",
10 | "test": "ng test",
11 | "lint": "ng lint",
12 | "e2e": "ng e2e",
13 | "dist": "ng build --prod --base-href https://glenasmith.github.io/pluralsight-primeng/",
14 | "deploy": "angular-cli-ghpages --repo=git@github.com:glenasmith/pluralsight-primeng.git",
15 | "shiptogithub": "npm run dist && npm run 404 && npm run deploy"
16 | },
17 | "private": true,
18 | "dependencies": {
19 | "@angular/animations": "7.2.8",
20 | "@angular/common": "7.2.8",
21 | "@angular/compiler": "7.2.8",
22 | "@angular/core": "7.2.8",
23 | "@angular/forms": "7.2.8",
24 | "@angular/http": "7.2.8",
25 | "@angular/platform-browser": "7.2.8",
26 | "@angular/platform-browser-dynamic": "7.2.8",
27 | "@angular/router": "7.2.8",
28 | "@angular/cdk": "7.3.4",
29 | "chart.js": "2.7.3",
30 | "core-js": "2.5.1",
31 | "dexie": "2.0.4",
32 | "font-awesome": "^4.7.0",
33 | "fullcalendar": "4.0.0-alpha.2",
34 | "jquery": "3.3.1",
35 | "moment": "2.22.2",
36 | "primeicons": "1.0.0",
37 | "primeng": "7.0.5",
38 | "quill": "1.3.6",
39 | "roboto-fontface": "0.10.0",
40 | "rxjs": "6.4.0",
41 | "ts-helpers": "^1.1.1",
42 | "zone.js": "0.8.29"
43 | },
44 | "devDependencies": {
45 | "@angular-devkit/build-angular": "0.13.5",
46 | "@angular/cli": "^7.3.5",
47 | "@angular/compiler-cli": "7.2.8",
48 | "@types/dexie": "1.3.1",
49 | "@types/jasmine": "2.8.2",
50 | "@types/node": "7.0.18",
51 | "codelyzer": "4.5.0",
52 | "jasmine-core": "2.8.0",
53 | "jasmine-spec-reporter": "4.2.1",
54 | "karma": "1.7.1",
55 | "karma-chrome-launcher": "2.2.0",
56 | "karma-cli": "^1.0.1",
57 | "karma-coverage-istanbul-reporter": "1.3.0",
58 | "karma-jasmine": "1.1.1",
59 | "protractor": "5.2.0",
60 | "shx": "0.3.2",
61 | "ts-node": "3.3.0",
62 | "tslint": "5.8.0",
63 | "typescript": "3.1.6"
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/pluralsight-colours.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glenasmith/pluralsight-primeng/8e98e519a71cf70f8927fffe2289bbbaf4dac3f8/pluralsight-colours.png
--------------------------------------------------------------------------------
/protractor.conf.js:
--------------------------------------------------------------------------------
1 | // Protractor configuration file, see link for more information
2 | // https://github.com/angular/protractor/blob/master/lib/config.ts
3 |
4 | /*global jasmine */
5 | var SpecReporter = require('jasmine-spec-reporter');
6 |
7 | exports.config = {
8 | allScriptsTimeout: 11000,
9 | specs: [
10 | './e2e/**/*.e2e-spec.ts'
11 | ],
12 | capabilities: {
13 | 'browserName': 'chrome'
14 | },
15 | directConnect: true,
16 | baseUrl: 'http://localhost:4200/',
17 | framework: 'jasmine',
18 | jasmineNodeOpts: {
19 | showColors: true,
20 | defaultTimeoutInterval: 30000,
21 | print: function() {}
22 | },
23 | useAllAngular2AppRoots: true,
24 | beforeLaunch: function() {
25 | require('ts-node').register({
26 | project: 'e2e'
27 | });
28 | },
29 | onPrepare: function() {
30 | jasmine.getEnv().addReporter(new SpecReporter());
31 | }
32 | };
33 |
--------------------------------------------------------------------------------
/src/app/alltimes/alltimes.component.css:
--------------------------------------------------------------------------------
1 | .alltimesheets {
2 | background-color: white;
3 | font-family: "Roboto";
4 | }
5 |
6 | .header {
7 | padding: 1em;
8 | color: white;
9 | background-color: #0275D8;
10 | margin-bottom: 1em;
11 | }
12 |
13 | h2 {
14 | font-weight: bolder;
15 | font-size: xx-large;
16 | display: inline;
17 | }
18 |
19 | h3 {
20 | font-weight: lighter;
21 | font-size: xx-large;
22 | display: inline;
23 | }
24 |
25 | p-dataTable >>> .ui-datatable-footer {
26 | min-height: 60px;
27 | }
28 |
29 | p-dataTable >>> .ui-datatable-footer input {
30 | margin-left: 0.5em;
31 | font-size: larger;
32 | padding: 3px;
33 | }
34 |
35 | p-dataTable >>> .ui-datatable-footer {
36 | min-height: 60px;
37 | }
38 |
39 | p-dataTable >>> .selectBoxColumn {
40 | width: 43px;
41 | }
42 |
43 | p-contextMenu >>> .ui-menuitem-active a {
44 | background-color: #F15B2A !important;
45 | }
46 |
--------------------------------------------------------------------------------
/src/app/alltimes/alltimes.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
14 |
15 |
16 |
21 |
22 |
23 |
24 |
25 |
27 |
28 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | Search:
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/src/app/alltimes/alltimes.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, ViewChild } from '@angular/core';
2 | import { MenuItem, DataTable, LazyLoadEvent } from "primeng/primeng";
3 | import { range } from 'rxjs';
4 | import Dexie from 'dexie';
5 |
6 | const MAX_EXAMPLE_RECORDS = 1000;
7 |
8 | @Component({
9 | selector: 'at-alltimes',
10 | templateUrl: './alltimes.component.html',
11 | styleUrls: ['./alltimes.component.css']
12 | })
13 | export class AlltimesComponent implements OnInit {
14 |
15 | @ViewChild("dt") dt : DataTable;
16 |
17 | db: Dexie;
18 |
19 | allTimesheetData = [
20 |
21 | { user: 'Glen', project: 'Payroll App', category: 'Backend', startTime: 1000, endTime: 1700, date: 1434243 },
22 | { user: 'Karen', project: 'Agile Times', category: 'Frontend', startTime: 900, endTime: 1700, date: 1434243 },
23 | { user: 'Si', project: 'Mobile App', category: 'Operations', startTime: 1100, endTime: 1700, date: 1434243 },
24 | { user: 'Rohit', project: 'Agile Times', category: 'Backend', startTime: 800, endTime: 1700, date: 1434243 },
25 |
26 | ];
27 |
28 | allProjectNames = ['', 'Payroll App', 'Mobile App', 'Agile Times'];
29 |
30 | allProjects = this.allProjectNames.map((proj) => {
31 | return { label: proj, value: proj }
32 | });
33 |
34 | selectedRows: Array;
35 |
36 | contextMenu: MenuItem[];
37 |
38 | recordCount : number;
39 |
40 | constructor() {
41 | // for (let x = 0; x < 5; x++) {
42 | // this.allTimesheetData = this.allTimesheetData.concat(this.allTimesheetData);
43 | // }
44 | this.recordCount = this.allTimesheetData.length;
45 |
46 | this.configureDatabase();
47 | this.populateDatabase();
48 |
49 | }
50 |
51 | private configureDatabase() {
52 |
53 | this.db = new Dexie('AgileTimes');
54 |
55 | // Define a schema
56 | this.db.version(1).stores({
57 | timesheet: 'id,user,project,category,startTime,endTime,date'
58 | });
59 |
60 | }
61 |
62 | private populateDatabase() {
63 |
64 | this.getRecordCount().then((count) => {
65 | this.recordCount = count;
66 | if (!count) {
67 | this.resetDatabase();
68 | }
69 | });
70 |
71 | }
72 |
73 | generateRandomUser(id: number) {
74 |
75 | var names = ["Joe", "Mary", "Phil", "Karen", "Si", "Tim", "Rohit", "Jenny", "Kim", "Greg", "Danni"]
76 | var allProjectNames = ['Payroll App', 'Mobile App', 'Agile Times'];
77 | var allCategories = ['Frontend', 'Backend', 'Operations'];
78 |
79 | let newUser = {
80 | id: id,
81 | user: names[id % names.length],
82 | project: allProjectNames[id % allProjectNames.length],
83 | category: allCategories[id % allCategories.length],
84 | startTime: Math.round(Math.random() * 1000),
85 | endTime: Math.round(Math.random() * 1000),
86 | date: Math.round(Math.random() * 100000)
87 | };
88 | newUser.endTime += newUser.startTime; // to make sure it's later
89 |
90 | return newUser;
91 |
92 | }
93 |
94 | getRecordCount(): Dexie.Promise {
95 | return this.db.table("timesheet").count();
96 | }
97 |
98 | resetDatabase() {
99 |
100 | let that = this;
101 |
102 | this.dt.loading = true;
103 |
104 | this.db.table("timesheet").clear().then(() => {
105 | console.log("Database Cleared");
106 | range(0, MAX_EXAMPLE_RECORDS).subscribe(
107 | function (id) {
108 | let randomUser = that.generateRandomUser(id);
109 | that.db.table("timesheet").add(randomUser);
110 | if (id % 100 == 0) {
111 | that.getRecordCount().then((count) => {
112 | that.recordCount = count;
113 | })
114 | }
115 |
116 | },
117 | function (err) {
118 | console.log("Do Error: %s", err);
119 | },
120 | function () {
121 | console.log("Do complete");
122 | that.dt.loading = false;
123 | that.dt.reset();
124 | console.log("Finished Reset database");
125 | that.getRecordCount().then((count) => {
126 | that.recordCount = count;
127 | })
128 | });
129 | })
130 | }
131 |
132 | loadTimes(event: LazyLoadEvent) {
133 |
134 | console.log(JSON.stringify(event));
135 |
136 | let table = this.db.table("timesheet");
137 |
138 | var query: any;
139 |
140 | // Dexie doesn't support ordering AND filtering, so we branch here
141 | // Alternative strategies here: https://github.com/dfahlander/Dexie.js/issues/297
142 | if (event.filters && event.filters["project"]) {
143 | query = table.where("project").equals(event.filters["project"]["value"]);
144 | } else if (event.globalFilter) {
145 | query = table.where("project").startsWithIgnoreCase(event.globalFilter)
146 | .or("user").startsWithIgnoreCase(event.globalFilter)
147 | .or("category").startsWithIgnoreCase(event.globalFilter);
148 | } else {
149 | query = table.orderBy(event.sortField);
150 | }
151 |
152 | query = query
153 | .offset(event.first)
154 | .limit(event.rows);
155 |
156 | if (event.sortOrder == -1) {
157 | query = query.reverse();
158 | };
159 |
160 | query.toArray((nextBlockOfTimes) => {
161 | // console.log("Loaded times: %s", JSON.stringify(nextBlockOfTimes));
162 | this.allTimesheetData = nextBlockOfTimes;
163 | });
164 | }
165 |
166 |
167 | ngOnInit() {
168 | this.contextMenu = [
169 | { label: 'Debug', icon: 'fa fa-bug', command: (event) => this.onDebug(this.selectedRows) },
170 | { label: 'Delete', icon: 'fa fa-close', command: (event) => this.onDelete(this.selectedRows) }
171 | ];
172 |
173 | }
174 |
175 | onDebug(selectedRows: any) {
176 | console.log(JSON.stringify(selectedRows));
177 | }
178 |
179 | onDelete(selectedRows: any) {
180 | this.allTimesheetData = this.allTimesheetData.filter((row) => {
181 | return !selectedRows.includes(row);
182 | });
183 | }
184 |
185 |
186 |
187 | onEditComplete(editInfo) {
188 | let fieldChanged = editInfo.column.field;
189 | let newRowValues = editInfo.data;
190 | alert(`You edited ${fieldChanged} to ${newRowValues[fieldChanged]}`);
191 | }
192 |
193 | onRowSelect(rowInfo) {
194 | //console.log(JSON.stringify(rowInfo.data)); // or this.selectedRow
195 | }
196 |
197 |
198 |
199 |
200 | }
201 |
--------------------------------------------------------------------------------
/src/app/app.component.css:
--------------------------------------------------------------------------------
1 | #header {
2 | background-color: #F0F3F5;
3 | height: 60px;
4 | position: fixed;
5 | top: 0px;
6 | z-index: 99;
7 | width: 100%;
8 | }
9 |
10 | #top-logo {
11 | height: 60px;
12 | }
13 |
14 | #notifications {
15 | color: blue;
16 | float: right;
17 | }
18 |
19 | #sidenav, #sidenav-mini {
20 | height: 100%;
21 | position: fixed;
22 | top: 60px;
23 | padding-left: 0px;
24 | padding-top: 1em;
25 | background-color: #2D353C !important;
26 | }
27 |
28 | #content-body {
29 | padding-top: 55px;
30 | position: absolute;
31 | left: 190px;
32 | }
33 |
34 | #sidegutter {
35 | width: 340px;
36 | }
37 |
38 | #minigutter {
39 | display:none;
40 | }
41 |
42 | /*
43 |
44 | @media screen and (min-width: 40.063em) {
45 | */
46 |
47 | @media only screen and (max-width: 1000px) {
48 |
49 | #sidegutter {
50 | display: none;
51 | }
52 |
53 | #minigutter {
54 | display: block;
55 | }
56 |
57 | #content-body {
58 | left: 80px;
59 | }
60 |
61 | }
62 |
63 | /*
64 | @media only screen and (max-width: 40em) {
65 | */
66 | @media only screen and (max-width: 400px) {
67 |
68 |
69 | #sidegutter {
70 | display: none;
71 | }
72 |
73 | #minigutter {
74 | display: block;
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
22 |
23 |
32 |
33 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import {Component, OnInit, ViewChild, ElementRef, AfterViewInit} from '@angular/core';
2 | import {MenuItem} from "primeng/primeng";
3 | import {Menu} from "primeng/components/menu/menu";
4 | import {ActivatedRoute, Router} from "@angular/router";
5 |
6 | declare var jQuery :any;
7 |
8 | @Component({
9 | selector: 'app-root',
10 | templateUrl: './app.component.html',
11 | styleUrls: ['./app.component.css']
12 | })
13 | export class AppComponent implements OnInit, AfterViewInit {
14 |
15 | menuItems: MenuItem[];
16 | miniMenuItems: MenuItem[];
17 |
18 | @ViewChild('bigMenu') bigMenu : Menu;
19 | @ViewChild('smallMenu') smallMenu : Menu;
20 |
21 | constructor(private router : Router) {
22 |
23 | }
24 |
25 | ngOnInit() {
26 |
27 | let handleSelected = function(event) {
28 | let allMenus = jQuery(event.originalEvent.target).closest('ul');
29 | let allLinks = allMenus.find('.menu-selected');
30 |
31 | allLinks.removeClass("menu-selected");
32 | let selected = jQuery(event.originalEvent.target).closest('a');
33 | selected.addClass('menu-selected');
34 | }
35 |
36 | this.menuItems = [
37 | {label: 'Dashboard', icon: 'fa fa-home', routerLink: ['/dashboard'], command: (event) => handleSelected(event)},
38 | {label: 'All Times', icon: 'fa fa-calendar', routerLink: ['/alltimes'], command: (event) => handleSelected(event)},
39 | {label: 'My Timesheet', icon: 'fa fa-clock-o', routerLink: ['/timesheet'], command: (event) => handleSelected(event)},
40 | {label: 'Add Project', icon: 'fa fa-tasks', routerLink: ['/projects'], command: (event) => handleSelected(event)},
41 | {label: 'My Profile', icon: 'fa fa-users', routerLink: ['/profile'], command: (event) => handleSelected(event)},
42 | {label: 'Settings', icon: 'fa fa-sliders', routerLink: ['/settings'], command: (event) => handleSelected(event)},
43 | ]
44 |
45 | this.miniMenuItems = [];
46 | this.menuItems.forEach( (item : MenuItem) => {
47 | let miniItem = { icon: item.icon, routerLink: item.routerLink }
48 | this.miniMenuItems.push(miniItem);
49 | })
50 |
51 | }
52 |
53 | selectInitialMenuItemBasedOnUrl() {
54 | let path = document.location.pathname;
55 | let menuItem = this.menuItems.find( (item) => { return item.routerLink[0] == path });
56 | if (menuItem) {
57 | let iconToFind = '.' + menuItem.icon.replace('fa ', ''); // make fa fa-home into .fa-home
58 | let selectedIcon = document.querySelector(`${iconToFind}`);
59 | jQuery(selectedIcon).closest('li').addClass('menu-selected');
60 | }
61 | }
62 |
63 | ngAfterViewInit() {
64 | this.selectInitialMenuItemBasedOnUrl();
65 | }
66 |
67 |
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { BrowserModule } from '@angular/platform-browser';
2 | import { NgModule } from '@angular/core';
3 | import { FormsModule, ReactiveFormsModule } from '@angular/forms';
4 | import { HttpModule } from '@angular/http';
5 |
6 | import { MenuModule, PanelModule, ChartModule, InputTextModule, ButtonModule, InputMaskModule, InputTextareaModule, EditorModule, CalendarModule, RadioButtonModule, FieldsetModule, DropdownModule, MultiSelectModule, ListboxModule, SpinnerModule, SliderModule, RatingModule, DataTableModule, ContextMenuModule, TabViewModule, DialogModule, StepsModule, ScheduleModule, TreeModule, GMapModule, DataGridModule, TooltipModule, ConfirmationService, ConfirmDialogModule, GrowlModule, DragDropModule, GalleriaModule } from 'primeng/primeng';
7 |
8 | import { AppComponent } from './app.component';
9 | import {RouterModule, Routes} from "@angular/router";
10 | import { DashboardComponent } from './dashboard/dashboard.component';
11 | import { StatisticComponent } from './statistic/statistic.component';
12 | import { TimesheetComponent } from './timesheet/timesheet.component';
13 | import { ProjectsComponent } from './projects/projects.component';
14 | import { ProfileComponent } from './profile/profile.component';
15 | import { SettingsComponent } from './settings/settings.component';
16 |
17 | import { AlltimesComponent } from './alltimes/alltimes.component';
18 | import {BrowserAnimationsModule} from "@angular/platform-browser/animations";
19 | import { FielderrorsComponent } from './fielderrors/fielderrors.component';
20 |
21 |
22 | const appRoutes: Routes = [
23 | { path: "", redirectTo: "/dashboard", pathMatch: "full" },
24 | { path: "dashboard", component: DashboardComponent },
25 | { path: "alltimes", component: AlltimesComponent },
26 | { path: "timesheet", component: TimesheetComponent},
27 | { path: "projects", component: ProjectsComponent},
28 | { path: "profile", component: ProfileComponent},
29 | { path: "settings", component: SettingsComponent},
30 | ];
31 |
32 | @NgModule({
33 | declarations: [
34 | AppComponent,
35 | DashboardComponent,
36 | StatisticComponent,
37 | TimesheetComponent,
38 | ProjectsComponent,
39 | AlltimesComponent,
40 | ProfileComponent,
41 | SettingsComponent,
42 | FielderrorsComponent
43 | ],
44 | imports: [
45 | BrowserModule,
46 | FormsModule,
47 | ReactiveFormsModule,
48 | HttpModule,
49 | RouterModule.forRoot(appRoutes),
50 | BrowserAnimationsModule,
51 | MenuModule,
52 | PanelModule,
53 | ChartModule,
54 | InputTextModule,
55 | ButtonModule,
56 | InputMaskModule,
57 | InputTextareaModule,
58 | EditorModule,
59 | CalendarModule,
60 | RadioButtonModule,
61 | FieldsetModule,
62 | DropdownModule,
63 | MultiSelectModule,
64 | ListboxModule,
65 | SpinnerModule,
66 | SliderModule,
67 | RatingModule,
68 | DataTableModule,
69 | ContextMenuModule,
70 | TabViewModule,
71 | DialogModule,
72 | StepsModule,
73 | ScheduleModule,
74 | TreeModule,
75 | GMapModule,
76 | DataGridModule,
77 | TooltipModule,
78 | ConfirmDialogModule,
79 | GrowlModule,
80 | DragDropModule,
81 | GalleriaModule
82 | ],
83 | providers: [ ConfirmationService ],
84 | bootstrap: [AppComponent]
85 | })
86 | export class AppModule { }
87 |
--------------------------------------------------------------------------------
/src/app/dashboard/dashboard.component.css:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/app/dashboard/dashboard.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/src/app/dashboard/dashboard.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, ViewChild, AfterViewInit } from '@angular/core';
2 | import { UIChart } from "primeng/primeng";
3 | import {interval} from 'rxjs';
4 |
5 |
6 |
7 | const DEFAULT_COLORS = ['#3366CC', '#DC3912', '#FF9900', '#109618', '#990099',
8 | '#3B3EAC', '#0099C6', '#DD4477', '#66AA00', '#B82E2E',
9 | '#316395', '#994499', '#22AA99', '#AAAA11', '#6633CC',
10 | '#E67300', '#8B0707', '#329262', '#5574A6', '#3B3EAC']
11 |
12 |
13 | @Component({
14 | selector: 'at-dashboard',
15 | templateUrl: './dashboard.component.html',
16 | styleUrls: ['./dashboard.component.css']
17 | })
18 | export class DashboardComponent implements AfterViewInit {
19 |
20 | @ViewChild("mixedChart") mixedChart: UIChart;
21 |
22 | hoursByProject = [
23 | { id: 1, name: 'Payroll App', hoursSpent: 8 },
24 | { id: 2, name: 'Agile Times App', hoursSpent: 16 },
25 | { id: 3, name: 'Point of Sale App', hoursSpent: 24 },
26 | ]
27 |
28 | chartOptions = {
29 | title: {
30 | display: true,
31 | text: 'Hours By Project'
32 | },
33 | legend: {
34 | position: 'bottom'
35 | },
36 | };
37 |
38 | pieLabels = this.hoursByProject.map((proj) => proj.name);
39 |
40 | pieData = this.hoursByProject.map((proj) => proj.hoursSpent);
41 |
42 | pieColors = this.configureDefaultColours(this.pieData);
43 |
44 |
45 | private configureDefaultColours(data: number[]): string[] {
46 | let customColours = []
47 | if (data.length) {
48 |
49 | customColours = data.map((element, idx) => {
50 | return DEFAULT_COLORS[idx % DEFAULT_COLORS.length];
51 | });
52 | }
53 |
54 | return customColours;
55 | }
56 |
57 |
58 |
59 | hoursByProjectChartData = {
60 | labels: this.pieLabels,
61 | datasets: [
62 | {
63 | data: this.pieData,
64 | backgroundColor: this.pieColors
65 | }
66 | ]
67 | }
68 |
69 |
70 | hoursByTeamChartData = {
71 |
72 | labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
73 | datasets: [
74 | {
75 | label: 'Dev Team',
76 | backgroundColor: DEFAULT_COLORS[0],
77 | data: [65, 59, 80, 55, 67, 73]
78 | },
79 | {
80 | label: 'Ops Team',
81 | backgroundColor: DEFAULT_COLORS[1],
82 | data: [44, 63, 57, 90, 77, 70]
83 | }
84 | ]
85 |
86 | }
87 |
88 | hoursByTeamChartDataMixed = {
89 |
90 | labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
91 | datasets: [
92 | {
93 | label: 'Dev Team',
94 | type: 'bar',
95 | backgroundColor: DEFAULT_COLORS[0],
96 | data: [65, 59, 80, 55, 67, 73]
97 | },
98 | {
99 | label: 'Ops Team',
100 | type: 'line',
101 | backgroundColor: DEFAULT_COLORS[1],
102 | data: [44, 63, 57, 90, 77, 70]
103 | }
104 | ]
105 |
106 | }
107 |
108 | onDataSelect(event) {
109 |
110 | let dataSetIndex = event.element._datasetIndex;
111 | let dataItemIndex = event.element._index;
112 |
113 | let labelClicked = this.hoursByTeamChartDataMixed.datasets[dataSetIndex].label;
114 | let valueClicked = this.hoursByTeamChartDataMixed.datasets[dataSetIndex].data[dataItemIndex];
115 |
116 | alert(`Looks like ${labelClicked} worked ${valueClicked} hours`);
117 | }
118 |
119 |
120 | ngAfterViewInit() {
121 | interval(3000).subscribe(() => {
122 |
123 | var hoursByTeam = this.hoursByTeamChartDataMixed.datasets;
124 | var randomised = hoursByTeam.map((dataset) => {
125 |
126 | dataset.data = dataset.data.map((hours) => hours * (Math.random() * 2));
127 |
128 | });
129 | this.mixedChart.refresh();
130 | });
131 |
132 | }
133 |
134 |
135 |
136 | }
137 |
--------------------------------------------------------------------------------
/src/app/fielderrors/fielderrors.component.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glenasmith/pluralsight-primeng/8e98e519a71cf70f8927fffe2289bbbaf4dac3f8/src/app/fielderrors/fielderrors.component.css
--------------------------------------------------------------------------------
/src/app/fielderrors/fielderrors.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ niceName}} is required
5 | {{ niceName}} must be {{ fieldErrors(fieldName).minlength.requiredLength }} characters
6 | {{ niceName}} must not exceed {{ fieldErrors(fieldName).maxlength.requiredLength }} characters
7 |
8 |
--------------------------------------------------------------------------------
/src/app/fielderrors/fielderrors.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, Input } from '@angular/core';
2 | import { FormGroup } from "@angular/forms";
3 |
4 | @Component({
5 | selector: 'at-fielderrors',
6 | templateUrl: './fielderrors.component.html',
7 | styleUrls: ['./fielderrors.component.css']
8 | })
9 | export class FielderrorsComponent implements OnInit {
10 |
11 | @Input("form") form: FormGroup;
12 | @Input("field") fieldName: string;
13 | @Input("nicename") niceName: string;
14 |
15 |
16 |
17 | constructor() { }
18 |
19 | ngOnInit() {
20 | }
21 |
22 | fieldErrors(field: string) {
23 | let controlState = this.form.controls[field];
24 | return (controlState.dirty && controlState.errors) ? controlState.errors : null;
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/src/app/profile/profile.component.css:
--------------------------------------------------------------------------------
1 | .profile {
2 | background-color: white;
3 | font-family: "Roboto";
4 | }
5 |
6 | .header {
7 | padding: 1em;
8 | color: white;
9 | background-color: #0275D8;
10 | margin-bottom: 1em;
11 | }
12 |
13 | h2 {
14 | font-weight: bolder;
15 | font-size: xx-large;
16 | display: inline;
17 | }
18 |
19 | h3 {
20 | font-weight: lighter;
21 | font-size: xx-large;
22 | display: inline;
23 | }
24 |
25 | p-panel >>> .ui-panel-content {
26 | height: 320px;
27 | }
28 |
29 | p-panel img {
30 | width: 100%;
31 | }
32 |
33 |
34 | #drop-message {
35 | font-size: xx-large;
36 | color: #2D353C;
37 | background-color: lightgray;
38 | width: 100%;
39 | height: 85%;
40 | border: 3px solid #2D353C;
41 | text-align: center;
42 | }
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/src/app/profile/profile.component.html:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | Drop Your Image Here
16 |
17 |
18 |
19 |
20 |
21 |
22 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/app/profile/profile.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { ProfileComponent } from './profile.component';
4 | import {
5 | DataTableModule, DragDropModule, FieldsetModule, GalleriaModule, GrowlModule,
6 | PanelModule
7 | } from "primeng/primeng";
8 | import { NoopAnimationsModule } from "@angular/platform-browser/animations";
9 | import createSpy = jasmine.createSpy;
10 | import { By } from "@angular/platform-browser";
11 |
12 | describe('ProfileComponent', () => {
13 | let component: ProfileComponent;
14 | let fixture: ComponentFixture;
15 |
16 | beforeEach(async(() => {
17 | TestBed.configureTestingModule({
18 | declarations: [ProfileComponent],
19 | imports: [GrowlModule, GalleriaModule, DragDropModule, PanelModule, NoopAnimationsModule]
20 | })
21 | .compileComponents();
22 | }));
23 |
24 | beforeEach(() => {
25 | fixture = TestBed.createComponent(ProfileComponent);
26 | component = fixture.componentInstance;
27 | fixture.detectChanges();
28 | });
29 |
30 | it('should stop the slideshow on starting drag', () => {
31 |
32 | let mockGalleria = {
33 | activeIndex: 2,
34 | stopSlideshow: createSpy('stopSlideshow')
35 | };
36 |
37 | component.onDragStart(mockGalleria);
38 | expect(mockGalleria.stopSlideshow).toHaveBeenCalled();
39 | });
40 |
41 | it('should update the image on drop', () => {
42 |
43 | let mockGalleria = {
44 | activeIndex: 2,
45 | stopSlideshow: createSpy('stopSlideshow')
46 | };
47 |
48 | component.onDragStart(mockGalleria);
49 | component.onPicDrop();
50 |
51 | fixture.detectChanges();
52 |
53 | expect(component.profileImage).toEqual("http://i.pravatar.cc/300?u=Mary");
54 | let imgElement = fixture.debugElement.query(By.css('#profilePic')).nativeElement;
55 | expect(imgElement).toBeTruthy();
56 |
57 | });
58 |
59 | });
60 |
--------------------------------------------------------------------------------
/src/app/profile/profile.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { Galleria, Message } from "primeng/primeng";
3 |
4 | @Component({
5 | selector: 'at-profile',
6 | templateUrl: './profile.component.html',
7 | styleUrls: ['./profile.component.css']
8 | })
9 | export class ProfileComponent implements OnInit {
10 |
11 | profileImage: string;
12 |
13 | images = [
14 | { source: "http://i.pravatar.cc/300?u=Anne", title: "Anne" },
15 | { source: "http://i.pravatar.cc/300?u=Kerri", title: "Kerri" },
16 | { source: "http://i.pravatar.cc/300?u=Mary", title: "Mary" },
17 | { source: "http://i.pravatar.cc/300?u=Nancy", title: "Nancy" },
18 | { source: "http://i.pravatar.cc/300?u=Peta", title: "Peta" },
19 | ]
20 |
21 | selectedProfile: any;
22 |
23 | messages : Message[] = [];
24 |
25 | constructor() { }
26 |
27 | ngOnInit() {
28 | }
29 |
30 | onImageSelected(event) {
31 | console.log(JSON.stringify(event));
32 | }
33 |
34 | onDragStart(galleria) {
35 | this.selectedProfile = this.images[galleria.activeIndex];
36 | galleria.stopSlideshow();
37 | }
38 |
39 | onPicDrop() {
40 | this.profileImage = this.selectedProfile.source;
41 | this.messages.push({ severity: "info", summary: "New Profile", detail: `Changed pic to ${this.selectedProfile.title}` });
42 | }
43 |
44 |
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/src/app/projects/projects.component.css:
--------------------------------------------------------------------------------
1 | .ui-multiselect-label-container {
2 | background-color: red !important;
3 | }
4 |
5 | label {
6 | margin-top: 5px;
7 | }
8 |
9 | button {
10 | margin-top: 1em;
11 | }
12 |
13 | p-radioButton {
14 | display:block;
15 | margin: .7em;
16 | }
17 |
18 | p-listbox >>> .ui-listbox {
19 | width: 100%;
20 | }
21 |
22 | p-listbox >>> .ui-listbox-list-wrapper {
23 | height: 250px;
24 | }
25 |
26 | .avatar {
27 | float: left;
28 | margin: 5px;
29 | }
30 |
31 | .devName {
32 | font-size: xx-large;
33 | display:inline-block;
34 | margin:15px 10px 0 10px;
35 | min-height: 100px;
36 | }
37 |
38 | p-rating {
39 | font-size: xx-large;
40 | }
41 |
42 | #ratingLabel {
43 | margin-top: 20px;
44 | }
45 |
46 |
47 |
--------------------------------------------------------------------------------
/src/app/projects/projects.component.html:
--------------------------------------------------------------------------------
1 |
74 |
--------------------------------------------------------------------------------
/src/app/projects/projects.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, ElementRef } from '@angular/core';
2 | import { FormBuilder, FormGroup, Validators } from "@angular/forms";
3 |
4 |
5 | @Component({
6 | selector: 'at-projects',
7 | templateUrl: './projects.component.html',
8 | styleUrls: ['./projects.component.css']
9 | })
10 | export class ProjectsComponent implements OnInit {
11 |
12 | projectForm: FormGroup;
13 |
14 | minProjectDate = new Date();
15 |
16 | allDevs = [
17 |
18 | { label: 'Jill', value: 'Jill Cool' },
19 | { label: 'Joe', value: 'Joe Cool' },
20 | { label: 'Mary', value: 'Mary Cool' },
21 | { label: 'Susan', value: 'Susan Jones' },
22 | { label: 'Phil', value: 'Phil Stephens' },
23 | { label: 'Karen', value: 'Karen Phillips' },
24 | { label: 'Chris', value: 'Chris Hampton' },
25 | { label: 'Si', value: 'Si Chew' },
26 | { label: 'Terri', value: 'Terri Smith' }
27 |
28 | ]
29 |
30 |
31 | constructor(private fb: FormBuilder) { }
32 |
33 | ngOnInit() {
34 | this.projectForm = this.fb.group({
35 | projectId: ['', [Validators.required, Validators.minLength(5)]],
36 | description: ['My cool project', [Validators.required, Validators.maxLength(140)]],
37 | startDate: [new Date(), Validators.required],
38 | projectType: ['B'],
39 | selectedDevs: [[]],
40 | rating: [3]
41 | })
42 |
43 | }
44 |
45 | hasFormErrors() {
46 | return !this.projectForm.valid;
47 | }
48 |
49 | onSubmit() {
50 | alert(JSON.stringify(this.projectForm.value));
51 | }
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/src/app/rxjs-operators.ts:
--------------------------------------------------------------------------------
1 |
2 | // Not sure what operator you need?
3 | // Head over here:
4 | // https://github.com/Reactive-Extensions/RxJS/blob/master/doc/gettingstarted/which-instance.md
5 |
6 |
7 | // Observable class extensions
8 | import 'rxjs/add/observable/of';
9 | import 'rxjs/add/observable/from';
10 | import 'rxjs/add/observable/range';
11 |
12 | import 'rxjs/add/observable/interval';
13 | import 'rxjs/add/observable/timer';
14 |
15 | // Observable operators
16 | import 'rxjs/add/operator/map';
17 | import 'rxjs/add/operator/do';
18 | import 'rxjs/add/operator/catch';
19 | import 'rxjs/add/operator/switchMap';
20 | import 'rxjs/add/operator/mergeMap';
21 | import 'rxjs/add/operator/filter';
22 | import 'rxjs/add/operator/debounceTime';
23 | import 'rxjs/add/operator/distinctUntilChanged';
24 | import 'rxjs/add/operator/timeInterval';
25 |
26 | // Subscribe once and dispose: https://stackoverflow.com/questions/28007777/rxjs-create-subscribe-once-and-dispose-method
27 | import 'rxjs/add/operator/first';
28 | import 'rxjs/add/operator/take';
29 | import 'rxjs/add/operator/withLatestFrom';
30 |
--------------------------------------------------------------------------------
/src/app/settings/settings.component.css:
--------------------------------------------------------------------------------
1 | p-panel >>> .ui-panel {
2 | margin-top: 1em;
3 | width: 98%;
4 | min-height: 300px;
5 | }
6 |
7 | .ui-g {
8 | padding: 4px;
9 | }
10 |
11 | #statistic {
12 |
13 | width: 300px;
14 | color: white;
15 | text-align: center;
16 | font-family: "Roboto";
17 | background-color: #00ACAC;
18 | }
19 |
20 | .icon {
21 | font-size: 80px;
22 | margin: 0px;
23 | padding: 5px;
24 | background:rgba(0,0,0,.1);
25 | }
26 |
27 | .data {
28 | padding: 1em;
29 | vertical-align: middle;
30 | }
31 |
32 | .value {
33 | font-size: 40px;
34 | }
35 |
36 | .label {
37 | text-transform: uppercase;
38 | }
39 |
--------------------------------------------------------------------------------
/src/app/settings/settings.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Email To Notify:
5 |
6 |
7 |
8 |
9 | Force Backup:
10 | Backup System Now
11 |
12 |
13 |
14 |
Uptime Stats:
15 |
16 |
17 |
18 |
19 |
20 |
20
21 |
Days Uptime
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/app/settings/settings.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'at-settings',
5 | templateUrl: './settings.component.html',
6 | styleUrls: ['./settings.component.css']
7 | })
8 | export class SettingsComponent implements OnInit {
9 |
10 | constructor() { }
11 |
12 | ngOnInit() {
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/src/app/statistic/statistic.component.css:
--------------------------------------------------------------------------------
1 | .statistic {
2 | margin: 1em;
3 | min-width: 200px;
4 | color: white;
5 | text-align: center;
6 | font-family: "Roboto";
7 | }
8 |
9 | .icon {
10 | font-size: 70px;
11 | margin: 0px;
12 | padding: 5px;
13 | background:rgba(0,0,0,.1);
14 | }
15 |
16 | .data {
17 | padding: 1em;
18 | vertical-align: middle;
19 | }
20 |
21 | .value {
22 | font-size: 40px;
23 | }
24 |
25 | .label {
26 | text-transform: uppercase;
27 | }
28 |
29 |
30 |
--------------------------------------------------------------------------------
/src/app/statistic/statistic.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | {{ value }}
11 |
12 |
13 |
14 | {{ label }}
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/app/statistic/statistic.component.ts:
--------------------------------------------------------------------------------
1 | import {Component, OnInit, Input} from '@angular/core';
2 |
3 | @Component({
4 | selector: 'at-statistic',
5 | templateUrl: './statistic.component.html',
6 | styleUrls: ['./statistic.component.css']
7 | })
8 | export class StatisticComponent implements OnInit {
9 |
10 | @Input() icon : string;
11 | @Input() label : string;
12 | @Input() value: string;
13 | @Input() colour: string;
14 |
15 | constructor() { }
16 |
17 | ngOnInit() {
18 | }
19 |
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/src/app/timesheet/sample.projects.data.ts:
--------------------------------------------------------------------------------
1 | export class SampleProjectsData {
2 |
3 | static projects = [
4 | {
5 | "label": "Projects",
6 | "data": "proj",
7 | "expandedIcon": "fa fa-folder-open",
8 | "collapsedIcon": "fa fa-folder",
9 | "selectable": false,
10 | "children": [{
11 | "label": "Agile Times",
12 | "selectable": false,
13 | "data": "agile",
14 | "expandedIcon": "fa fa-folder-open",
15 | "collapsedIcon": "fa fa-folder",
16 | "children": [
17 | {"label": "Frontend", "icon": "fa fa-chrome", "data": "fe"},
18 | {"label": "Backend", "icon": "fa fa-cloud", "data": "be"},
19 | {"label": "Operations", "icon": "fa fa-cogs", "data": "ops"}
20 | ]
21 | },
22 | {
23 | "label": "Mobile App",
24 | "data": "mobile",
25 | "expandedIcon": "fa fa-folder-open",
26 | "collapsedIcon": "fa fa-folder",
27 | "selectable": false,
28 | "children": [
29 | {"label": "Frontend", "icon": "fa fa-chrome", "data": "fe"},
30 | {"label": "Backend", "icon": "fa fa-cloud", "data": "be"},
31 | {"label": "Operations", "icon": "fa fa-cogs", "data": "ops"}
32 | ]
33 | }]
34 |
35 |
36 | }
37 | ]
38 | }
39 |
--------------------------------------------------------------------------------
/src/app/timesheet/timesheet.component.css:
--------------------------------------------------------------------------------
1 | .timesheet {
2 | background-color: white;
3 | font-family: "Roboto";
4 | }
5 |
6 | .header {
7 | padding: 1em;
8 | color: white;
9 | background-color: #0275D8;
10 | margin-bottom: 1em;
11 | }
12 |
13 | .dialogBody {
14 | height: 400px;
15 | }
16 |
17 | h2 {
18 | font-weight: bolder;
19 | font-size: xx-large;
20 | display: inline;
21 | }
22 |
23 | h3 {
24 | font-weight: lighter;
25 | font-size: xx-large;
26 | display: inline;
27 | }
28 |
29 | .tabs >>> li {
30 | width: 19%;
31 | }
32 |
33 | .timesheet-grid >>> .ui-datatable {
34 | margin: 1em;
35 | }
36 |
37 | p-schedule >>> .calendar {
38 | height: 250px;
39 | }
40 |
41 |
42 | p-gmap >>> .gmap {
43 | width:100%;
44 | height: 300px;
45 | }
46 |
47 | p-steps >>> .ui-steps-item {
48 | width: 25%;
49 | }
50 |
51 | p-dataGrid >>> .ui-panel {
52 | border: 0px;
53 | }
54 |
55 | /**
56 | p-dataGrid >>> .ui-panel-titlebar {
57 | font-size: smaller;
58 | background-color: #F15B2A !important;
59 | text-align: center;
60 | height: 35px;
61 | }
62 | */
63 |
64 | p-dataGrid >>> .ui-panel-content {
65 | padding: 0px;
66 | }
67 |
68 | p-dataGrid >>> .ui-panel-content img {
69 | display: block;
70 | margin-left: auto;
71 | margin-right: auto;
72 | width: 144px;
73 | }
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/src/app/timesheet/timesheet.component.html:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
40 |
41 |
42 |
43 |
44 |
50 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
--------------------------------------------------------------------------------
/src/app/timesheet/timesheet.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { MenuItem, TreeNode, ConfirmationService, Message } from "primeng/primeng";
3 | import { SampleProjectsData } from "app/timesheet/sample.projects.data";
4 | import { SamplePeopleData } from "app/timesheet/sample.people.data";
5 |
6 | declare var moment: any;
7 |
8 | declare var google: any;
9 |
10 | export enum PageNames {
11 | TimePage,
12 | ProjectPage,
13 | PlacePage,
14 | PeoplePage
15 | }
16 |
17 |
18 | @Component({
19 | selector: 'at-timesheet',
20 | templateUrl: './timesheet.component.html',
21 | styleUrls: ['./timesheet.component.css']
22 | })
23 | export class TimesheetComponent {
24 |
25 | private userTimeData = [
26 |
27 | { day: "Monday", startTime: '9:00', endTime: '17:00', project: 'Agile Times', category: "Frontend" },
28 | { day: "Tuesday", startTime: '9:00', endTime: '17:00', project: 'Payroll App', category: "Backend" },
29 | { day: "Wednesday", startTime: '9:00', endTime: '17:00', project: 'Point of Sale App', category: "Operations" },
30 | { day: "Thursday", startTime: '9:00', endTime: '17:00', project: 'Mobile App', category: "Planning" },
31 | { day: "Friday", startTime: '9:00', endTime: '17:00', project: 'Agile Times', category: "Requirements" },
32 |
33 | ]
34 |
35 | daysOfWeek = [
36 | "Monday", "Tuesday", "Wednesday", "Thursday", "Friday"
37 | ]
38 |
39 | displayEditDialog = false;
40 |
41 | PageNames = PageNames;
42 |
43 | dialogPageIndex = PageNames.TimePage;
44 |
45 | dialogPages: MenuItem[] = [
46 | { label: "Time" },
47 | { label: "Project" },
48 | { label: "Place" },
49 | { label: "People" }
50 | ];
51 |
52 | private headerConfig = {
53 | left: 'prev,next today',
54 | center: 'title',
55 | right: 'month,agendaWeek,agendaDay'
56 | };
57 |
58 | private events = [
59 | {
60 | title: 'Recent Work',
61 | start: moment().format(), // '2017-06-02 07:00:00'
62 | end: moment().add(1, "hour").format()
63 | }
64 | ]
65 |
66 | projectsTree: TreeNode[] = SampleProjectsData.projects;
67 |
68 | selectedProject: TreeNode;
69 |
70 | private mapOptions = {
71 |
72 | center: { lat: -33.8688, lng: 151.2093 },
73 | zoom: 5
74 | };
75 |
76 | private mapOverlays = [
77 | new google.maps.Marker({ position: { lat: -35.3075, lng: 149.124417 }, title: "Canberra Office" }),
78 | new google.maps.Marker({ position: { lat: -33.8688, lng: 151.2093 }, title: "Sydney Office" }),
79 | new google.maps.Marker({ position: { lat: -37.813611, lng: 144.963056 }, title: "Melbourne Office" }),
80 | new google.maps.Marker({ position: { lat: -28.016667, lng: 153.4 }, title: "Gold Coast Office" })
81 | ];
82 |
83 | people = SamplePeopleData.people;
84 |
85 |
86 | messages: Message[] = [];
87 |
88 | constructor(private confirmationService: ConfirmationService) {
89 |
90 | }
91 |
92 | getTimesForDay(tabName: string) {
93 | return this.userTimeData.filter((row) => {
94 | return row.day == tabName;
95 | })
96 | }
97 |
98 | day = "Monday";
99 | dateAndMonth = moment().day(this.day).format("MMMM Do, YYYY");
100 |
101 | onChangeTabs(event) {
102 | let index = event.index;
103 | this.day = this.daysOfWeek[index];
104 | this.dateAndMonth = moment().day(this.day).format("MMMM Do, YYYY");
105 | }
106 |
107 | cancelDialog() {
108 |
109 | this.confirmationService.confirm({
110 | header: 'Cancel Time Creation',
111 | message: 'Cancel all changes. Are you sure?',
112 | accept: () => {
113 | this.displayEditDialog = false;
114 | this.messages.push({ severity: 'info', summary: 'Edits Cancelled', detail: 'No changes were saved' });
115 | },
116 | reject: () => {
117 | this.messages.push({ severity: 'warn', summary: 'Cancelled the Cancel', detail: 'Please continue your editing' });
118 | console.log("False cancel. Just keep editing.");
119 | }
120 | });
121 |
122 |
123 | }
124 |
125 | onMarkerClick(markerEvent) {
126 |
127 | let markerTitle = markerEvent.overlay.title;
128 | let markerPosition = markerEvent.overlay.position;
129 |
130 | alert(`You clicked on ${markerTitle} at ${markerPosition}`);
131 |
132 | markerEvent.map.panTo(markerPosition);
133 | markerEvent.map.setZoom(12);
134 |
135 | }
136 |
137 | saveNewEntry() {
138 | this.displayEditDialog = false;
139 | this.messages.push({ severity: 'success', summary: 'Entry Created', detail: 'Your entry has been created' });
140 | }
141 |
142 |
143 |
144 |
145 | }
146 |
--------------------------------------------------------------------------------
/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glenasmith/pluralsight-primeng/8e98e519a71cf70f8927fffe2289bbbaf4dac3f8/src/assets/.gitkeep
--------------------------------------------------------------------------------
/src/assets/data/hoursByTeam.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "Dev Team",
4 | "monthlyHours": {
5 | "Jan": 65,
6 | "Feb": 59,
7 | "Mar": 80,
8 | "Apr": 55,
9 | "May": 67,
10 | "Jun": 73
11 | }
12 | },
13 | {
14 | "name": "Ops Team",
15 | "monthlyHours": {
16 | "Jan": 44,
17 | "Feb": 63,
18 | "Mar": 57,
19 | "Apr": 90,
20 | "May": 77,
21 | "Jun": 70
22 | }
23 | },
24 | {
25 | "name": "Management Team",
26 | "monthlyHours": {
27 | "Jan": 8,
28 | "Feb": 12,
29 | "Mar": 31,
30 | "Apr": 20,
31 | "May": 16,
32 | "Jun": 11
33 | }
34 | }
35 | ]
--------------------------------------------------------------------------------
/src/assets/data/people.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "id": 101,
4 | "name": "Glen Smith",
5 | "avatar": "/profiles/glen.jpg",
6 | "role": "Developer",
7 | "times": [
8 |
9 | ]
10 | },
11 | {
12 | "id": 102,
13 | "name": "Mary Cool",
14 | "avatar": "/profiles/mary.jpg",
15 | "role": "Developer",
16 | "times": [
17 |
18 | ]
19 | }
20 | ]
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/assets/data/projects.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "id": 201,
4 | "name": "Payroll System"
5 | },
6 | {
7 | "id": 202,
8 | "name": "Inventory System"
9 | }
10 | ]
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/assets/data/teams.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "id": 301,
4 | "name": "Front End Developers",
5 | "budgetHours" : 4000,
6 | "hoursSpent": 2600,
7 | "people": [
8 |
9 | ]
10 | },
11 | {
12 | "id": 302,
13 | "name": "Back End Developers",
14 | "budgetHours" : 6000,
15 | "hoursSpent": 3700,
16 | "people": [
17 |
18 | ]
19 | }
20 | ]
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/assets/img/logo-clear.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | Agile Times
53 |
54 |
55 |
--------------------------------------------------------------------------------
/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true
3 | };
4 |
--------------------------------------------------------------------------------
/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | // The file contents for the current environment will overwrite these during build.
2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do
3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead.
4 | // The list of which env maps to which file can be found in `angular-cli.json`.
5 |
6 | export const environment = {
7 | production: false
8 | };
9 |
--------------------------------------------------------------------------------
/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glenasmith/pluralsight-primeng/8e98e519a71cf70f8927fffe2289bbbaf4dac3f8/src/favicon.ico
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Agile Times
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | Loading...
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
2 | import { enableProdMode } from '@angular/core';
3 | import { environment } from './environments/environment';
4 | import { AppModule } from './app/app.module';
5 |
6 | if (environment.production) {
7 | enableProdMode();
8 | }
9 |
10 | platformBrowserDynamic().bootstrapModule(AppModule);
11 |
--------------------------------------------------------------------------------
/src/polyfills.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This file includes polyfills needed by Angular and is loaded before the app.
3 | * You can add your own extra polyfills to this file.
4 | *
5 | * This file is divided into 2 sections:
6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main
8 | * file.
9 | *
10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
13 | *
14 | * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
15 | */
16 |
17 | /***************************************************************************************************
18 | * BROWSER POLYFILLS
19 | */
20 |
21 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/
22 | // import 'core-js/es6/symbol';
23 | // import 'core-js/es6/object';
24 | // import 'core-js/es6/function';
25 | // import 'core-js/es6/parse-int';
26 | // import 'core-js/es6/parse-float';
27 | // import 'core-js/es6/number';
28 | // import 'core-js/es6/math';
29 | // import 'core-js/es6/string';
30 | // import 'core-js/es6/date';
31 | // import 'core-js/es6/array';
32 | // import 'core-js/es6/regexp';
33 | // import 'core-js/es6/map';
34 | // import 'core-js/es6/set';
35 |
36 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */
37 | // import 'classlist.js'; // Run `npm install --save classlist.js`.
38 |
39 | /** IE10 and IE11 requires the following to support `@angular/animation`. */
40 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`.
41 |
42 |
43 | /** Evergreen browsers require these. **/
44 | import 'core-js/es6/reflect';
45 |
46 |
47 |
48 | /** ALL Firefox browsers require the following to support `@angular/animation`. **/
49 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`.
50 |
51 |
52 |
53 | /***************************************************************************************************
54 | * Zone JS is required by Angular itself.
55 | */
56 | import 'zone.js/dist/zone'; // Included with Angular-CLI.
57 |
58 |
59 |
60 | /***************************************************************************************************
61 | * APPLICATION IMPORTS
62 | */
63 |
64 | /**
65 | * Date, currency, decimal and percent pipes.
66 | * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10
67 | */
68 | // import 'intl'; // Run `npm install --save intl`.
69 |
--------------------------------------------------------------------------------
/src/styles.css:
--------------------------------------------------------------------------------
1 | @import '~primeicons/primeicons.css';
2 |
3 | /* You can add global styles to this file, and also import other style files */
4 | body {
5 | background-color: #D9E0E7 !important;
6 | margin: 0px;
7 | font-size: 1em !important;
8 | }
9 |
10 | .ui-panel-titlebar {
11 | background-color: #2F8EE5 !important;
12 | color: white !important;
13 | }
14 |
15 | .ui-menu {
16 | width: auto !important;
17 | margin: 0px !important;
18 | border: none !important;
19 | background-color: #2D353C !important;
20 | }
21 |
22 | .ui-menu-list {
23 | background-color: black;
24 | }
25 |
26 | .ui-menuitem-text {
27 | color: white !important;
28 | }
29 |
30 | .ui-menuitem-icon {
31 | color: white !important;
32 | font-size: large !important;
33 | margin-right: 1em;
34 | }
35 |
36 | body .ui-menu .ui-menuitem :hover {
37 | background-color: #2F8EE5 !important;
38 | }
39 |
40 | .menu-selected {
41 | background-color: #F15B2A !important;
42 | color: #ffffff;
43 | }
44 |
45 | /* Debugging Layouts
46 | @media screen and (min-width: 64.063em) {
47 | .ui-lg-3 {
48 | background-color: green;
49 | }
50 | }
51 |
52 | @media screen and (min-width: 40.063em) {
53 | .ui-md-6 {
54 | background-color: yellow;
55 | }
56 | }
57 |
58 | @media screen and (max-width: 40em) {
59 | .ui-sm-12 {
60 | background-color: palevioletred;
61 | }
62 | }
63 | */
64 |
65 |
--------------------------------------------------------------------------------
/src/test.ts:
--------------------------------------------------------------------------------
1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files
2 |
3 | import 'zone.js/dist/long-stack-trace-zone';
4 | import 'zone.js/dist/proxy.js';
5 | import 'zone.js/dist/sync-test';
6 | import 'zone.js/dist/jasmine-patch';
7 | import 'zone.js/dist/async-test';
8 | import 'zone.js/dist/fake-async-test';
9 | import { getTestBed } from '@angular/core/testing';
10 | import {
11 | BrowserDynamicTestingModule,
12 | platformBrowserDynamicTesting
13 | } from '@angular/platform-browser-dynamic/testing';
14 |
15 | // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any.
16 | declare var __karma__: any;
17 | declare var require: any;
18 |
19 | // Prevent Karma from running prematurely.
20 | __karma__.loaded = function () {};
21 |
22 | // First, initialize the Angular testing environment.
23 | getTestBed().initTestEnvironment(
24 | BrowserDynamicTestingModule,
25 | platformBrowserDynamicTesting()
26 | );
27 | // Then we find all the tests.
28 | const context = require.context('./', true, /\.spec\.ts$/);
29 | // And load the modules.
30 | context.keys().map(context);
31 | // Finally, start Karma to run the tests.
32 | __karma__.start();
33 |
--------------------------------------------------------------------------------
/src/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": "",
4 | "declaration": false,
5 | "emitDecoratorMetadata": true,
6 | "experimentalDecorators": true,
7 | "lib": [
8 | "es2016",
9 | "dom"
10 | ],
11 | "mapRoot": "./",
12 | "module": "es2015",
13 | "moduleResolution": "node",
14 | "outDir": "../dist/out-tsc",
15 | "sourceMap": true,
16 | "target": "es5",
17 | "typeRoots": [
18 | "../node_modules/@types"
19 | ]
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rulesDirectory": [
3 | "node_modules/codelyzer"
4 | ],
5 | "rules": {
6 | "callable-types": true,
7 | "class-name": true,
8 | "comment-format": [
9 | true,
10 | "check-space"
11 | ],
12 | "curly": true,
13 | "eofline": true,
14 | "forin": true,
15 | "import-blacklist": [true],
16 | "import-spacing": true,
17 | "indent": [
18 | true,
19 | "spaces"
20 | ],
21 | "interface-over-type-literal": true,
22 | "label-position": true,
23 | "max-line-length": [
24 | true,
25 | 140
26 | ],
27 | "member-access": false,
28 | "member-ordering": [
29 | true,
30 | "static-before-instance",
31 | "variables-before-functions"
32 | ],
33 | "no-arg": true,
34 | "no-bitwise": true,
35 | "no-console": [
36 | true,
37 | "debug",
38 | "info",
39 | "time",
40 | "timeEnd",
41 | "trace"
42 | ],
43 | "no-construct": true,
44 | "no-debugger": true,
45 | "no-duplicate-variable": true,
46 | "no-empty": false,
47 | "no-empty-interface": true,
48 | "no-eval": true,
49 | "no-inferrable-types": true,
50 | "no-shadowed-variable": true,
51 | "no-string-literal": false,
52 | "no-string-throw": true,
53 | "no-switch-case-fall-through": true,
54 | "no-trailing-whitespace": true,
55 | "no-unused-expression": true,
56 | "no-use-before-declare": true,
57 | "no-var-keyword": true,
58 | "object-literal-sort-keys": false,
59 | "one-line": [
60 | true,
61 | "check-open-brace",
62 | "check-catch",
63 | "check-else",
64 | "check-whitespace"
65 | ],
66 | "prefer-const": true,
67 | "quotemark": [
68 | true,
69 | "single"
70 | ],
71 | "radix": true,
72 | "semicolon": [
73 | "always"
74 | ],
75 | "triple-equals": [
76 | true,
77 | "allow-null-check"
78 | ],
79 | "typedef-whitespace": [
80 | true,
81 | {
82 | "call-signature": "nospace",
83 | "index-signature": "nospace",
84 | "parameter": "nospace",
85 | "property-declaration": "nospace",
86 | "variable-declaration": "nospace"
87 | }
88 | ],
89 | "typeof-compare": true,
90 | "unified-signatures": true,
91 | "variable-name": false,
92 | "whitespace": [
93 | true,
94 | "check-branch",
95 | "check-decl",
96 | "check-operator",
97 | "check-separator",
98 | "check-type"
99 | ],
100 |
101 | "directive-selector": [true, "attribute", "app", "camelCase"],
102 | "component-selector": [true, "element", "app", "kebab-case"],
103 | "use-input-property-decorator": true,
104 | "use-output-property-decorator": true,
105 | "use-host-property-decorator": true,
106 | "no-input-rename": true,
107 | "no-output-rename": true,
108 | "use-life-cycle-interface": true,
109 | "use-pipe-transform-interface": true,
110 | "component-class-suffix": true,
111 | "directive-class-suffix": true,
112 | "no-access-missing-member": true,
113 | "templates-use-public": true,
114 | "invoke-injectable": true
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/xample-scripts/1.intro.md:
--------------------------------------------------------------------------------
1 | # Intro Module
2 |
3 | ## Overview of PrimeNG
4 |
5 | See Google Doc
6 |
7 | ## What's in the Box
8 |
9 | See Google Doc
10 |
11 | ## Getting Setup
12 |
13 | I'm presuming your using [angular-cli](https://github.com/angular/angular-cli) - and that tool churns hard.
14 |
15 | npm install -g @angular/cli
16 |
17 | At this moment in time, it works like this:
18 |
19 | Setup your project:
20 |
21 | ng new agile-times
22 |
23 | npm install primeng --save
24 | npm install font-awesome --save
25 |
26 | Edit your .angular-cli.json in your root dir to drag in the styles:
27 |
28 | "styles": [
29 | "../node_modules/font-awesome/css/font-awesome.css",
30 | "../node_modules/primeng/resources/primeng.css"
31 | "styles.css",
32 | ],
33 |
34 |
35 | ## Migrating Between Prime Versions (upgrading to the current version from the course version)
36 |
37 | A word on semantic versioning. Run NPM outdated to see what's changed. I'll keep the GitHub repo up to speed.
38 |
39 | npm outdated
40 |
41 | ## Themes
42 |
43 | Themes are build in with [good docs](https://www.primefaces.org/primeng/#/theming) based around [jQueryUI Themeroller](https://jqueryui.com/themeroller/). You can also purchase commercial themes.
44 |
45 | In this course we're using the free Bootstrap theme - but it won't look like the cliche Bootstrap site you're thinking of.
46 |
47 | Themes are configured just through CSS style application in your angular-cli.json. Here's what you need to know up front (I've added font-awesome to the mix)
48 |
49 | "styles": [
50 | "../node_modules/font-awesome/css/font-awesome.css",
51 | "../node_modules/primeng/resources/primeng.css",
52 | "../node_modules/primeng/resources/themes/bootstrap/theme.css",
53 | "styles.css"
54 | ],
55 |
56 |
57 |
58 | ## Your First Component: Panel
59 |
60 | The three step process of:
61 |
62 | 1. Find and import the component module (sometimes more than one)
63 | 1. Add markup to your page to invoke the component
64 | 1. (Optionally) wire up to a component backing data properties or methods
65 |
66 |
67 |
68 |
69 |
70 | Email To Notify:
71 |
72 |
73 |
74 |
75 | Force Backup:
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 | ## Styling Your First Component - into the /deep/ of CSS
84 |
85 | You can use /deep/ to style inside a panel (or the >>> selector which has now replaced it)
86 |
87 | p-panel >>> .ui-panel {
88 | margin-top: 1em;
89 | width: 98%;
90 | min-height: 300px;
91 | }
92 |
93 | ## Making use of Grid Systems
94 |
95 | Specify the grid using a combination of ui-g- ui-lg- ui-md- ui-sm-
96 |
97 | Drag your browser or use F12 tools to change to mobile view.
98 |
99 |
100 |
101 |
102 | Email To Notify:
103 |
104 |
105 |
106 |
107 | Force Backup:
108 | Backup System Now
109 |
110 |
111 |
112 |
113 |
114 | ## Making use of Font Awesome
115 |
116 | Once you install font-awesome, you have access to a huge collection of CSS fonts. Using they is a matter of styling an element with the appropriate class:
117 |
118 |
119 |
120 | which I could style for size and color with font-size: 50px or color: red or whatever.
121 |
122 | Because I'm styling the I element, I need to add both fa and fa-cloud-download, but many Prime components support an icon element which you can specify the name of the icon `fa-cloud-download` directly. We'll see that shortly in the menu.
123 |
124 |
125 | ## Building a Stats Component
126 |
127 | You can, of course, nest grids within grids:
128 |
129 |
130 |
131 |
Uptime Stats:
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
20
140 |
Days Uptime
141 |
142 |
143 |
144 |
145 |
146 |
147 | Challenge: Make your own statistics component that makes the labels and icons configurable
148 |
149 | ## Data Backed Components - The Main Menu
150 |
151 | Our p-panel isn't Data-backed, but some components are. The menu that you see in the application is a p-menu component:
152 |
153 | If you were using this component in your app, you'd first add it to your imports.
154 |
155 | MenuModule
156 |
157 | Then you'd add the markup:
158 |
159 |
160 |
161 | But in this case, the menu is populated by a backing model:
162 |
163 | private menuItems: MenuItem[];
164 |
165 | Remember, types aren't really a thing in JavaScript, they will be compiled away. But they are awesome for developer happiness.
166 |
167 | this.menuItems =
168 | {label: 'Dashboard', icon: 'fa-home', routerLink: ['/dashboard'] }
169 | {label: 'All Timesheets', icon: 'fa-calendar', routerLink: ['/alltimes'] },
170 | {label: 'My Timesheet', icon: 'fa-clock-o', routerLink: ['/mytimes'] },
171 | {label: 'My Projects', icon: 'fa-tasks', routerLink: ['/projects'] },
172 | {label: 'My Profile', icon: 'fa-users', routerLink: ['/profile'] },
173 | {label: 'Settings', icon: 'fa-sliders', routerLink: ['/settings'] }
174 | ]
175 |
176 | That's an example of that icon property we mentioned earlier!
177 |
178 |
179 | ## Architecture Map of Agile Times - Source Code Layout
180 |
181 | Show each menu of the sample app mapping to a component in the matching directory property.
182 |
183 | Our components will be pre-created in the sample app, ready for you to add your own goodness.
184 |
185 | Be cool to have a plunker to experiment with too. TODO make one!
186 |
187 |
188 |
--------------------------------------------------------------------------------
/xample-scripts/2.charts.md:
--------------------------------------------------------------------------------
1 |
2 | # Charts
3 |
4 |
5 |
6 | ## Intro the Chart Module
7 |
8 |
9 | import { ChartModule, MenuModule, PanelModule, DataTableModule} from 'primeng/primeng';
10 |
11 | imports: [
12 | ...
13 | ChartModule,
14 | ...
15 | ],
16 |
17 |
18 | ## Install Chart.js
19 |
20 | Install supporting library
21 |
22 | npm install chart.js --save
23 |
24 | Update the scripts section to run Chart.js on index.html startup
25 |
26 | "scripts": [
27 | "../node_modules/chart.js/dist/Chart.js"
28 | ],
29 |
30 |
31 | ## Creating your first Pie chart
32 |
33 |
34 |
35 |
36 |
37 | ## Structuring the Data
38 |
39 |
40 | But you need the data in a format like this:
41 |
42 | private hoursByProjectChartData = {
43 | labels: ['Payroll App', 'Agile Times App', 'Point of Sale App'],
44 | datasets: [
45 | {
46 | data: [8, 16, 24],
47 | backgroundColor: [
48 | "red",
49 | "blue",
50 | "yellow"
51 | ],
52 |
53 | }
54 | ]
55 | };
56 |
57 | But in reality, you probably have structured JSON data from a backend service:
58 |
59 |
60 | private hoursByProject = [
61 | {id: 1, name: 'Payroll App', hoursSpent: 8},
62 | {id: 2, name: 'Agile Times App', hoursSpent: 16},
63 | {id: 3, name: 'Point of Sale App', hoursSpent: 24},
64 | ]
65 |
66 |
67 | So you'll want to remap it:
68 |
69 |
70 | private pieData = this.hoursByProject.map((proj) => proj.hoursSpent);
71 | private pieLabels = this.hoursByProject.map((proj) => proj.name);
72 |
73 | Then run the demo:
74 |
75 |
76 |
77 | ## Fixing the Colours
78 |
79 | And those colours need to change to:
80 |
81 | private pieColors = this.configureDefaultColours(this.pieData);
82 |
83 | But how does the magic work? An array of colours to pick from for each new data item:
84 |
85 | const DEFAULT_COLORS = [
86 | '#6C76AF', '#EFA64C', '#00ACAC', '#2F8EE5',
87 | '#F15B2A', '#A62E5C', '#2A9FBC', '#9BC850',
88 | '#404040', '#675BA7'
89 | ]
90 |
91 | Then I'll write some code to slurp out those colours:
92 |
93 | private configureDefaultColours(data: number[]): string[] {
94 | let customColours = []
95 | if (data.length) {
96 | customColours = data.map((element, idx) => {
97 | return DEFAULT_COLORS[idx % DEFAULT_COLORS.length];
98 | });
99 | }
100 |
101 | return customColours;
102 | }
103 |
104 | Which gives us something much more please, but still readable
105 |
106 | private hoursByProjectChartData = {
107 | labels: this.pieLabels,
108 | datasets: [
109 | {
110 | data: this.pieData,
111 | backgroundColor: this.pieColors,
112 | }
113 | ]
114 | };
115 |
116 | ## Pies to Donuts
117 |
118 | Same datamodel. So it's a one-liner change:
119 |
120 |
121 |
122 | Same for polarArea
123 |
124 |
125 |
126 | ## Moving to Bar Charts
127 |
128 | Bars and Line charts work around series data. We have monthly hours by team:
129 |
130 | private hoursByTeam = [
131 |
132 | {
133 | name: 'Dev Team',
134 | monthlyHours: {
135 | 'Jan': 65,
136 | 'Feb': 59,
137 | 'Mar': 80,
138 | 'Apr': 55,
139 | 'May': 67,
140 | 'Jun': 73,
141 | }
142 | },
143 | {
144 | name: 'Ops Team',
145 | monthlyHours: {
146 | 'Jan': 44,
147 | 'Feb': 63,
148 | 'Mar': 57,
149 | 'Apr': 90,
150 | 'May': 77,
151 | 'Jun': 70,
152 |
153 | }
154 | }
155 |
156 | But we want to end up with series data:
157 |
158 | private hoursByTeamChartData = {
159 | labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
160 | datasets: [
161 | {
162 | label: 'Dev Team',
163 | backgroundColor: DEFAULT_COLORS[0],
164 | data: [65, 59, 80, 55, 67, 73]
165 | },
166 | {
167 | label: 'Ops Team',
168 | backgroundColor: DEFAULT_COLORS[1],
169 | data: [44, 63, 57, 90, 77, 70]
170 | }
171 | ]
172 | }
173 |
174 | We'll need to transform our JSON:
175 |
176 |
177 | private tranformHoursByTeamDataIntoChartData(hoursByTeamData) {
178 |
179 | let labels = Object.keys(hoursByTeamData[0].monthlyHours);
180 | let dataSets = hoursByTeamData.map( (nextTeam, idx) => {
181 | return {
182 | label: nextTeam.name,
183 | backgroundColor: DEFAULT_COLORS[idx % DEFAULT_COLORS.length],
184 | data: Object.keys(nextTeam.monthlyHours).map(key => nextTeam.monthlyHours[key])
185 | }
186 | });
187 |
188 | return {
189 | labels: labels,
190 | datasets: dataSets
191 | }
192 | }
193 |
194 |
195 | ## Line and Area Charts
196 |
197 | Again, a one-liner:
198 |
199 |
200 |
201 |
202 | If you don't want the fills - line, not area:
203 |
204 |
205 | return {
206 | label: nextTeam.name,
207 | fill: false,
208 | backgroundColor: DEFAULT_COLORS[idx % DEFAULT_COLORS.length],
209 | data: Object.keys(nextTeam.monthlyHours).map(key => nextTeam.monthlyHours[key])
210 | }
211 |
212 | ## Mixing Chart Data
213 |
214 | Chart type is set to bar, but then each data series can set it's preferred type:
215 |
216 |
217 | private hoursByTeamChartData = {
218 | labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
219 | datasets: [
220 | {
221 | type: 'line',
222 | fill: false,
223 | label: 'Dev Team',
224 | backgroundColor: DEFAULT_COLORS[0],
225 | data: [65, 59, 80, 55, 67, 73]
226 | },
227 | {
228 | type: 'bar',
229 | label: 'Ops Team',
230 | backgroundColor: DEFAULT_COLORS[1],
231 | data: [44, 63, 57, 90, 77, 70]
232 | }
233 | ]
234 | }
235 |
236 | So let's create a fresh tag for it:
237 |
238 |
239 |
240 |
241 |
242 | So let's re-use our current transformer to popular our Mixed Chart data:
243 |
244 | private hoursByTeamChartMixedData = {};
245 |
246 | constructor() {
247 |
248 | let mixedData = this.tranformHoursByTeamDataIntoChartData(this.hoursByTeam);
249 |
250 | mixedData.datasets[0].type = 'bar';
251 | mixedData.datasets[1].type = 'line';
252 | mixedData.datasets[2].type = 'line';
253 | mixedData.datasets[2].fill = true;
254 |
255 | this.hoursByTeamChartMixedData = mixedData;
256 |
257 | }
258 |
259 | ## Added interactivity with (onDataSelect)
260 |
261 | We can implement (onDataSelect):
262 |
263 |
265 |
266 | For now, let's just show an alert box. We'll circle back later:
267 |
268 | private onMixedClick(event) {
269 |
270 |
271 | let labelClicked = this.hoursByTeamChartMixedData.datasets[event.element._datasetIndex].label;
272 | let valueClicked = this.hoursByTeamChartMixedData.datasets[event.element._datasetIndex].data[event.element._index];
273 |
274 | alert(`Looks like ${labelClicked} worked ${valueClicked} hours`);
275 |
276 | }
277 |
278 | This gives us access to:
279 |
280 | //event.dataset = Selected dataset
281 | //event.element = Selected element
282 | //event.element._datasetIndex = Which dataset series was clicked on - ie. which array was clicked (0 indexed)
283 | //event.element._index = Which element within that array was clicked (0 indexed)
284 |
285 |
286 | ## Deeper Customisation
287 |
288 | There's more treasure in [Chart.js](http://www.chartjs.org/docs/) if you want to dig further:
289 |
290 |
291 | private chartOptions = {
292 | title: {
293 | display: true,
294 | text: 'Hours By Team'
295 | },
296 | legend: {
297 | position: 'left'
298 | },
299 | responsive: false,
300 | animation : false
301 | }
302 |
303 | Plus add them to the tag you care about:
304 |
305 |
307 |
308 |
309 | ## Realtime Charting
310 |
311 |
312 |
313 |
314 | Bind to the element:
315 |
316 |
319 |
320 | Grab a handle to the element:
321 |
322 | @ViewChild("mixedChart") mixedChart : UIChart;
323 |
324 | And the import:
325 |
326 | import { UIChart } from 'primeng/primeng';
327 |
328 | Implement after view init:
329 |
330 | import {Component, OnInit, ViewChild, AfterViewInit} from '@angular/core';
331 | export class DashboardComponent implements OnInit, AfterViewInit {
332 |
333 | Implement the view init code to make the magic happen:
334 |
335 | Observable.interval(3000).timeInterval().subscribe(() => {
336 |
337 | var hoursByTeam = this.hoursByTeamChartMixedData.datasets;
338 | var randomised = hoursByTeam.map( (dataset) => {
339 |
340 | dataset.data = dataset.data.map( (hours) => hours * Math.random() );
341 |
342 | });
343 | this.mixedChart.refresh();
344 | });
345 |
346 | If you're changing the whole chart object, you'll need to call:
347 |
348 | this.mixedChart.reinit();
349 |
350 | ## Summarise and take action
351 |
352 |
--------------------------------------------------------------------------------
/xample-scripts/3.forms.md:
--------------------------------------------------------------------------------
1 |
2 | # Forms
3 |
4 | Reactive is the way to go. Much more configurable than Template Driven.
5 |
6 | Import the ReactiveFormsModule before you start!
7 |
8 | imports: [
9 | BrowserModule,
10 | FormsModule,
11 | ReactiveFormsModule,
12 |
13 |
14 | ## Create a basic reactive form
15 |
16 | Note to get theme and validation styling, we need to add a `pInputText` directive.
17 |
18 |