add component for a custom date picker that also knows relative date ranges like 'last month'

This commit is contained in:
2024-03-31 14:48:33 +02:00
parent b0467c4571
commit 6b8e3d2089
8 changed files with 443 additions and 75 deletions

View File

@@ -1,30 +1,31 @@
import { NgModule } from '@angular/core'; import { NgModule } from "@angular/core";
import { Routes, RouterModule } from '@angular/router'; import { RouterModule, Routes } from "@angular/router";
import { VisualizationPageComponent } from './visualization-page/visualization-page.component'; import { VisualizationPageComponent } from "./visualization-page/visualization-page.component";
import { MainPageComponent } from './main-page/main-page.component'; import { MainPageComponent } from "./main-page/main-page.component";
import { UploadPageComponent } from './upload-page/upload-page.component'; import { UploadPageComponent } from "./upload-page/upload-page.component";
import { HelpPageComponent } from './help-page/help-page.component'; import { HelpPageComponent } from "./help-page/help-page.component";
import { DashboardPageComponent } from './dashboard-page/dashboard-page.component'; import { DashboardPageComponent } from "./dashboard-page/dashboard-page.component";
import { DashboardComponent } from './dashboard-page/dashboard/dashboard.component'; import { DashboardComponent } from "./dashboard-page/dashboard/dashboard.component";
import { CustomizableGridComponent } from './customizable-grid/customizable-grid.component'; import { CustomizableGridComponent } from "./customizable-grid/customizable-grid.component";
import { DatePickerTestComponent } from "./components/datepicker/date-picker-test.component";
const routes: Routes = [ const routes: Routes = [
{ path: '', component: MainPageComponent}, { path: "", component: MainPageComponent },
{ path: 'vis', component: VisualizationPageComponent }, { path: "vis", component: VisualizationPageComponent },
{ path: 'dashboard', component: DashboardPageComponent}, { path: "dashboard", component: DashboardPageComponent },
{ path: 'dashboard/:id', component: DashboardComponent}, { path: "dashboard/:id", component: DashboardComponent },
{ path: 'upload', component: UploadPageComponent }, { path: "testing/datepicker", component: DatePickerTestComponent },
{ path: 'grid', component: CustomizableGridComponent }, { path: "upload", component: UploadPageComponent },
{ path: 'help', component: HelpPageComponent }, { path: "grid", component: CustomizableGridComponent },
// { path: '**', component: PageNotFoundComponent } { path: "help", component: HelpPageComponent },
// { path: '**', component: PageNotFoundComponent }
]; ];
@NgModule({ @NgModule({
imports: [ imports: [
RouterModule.forRoot(routes, {}) RouterModule.forRoot(routes, {}),
], ],
declarations: [], declarations: [],
exports: [RouterModule] exports: [RouterModule],
}) })
export class AppRoutingModule { } export class AppRoutingModule {}

View File

