diff --git a/frontend/angular.json b/frontend/angular.json index 6e7641a..1a103b3 100644 --- a/frontend/angular.json +++ b/frontend/angular.json @@ -31,7 +31,9 @@ "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", "src/styles.css" ], - "scripts": [] + "scripts": [ + "node_modules/apexcharts/dist/apexcharts.min.js" + ] }, "configurations": { "production": { @@ -100,7 +102,9 @@ "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", "src/styles.css" ], - "scripts": [] + "scripts": [ + "node_modules/apexcharts/dist/apexcharts.min.js" + ] } } } diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 2bf9e76..5f35219 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -22,10 +22,13 @@ "@angular/platform-browser-dynamic": "^16.2.7", "@angular/router": "^16.2.7", "angular-calendar": "^0.31.0", + "apexcharts": "^3.44.0", "date-fns": "^2.29.3", "luxon": "^3.4.3", "moment": "^2.29.4", + "ng-apexcharts": "^1.8.0", "ngx-material-timepicker": "^13.1.1", + "ngx-slider-v2": "^16.0.2", "rxjs": "~7.5.0", "tslib": "^2.3.0", "zone.js": "~0.13.3" @@ -4738,6 +4741,11 @@ "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", "dev": true }, + "node_modules/@yr/monotone-cubic-spline": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz", + "integrity": "sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==" + }, "node_modules/abab": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", @@ -5036,6 +5044,20 @@ "node": ">= 8" } }, + "node_modules/apexcharts": { + "version": "3.44.0", + "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.44.0.tgz", + "integrity": "sha512-u7Xzrbcxc2yWznN78Jh5NMCYVAsWDfBjRl5ea++rVzFAqjU2hLz4RgKIFwYOBDRQtW1e/Qz8azJTqIJ1+Vu9Qg==", + "dependencies": { + "@yr/monotone-cubic-spline": "^1.0.3", + "svg.draggable.js": "^2.2.2", + "svg.easing.js": "^2.0.0", + "svg.filter.js": "^2.0.2", + "svg.pathmorphing.js": "^0.1.3", + "svg.resize.js": "^1.4.3", + "svg.select.js": "^3.0.1" + } + }, "node_modules/aproba": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", @@ -6334,12 +6356,25 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/detect-it": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/detect-it/-/detect-it-4.0.1.tgz", + "integrity": "sha512-dg5YBTJYvogK1+dA2mBUDKzOWfYZtHVba89SyZUhc4+e3i2tzgjANFg5lDRCd3UOtRcw00vUTMK8LELcMdicug==" + }, "node_modules/detect-node": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", "dev": true }, + "node_modules/detect-passive-events": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-passive-events/-/detect-passive-events-2.0.3.tgz", + "integrity": "sha512-QN/1X65Axis6a9D8qg8Py9cwY/fkWAmAH/edTbmLMcv4m5dboLJ7LcAi8CfaCON2tjk904KwKX/HTdsHC6yeRg==", + "dependencies": { + "detect-it": "^4.0.1" + } + }, "node_modules/di": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", @@ -9753,6 +9788,20 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, + "node_modules/ng-apexcharts": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/ng-apexcharts/-/ng-apexcharts-1.8.0.tgz", + "integrity": "sha512-NwJuMLHoLm52LSzM08RXV6oOOTyUYREAV53WHVGs+L2qi8UWbxCz19hX0kk+F/xFLEhhuiLegO3T1v30jLbKSQ==", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@angular/common": ">=13.0.0", + "@angular/core": ">=13.0.0", + "apexcharts": "^3.41.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, "node_modules/ngx-material-timepicker": { "version": "13.1.1", "resolved": "https://registry.npmjs.org/ngx-material-timepicker/-/ngx-material-timepicker-13.1.1.tgz", @@ -9765,6 +9814,21 @@ "luxon": ">= 1.24.0" } }, + "node_modules/ngx-slider-v2": { + "version": "16.0.2", + "resolved": "https://registry.npmjs.org/ngx-slider-v2/-/ngx-slider-v2-16.0.2.tgz", + "integrity": "sha512-Lpl7SlErL+tJJvTRZYdyZoXTThKN8Ro1z3vscJQ1O5azHXwvbv3pnTcsOwY4ltfaP+dpzY27KL1QXyDr6QMaxQ==", + "dependencies": { + "detect-passive-events": "^2.0.3", + "rxjs": "^7.4.0", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": "^16.0.0", + "@angular/core": "^16.0.0", + "@angular/forms": "^16.0.0" + } + }, "node_modules/nice-napi": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz", @@ -12181,6 +12245,89 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/svg.draggable.js": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz", + "integrity": "sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw==", + "dependencies": { + "svg.js": "^2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.easing.js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/svg.easing.js/-/svg.easing.js-2.0.0.tgz", + "integrity": "sha512-//ctPdJMGy22YoYGV+3HEfHbm6/69LJUTAqI2/5qBvaNHZ9uUFVC82B0Pl299HzgH13rKrBgi4+XyXXyVWWthA==", + "dependencies": { + "svg.js": ">=2.3.x" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.filter.js": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/svg.filter.js/-/svg.filter.js-2.0.2.tgz", + "integrity": "sha512-xkGBwU+dKBzqg5PtilaTb0EYPqPfJ9Q6saVldX+5vCRy31P6TlRCP3U9NxH3HEufkKkpNgdTLBJnmhDHeTqAkw==", + "dependencies": { + "svg.js": "^2.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.js": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/svg.js/-/svg.js-2.7.1.tgz", + "integrity": "sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA==" + }, + "node_modules/svg.pathmorphing.js": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/svg.pathmorphing.js/-/svg.pathmorphing.js-0.1.3.tgz", + "integrity": "sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww==", + "dependencies": { + "svg.js": "^2.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.resize.js": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/svg.resize.js/-/svg.resize.js-1.4.3.tgz", + "integrity": "sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw==", + "dependencies": { + "svg.js": "^2.6.5", + "svg.select.js": "^2.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.resize.js/node_modules/svg.select.js": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-2.1.2.tgz", + "integrity": "sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ==", + "dependencies": { + "svg.js": "^2.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.select.js": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-3.0.1.tgz", + "integrity": "sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw==", + "dependencies": { + "svg.js": "^2.6.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/symbol-observable": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index eabd35d..b83dad7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -24,10 +24,13 @@ "@angular/platform-browser-dynamic": "^16.2.7", "@angular/router": "^16.2.7", "angular-calendar": "^0.31.0", + "apexcharts": "^3.44.0", "date-fns": "^2.29.3", "luxon": "^3.4.3", "moment": "^2.29.4", + "ng-apexcharts": "^1.8.0", "ngx-material-timepicker": "^13.1.1", + "ngx-slider-v2": "^16.0.2", "rxjs": "~7.5.0", "tslib": "^2.3.0", "zone.js": "~0.13.3" diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index 540fa74..37b2049 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -15,6 +15,7 @@ import {DraggableSchedulerComponent} from "./schedules/draggable-scheduler/dragg import { ForgottenTaskStartDialogComponent } from "./dashboard/forgotten-task-start-dialog/forgotten-task-start-dialog.component"; +import {TaskgroupActivityComponent} from "./statistics/taskgroup-activity/taskgroup-activity.component"; const routes: Routes = [ {path: '', component: MainComponent}, @@ -30,7 +31,8 @@ const routes: Routes = [ {path: 'upcoming', component: UpcomingTaskOverviewComponent}, {path: 'active', component: ActiveTaskOverviewComponent}, {path: 'scheduler', component: DraggableSchedulerComponent}, - {path: 'forgotten', component: ForgottenTaskStartDialogComponent} + {path: 'forgotten', component: ForgottenTaskStartDialogComponent}, + {path: 'statistics/taskgroup-activity', component: TaskgroupActivityComponent} ]; @NgModule({ diff --git a/frontend/src/app/app.component.html b/frontend/src/app/app.component.html index ab245f6..58df28c 100644 --- a/frontend/src/app/app.component.html +++ b/frontend/src/app/app.component.html @@ -2,6 +2,7 @@ TimeManager + @@ -10,6 +11,10 @@ + + + + diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index 24254ec..ed4e568 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -80,6 +80,9 @@ import { DateTimePickerComponent } from './date-time-picker/date-time-picker.com import {MatSliderModule} from "@angular/material/slider"; import {MatLegacySliderModule} from "@angular/material/legacy-slider"; import { DraggableSchedulerComponent } from './schedules/draggable-scheduler/draggable-scheduler.component'; +import { TaskgroupActivityComponent } from './statistics/taskgroup-activity/taskgroup-activity.component'; +import {NgxSliderModule} from "ngx-slider-v2"; +import {NgApexchartsModule} from "ng-apexcharts"; @NgModule({ declarations: [ AppComponent, @@ -120,6 +123,7 @@ import { DraggableSchedulerComponent } from './schedules/draggable-scheduler/dra AdvancedSchedulerComponent, DateTimePickerComponent, DraggableSchedulerComponent, + TaskgroupActivityComponent, ], imports: [ BrowserModule, @@ -158,7 +162,9 @@ import { DraggableSchedulerComponent } from './schedules/draggable-scheduler/dra MatTreeModule, MatAutocompleteModule, NgxMaterialTimepickerModule, - MatSliderModule + MatSliderModule, + NgxSliderModule, + NgApexchartsModule, ], providers: [ HttpClientModule, diff --git a/frontend/src/app/statistics/taskgroup-activity/taskgroup-activity.component.css b/frontend/src/app/statistics/taskgroup-activity/taskgroup-activity.component.css new file mode 100644 index 0000000..bf9df91 --- /dev/null +++ b/frontend/src/app/statistics/taskgroup-activity/taskgroup-activity.component.css @@ -0,0 +1,66 @@ +.container { + margin: 20px auto; + width: 80%; +} + +.spacer { + margin-bottom: 2.5%; +} + + +@media screen and (max-width: 600px) { + .container { + width: 100%; + margin: 20px 10px; + } +} + +#date-range-selector { + width: 100%; +} + +::ng-deep { + .custom-slider .ngx-slider .ngx-slider-bar { + background: #ffe4d1; + height: 2px; + } + .custom-slider .ngx-slider .ngx-slider-selection { + background: orange; + } + + .custom-slider .ngx-slider .ngx-slider-pointer { + width: 8px; + height: 16px; + top: auto; /* to remove the default positioning */ + bottom: 0; + background-color: #333; + border-top-left-radius: 3px; + border-top-right-radius: 3px; + } + + .custom-slider .ngx-slider .ngx-slider-pointer:after { + display: none; + } + + .custom-slider .ngx-slider .ngx-slider-bubble { + bottom: 14px; + } + + .custom-slider .ngx-slider .ngx-slider-limit { + font-weight: bold; + color: orange; + } + + .custom-slider .ngx-slider .ngx-slider-tick { + width: 1px; + height: 10px; + margin-left: 4px; + border-radius: 0; + background: #ffe4d1; + top: -1px; + } + + .custom-slider .ngx-slider .ngx-slider-tick.ngx-slider-selected { + background: orange; + } +} diff --git a/frontend/src/app/statistics/taskgroup-activity/taskgroup-activity.component.html b/frontend/src/app/statistics/taskgroup-activity/taskgroup-activity.component.html new file mode 100644 index 0000000..affe899 --- /dev/null +++ b/frontend/src/app/statistics/taskgroup-activity/taskgroup-activity.component.html @@ -0,0 +1,14 @@ +
+ +
+ +
+
+ +
+
diff --git a/frontend/src/app/statistics/taskgroup-activity/taskgroup-activity.component.spec.ts b/frontend/src/app/statistics/taskgroup-activity/taskgroup-activity.component.spec.ts new file mode 100644 index 0000000..1c8faeb --- /dev/null +++ b/frontend/src/app/statistics/taskgroup-activity/taskgroup-activity.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TaskgroupActivityComponent } from './taskgroup-activity.component'; + +describe('TaskgroupActivityComponent', () => { + let component: TaskgroupActivityComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [TaskgroupActivityComponent] + }); + fixture = TestBed.createComponent(TaskgroupActivityComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/statistics/taskgroup-activity/taskgroup-activity.component.ts b/frontend/src/app/statistics/taskgroup-activity/taskgroup-activity.component.ts new file mode 100644 index 0000000..bb94ef5 --- /dev/null +++ b/frontend/src/app/statistics/taskgroup-activity/taskgroup-activity.component.ts @@ -0,0 +1,89 @@ +import {Component, ViewChild} from '@angular/core'; +import {NavigationLink} from "../../navigation-link-list/navigation-link-list.component"; +import * as moment from "moment"; +import {LabelType, Options} from "ngx-slider-v2"; +import { + ChartComponent, + ApexAxisChartSeries, + ApexChart, + ApexXAxis, + ApexTitleSubtitle +} from "ng-apexcharts"; + +export type ChartOptions = { + series: ApexAxisChartSeries; + chart: ApexChart; + xaxis: ApexXAxis; + title: ApexTitleSubtitle; +}; +@Component({ + selector: 'app-taskgroup-activity', + templateUrl: './taskgroup-activity.component.html', + styleUrls: ['./taskgroup-activity.component.css'] +}) +export class TaskgroupActivityComponent { + defaultNavigationLinkPath: NavigationLink[] = [ + { + linkText: 'Dashboard', + routerLink: ['/'] + }, + { + linkText: 'Statistics', + routerLink: [] + }, + { + linkText: 'Taskgroup Activity', + routerLink: ['/statistics/taskgroup-activity'] + } + ]; + + minValue: number = 50; + maxValue: number = 200; + /*options: Options = { + floor: 0, + ceil: 250 + };*/ + + dateRange: Date[] = this.createDateRange(); + value: number = this.dateRange[0].getTime(); + options: Options = { + stepsArray: this.dateRange.map((date: Date) => { + return { value: date.getTime() }; + }), + translate: (value: number, label: LabelType): string => { + return new Date(value).toDateString(); + }, + floor: 0, + ceil: this.dateRange.length, + }; + + @ViewChild("chart") chart?: ChartComponent; + public chartOptions: Partial = { + series: [ + { + name: "My-series", + data: [10, 41, 35, 51, 49, 62, 69, 91, 148] + } + ], + chart: { + height: 350, + type: "bar" + }, + title: { + text: "My First Angular Chart" + }, + xaxis: { + categories: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep"] + } + }; + + createDateRange(): Date[] { + const dates: Date[] = []; + for (let i: number = 1; i <= 31; i++) { + + dates.push(moment().subtract(30-i, 'd').toDate()); + } + return dates; + } + +}