From c724e2adaa7e93986a3f7d3d64a3a896b9f6af52 Mon Sep 17 00:00:00 2001 From: Richard Guthrie Date: Mon, 17 Sep 2018 09:37:10 -0700 Subject: [PATCH] Manage challenge - Add Challenge Support (#211) * Add Challenge Definition API to leaderboard * Initial Implementation of Add a challenge * Linting fixes --- .../web/Controllers/LeaderboardController.cs | 64 ++++- leaderboard/web/angular.json | 1 + leaderboard/web/package-lock.json | 43 +++ leaderboard/web/package.json | 7 +- leaderboard/web/src/app/app.module.ts | 2 - .../web/src/app/pages/challenges/README.md | 52 ++++ .../web/src/app/pages/challenges/challenge.ts | 125 +++++++++ .../challenges-add.component.html | 41 +++ .../challenges-add.component.scss | 0 .../challenges-add.component.spec.ts | 25 ++ .../challenges-add.component.ts | 121 ++++++++ .../challenges-delete.component.html | 119 +++++++- .../challenges-delete.component.ts | 117 +++++++- .../challenges-manage.component.html | 120 +++++++- .../challenges-manage.component.scss | 2 + .../challenges-manage.component.ts | 263 +++++++++++++++++- .../challenges/challenges.component.html | 11 +- .../app/pages/challenges/challenges.module.ts | 40 ++- .../pages/dashboard/dashboard.component.ts | 14 +- .../web/src/app/pages/pages-routing.module.ts | 9 +- .../src/app/pages/teams/teams.component.html | 99 ++++--- .../src/app/services/challenges.service.ts | 37 ++- leaderboard/web/src/index.html | 1 + 23 files changed, 1226 insertions(+), 87 deletions(-) create mode 100644 leaderboard/web/src/app/pages/challenges/README.md create mode 100644 leaderboard/web/src/app/pages/challenges/challenge.ts create mode 100644 leaderboard/web/src/app/pages/challenges/challenges-add/challenges-add.component.html create mode 100644 leaderboard/web/src/app/pages/challenges/challenges-add/challenges-add.component.scss create mode 100644 leaderboard/web/src/app/pages/challenges/challenges-add/challenges-add.component.spec.ts create mode 100644 leaderboard/web/src/app/pages/challenges/challenges-add/challenges-add.component.ts diff --git a/leaderboard/api/web/Controllers/LeaderboardController.cs b/leaderboard/api/web/Controllers/LeaderboardController.cs index cb42f566..62c0cdf9 100644 --- a/leaderboard/api/web/Controllers/LeaderboardController.cs +++ b/leaderboard/api/web/Controllers/LeaderboardController.cs @@ -35,16 +35,17 @@ public LeaderboardController(LeaderboardContext context) // Challenges // * GET /api/leaderboard/challenges/ - get challenges for all teams // * GET /api/leaderboard/challenges/{teamName} - get challenges for a team + // * GET /api/leaderboard/challenges/id/{challengeId} - get challenge by id // * POST /api/leaderboard/challenges/ - create a challenge for a team // * PATCH /api/leaderboard/challenges/{challengeId} - update a challenge. Update start/end times for a challenge + //Challenge Definitions + // * GET /api/leaderboard/challengedefinitions - Get list of challenge definitions. + //Service Health // * GET /api/leaderboard/servicehealth/ - get health for all teams services // * GET /api/leaderboard/servicehealth/{teamName} - get health for a team` - //Challenge Definitions - // * GET /api/leaderboard/challengedefinitions/id/{challengeId} - // Sentinel Controller // * GET /api/sentinel/logs/{teamId} - gets all logs for a team // * POST /api/sentinel/logs/{teamId} - posts logs for a team @@ -176,11 +177,31 @@ public List GetChallenges() [HttpGet("challenges/{teamName}",Name = "GetChallengesForTeam")] public List GetChallengesForTeam(string teamName){ - var query = from c in _context.Challenges join t in _context.Teams on c.TeamId equals t.Id where t.TeamName == teamName select c; + Team team = _context.Teams.Where(t => t.TeamName == teamName).Single(); + //var query = from c in _context.Challenges join t in _context.Teams on c.TeamId equals t.Id where t.TeamName == teamName select c; + var query = _context.Challenges.Where(c => c.TeamId == team.Id) + .Include(tm => tm.Team) + .Include(cd => cd.ChallengeDefinition); return query.ToList(); } + /// + /// GET /api/leaderboard/challenges/id/{challengeId} - get challenge by id + /// + /// + /// List of challenges + [HttpGet("challenges/id/{challengeId}",Name = "GetChallengeById")] + public Challenge GetChallengeById(string challengeId){ + + var query = _context.Challenges.Where(c => c.Id == challengeId) + .Include(tm => tm.Team) + .Include(cd => cd.ChallengeDefinition); + return query.Single(); + } + + + /// /// POST /api/leaderboard/challenges/ - create a challenge for a team /// @@ -225,14 +246,26 @@ public IActionResult UpdateChallenge([FromBody] Challenge c) return NoContent(); } - //Service Health - // * GET /api/leaderboard/servicehealth/ - get health for all teams services - - // * GET /api/leaderboard/servicehealth/{teamName} - get health for a team` + //Challenge Definitions + // * GET /api/leaderboard/challengedefinitions - Get list of challenge definitions. /// /// /// + /// + [HttpGet("challengedefinitions", Name = "GetAllChallengeDefinitions")] + public List GetAllChallengeDefinitions() + { + var query = from cd in _context.ChallengeDefinitions orderby cd.MaxPoints ascending, cd.Name ascending select cd; + + return query.ToList(); + } + + //Service Health APIS + + /// + /// GET /api/leaderboard/servicehealth/ - get health for all teams services + /// /// ListOfServiceHealth Records [HttpGet("servicehealth", Name = "GetAllServiceHealth")] public List GetAllServiceHealth() @@ -240,5 +273,20 @@ public List GetAllServiceHealth() var query = _context.Teams.Include(tm => tm.ServiceStatus); return query.ToList(); } + + /// + /// GET /api/leaderboard/servicehealth/{teamName} - get health for a team + /// + /// + /// ListOfServiceHealth Records + + [HttpGet("servicehealth/{teamName}", Name = "GetServiceHealthForTeam")] + [Produces("application/json", Type = typeof(Team))] + public List GetServiceHealthForTeam(string teamName) + { + var query = _context.Teams.Where(tm => tm.TeamName == teamName) + .Include(tm => tm.ServiceStatus); + return query.ToList(); + } } } \ No newline at end of file diff --git a/leaderboard/web/angular.json b/leaderboard/web/angular.json index 16f68e18..fb64f0db 100644 --- a/leaderboard/web/angular.json +++ b/leaderboard/web/angular.json @@ -29,6 +29,7 @@ "node_modules/socicon/css/socicon.css", "node_modules/nebular-icons/scss/nebular-icons.scss", "node_modules/pace-js/templates/pace-theme-flash.tmpl.css", + "node_modules/@angular/material/prebuilt-themes/purple-green.css", "src/app/@theme/styles/styles.scss" ], "scripts": [ diff --git a/leaderboard/web/package-lock.json b/leaderboard/web/package-lock.json index b72722b8..f3d2ea8a 100644 --- a/leaderboard/web/package-lock.json +++ b/leaderboard/web/package-lock.json @@ -155,6 +155,14 @@ "tslib": "^1.9.0" } }, + "@angular/cdk": { + "version": "6.4.7", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-6.4.7.tgz", + "integrity": "sha512-18x0U66fLD5kGQWZ9n3nb75xQouXlWs7kUDaTd8HTrHpT1s2QIAqlLd1KxfrYiVhsEC2jPQaoiae7VnBlcvkBg==", + "requires": { + "tslib": "^1.7.1" + } + }, "@angular/cli": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-6.2.1.tgz", @@ -419,6 +427,23 @@ "integrity": "sha512-L6upXuyO42Z5XhtvbDoDuQEmXEOdSYeGOBmXSxb3ywb/0eh8kHk1Xft+8aaKKtazYjol0t+M+DlZgehqvk4vEA==", "dev": true }, + "@angular/material": { + "version": "6.4.7", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-6.4.7.tgz", + "integrity": "sha512-SdNx7Xovi24Kw9eU6lkLhY/7f2M7L9F+/uh6XuPr4jbGgCUVVpeeVI5ztZhsZRbj1sN+/r1p5w8u62apWWl5Ww==", + "requires": { + "parse5": "^5.0.0", + "tslib": "^1.7.1" + }, + "dependencies": { + "parse5": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz", + "integrity": "sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==", + "optional": true + } + } + }, "@angular/platform-browser": { "version": "6.1.7", "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-6.1.7.tgz", @@ -1111,6 +1136,14 @@ "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", "dev": true }, + "angular-bootstrap-datetimepicker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/angular-bootstrap-datetimepicker/-/angular-bootstrap-datetimepicker-2.2.1.tgz", + "integrity": "sha512-+iwo1Dc+ue10Ey8uS76ehkYcHHkGPld1p52LFvXc8p5WliFC1qulEZRyVW7/pCVcy2N16inVEzyNpIGod+Wo+A==", + "requires": { + "tslib": "^1.7.1" + } + }, "angular-font-awesome": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/angular-font-awesome/-/angular-font-awesome-3.1.2.tgz", @@ -7202,6 +7235,11 @@ "caller-id": "^0.1.0" } }, + "moment": { + "version": "2.22.2", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz", + "integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=" + }, "morgan": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.0.tgz", @@ -7800,6 +7838,11 @@ "integrity": "sha1-Nu/zIgE3nv3xGA+0ReUajiQl+fY=", "dev": true }, + "open-iconic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/open-iconic/-/open-iconic-1.1.1.tgz", + "integrity": "sha1-nc/Ix808Yc20ojaxo0eJTJetwMY=" + }, "opn": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/opn/-/opn-5.3.0.tgz", diff --git a/leaderboard/web/package.json b/leaderboard/web/package.json index d829faf5..a91824d0 100644 --- a/leaderboard/web/package.json +++ b/leaderboard/web/package.json @@ -23,12 +23,14 @@ "prepush": "npm run lint:ci" }, "dependencies": { - "@angular/animations": "~6.1.2", + "@angular/animations": "^6.1.7", + "@angular/cdk": "^6.4.7", "@angular/common": "~6.1.2", "@angular/compiler": "~6.1.2", "@angular/core": "~6.1.2", "@angular/forms": "~6.1.2", "@angular/http": "~6.1.2", + "@angular/material": "^6.4.7", "@angular/platform-browser": "~6.1.2", "@angular/platform-browser-dynamic": "~6.1.2", "@angular/router": "~6.1.2", @@ -42,6 +44,7 @@ "@nebular/theme": "~2.0.0-rc.10", "@ng-bootstrap/ng-bootstrap": "~2.2.2", "add": "^2.0.6", + "angular-bootstrap-datetimepicker": "^2.2.1", "angular-font-awesome": "^3.1.2", "bootstrap": "^4.1.3", "classlist.js": "1.1.20150312", @@ -49,9 +52,11 @@ "echarts": "~4.1.0", "intl": "1.2.5", "ionicons": "2.0.1", + "moment": "^2.22.2", "nebular-icons": "1.0.9", "ngx-echarts": "^2.3.1", "normalize.css": "6.0.0", + "open-iconic": "^1.1.1", "pace-js": "1.0.2", "roboto-fontface": "0.10.0", "rxjs": "^6.3.2", diff --git a/leaderboard/web/src/app/app.module.ts b/leaderboard/web/src/app/app.module.ts index 6ce186a5..0a9cbc78 100644 --- a/leaderboard/web/src/app/app.module.ts +++ b/leaderboard/web/src/app/app.module.ts @@ -24,8 +24,6 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; HttpClientModule, AppRoutingModule, FormsModule, - - NgbModule.forRoot(), ThemeModule.forRoot(), CoreModule.forRoot(), diff --git a/leaderboard/web/src/app/pages/challenges/README.md b/leaderboard/web/src/app/pages/challenges/README.md new file mode 100644 index 00000000..4a9930cb --- /dev/null +++ b/leaderboard/web/src/app/pages/challenges/README.md @@ -0,0 +1,52 @@ +# TODO + +## Add + +### Fields + • Team: required + • Challenge: required + • StartDateTime: set using custom control + • EndDateTime: disabled + • Score: read only + • MaxScore: read only + +### Init + • Create dummy challenge + +### Validations + • Team: required + • Challenge: required + • Team/ChallengeCombo: Not already completed, prompt user if not completed. + • StartDateTime: greater than enddate < end time of last completed challenge + • EndDateTime: greater than start time + +### Guards + • Can Deactivate: ask if want to lose changes + +## Edit + +### Fields + • Team: required + • Challenge: required + • StartDateTime: set using custom control + • EndDateTime: disabled + • Score: read only + • MaxScore: read only + +### Init + • Look up challenge to edit + • Enable startDateTime, enddatetime + • Disable team, challenge, score + +### Validations + • Team: required + • Challenge: required + • Team/ChallengeCombo: Not already completed, prompt user if not completed. Should not fire though + • StartDateTime: greater than enddate < end time of last completed challenge + • EndDateTime: greater than start time and < end Event Time + +### Guards + • Can Deactivate: ask if want to lose changes + + +## Add unsubscribe to list views diff --git a/leaderboard/web/src/app/pages/challenges/challenge.ts b/leaderboard/web/src/app/pages/challenges/challenge.ts new file mode 100644 index 00000000..684defb9 --- /dev/null +++ b/leaderboard/web/src/app/pages/challenges/challenge.ts @@ -0,0 +1,125 @@ +import {IChallenge} from '../../shared/challenge'; +import { ITeam } from '../../shared/team'; +import { IChallengeDefinition } from '../../shared/challengedefinition'; + +export enum ChallengeDateType { + Start, + End, +}; +export class Challenge implements IChallenge { + public id: string; + public teamId: string; + public challengeDefinitionId: string; + public startDateTime: string; + public endDateTime: string; + public isCompleted: boolean; + public score: number; + public challengeDefinition: IChallengeDefinition; + public team: ITeam; + + + + constructor() { + this.id = ''; + this.teamId = ''; + this.challengeDefinitionId = ''; + this.initStartDate(); + this.endDateTime = null; + this.isCompleted = false; + this.score = 0; + } + + getChallengeDate(dt: ChallengeDateType): string { + return dt === ChallengeDateType.Start ? this.startDateTime : this.endDateTime; + } + + getDate(dt: ChallengeDateType): Date { + return new Date(this.getYear(dt), this.getMonth(dt), this.getDay(dt)); + } + + getDay(dt: ChallengeDateType): number { + const d: Date = new Date(this.getChallengeDate(dt)); + return d.getDay(); + } + + getDayString(dt: ChallengeDateType): string { + const d: Date = new Date(this.getChallengeDate(dt)); + return ('0' + d.getDate().toString()).slice(-2); + } + + getMonth(dt: ChallengeDateType): number { + const d: Date = new Date(this.getChallengeDate(dt)); + // javascript getMonth returns 0-11 https://www.w3schools.com/jsref/jsref_getmonth.asp + return d.getMonth() + 1; + } + + getMonthString(dt: ChallengeDateType): string { + const d: Date = new Date(this.getChallengeDate(dt)); + // javascript getMonth returns 0-11 https://www.w3schools.com/jsref/jsref_getmonth.asp + const m: number = d.getMonth() + 1; + return ('0' + m.toString()).slice(-2); + } + + getYear(dt: ChallengeDateType): number { + const d: Date = new Date(this.getChallengeDate(dt)); + return d.getFullYear(); + } + + getYearString(dt: ChallengeDateType): string { + const d: Date = new Date(this.getChallengeDate(dt)); + const y: number = d.getFullYear(); + return ('0000' + y.toString()).slice(-4); + } + + getHours(dt: ChallengeDateType): number { + const d: Date = new Date(this.getChallengeDate(dt)); + return d.getHours(); + } + + gethoursString(dt: ChallengeDateType): string { + const d: Date = new Date(this.getChallengeDate(dt)); + const h: number = d.getHours(); + return ('0' + h.toString()).slice(-2); + } + getMinutes(dt: ChallengeDateType): number { + const d: Date = new Date(this.getChallengeDate(dt)); + return d.getMinutes(); + } + + getMinutesString(dt: ChallengeDateType): string { + const d: Date = new Date(this.getChallengeDate(dt)); + const m: number = d.getMinutes(); + return ('0' + m.toString()).slice(-2); + } + + initStartDate(): void { + const d: Date = new Date(); + const h: number = d.getHours(); + const m: number = this.round5(d.getMinutes()); + + this.setDate(ChallengeDateType.Start, d, h, m); + } + + round5(x: number): number { + if (x > 55) { + return 55; + } + + if (x < 0) { + return 0; + } + return Math.ceil(x / 5) * 5; + } + + setDate(dt: ChallengeDateType, dateCtrlValue: Date, h: number, m: number): void { + const day: string = ('0' + dateCtrlValue.getDate().toString()).slice(-2); + const month: string = ('0' + (dateCtrlValue.getMonth() + 1).toString()).slice(-2); + const year: string = ('0000' + dateCtrlValue.getFullYear().toString()).slice(-4); + const hour: string = ('0' + h.toString()).slice(-2); + const min: string = ('0' + m.toString()).slice(-2); + + const dateString = year + '-' + month + '-' + day + 'T' + hour + ':' + min + ':00.0000000' + + dt === ChallengeDateType.Start ? this.startDateTime = dateString : this.endDateTime = dateString; + } +} diff --git a/leaderboard/web/src/app/pages/challenges/challenges-add/challenges-add.component.html b/leaderboard/web/src/app/pages/challenges/challenges-add/challenges-add.component.html new file mode 100644 index 00000000..6b4ab160 --- /dev/null +++ b/leaderboard/web/src/app/pages/challenges/challenges-add/challenges-add.component.html @@ -0,0 +1,41 @@ +

Add Challenge

+
+
+ + +
+
+ + +
+ +
+ +
+ + +
+
+ Error: {{ errorMessage }} +
+
+
+
+

+ Form Value: {{ model | json }} +

+ +

+

+ Form Value: {{ addChallengeForm.value | json }} +

+ + +

+ Form Status: {{ addChallengeForm.status }} +

+ diff --git a/leaderboard/web/src/app/pages/challenges/challenges-add/challenges-add.component.scss b/leaderboard/web/src/app/pages/challenges/challenges-add/challenges-add.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/leaderboard/web/src/app/pages/challenges/challenges-add/challenges-add.component.spec.ts b/leaderboard/web/src/app/pages/challenges/challenges-add/challenges-add.component.spec.ts new file mode 100644 index 00000000..06f6eadb --- /dev/null +++ b/leaderboard/web/src/app/pages/challenges/challenges-add/challenges-add.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ChallengesAddComponent } from './challenges-add.component'; + +describe('ChallengesAddComponent', () => { + let component: ChallengesAddComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ ChallengesAddComponent ], + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ChallengesAddComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/leaderboard/web/src/app/pages/challenges/challenges-add/challenges-add.component.ts b/leaderboard/web/src/app/pages/challenges/challenges-add/challenges-add.component.ts new file mode 100644 index 00000000..f1d91dd5 --- /dev/null +++ b/leaderboard/web/src/app/pages/challenges/challenges-add/challenges-add.component.ts @@ -0,0 +1,121 @@ +import { Component, OnInit } from '@angular/core'; +import { FormGroup, FormBuilder, Validators } from '@angular/forms'; +import { ChallengesService } from '../../../services/challenges.service'; +import { Challenge } from '../challenge'; +import { TeamsService } from '../../../services/teams.service'; +import { ITeam } from '../../../shared/team'; +import { IChallengeDefinition } from '../../../shared/challengedefinition'; +@Component({ + selector: 'ngx-challenges-add', + templateUrl: './challenges-add.component.html', + styleUrls: ['./challenges-add.component.scss'], +}) +export class ChallengesAddComponent implements OnInit { + id: string; + model = new Challenge(); + teams: ITeam[]; + challengeDefinitions: IChallengeDefinition[]; + errorMessage = ''; + + model_test: Challenge = new Challenge(); + // { + // id: "0CC7CD9E-315F-42D2-9FFD-E6E23C856610", + // teamId: "931EA0C8-62CA-4663-BD56-7693AC09994C", + // challengeDefinitionId: "4C9B8D9A-62F0-45C7-ABD5-C44F92438D7A", + // startDateTime: "2018-08-29T12:30:00", + // endDateTime: null, + // isCompleted: false, + // score: 0, + // challengeDefinition: { + // id: "4C9B8D9A-62F0-45C7-ABD5-C44F92438D7A", + // name: "Challenge 3", + // maxPoints: 50, + // description: "Implement a monitoring solution for your MyDriving", + // scoreEnabled: true + // }, + // team: { + // id: "931EA0C8-62CA-4663-BD56-7693AC09994C", + // teamName: "otaprd510", + // downTimeMinutes: 184, + // points: 41, + // isScoringEnabled: false, + // serviceStatus: null, + // } + // }; + + constructor( private cs: ChallengesService, + private ts: TeamsService, + private fb: FormBuilder) { + + } + + addChallengeForm = this.fb.group({ + teamSelect: this.fb.group({ + team: ['', Validators.required], + }), + challengeDefinitionSelect: this.fb.group({ + challengeDefinition: [''], + }), + startDateTimeGroup: this.fb.group({ + }), + }); + + ngOnInit() { + + Promise.all([ + this.getTeams(), + this.getChallengeDefinitions()]) + .then((results: any[]) => { + // let dt = new Date(); + // this.model.startDateTime = dt.toLocaleDateString(); + // let startDateTimeGroup: FormGroup =this.addChallengeForm.controls.startDateTimeGroup as FormGroup; + // startDateTimeGroup.controls.startDateTime.setValue(this.model.startDateTime); + }); + } + + onSubmit() { + const teamSelect: FormGroup = this.addChallengeForm.controls.teamSelect as FormGroup; + this.model.team = teamSelect.controls.team.value; + this.model.teamId = this.model.team.id; + + const challengeSelect: FormGroup = this.addChallengeForm.controls.challengeDefinitionSelect as FormGroup; + this.model.challengeDefinition = challengeSelect.controls.team.value; + this.model.challengeDefinitionId = this.model.challengeDefinition.id; + + const startDateTimeGroup: FormGroup = this.addChallengeForm.controls.startDateTimeGroup as FormGroup; + + const d = new Date(startDateTimeGroup.controls.startDateTime.value); + this.model.startDateTime = d.toString() + + } + + getTeams() { + return new Promise((resolve, reject) => + this.ts.getTeams() + .subscribe( + data => { + this.teams = data; + resolve(data); + }, + error => { + this.errorMessage = error; + reject(error); + }, + )); + } + + getChallengeDefinitions() { + return new Promise((resolve, reject) => + this.cs.getChallengeDefinitions() + .subscribe( + data => { + this.challengeDefinitions = data; + resolve(data); + }, + error => { + this.errorMessage = error; + reject(error); + }, + )); + } +} diff --git a/leaderboard/web/src/app/pages/challenges/challenges-delete/challenges-delete.component.html b/leaderboard/web/src/app/pages/challenges/challenges-delete/challenges-delete.component.html index f305c519..776f45dc 100644 --- a/leaderboard/web/src/app/pages/challenges/challenges-delete/challenges-delete.component.html +++ b/leaderboard/web/src/app/pages/challenges/challenges-delete/challenges-delete.component.html @@ -1,3 +1,116 @@ -

- challenges-delete works! -

+ + + {{addEdit}} Challenge + + +
+ +
+ +
+ + + {{teamName}} + + +
+
+ +
+ +
+ + + {{cdName}} + + +
+
+ +
+ +
+ + + + keyboard_arrow_down + + + +
+
+ +
+ +
+ + + {{hour}} + +    + + + {{minute}} + + +
+
+ +
+ +
+ + + + keyboard_arrow_down + + + +
+
+ +
+ +
+ + + {{hour}} + +    + + + {{minute}} + + +
+
+ + +
+
+ +
+
+ +
+
+
+
Dirty: {{ form.dirty }} +
Touched: {{ form.touched }} +
Valid: {{ form.valid }} +
Value: {{ form.value | json }} +
{{errorMessage}} +
+
+
diff --git a/leaderboard/web/src/app/pages/challenges/challenges-delete/challenges-delete.component.ts b/leaderboard/web/src/app/pages/challenges/challenges-delete/challenges-delete.component.ts index 5262ac1a..e3b718c0 100644 --- a/leaderboard/web/src/app/pages/challenges/challenges-delete/challenges-delete.component.ts +++ b/leaderboard/web/src/app/pages/challenges/challenges-delete/challenges-delete.component.ts @@ -1,15 +1,126 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { FormGroup, FormBuilder } from '@angular/forms'; +import { Subscription } from 'rxjs'; +import { ChallengesService } from '../../../services/challenges.service'; +import { Challenge } from '../challenge'; +import { TeamsService } from '../../../services/teams.service'; +import { ITeam } from '../../../shared/team'; +import { IChallengeDefinition } from '../../../shared/challengedefinition'; + @Component({ selector: 'ngx-challenges-delete', templateUrl: './challenges-delete.component.html', styleUrls: ['./challenges-delete.component.scss'], }) -export class ChallengesDeleteComponent implements OnInit { +export class ChallengesDeleteComponent implements OnInit, OnDestroy { + form: FormGroup; + private sub: Subscription; + hours = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23]; + minutes = [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50]; + teamNames: string[]; + challengeDefinitionNames: string[]; + + + id: string; + teamName: string; + addEdit = 'Add'; + teams: ITeam[]; + challengeDefinitions: IChallengeDefinition[]; + challenges: Challenge[]; + model: Challenge = new Challenge(); + + errorMessage = ''; + + constructor(private route: ActivatedRoute, + private cs: ChallengesService, + private ts: TeamsService, + private fb: FormBuilder) { } - constructor() { } ngOnInit() { + this.form = this.fb.group({ + selectTeam: '', + selectChallenge: '', + startDateTime: '', + startHours: 1, + startMins: 0, + endDateTime: '', + endHours: 1, + endMins: 0, + }); + + this.sub = this.route.params.subscribe(params => { + this.id = params['id']; + this.teamName = params['teamname']; + }); + this.id = this.route.snapshot.paramMap.get('id'); + + Promise.all([ + this.getTeams(), + this.getChallengeDefinitions(), + // this.getChallengesForTeam(), + ]) + .then((results: any[]) => { + if (this.id !== null && this.id !== undefined) { + this.addEdit = 'Edit'; + + } + }); } + ngOnDestroy(): void { + this.sub.unsubscribe(); + } + + onSubmit() { } + + + getTeams() { + return new Promise((resolve, reject) => + this.ts.getTeams() + .subscribe( + data => { + this.teams = data; + this.teamNames = this.teams.map(tm => tm.teamName); + resolve(data); + }, + error => { + this.errorMessage = error; + reject(error); + }, + )); + } + + getChallenge(id: string) { + return new Promise((resolve, reject) => + this.cs.getChallenge(id) + .subscribe( + data => { + this.model = data; + resolve(data); + }, + error => { + this.errorMessage = error; + reject(error); + }, + )); + } + + getChallengeDefinitions() { + return new Promise((resolve, reject) => + this.cs.getChallengeDefinitions() + .subscribe( + data => { + this.challengeDefinitions = data; + this.challengeDefinitionNames = this.challengeDefinitions.map(cd => cd.name); + resolve(data); + }, + error => { + this.errorMessage = error; + reject(error); + }, + )); + } } diff --git a/leaderboard/web/src/app/pages/challenges/challenges-manage/challenges-manage.component.html b/leaderboard/web/src/app/pages/challenges/challenges-manage/challenges-manage.component.html index 123c41f8..839f0bfc 100644 --- a/leaderboard/web/src/app/pages/challenges/challenges-manage/challenges-manage.component.html +++ b/leaderboard/web/src/app/pages/challenges/challenges-manage/challenges-manage.component.html @@ -1,3 +1,117 @@ -

- challenges-add works! -

+ + + {{addEdit}} Challenge + + +
+ +
+ +
+ + + {{teamName}} + + +
+
+ +
+ +
+ + + {{cdName}} + + +
+
+
+
+
+ +
+ + + + keyboard_arrow_down + + {{startDate}} + +
+
+ +
+ +
+ + + {{hour}} + +    + + + {{minute}} + + +
+
+
+
+
+
+
+ +
+ + + + keyboard_arrow_down + + + +
+
+ +
+ +
+ + + {{hour}} + +    + + + {{minute}} + + +
+
+
+
+ +
+
+ +   + +
+
+
+
Dirty: {{ form.dirty }} +
Touched: {{ form.touched }} +
Valid: {{ form.valid }} +
Controls valid:{{controlsValid()}} +
Value: {{ form.value | json }} +
Model: {{model | json}} +
{{errorMessage}} +
+
+
diff --git a/leaderboard/web/src/app/pages/challenges/challenges-manage/challenges-manage.component.scss b/leaderboard/web/src/app/pages/challenges/challenges-manage/challenges-manage.component.scss index e69de29b..b6f0bbeb 100644 --- a/leaderboard/web/src/app/pages/challenges/challenges-manage/challenges-manage.component.scss +++ b/leaderboard/web/src/app/pages/challenges/challenges-manage/challenges-manage.component.scss @@ -0,0 +1,2 @@ +@import '~bootstrap/dist/css/bootstrap.min.css'; +@import '~open-iconic/font/css/open-iconic-bootstrap.css'; diff --git a/leaderboard/web/src/app/pages/challenges/challenges-manage/challenges-manage.component.ts b/leaderboard/web/src/app/pages/challenges/challenges-manage/challenges-manage.component.ts index b27072b0..e91fe002 100644 --- a/leaderboard/web/src/app/pages/challenges/challenges-manage/challenges-manage.component.ts +++ b/leaderboard/web/src/app/pages/challenges/challenges-manage/challenges-manage.component.ts @@ -1,15 +1,272 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Router, ActivatedRoute } from '@angular/router'; +import { FormGroup, FormControl, FormBuilder, Validators } from '@angular/forms'; +import { Subscription } from 'rxjs'; +import { ChallengesService } from '../../../services/challenges.service'; +import { Challenge, ChallengeDateType } from '../challenge'; +import { TeamsService } from '../../../services/teams.service'; +import { ITeam } from '../../../shared/team'; +import { IChallengeDefinition } from '../../../shared/challengedefinition'; @Component({ selector: 'ngx-challenges-manage', templateUrl: './challenges-manage.component.html', styleUrls: ['./challenges-manage.component.scss'], }) -export class ChallengesManageComponent implements OnInit { +export class ChallengesManageComponent implements OnInit, OnDestroy { + form: FormGroup; + private sub: Subscription; + hours = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23]; + minutes = [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55]; + teamNames: string[]; + challengeDefinitionNames: string[]; + + + id: string; + teamName: string; + addEdit = 'Add'; + teams: ITeam[]; + startDate: Date; + challengeDefinitions: IChallengeDefinition[]; + challengesForTeam: Challenge[]; + model: Challenge; + + errorMessage = ''; + + constructor(private route: ActivatedRoute, + private router: Router, + private cs: ChallengesService, + private ts: TeamsService, + private fb: FormBuilder) { } - constructor() { } ngOnInit() { + this.form = this.fb.group({ + selectTeam: ['', Validators.required], + selectChallenge: ['', Validators.required], + startDateTimeGroup: this.fb.group({ + startDateTime: [new Date(), Validators.required], + startHours: [1, Validators.required], + startMins: [0, Validators.required], + }), + endDateTimeGroup: this.fb.group({ + endDateTime: '', + endHours: 1, + endMins: 0, + }), + }); + + this.sub = this.route.params.subscribe(params => { + this.id = params['id']; + this.teamName = params['teamname']; + }); + + Promise.all([ + this.getTeams(), + this.getChallengeDefinitions(), + this.teamName !== undefined && this.teamName !== null ? this.getChallengesForTeam(this.teamName) : null, + this.id !== undefined && this.id !== null ? this.getChallenge(this.id) : null, + ]) + .then((results: any[]) => { + if (this.id !== null && this.id !== undefined) { + // edit code path + this.addEdit = 'Edit'; + + if (this.form) { + this.form.reset(); + } + + this.filterChallengeDefinitions(); + this.setSelectedTeam(); + this.setSelectedChallenge(); + } else { + // add code path + this.model = new Challenge(); + this.startDate = this.model.getDate(ChallengeDateType.Start); + } + + this.setDateTimes(); + }); + } + + ngOnDestroy(): void { + this.sub.unsubscribe(); + } + + onSubmit() { + this.model.challengeDefinitionId = + this.challengeDefinitions.find(cd => cd.name === this.form.controls.selectChallenge.value).id; + this.model.teamId = + this.teams.find(t => t.teamName === this.form.controls.selectTeam.value).id; + this.model.challengeDefinition = null; + this.model.team = null; + + const startDateGroup: FormGroup = this.form.controls.startDateTimeGroup as FormGroup; + + + const ds: Date = ((startDateGroup.controls.startDateTime as FormControl).value); + const hs: number = (startDateGroup.controls.startHours.value); + const ms: number = (startDateGroup.controls.startMins.value); + + this.model.setDate(ChallengeDateType.Start, ds, hs, ms); + + if (this.addEdit === 'Edit') { + const endDateGroup: FormGroup = this.form.controls.endDateTimeGroup as FormGroup; + + const de: Date = ((endDateGroup.controls.endDateTime as FormControl).value); + const he: number = (endDateGroup.controls.endHours.value); + const me: number = (endDateGroup.controls.endHours.value); + + this.model.setDate(ChallengeDateType.End, de, he, me); + } + + this.createChallenge().then(r => { + if (this.form) { + this.form.reset(); + } + this.router.navigate(['/pages/challenges']); + }); + } + + filterChallengeDefinitions() { + if (this.challengesForTeam && this.challengesForTeam.length > 0) { + const challengeNamesForTeam = this.challengesForTeam.filter(c => c.endDateTime !== null).map(c => c.challengeDefinition.name); + const challengesNotCompleted = this.challengeDefinitionNames + .filter(item => challengeNamesForTeam.indexOf(item) < 0); + this.challengeDefinitionNames = challengesNotCompleted; + } + } + + + getTeams() { + return new Promise((resolve, reject) => + this.ts.getTeams() + .subscribe( + data => { + this.teams = data; + this.teamNames = this.teams.map(tm => tm.teamName); + resolve(data); + }, + error => { + this.errorMessage = error; + reject(error); + }, + )); } + getChallenge(id: string) { + return new Promise((resolve, reject) => + this.cs.getChallenge(id) + .subscribe( + data => { + this.model = data; + resolve(data); + }, + error => { + this.errorMessage = error; + reject(error); + }, + )); + } + + getChallengeDefinitions() { + return new Promise((resolve, reject) => + this.cs.getChallengeDefinitions() + .subscribe( + data => { + this.challengeDefinitions = data; + this.challengeDefinitionNames = this.challengeDefinitions.map(cd => cd.name); + resolve(data); + }, + error => { + this.errorMessage = error; + reject(error); + }, + )); + } + + getChallengesForTeam(teamName: string) { + return new Promise((resolve, reject) => + this.cs.getChallengesForTeam(teamName) + .subscribe( + data => { + this.challengesForTeam = data; + resolve(data); + }, + error => { + this.errorMessage = error; + reject(error); + }, + )); + } + + createChallenge() { + return new Promise((resolve, reject) => + this.cs.createChallengeForTeam(this.model) + .subscribe( + data => { + this.model = data; + resolve(data); + }, + error => { + this.errorMessage = error; + reject(error); + }, + )); + } + + setSelectedTeam() { + const selectedTeamName = this.model.team.teamName; + this.form.controls.selectTeam.patchValue(selectedTeamName); + } + setSelectedChallenge() { + const selectedChallengeDefinitionName = this.model.challengeDefinition.name; + this.form.controls.selectChallenge.patchValue(selectedChallengeDefinitionName); + } + + setDateTimes(): void { + const startTimeGroup: FormGroup = this.form.controls.startDateTimeGroup as FormGroup; + const endTimeGroup: FormGroup = this.form.controls.endDateTimeGroup as FormGroup; + + let n: number = this.model.getHours(ChallengeDateType.Start); + startTimeGroup.controls.startHours.patchValue(n); + + n = this.model.getMinutes(ChallengeDateType.Start); + startTimeGroup.controls.startMins.patchValue(n); + + if (this.model.endDateTime != null) { + n = this.model.getHours(ChallengeDateType.End); + endTimeGroup.controls.endHours.patchValue(n); + + n = this.model.getMinutes(ChallengeDateType.End); + endTimeGroup.controls.endMins.patchValue(n); + } + } + + updateChallengeList() { + const selTeam: string = this.form.controls.selectTeam.value; + + Promise.all([ + this.getChallengesForTeam( selTeam ), + ]).then((results: any[]) => { + const cd: FormControl = this.form.controls.selectChallenge as FormControl; + cd.reset(); + this.filterChallengeDefinitions(); + }); + + } + + controlsValid(): string { + const startDateTimeGroup: FormGroup = this.form.controls.startDateTimeGroup as FormGroup; + + return 'selectTeam: ' + this.form.controls.selectTeam.valid + + ', selectChallenge: ' + this.form.controls.selectChallenge.valid + + ', startDateTime: ' + startDateTimeGroup.controls.startDateTime.valid + + ', startHours: ' + startDateTimeGroup.controls.startHours.valid + + ', startMins: ' + startDateTimeGroup.controls.startMins.valid; + } + + editEnabled(): boolean { + return this.addEdit === 'Edit' ? true : false; + } } diff --git a/leaderboard/web/src/app/pages/challenges/challenges.component.html b/leaderboard/web/src/app/pages/challenges/challenges.component.html index b6d417b5..1cd905d6 100644 --- a/leaderboard/web/src/app/pages/challenges/challenges.component.html +++ b/leaderboard/web/src/app/pages/challenges/challenges.component.html @@ -4,8 +4,13 @@
-
Filter by Team:
-
+ +
Filter by Team:
+
@@ -30,7 +35,7 @@

Filtered by: {{listFilter}}

- {{ challenge.team.teamName }} + {{ challenge.team.teamName }} {{ challenge.challengeDefinition.name}} {{ challenge.startDateTime }} diff --git a/leaderboard/web/src/app/pages/challenges/challenges.module.ts b/leaderboard/web/src/app/pages/challenges/challenges.module.ts index c3ffaec7..abe7717e 100644 --- a/leaderboard/web/src/app/pages/challenges/challenges.module.ts +++ b/leaderboard/web/src/app/pages/challenges/challenges.module.ts @@ -1,17 +1,53 @@ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { CommonModule } from '@angular/common'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { ChallengesComponent } from './challenges.component'; import { ChallengesManageComponent } from './challenges-manage/challenges-manage.component'; import { ChallengesDeleteComponent } from './challenges-delete/challenges-delete.component'; -import { FormsModule } from '@angular/forms'; +import { ChallengesAddComponent } from './challenges-add/challenges-add.component'; +import { + MatCardModule, + MatFormFieldModule, + MatDatepickerModule, + MatInputModule, + MatNativeDateModule, + MatIconModule, + MatSelectModule, + MatOptionModule, + MatButtonModule } from '@angular/material'; @NgModule({ imports: [ CommonModule, FormsModule, + ReactiveFormsModule, RouterModule, + MatCardModule, + MatFormFieldModule, + MatDatepickerModule, + MatInputModule, + MatNativeDateModule, + MatIconModule, + MatSelectModule, + MatOptionModule, + MatButtonModule, ], - declarations: [ChallengesComponent, ChallengesManageComponent, ChallengesDeleteComponent], + exports: [ + MatCardModule, + MatFormFieldModule, + MatDatepickerModule, + MatInputModule, + MatNativeDateModule, + MatIconModule, + MatSelectModule, + MatOptionModule, + MatButtonModule, + ], + declarations: [ + ChallengesComponent, + ChallengesManageComponent, + ChallengesDeleteComponent, + ChallengesAddComponent], }) export class ChallengesModule { } diff --git a/leaderboard/web/src/app/pages/dashboard/dashboard.component.ts b/leaderboard/web/src/app/pages/dashboard/dashboard.component.ts index 10614e6b..f7ad1d20 100644 --- a/leaderboard/web/src/app/pages/dashboard/dashboard.component.ts +++ b/leaderboard/web/src/app/pages/dashboard/dashboard.component.ts @@ -1,5 +1,5 @@ -import { Component, OnInit, Injectable } from '@angular/core'; -import {interval} from 'rxjs'; +import { Component, OnInit, Injectable, OnDestroy } from '@angular/core'; +import {interval, Subscription} from 'rxjs'; import { ITeam } from '../../shared/team'; import { TeamsService } from '../../services/teams.service'; @@ -10,9 +10,9 @@ import { TeamsService } from '../../services/teams.service'; styleUrls: ['./dashboard.component.scss'], templateUrl: './dashboard.component.html', }) -export class DashboardComponent implements OnInit { +export class DashboardComponent implements OnInit, OnDestroy { teams: ITeam[]; - private pollingData: any; // tslint:disable-line + private pollingData: Subscription; // tslint:disable-line errorMessage = ''; constructor(private teamService: TeamsService) { @@ -33,4 +33,10 @@ export class DashboardComponent implements OnInit { ); }); } + + ngOnDestroy(): void { + // Called once, before the instance is destroyed. + // Add 'implements OnDestroy' to the class. + this.pollingData.unsubscribe(); + } } diff --git a/leaderboard/web/src/app/pages/pages-routing.module.ts b/leaderboard/web/src/app/pages/pages-routing.module.ts index b5596c95..c69a3fc0 100644 --- a/leaderboard/web/src/app/pages/pages-routing.module.ts +++ b/leaderboard/web/src/app/pages/pages-routing.module.ts @@ -8,6 +8,7 @@ import { TeamsAddComponent } from './teams/teams-add/teams-add.component'; import { TeamsDeleteComponent } from './teams/teams-delete/teams-delete.component'; import { ChallengesComponent } from './challenges/challenges.component'; import { ChallengesManageComponent } from './challenges//challenges-manage/challenges-manage.component'; +import { ChallengesAddComponent } from './challenges//challenges-add/challenges-add.component'; import { ChallengesDeleteComponent } from './challenges/challenges-delete/challenges-delete.component'; const routes: Routes = [{ @@ -35,7 +36,7 @@ const routes: Routes = [{ component: ChallengesComponent, }, { - path: 'challenges/challenges-manage/:id', + path: 'challenges/challenges-manage/:id/:teamname', component: ChallengesManageComponent, }, { @@ -43,7 +44,11 @@ const routes: Routes = [{ component: ChallengesManageComponent, }, { - path: 'challenges/challenges-delete', + path: 'challenges/challenges-add', + component: ChallengesAddComponent, + }, + { + path: 'challenges/challenges-delete/:id', component: ChallengesDeleteComponent, }, { diff --git a/leaderboard/web/src/app/pages/teams/teams.component.html b/leaderboard/web/src/app/pages/teams/teams.component.html index 82f3c817..fb5d1728 100644 --- a/leaderboard/web/src/app/pages/teams/teams.component.html +++ b/leaderboard/web/src/app/pages/teams/teams.component.html @@ -1,58 +1,53 @@ -
-
- {{pageTitle}} +
+
+ {{pageTitle}} +
+
+
+ -
-
- -
Filter by:
-
- - link to user component -
-
-
-
-

Filtered by team name: {{listFilter}}

-
-
-
- - - - - - - - - - - - - - - - - -
Team NameScoreDowntime (mins)Is Scoring Enabled
- {{ team.teamName }} - {{ team.points }}{{ team.downTimeMinutes }} - - -
-
+
Filter by Team Name:
+
+ +
+
+
+
+

Filtered by team name: {{listFilter}}

-
- Error: {{ errorMessage }} +
+ + + + + + + + + + + + + + + + + +
Team NameScoreDowntime (mins)Is Scoring Enabled
+ {{ team.teamName }} + {{ team.points }}{{ team.downTimeMinutes }} + + +
+
+
+
+ Error: {{ errorMessage }} +
diff --git a/leaderboard/web/src/app/services/challenges.service.ts b/leaderboard/web/src/app/services/challenges.service.ts index 812e4b77..3db08b51 100644 --- a/leaderboard/web/src/app/services/challenges.service.ts +++ b/leaderboard/web/src/app/services/challenges.service.ts @@ -1,9 +1,10 @@ import { Injectable } from '@angular/core'; -import { HttpClient, HttpErrorResponse } from '@angular/common/http'; +import { HttpClient, HttpErrorResponse, HttpHeaders } from '@angular/common/http'; import { Observable, throwError } from 'rxjs'; import { catchError, tap } from 'rxjs/operators'; import {environment} from '../../environments/environment'; import { IChallenge } from '../shared/challenge'; +import { IChallengeDefinition } from '../shared/challengedefinition'; @Injectable({ providedIn: 'root', @@ -20,6 +21,40 @@ export class ChallengesService { catchError(this.handleError)); }; + getChallengeDefinitions(): Observable { + return this.http.get(this.backendUrl + 'challengeDefinitions').pipe( + // tslint:disable-next-line:no-console + tap(data => console.log('All: ' + JSON.stringify(data))), + catchError(this.handleError)); + }; + + getChallenge(id: string): Observable { + return this.http.get(this.backendUrl + 'challenges/id/' + id).pipe( + // tslint:disable-next-line:no-console + tap(data => console.log('All: ' + JSON.stringify(data))), + catchError(this.handleError)); + } + + getChallengesForTeam(teamName: string): Observable { + return this.http.get(this.backendUrl + 'challenges/' + teamName).pipe( + // tslint:disable-next-line:no-console + tap(data => console.log('All: ' + JSON.stringify(data))), + catchError(this.handleError)); + } + + createChallengeForTeam(c: IChallenge): Observable { + const url = this.backendUrl + 'challenges'; + const payload = JSON.stringify(c); + const headers = new HttpHeaders({'Content-Type': 'application/json', 'Accept': '*/*'}); + const options = { + headers: headers, + }; + + return this.http.post(url, payload, options).pipe( + // tslint:disable-next-line:no-console + tap(data => console.log('All: ' + JSON.stringify(data))), + catchError(this.handleError)); + } private handleError(err: HttpErrorResponse) { // logging it to the console diff --git a/leaderboard/web/src/index.html b/leaderboard/web/src/index.html index 706538ce..ecc1c88d 100644 --- a/leaderboard/web/src/index.html +++ b/leaderboard/web/src/index.html @@ -6,6 +6,7 @@ +