@@ -1,54 +1,65 @@
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from "@angular/platform-browser";
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { NgModule, enableProdMode } from '@angular/core'; import { enableProdMode, NgModule } from "@angular/core";
import { HttpClientModule } from '@angular/common/http'; import { HttpClientModule } from "@angular/common/http";
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { AppRoutingModule } from './app-routing.module'; import { AppRoutingModule } from "./app-routing.module";
import { AppComponent } from './app.component'; import { AppComponent } from "./app.component";
import { MainPageComponent } from './main-page/main-page.component'; import { MainPageComponent } from "./main-page/main-page.component";
import { HelpPageComponent } from './help-page/help-page.component'; import { HelpPageComponent } from "./help-page/help-page.component";
import { UploadPageComponent } from './upload-page/upload-page.component'; import { UploadPageComponent } from "./upload-page/upload-page.component";
import { VisualizationPageComponent } from './visualization-page/visualization-page.component'; import { VisualizationPageComponent } from "./visualization-page/visualization-page.component";
import {MatAutocompleteModule} from '@angular/material/autocomplete'; import { DatePickerComponent } from "./components/datepicker/date-picker.component";
import {MatButtonModule} from '@angular/material/button'; import { DatePickerTestComponent } from "./components/datepicker/date-picker-test.component";
import {MatCheckboxModule} from '@angular/material/checkbox';
import {MatSelectModule} from '@angular/material/select';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import {MatProgressBarModule} from '@angular/material/progress-bar';
import {MatProgressSpinnerModule} from '@angular/material/progress-spinner';
import {MatRadioModule} from '@angular/material/radio';
import {MatSnackBarModule} from '@angular/material/snack-bar';
import {MatTooltipModule} from '@angular/material/tooltip';
import {OverlayModule} from '@angular/cdk/overlay';
import { YAxisDefinitionComponent } from './y-axis-definition/y-axis-definition.component';
import { QueryAutocompleteComponent } from './query-autocomplete/query-autocomplete.component';
import { LimitByComponent } from './limit-by/limit-by.component';
import { PlotDetailsComponent } from './plot-details/plot-details.component';
import { PlotViewComponent } from './plot-view/plot-view.component';
import { GalleryViewComponent, GalleryItemView, GalleryFilterView } from './gallery-view/gallery-view.component';
import { ImageToggleComponent } from './image-toggle/image-toggle.component';
import { DashboardPageComponent } from './dashboard-page/dashboard-page.component';
import { NewDashboardComponent } from './dashboard-page/new-dashboard/new-dashboard.component';
import { MatDialogModule, MAT_DIALOG_DEFAULT_OPTIONS } from '@angular/material/dialog';
import {MatTableModule} from '@angular/material/table';
import {MatGridListModule} from '@angular/material/grid-list';
import {MatCardModule} from '@angular/material/card';
import {MatBadgeModule} from '@angular/material/badge';
import { DashboardComponent } from './dashboard-page/dashboard/dashboard.component';
import { AddTextDialogComponent } from './dashboard-page/dashboard/add-text-dialog/add-text-dialog.component';
import { TextWidgetComponent } from './dashboard-page/dashboard/text-widget/text-widget.component';
import { AddPlotDialogComponent } from './dashboard-page/dashboard/add-plot-dialog/add-plot-dialog.component';
import { PlotWidgetComponent } from './dashboard-page/dashboard/plot-widget/plot-widget.component';
import { FullScreenPlotDialogComponent } from './dashboard-page/dashboard/full-screen-plot-dialog/full-screen-plot-dialog.component';
import { CustomizableGridComponent } from './customizable-grid/customizable-grid.component';
import {DragDropModule} from '@angular/cdk/drag-drop'; import { MatAutocompleteModule } from "@angular/material/autocomplete";
import { ConfirmationDialogComponent } from './confirmation-dialog/confirmation-dialog.component'; import { MatButtonModule } from "@angular/material/button";
import { FocusDirective } from './focus.directive'; import { MatCheckboxModule } from "@angular/material/checkbox";
import { MarkdownModule } from 'ngx-markdown'; import { MatSelectModule } from "@angular/material/select";
import { MatFormFieldModule } from "@angular/material/form-field";
import { MatInputModule } from "@angular/material/input";
import { MatProgressBarModule } from "@angular/material/progress-bar";
import { MatProgressSpinnerModule } from "@angular/material/progress-spinner";
import { MatRadioModule } from "@angular/material/radio";
import { MatSnackBarModule } from "@angular/material/snack-bar";
import { MatTooltipModule } from "@angular/material/tooltip";
import { OverlayModule } from "@angular/cdk/overlay";
import { YAxisDefinitionComponent } from "./y-axis-definition/y-axis-definition.component";
import { QueryAutocompleteComponent } from "./query-autocomplete/query-autocomplete.component";
import { LimitByComponent } from "./limit-by/limit-by.component";
import { PlotDetailsComponent } from "./plot-details/plot-details.component";
import { PlotViewComponent } from "./plot-view/plot-view.component";
import {
GalleryFilterView,
GalleryItemView,
GalleryViewComponent,
} from "./gallery-view/gallery-view.component";
import { ImageToggleComponent } from "./image-toggle/image-toggle.component";
import { DashboardPageComponent } from "./dashboard-page/dashboard-page.component";
import { NewDashboardComponent } from "./dashboard-page/new-dashboard/new-dashboard.component";
import {
MAT_DIALOG_DEFAULT_OPTIONS,
MatDialogModule,
} from "@angular/material/dialog";
import { MatTabsModule } from "@angular/material/tabs";
import { MatTableModule } from "@angular/material/table";
import { MatGridListModule } from "@angular/material/grid-list";
import { MatCardModule } from "@angular/material/card";
import { MatBadgeModule } from "@angular/material/badge";
import { DashboardComponent } from "./dashboard-page/dashboard/dashboard.component";
import { AddTextDialogComponent } from "./dashboard-page/dashboard/add-text-dialog/add-text-dialog.component";
import { TextWidgetComponent } from "./dashboard-page/dashboard/text-widget/text-widget.component";
import { AddPlotDialogComponent } from "./dashboard-page/dashboard/add-plot-dialog/add-plot-dialog.component";
import { PlotWidgetComponent } from "./dashboard-page/dashboard/plot-widget/plot-widget.component";
import { FullScreenPlotDialogComponent } from "./dashboard-page/dashboard/full-screen-plot-dialog/full-screen-plot-dialog.component";
import { CustomizableGridComponent } from "./customizable-grid/customizable-grid.component";
import { DragDropModule } from "@angular/cdk/drag-drop";
import { ConfirmationDialogComponent } from "./confirmation-dialog/confirmation-dialog.component";
import { FocusDirective } from "./focus.directive";
import { MarkdownModule } from "ngx-markdown";
@NgModule({ @NgModule({
declarations: [ declarations: [
@@ -56,6 +67,8 @@ import { MarkdownModule } from 'ngx-markdown';
MainPageComponent, MainPageComponent,
HelpPageComponent, HelpPageComponent,
UploadPageComponent, UploadPageComponent,
DatePickerComponent,
DatePickerTestComponent,
VisualizationPageComponent, VisualizationPageComponent,
YAxisDefinitionComponent, YAxisDefinitionComponent,
QueryAutocompleteComponent, QueryAutocompleteComponent,
@@ -76,7 +89,7 @@ import { MarkdownModule } from 'ngx-markdown';
FullScreenPlotDialogComponent, FullScreenPlotDialogComponent,
CustomizableGridComponent, CustomizableGridComponent,
ConfirmationDialogComponent, ConfirmationDialogComponent,
FocusDirective FocusDirective,
], ],
imports: [ imports: [
MarkdownModule.forRoot(), MarkdownModule.forRoot(),
@@ -99,15 +112,19 @@ import { MarkdownModule } from 'ngx-markdown';
MatProgressSpinnerModule, MatProgressSpinnerModule,
MatSelectModule, MatSelectModule,
MatSnackBarModule, MatSnackBarModule,
MatTabsModule,
MatTableModule, MatTableModule,
MatTooltipModule, MatTooltipModule,
BrowserAnimationsModule, BrowserAnimationsModule,
HttpClientModule, HttpClientModule,
OverlayModule OverlayModule,
], ],
providers: [{provide: MAT_DIALOG_DEFAULT_OPTIONS, useValue: {hasBackdrop: true}}], providers: [{
bootstrap: [AppComponent] provide: MAT_DIALOG_DEFAULT_OPTIONS,
useValue: { hasBackdrop: true },
}],
bootstrap: [AppComponent],
}) })
export class AppModule { } export class AppModule {}
enableProdMode() enableProdMode();

