[Scouting App] Add Driver Ranking
Move the driver ranking data collection from google forms to the scouting app.
The data collected is used to calculate a overall driver rank for the picklist.
Signed-off-by: Filip Kujawa <filip.j.kujawa@gmail.com>
Change-Id: Id459e8dec1fa79c1a9f49cc40ffa83014e51db16
diff --git a/scouting/www/BUILD b/scouting/www/BUILD
index 0b7cebb..ee0659b 100644
--- a/scouting/www/BUILD
+++ b/scouting/www/BUILD
@@ -16,6 +16,7 @@
use_angular_plugin = True,
visibility = ["//visibility:public"],
deps = [
+ "//scouting/www/driver_ranking",
"//scouting/www/entry",
"//scouting/www/import_match_list",
"//scouting/www/match_list",
diff --git a/scouting/www/app.ng.html b/scouting/www/app.ng.html
index 297fd39..d9dbead 100644
--- a/scouting/www/app.ng.html
+++ b/scouting/www/app.ng.html
@@ -40,6 +40,15 @@
<li class="nav-item">
<a
class="nav-link"
+ [class.active]="tabIs('DriverRanking')"
+ (click)="switchTabToGuarded('DriverRanking')"
+ >
+ Driver Ranking
+ </a>
+ </li>
+ <li class="nav-item">
+ <a
+ class="nav-link"
[class.active]="tabIs('ImportMatchList')"
(click)="switchTabToGuarded('ImportMatchList')"
>
@@ -80,6 +89,7 @@
*ngSwitchCase="'Entry'"
></app-entry>
<frc971-notes *ngSwitchCase="'Notes'"></frc971-notes>
+ <app-driver-ranking *ngSwitchCase="'DriverRanking'"></app-driver-ranking>
<app-import-match-list
*ngSwitchCase="'ImportMatchList'"
></app-import-match-list>
diff --git a/scouting/www/app.ts b/scouting/www/app.ts
index 4f95c90..b26f815 100644
--- a/scouting/www/app.ts
+++ b/scouting/www/app.ts
@@ -4,6 +4,7 @@
| 'MatchList'
| 'Notes'
| 'Entry'
+ | 'DriverRanking'
| 'ImportMatchList'
| 'ShiftSchedule'
| 'View';
diff --git a/scouting/www/app_module.ts b/scouting/www/app_module.ts
index 8c18f7a..04d72b3 100644
--- a/scouting/www/app_module.ts
+++ b/scouting/www/app_module.ts
@@ -9,6 +9,7 @@
import {NotesModule} from './notes/notes.module';
import {ShiftScheduleModule} from './shift_schedule/shift_schedule.module';
import {ViewModule} from './view/view.module';
+import {DriverRankingModule} from './driver_ranking/driver_ranking.module';
@NgModule({
declarations: [App],
@@ -20,6 +21,7 @@
ImportMatchListModule,
MatchListModule,
ShiftScheduleModule,
+ DriverRankingModule,
ViewModule,
],
exports: [App],
diff --git a/scouting/www/driver_ranking/BUILD b/scouting/www/driver_ranking/BUILD
new file mode 100644
index 0000000..10b6f99
--- /dev/null
+++ b/scouting/www/driver_ranking/BUILD
@@ -0,0 +1,26 @@
+load("@npm//@bazel/typescript:index.bzl", "ts_library")
+
+ts_library(
+ name = "driver_ranking",
+ srcs = [
+ "driver_ranking.component.ts",
+ "driver_ranking.module.ts",
+ ],
+ angular_assets = [
+ "driver_ranking.component.css",
+ "driver_ranking.ng.html",
+ "//scouting/www:common_css",
+ ],
+ compiler = "//tools:tsc_wrapped_with_angular",
+ target_compatible_with = ["@platforms//cpu:x86_64"],
+ use_angular_plugin = True,
+ visibility = ["//visibility:public"],
+ deps = [
+ "//scouting/webserver/requests/messages:error_response_ts_fbs",
+ "//scouting/webserver/requests/messages:submit_driver_ranking_ts_fbs",
+ "@com_github_google_flatbuffers//ts:flatbuffers_ts",
+ "@npm//@angular/common",
+ "@npm//@angular/core",
+ "@npm//@angular/forms",
+ ],
+)
diff --git a/scouting/www/driver_ranking/driver_ranking.component.css b/scouting/www/driver_ranking/driver_ranking.component.css
new file mode 100644
index 0000000..e220645
--- /dev/null
+++ b/scouting/www/driver_ranking/driver_ranking.component.css
@@ -0,0 +1,3 @@
+* {
+ padding: 10px;
+}
diff --git a/scouting/www/driver_ranking/driver_ranking.component.ts b/scouting/www/driver_ranking/driver_ranking.component.ts
new file mode 100644
index 0000000..aadb3b0
--- /dev/null
+++ b/scouting/www/driver_ranking/driver_ranking.component.ts
@@ -0,0 +1,93 @@
+import {Component, OnInit} from '@angular/core';
+import {Builder, ByteBuffer} from 'flatbuffers';
+import {SubmitDriverRanking} from 'org_frc971/scouting/webserver/requests/messages/submit_driver_ranking_generated';
+import {ErrorResponse} from 'org_frc971/scouting/webserver/requests/messages/error_response_generated';
+
+// TeamSelection: Display form to input which
+// teams to rank and the match number.
+// Data: Display the ranking interface where
+// the scout can reorder teams and submit data.
+type Section = 'TeamSelection' | 'Data';
+
+@Component({
+ selector: 'app-driver-ranking',
+ templateUrl: './driver_ranking.ng.html',
+ styleUrls: ['../common.css', './driver_ranking.component.css'],
+})
+export class DriverRankingComponent {
+ section: Section = 'TeamSelection';
+
+ // Stores the team keys and rank (order of the array).
+ team_ranking: number[] = [971, 972, 973];
+
+ match_number: number = 1;
+
+ errorMessage = '';
+
+ setTeamNumbers() {
+ this.section = 'Data';
+ }
+
+ rankUp(index: number) {
+ if (index > 0) {
+ this.changeRank(index, index - 1);
+ }
+ }
+
+ rankDown(index: number) {
+ if (index < 2) {
+ this.changeRank(index, index + 1);
+ }
+ }
+
+ // Change the rank of a team in team_ranking.
+ // Move the the team at index 'fromIndex'
+ // to the index 'toIndex'.
+ // Ex. Moving the rank 2 (index 1) team to rank1 (index 0)
+ // would be changeRank(1, 0)
+
+ changeRank(fromIndex: number, toIndex: number) {
+ var element = this.team_ranking[fromIndex];
+ this.team_ranking.splice(fromIndex, 1);
+ this.team_ranking.splice(toIndex, 0, element);
+ }
+
+ editTeams() {
+ this.section = 'TeamSelection';
+ }
+
+ async submitData() {
+ const builder = new Builder();
+ builder.finish(
+ SubmitDriverRanking.createSubmitDriverRanking(
+ builder,
+ this.match_number,
+ this.team_ranking[0],
+ this.team_ranking[1],
+ this.team_ranking[2]
+ )
+ );
+ const buffer = builder.asUint8Array();
+ const res = await fetch('/requests/submit/submit_driver_ranking', {
+ method: 'POST',
+ body: buffer,
+ });
+
+ if (!res.ok) {
+ const resBuffer = await res.arrayBuffer();
+ const fbBuffer = new ByteBuffer(new Uint8Array(resBuffer));
+ const parsedResponse = ErrorResponse.getRootAsErrorResponse(fbBuffer);
+
+ const errorMessage = parsedResponse.errorMessage();
+ this.errorMessage = `Received ${res.status} ${res.statusText}: "${errorMessage}"`;
+ return;
+ }
+
+ // Increment the match number.
+ this.match_number = this.match_number + 1;
+
+ // Reset Data.
+ this.section = 'TeamSelection';
+ this.team_ranking = [971, 972, 973];
+ }
+}
diff --git a/scouting/www/driver_ranking/driver_ranking.module.ts b/scouting/www/driver_ranking/driver_ranking.module.ts
new file mode 100644
index 0000000..7fe3623
--- /dev/null
+++ b/scouting/www/driver_ranking/driver_ranking.module.ts
@@ -0,0 +1,11 @@
+import {CommonModule} from '@angular/common';
+import {NgModule} from '@angular/core';
+import {FormsModule} from '@angular/forms';
+import {DriverRankingComponent} from './driver_ranking.component';
+
+@NgModule({
+ declarations: [DriverRankingComponent],
+ exports: [DriverRankingComponent],
+ imports: [CommonModule, FormsModule],
+})
+export class DriverRankingModule {}
diff --git a/scouting/www/driver_ranking/driver_ranking.ng.html b/scouting/www/driver_ranking/driver_ranking.ng.html
new file mode 100644
index 0000000..21aeec1
--- /dev/null
+++ b/scouting/www/driver_ranking/driver_ranking.ng.html
@@ -0,0 +1,51 @@
+<h2>Driver Ranking</h2>
+
+<ng-container [ngSwitch]="section">
+ <div *ngSwitchCase="'TeamSelection'">
+ <label for="match_number_selection">Match Number</label>
+ <input
+ [(ngModel)]="match_number"
+ type="number"
+ id="match_number_selection"
+ min="1"
+ max="9999"
+ />
+ <br />
+ <br />
+ <label>Team Numbers</label>
+ <input
+ *ngFor="let x of [1,2,3]; let i = index;"
+ [(ngModel)]="team_ranking[i]"
+ type="number"
+ min="1"
+ max="9999"
+ />
+ <button class="btn btn-primary" (click)="setTeamNumbers()">Select</button>
+ </div>
+ <div *ngSwitchCase="'Data'">
+ <h4>Match #{{match_number}}</h4>
+ <div *ngFor="let team_key of team_ranking; let i = index">
+ <div class="d-flex flex-row justify-content-center pt-2">
+ <div class="d-flex flex-row">
+ <h4 class="align-self-center">{{i + 1}}</h4>
+ <h1 class="align-self-center">{{team_key}}</h1>
+ </div>
+ <button class="btn btn-success" (click)="rankUp(i)">↑</button>
+ <!--↑ is the html code for an up arrow-->
+ <button class="btn btn-danger" (click)="rankDown(i)">↓</button>
+ <!--↓ is the html code for a down arrow-->
+ </div>
+ </div>
+ <div class="d-flex flex-row justify-content-center pt-2">
+ <div>
+ <button class="btn btn-secondary" (click)="editTeams()">
+ Edit Teams
+ </button>
+ </div>
+ <div>
+ <button class="btn btn-success" (click)="submitData()">Submit</button>
+ </div>
+ </div>
+ </div>
+ <div class="error">{{errorMessage}}</div>
+</ng-container>