View File

@@ -0,0 +1,5 @@
<style>
</style>
<app-date-picker></app-date-picker>

View File

@@ -0,0 +1,12 @@
import { Component} from '@angular/core';
@Component({
selector: 'app-date-picker-test',
templateUrl: './date-picker-test.component.html'
})
export class DatePickerTestComponent {
constructor(){
}
}

View File

@@ -0,0 +1,207 @@
<style>
#date-box {
width: 23.5em;
}
.header-box {
display: flex;
justify-content: end;
}
.date-picker-overlay {
width: 500px;
transition: box-shadow 200ms cubic-bezier(0, 0, 0.2, 1);
box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2),
0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12);
background-color: white;
}
.tab-quick {
display: flex;
flex-direction: row;
gap: 2em;
}
.tab-quick-column {
display: flex;
flex-direction: column;
}
.date-picker-form-field {
width: 23.5em;
}
</style>
<div class="header-box">
<button
mat-button
matTooltip="Date Picker"
(click)="isOpen = !isOpen"
cdkOverlayOrigin
#trigger="cdkOverlayOrigin"
>
{{ dateValue.display }}
</button>
</div>
<ng-template
cdkConnectedOverlay
[cdkConnectedOverlayOrigin]="trigger"
[cdkConnectedOverlayOpen]="isOpen"
>
<div class="date-picker-overlay">
<mat-tab-group
animationDuration="0ms"
(selectedTabChange)="tabChange()"
[(selectedIndex)]="selectedTabIndex"
>
<mat-tab label="Quick">
<div class="tab-quick">
<div class="tab-quick-column">
<button mat-button (click)="applyQuick('BD/P1D', 'today')">
Today
</button>
<button mat-button (click)="applyQuick('B-1D/P1D', 'yesterday')">
Yesterday
</button>
<button mat-button (click)="applyQuick('BW/EW', 'this week')">
This Week
</button>
<button mat-button (click)="applyQuick('BM/EM', 'this month')">
This Month
</button>
<button mat-button (click)="applyQuick('BQ/EQ', 'this quarter')">
This Quarter
</button>
<button mat-button (click)="applyQuick('BY/EY', 'this year')">
This Year
</button>
</div>
<div class="tab-quick-column">
<button mat-button (click)="applyQuick('B-7D/ED', 'last 7 days')">
Last 7 Days
</button>
<button mat-button (click)="applyQuick('B-1W/ED', 'last week')">
Last Week
</button>
<button mat-button (click)="applyQuick('B-30D/ED', 'last 30 days')">
Last 30 Days
</button>
<button mat-button (click)="applyQuick('B-1M/E-1M', 'last month')">
Last Month
</button>
<button
mat-button
(click)="applyQuick('B-3M/E-1M', 'last 3 months')"
>
Last 3 Months
</button>
<button mat-button (click)="applyQuick('B-1Y/E-1Y', 'last year')">
Last Year
</button>
</div>
</div>
</mat-tab>
<mat-tab label="Relative">
<mat-form-field
class="pdb-form-number-small"
(wheel)="scrollRelativeTimeRange($event, 'seconds', 59)"
>
<mat-label>Seconds:</mat-label>
<input
matInput
name="relative-time-range-seconds"
[(ngModel)]="relativeTimeRange.seconds"
type="number"
min="0"
max="59"
/>
</mat-form-field>
<mat-form-field
class="pdb-form-number-small"
(wheel)="scrollRelativeTimeRange($event, 'minutes', 59)"
>
<mat-label>Minutes:</mat-label>
<input
matInput
name="relative-time-range-minutes"
[(ngModel)]="relativeTimeRange.minutes"
type="number"
min="0"
max="59"
/>
</mat-form-field>
<mat-form-field
class="pdb-form-number-small"
(wheel)="scrollRelativeTimeRange($event, 'hours', 23)"
>
<mat-label>Hours:</mat-label>
<input
matInput
name="relative-time-range-hours"
[(ngModel)]="relativeTimeRange.hours"
type="number"
min="0"
max="23"
/>
</mat-form-field>
<mat-form-field
class="pdb-form-number-small"
(wheel)="scrollRelativeTimeRange($event, 'days', 367)"
>
<mat-label>Days:</mat-label>
<input
matInput
name="relative-time-range-days"
[(ngModel)]="relativeTimeRange.days"
type="number"
min="0"
max="367"
/>
</mat-form-field>
<mat-form-field
class="pdb-form-number-small"
(wheel)="scrollRelativeTimeRange($event, 'months', 11)"
>
<mat-label>Months:</mat-label>
<input
matInput
name="relative-time-range-months"
[(ngModel)]="relativeTimeRange.months"
type="number"
min="0"
max="11"
/>
</mat-form-field>
<mat-form-field
class="pdb-form-number-small"
(wheel)="scrollRelativeTimeRange($event, 'years', 10)"
>
<mat-label>Years:</mat-label>
<input
matInput
name="relative-time-range-years"
[(ngModel)]="relativeTimeRange.years"
type="number"
min="0"
max="10"
/>
</mat-form-field>
<button mat-button (click)="applyRelativeTimeRange()">Apply</button>
</mat-tab>
<mat-tab label="Absolute">
<form [formGroup]="dateRangeFormGroup">
<mat-form-field class="date-picker-form-field">
<mat-label>Date Range:</mat-label>
<input
matInput
[formControl]="dateRangeFormGroup.controls.dateRange"
name="dates"
/>
</mat-form-field>
<button mat-button (click)="applyAbsoluteTime()">Apply</button>
</form>
</mat-tab>
</mat-tab-group>
</div>
</ng-template>

View File

@@ -0,0 +1,107 @@
import { Component } from "@angular/core";
import { FormControl, FormGroup, Validators } from "@angular/forms";
export type DateType = "quick" | "relative" | "absolute";
export type DateValue = { type: DateType; value: string; display: string };
@Component({
selector: "app-date-picker",
templateUrl: "./date-picker.component.html",
})
export class DatePickerComponent {
isOpen = false;
relativeTimeRangeUnit = "relativeTimeRangeMinutes";
relativeTimeRangeAmount = 15;
relativeTimeRange = {
seconds: 0,
minutes: 15,
hours: 0,
days: 0,
months: 0,
years: 0,
};
dateRangeFormGroup = new FormGroup({
dateRange: new FormControl<string>(
"2019-10-05 00:00:00 - 2019-10-11 23:59:59",
[
Validators.pattern(
/^\d{4}-\d{2}-\d{2} ([01][0-9]|2[0-3]):\d{2}:\d{2} - \d{4}-\d{2}-\d{2} ([01][0-9]|2[0-3]):\d{2}:\d{2}$/,
),
],
),
});
dateValue: DateValue = {
type: "quick",
value: "BM/EM",
display: "this month",
};
selectedTabIndex = 0;
constructor() {}
dateDisplay(): string {
return this.dateValue.value;
}
tabChange() {
//(<any> window).initSimpleDatePicker(); // breaks form control
}
applyQuick(value: string, display: string) {
this.dateValue.type = "quick";
this.dateValue.value = value;
this.dateValue.display = display;
this.isOpen = false;
}
private fixToRange(val: number, min: number, max: number) {
return val < min ? min : (val > max ? max : val);
}
applyRelativeTimeRange() {
const x = this.relativeTimeRange;
const years = x.years ? x.years + "Y" : "";
const months = x.months ? x.months + "M" : "";
const days = x.days ? x.days + "D" : "";
const time = x.hours || x.minutes || x.seconds ? "T" : "";
const hours = x.hours ? x.hours + "H" : "";
const minutes = x.minutes ? x.minutes + "M" : "";
const seconds = x.seconds ? x.seconds + "S" : "";
const isoTimeRange =
`P${years}${months}${days}${time}${hours}${minutes}${seconds}`;
this.dateValue.type = "relative";
this.dateValue.value = isoTimeRange;
this.dateValue.display = isoTimeRange;
this.isOpen = false;
}
applyAbsoluteTime() {
this.dateValue.type = "absolute";
this.dateValue.value = <string> this.dateRangeFormGroup.controls.dateRange
.value;
this.dateValue.display = <string> this.dateRangeFormGroup.controls.dateRange
.value;
this.isOpen = false;
console.log(this.dateValue);
}
scrollRelativeTimeRange(
event: WheelEvent,
unit: "seconds" | "minutes" | "hours" | "days" | "months" | "years",
max: number,
) {
this.relativeTimeRange[unit] = this.fixToRange(
this.relativeTimeRange[unit] + (event.deltaY > 0 ? -1 : 1),
0,
max,
);
}
}

View File

@@ -47,6 +47,22 @@
}); });
} }
function initSimpleDatePicker() {
$('input[name="dates"]').daterangepicker({
timePicker: true,
minDate: "2017-01-01",
maxDate: "2029-12-31",
maxYear: parseInt(moment().format('YYYY'),10),
timePicker24Hour: true,
timePickerSeconds: true,
showDropdowns: true, // drop downs for selecting year and month
locale: {
format: 'YYYY-MM-DD HH:mm:ss',
"firstDay": 1 // monday is the first day of the week
},
});
}
$( document ).ready(function() { $( document ).ready(function() {
initInvaders('results'); initInvaders('results');
document.addEventListener("invadersPause", function(event) { document.addEventListener("invadersPause", function(event) {

View File

@@ -182,6 +182,9 @@ a.external-link:after {
mat-form-field.pdb-form-full-width { mat-form-field.pdb-form-full-width {
width: 100%; width: 100%;
} }
mat-form-field.pdb-form-number-small {
width: 4.5em;
}
mat-form-field.pdb-form-number { mat-form-field.pdb-form-number {
width: 5.5em; width: 5.5em;
} }