mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-07-28 18:24:38 -05:00
Enhancement: reorganize dates dropdown, add more relative options (#9307)
This commit is contained in:
@@ -1,161 +1,158 @@
|
||||
<div class="btn-group w-100" ngbDropdown role="group" [popperOptions]="popperOptions">
|
||||
<div class="btn-group w-100" ngbDropdown role="group" [popperOptions]="popperOptions" [placement]="placement">
|
||||
<button class="btn btn-sm" id="dropdown{{title}}" ngbDropdownToggle [ngClass]="createdDateTo || createdDateFrom ? 'btn-primary' : 'btn-outline-primary'" [disabled]="disabled">
|
||||
<i-bs width="1em" height="1em" name="calendar-event-fill"></i-bs>
|
||||
<div class="d-none d-sm-inline"> {{title}}</div>
|
||||
<pngx-clearable-badge [selected]="isActive" (cleared)="reset()"></pngx-clearable-badge><span class="visually-hidden">selected</span>
|
||||
</button>
|
||||
<div class="dropdown-menu date-dropdown shadow p-2" ngbDropdownMenu attr.aria-labelledby="dropdown{{title}}">
|
||||
<div class="row d-flex">
|
||||
<div class="col border-end">
|
||||
<div class="list-group list-group-flush">
|
||||
<h6 class="dropdown-header border-bottom" i18n>Created</h6>
|
||||
@for (rd of relativeDates; track rd) {
|
||||
<button class="list-group-item small list-goup list-group-item-action d-flex p-2" role="menuitem" (click)="setCreatedRelativeDate(rd.id)">
|
||||
<div class="selected-icon">
|
||||
@if (createdRelativeDate === rd.id) {
|
||||
<i-bs width="1em" height="1em" name="check"></i-bs>
|
||||
}
|
||||
</div>
|
||||
<div class="d-flex justify-content-between w-100 align-items-center ps-2">
|
||||
<div class="pe-4">
|
||||
{{rd.name}}
|
||||
</div>
|
||||
<div class="text-muted small pe-2">
|
||||
<span class="small">
|
||||
{{ rd.date | customDate:'mediumDate' }} – <ng-container i18n>now</ng-container>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
<h6 class="dropdown-header border-bottom" i18n>Created</h6>
|
||||
<div class="list-group list-group-flush">
|
||||
<div class="list-group-item d-flex p-2 select-item" role="menuitem">
|
||||
<div class="selected-icon">
|
||||
@if (createdRelativeDate) {
|
||||
<a class="text-light focus-variants" href="javascript:void(0)" (click)="clearCreatedRelativeDate()">
|
||||
<i-bs width="1em" height="1em" name="check" class="variant-unfocused"></i-bs>
|
||||
<i-bs width="1em" height="1em" name="x" class="variant-focused text-primary"></i-bs>
|
||||
</a>
|
||||
}
|
||||
<div class="list-group-item d-flex p-2" role="menuitem">
|
||||
|
||||
<div class="selected-icon">
|
||||
@if (createdDateFrom) {
|
||||
<a class="text-light focus-variants" href="javascript:void(0)" (click)="clearCreatedFrom()">
|
||||
<i-bs width="1em" height="1em" name="check" class="variant-unfocused"></i-bs>
|
||||
<i-bs width="1em" height="1em" name="x" class="variant-focused text-primary"></i-bs>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
<div class="input-group input-group-sm small ps-1 pe-2">
|
||||
<span class="input-group-text w-25 small text-muted" i18n>From</span>
|
||||
<input class="form-control small" [placeholder]="datePlaceHolder" (dateSelect)="onChangeDebounce()" (change)="onChangeDebounce()" (keypress)="onKeyPress($event)"
|
||||
maxlength="10" [(ngModel)]="createdDateFrom" ngbDatepicker #createdDateFromPicker="ngbDatepicker" [footerTemplate]="createdFromFooterTemplate">
|
||||
<button class="btn btn-outline-secondary" (click)="createdDateFromPicker.toggle()" type="button">
|
||||
<i-bs width="1em" height="1em" name="calendar"></i-bs>
|
||||
</button>
|
||||
<ng-template #createdFromFooterTemplate>
|
||||
<div class="btn-group-xs border-top p-2 d-flex">
|
||||
<button class="btn btn-primary" (click)="createdDateFrom = today; onChangeDebounce()" i18n>Today</button>
|
||||
<button class="btn btn-secondary ms-auto" (click)="createdDateFromPicker.close()" i18n>Close</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="list-group-item d-flex p-2" role="menuitem">
|
||||
|
||||
<div class="selected-icon">
|
||||
@if (createdDateTo) {
|
||||
<a class="text-light focus-variants" href="javascript:void(0)" (click)="clearCreatedTo()">
|
||||
<i-bs width="1em" height="1em" name="check" class="variant-unfocused"></i-bs>
|
||||
<i-bs width="1em" height="1em" name="x" class="variant-focused text-primary"></i-bs>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
<div class="input-group input-group-sm small ps-1 pe-2">
|
||||
<span class="input-group-text w-25 small text-muted" i18n>To</span>
|
||||
<input class="form-control small" [placeholder]="datePlaceHolder" (dateSelect)="onChangeDebounce()" (change)="onChangeDebounce()" (keypress)="onKeyPress($event)"
|
||||
maxlength="10" [(ngModel)]="createdDateTo" ngbDatepicker #createdDateToPicker="ngbDatepicker" [footerTemplate]="createdToFooterTemplate">
|
||||
<button class="btn btn-outline-secondary" (click)="createdDateToPicker.toggle()" type="button">
|
||||
<i-bs width="1em" height="1em" name="calendar"></i-bs>
|
||||
</button>
|
||||
<ng-template #createdToFooterTemplate>
|
||||
<div class="btn-group-xs border-top p-2 d-flex">
|
||||
<button class="btn btn-primary" (click)="createdDateTo = today; onChangeDebounce()" i18n>Today</button>
|
||||
<button class="btn btn-secondary ms-auto" (click)="createdDateToPicker.close()" i18n>Close</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group input-group-sm small ps-1 pe-2">
|
||||
<ng-select class="w-100" name="createdRelativeDate"
|
||||
[items]="relativeDates" [(ngModel)]="createdRelativeDate"
|
||||
bindValue="id"
|
||||
bindLabel="name"
|
||||
clearable="false"
|
||||
placeholder="Relative dates"
|
||||
i18n-placeholder
|
||||
(change)="onSetCreatedRelativeDate($event)">
|
||||
<ng-template ng-option-tmp let-item="item">
|
||||
<div class="d-flex">{{ item.name }}<span class="ms-auto text-muted small">{{ item.date | customDate:'mediumDate' }} – <ng-container i18n>now</ng-container></span></div>
|
||||
</ng-template>
|
||||
</ng-select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<h6 class="dropdown-header border-bottom" i18n>Added</h6>
|
||||
<div class="list-group list-group-flush">
|
||||
@for (rd of relativeDates; track rd) {
|
||||
<button class="list-group-item small list-goup list-group-item-action d-flex p-2" role="menuitem" (click)="setAddedRelativeDate(rd.id)">
|
||||
<div class="selected-icon">
|
||||
@if (addedRelativeDate === rd.id) {
|
||||
<i-bs width="1em" height="1em" name="check"></i-bs>
|
||||
}
|
||||
</div>
|
||||
<div class="d-flex justify-content-between w-100 align-items-center ps-2">
|
||||
<div class="pe-4">
|
||||
{{rd.name}}
|
||||
</div>
|
||||
<div class="text-muted small pe-2">
|
||||
<span class="small">
|
||||
{{ rd.date | customDate:'mediumDate' }} – <ng-container i18n>now</ng-container>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
<div class="list-group-item d-flex p-2" role="menuitem">
|
||||
<div class="selected-icon">
|
||||
@if (createdDateFrom) {
|
||||
<a class="text-light focus-variants" href="javascript:void(0)" (click)="clearCreatedFrom()">
|
||||
<i-bs width="1em" height="1em" name="check" class="variant-unfocused"></i-bs>
|
||||
<i-bs width="1em" height="1em" name="x" class="variant-focused text-primary"></i-bs>
|
||||
</a>
|
||||
}
|
||||
<div class="list-group-item d-flex p-2" role="menuitem">
|
||||
|
||||
<div class="selected-icon">
|
||||
@if (addedDateFrom) {
|
||||
<a class="text-light focus-variants" href="javascript:void(0)" (click)="clearAddedFrom()">
|
||||
<i-bs width="1em" height="1em" name="check" class="variant-unfocused"></i-bs>
|
||||
<i-bs width="1em" height="1em" name="x" class="variant-focused text-primary"></i-bs>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
<div class="input-group input-group-sm small ps-1 pe-2">
|
||||
<span class="input-group-text w-25 small text-muted" i18n>From</span>
|
||||
<input class="form-control small" [placeholder]="datePlaceHolder" (dateSelect)="onChangeDebounce()" (change)="onChangeDebounce()" (keypress)="onKeyPress($event)"
|
||||
maxlength="10" [(ngModel)]="createdDateFrom" ngbDatepicker #createdDateFromPicker="ngbDatepicker" [footerTemplate]="createdFromFooterTemplate">
|
||||
<button class="btn btn-outline-secondary" (click)="createdDateFromPicker.toggle()" type="button">
|
||||
<i-bs width="1em" height="1em" name="calendar"></i-bs>
|
||||
</button>
|
||||
<ng-template #createdFromFooterTemplate>
|
||||
<div class="btn-group-xs border-top p-2 d-flex">
|
||||
<button class="btn btn-primary" (click)="createdDateFrom = today; onChangeDebounce()" i18n>Today</button>
|
||||
<button class="btn btn-secondary ms-auto" (click)="createdDateFromPicker.close()" i18n>Close</button>
|
||||
</div>
|
||||
<div class="input-group input-group-sm small ps-1 pe-2">
|
||||
<span class="input-group-text w-25 small text-muted" i18n>From</span>
|
||||
<input class="form-control small" [placeholder]="datePlaceHolder" (dateSelect)="onChangeDebounce()" (change)="onChangeDebounce()" (keypress)="onKeyPress($event)"
|
||||
maxlength="10" [(ngModel)]="addedDateFrom" ngbDatepicker #addedDateFromPicker="ngbDatepicker" [footerTemplate]="addedFromFooterTemplate">
|
||||
<button class="btn btn-outline-secondary" (click)="addedDateFromPicker.toggle()" type="button">
|
||||
<i-bs width="1em" height="1em" name="calendar"></i-bs>
|
||||
</button>
|
||||
<ng-template #addedFromFooterTemplate>
|
||||
<div class="btn-group-xs border-top p-2 d-flex">
|
||||
<button class="btn btn-primary" (click)="addedDateFrom = today; onChangeDebounce()" i18n>Today</button>
|
||||
<button class="btn btn-secondary ms-auto" (click)="addedDateFromPicker.close()" i18n>Close</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-group-item d-flex p-2" role="menuitem">
|
||||
<div class="selected-icon">
|
||||
@if (createdDateTo) {
|
||||
<a class="text-light focus-variants" href="javascript:void(0)" (click)="clearCreatedTo()">
|
||||
<i-bs width="1em" height="1em" name="check" class="variant-unfocused"></i-bs>
|
||||
<i-bs width="1em" height="1em" name="x" class="variant-focused text-primary"></i-bs>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
<div class="input-group input-group-sm small ps-1 pe-2">
|
||||
<span class="input-group-text w-25 small text-muted" i18n>To</span>
|
||||
<input class="form-control small" [placeholder]="datePlaceHolder" (dateSelect)="onChangeDebounce()" (change)="onChangeDebounce()" (keypress)="onKeyPress($event)"
|
||||
maxlength="10" [(ngModel)]="createdDateTo" ngbDatepicker #createdDateToPicker="ngbDatepicker" [footerTemplate]="createdToFooterTemplate">
|
||||
<button class="btn btn-outline-secondary" (click)="createdDateToPicker.toggle()" type="button">
|
||||
<i-bs width="1em" height="1em" name="calendar"></i-bs>
|
||||
</button>
|
||||
<ng-template #createdToFooterTemplate>
|
||||
<div class="btn-group-xs border-top p-2 d-flex">
|
||||
<button class="btn btn-primary" (click)="createdDateTo = today; onChangeDebounce()" i18n>Today</button>
|
||||
<button class="btn btn-secondary ms-auto" (click)="createdDateToPicker.close()" i18n>Close</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="list-group-item d-flex p-2" role="menuitem">
|
||||
|
||||
<div class="selected-icon">
|
||||
@if (addedDateTo) {
|
||||
<a class="text-light focus-variants" href="javascript:void(0)" (click)="clearAddedTo()">
|
||||
<i-bs width="1em" height="1em" name="check" class="variant-unfocused"></i-bs>
|
||||
<i-bs width="1em" height="1em" name="x" class="variant-focused text-primary"></i-bs>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<h6 class="dropdown-header border-bottom" i18n>Added</h6>
|
||||
<div class="list-group list-group-flush">
|
||||
<div class="list-group-item d-flex p-2 select-item" role="menuitem">
|
||||
<div class="selected-icon">
|
||||
@if (addedRelativeDate) {
|
||||
<a class="text-light focus-variants" href="javascript:void(0)" (click)="clearAddedRelativeDate()">
|
||||
<i-bs width="1em" height="1em" name="check" class="variant-unfocused"></i-bs>
|
||||
<i-bs width="1em" height="1em" name="x" class="variant-focused text-primary"></i-bs>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
<div class="input-group input-group-sm small ps-1 pe-2">
|
||||
<ng-select class="w-100" name="addedRelativeDate"
|
||||
[items]="relativeDates" [(ngModel)]="addedRelativeDate"
|
||||
bindValue="id"
|
||||
bindLabel="name"
|
||||
clearable="false"
|
||||
placeholder="Relative dates"
|
||||
i18n-placeholder
|
||||
(change)="onSetAddedRelativeDate($event)">
|
||||
<ng-template ng-option-tmp let-item="item">
|
||||
<div class="d-flex">{{ item.name }}<span class="ms-auto text-muted small">{{ item.date | customDate:'mediumDate' }} – <ng-container i18n>now</ng-container></span></div>
|
||||
</ng-template>
|
||||
</ng-select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-group-item d-flex p-2" role="menuitem">
|
||||
<div class="selected-icon">
|
||||
@if (addedDateFrom) {
|
||||
<a class="text-light focus-variants" href="javascript:void(0)" (click)="clearAddedFrom()">
|
||||
<i-bs width="1em" height="1em" name="check" class="variant-unfocused"></i-bs>
|
||||
<i-bs width="1em" height="1em" name="x" class="variant-focused text-primary"></i-bs>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
<div class="input-group input-group-sm small ps-1 pe-2">
|
||||
<span class="input-group-text w-25 small text-muted" i18n>From</span>
|
||||
<input class="form-control small" [placeholder]="datePlaceHolder" (dateSelect)="onChangeDebounce()" (change)="onChangeDebounce()" (keypress)="onKeyPress($event)"
|
||||
maxlength="10" [(ngModel)]="addedDateFrom" ngbDatepicker #addedDateFromPicker="ngbDatepicker" [footerTemplate]="addedFromFooterTemplate">
|
||||
<button class="btn btn-outline-secondary" (click)="addedDateFromPicker.toggle()" type="button">
|
||||
<i-bs width="1em" height="1em" name="calendar"></i-bs>
|
||||
</button>
|
||||
<ng-template #addedFromFooterTemplate>
|
||||
<div class="btn-group-xs border-top p-2 d-flex">
|
||||
<button class="btn btn-primary" (click)="addedDateFrom = today; onChangeDebounce()" i18n>Today</button>
|
||||
<button class="btn btn-secondary ms-auto" (click)="addedDateFromPicker.close()" i18n>Close</button>
|
||||
</div>
|
||||
<div class="input-group input-group-sm small ps-1 pe-2">
|
||||
<span class="input-group-text w-25 small text-muted" i18n>To</span>
|
||||
<input class="form-control small" [placeholder]="datePlaceHolder" (dateSelect)="onChangeDebounce()" (change)="onChangeDebounce()" (keypress)="onKeyPress($event)"
|
||||
maxlength="10" [(ngModel)]="addedDateTo" ngbDatepicker #addedDateToPicker="ngbDatepicker" [footerTemplate]="addedToFooterTemplate">
|
||||
<button class="btn btn-outline-secondary" (click)="addedDateToPicker.toggle()" type="button">
|
||||
<i-bs width="1em" height="1em" name="calendar"></i-bs>
|
||||
</button>
|
||||
<ng-template #addedToFooterTemplate>
|
||||
<div class="btn-group-xs border-top p-2 d-flex">
|
||||
<button class="btn btn-primary" (click)="addedDateTo = today; onChangeDebounce()" i18n>Today</button>
|
||||
<button class="btn btn-secondary ms-auto" (click)="addedDateToPicker.close()" i18n>Close</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-group-item d-flex p-2" role="menuitem">
|
||||
<div class="selected-icon">
|
||||
@if (addedDateTo) {
|
||||
<a class="text-light focus-variants" href="javascript:void(0)" (click)="clearAddedTo()">
|
||||
<i-bs width="1em" height="1em" name="check" class="variant-unfocused"></i-bs>
|
||||
<i-bs width="1em" height="1em" name="x" class="variant-focused text-primary"></i-bs>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
<div class="input-group input-group-sm small ps-1 pe-2">
|
||||
<span class="input-group-text w-25 small text-muted" i18n>To</span>
|
||||
<input class="form-control small" [placeholder]="datePlaceHolder" (dateSelect)="onChangeDebounce()" (change)="onChangeDebounce()" (keypress)="onKeyPress($event)"
|
||||
maxlength="10" [(ngModel)]="addedDateTo" ngbDatepicker #addedDateToPicker="ngbDatepicker" [footerTemplate]="addedToFooterTemplate">
|
||||
<button class="btn btn-outline-secondary" (click)="addedDateToPicker.toggle()" type="button">
|
||||
<i-bs width="1em" height="1em" name="calendar"></i-bs>
|
||||
</button>
|
||||
<ng-template #addedToFooterTemplate>
|
||||
<div class="btn-group-xs border-top p-2 d-flex">
|
||||
<button class="btn btn-primary" (click)="addedDateTo = today; onChangeDebounce()" i18n>Today</button>
|
||||
<button class="btn btn-secondary ms-auto" (click)="addedDateToPicker.close()" i18n>Close</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -1,16 +1,7 @@
|
||||
.date-dropdown {
|
||||
--bs-dropdown-min-width: 22rem;
|
||||
white-space: nowrap;
|
||||
|
||||
@media(min-width: 768px) {
|
||||
--bs-dropdown-min-width: 40rem;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 767px) {
|
||||
.border-end {
|
||||
border: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-link {
|
||||
line-height: 1;
|
||||
}
|
||||
@@ -21,6 +12,10 @@
|
||||
min-height: 1em;
|
||||
}
|
||||
|
||||
.select-item .selected-icon {
|
||||
line-height: 2em;
|
||||
}
|
||||
|
||||
.input-group-sm {
|
||||
.form-control {
|
||||
font-size: 0.875rem;
|
||||
|
@@ -82,10 +82,12 @@ describe('DatesDropdownComponent', () => {
|
||||
it('should support relative dates', fakeAsync(() => {
|
||||
let result: DateSelection
|
||||
component.datesSet.subscribe((date) => (result = date))
|
||||
component.setCreatedRelativeDate(null)
|
||||
component.setCreatedRelativeDate(RelativeDate.WITHIN_1_WEEK)
|
||||
component.setAddedRelativeDate(null)
|
||||
component.setAddedRelativeDate(RelativeDate.WITHIN_1_WEEK)
|
||||
component.createdRelativeDate = RelativeDate.WITHIN_1_WEEK // normally set by ngModel binding in dropdown
|
||||
component.onSetCreatedRelativeDate({
|
||||
id: RelativeDate.WITHIN_1_WEEK,
|
||||
} as any)
|
||||
component.addedRelativeDate = RelativeDate.WITHIN_1_WEEK // normally set by ngModel binding in dropdown
|
||||
component.onSetAddedRelativeDate({ id: RelativeDate.WITHIN_1_WEEK } as any)
|
||||
tick(500)
|
||||
expect(result).toEqual({
|
||||
createdFrom: null,
|
||||
@@ -147,8 +149,19 @@ describe('DatesDropdownComponent', () => {
|
||||
expect(component.addedDateTo).toBeNull()
|
||||
})
|
||||
|
||||
it('should support clearRelativeDate', () => {
|
||||
component.createdRelativeDate = RelativeDate.WITHIN_1_WEEK
|
||||
component.clearCreatedRelativeDate()
|
||||
expect(component.createdRelativeDate).toBeNull()
|
||||
|
||||
component.addedRelativeDate = RelativeDate.WITHIN_1_WEEK
|
||||
component.clearAddedRelativeDate()
|
||||
expect(component.addedRelativeDate).toBeNull()
|
||||
})
|
||||
|
||||
it('should limit keyboard events', () => {
|
||||
const input: HTMLInputElement = fixture.nativeElement.querySelector('input')
|
||||
const input: HTMLInputElement =
|
||||
fixture.nativeElement.querySelector('input.form-control')
|
||||
let event: KeyboardEvent = new KeyboardEvent('keypress', {
|
||||
key: '9',
|
||||
})
|
||||
@@ -163,4 +176,19 @@ describe('DatesDropdownComponent', () => {
|
||||
input.dispatchEvent(event)
|
||||
expect(eventSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should support debounce', fakeAsync(() => {
|
||||
let result: DateSelection
|
||||
component.datesSet.subscribe((date) => (result = date))
|
||||
component.onChangeDebounce()
|
||||
tick(500)
|
||||
expect(result).toEqual({
|
||||
createdFrom: null,
|
||||
createdTo: null,
|
||||
createdRelativeDateID: null,
|
||||
addedFrom: null,
|
||||
addedTo: null,
|
||||
addedRelativeDateID: null,
|
||||
})
|
||||
}))
|
||||
})
|
||||
|
@@ -13,6 +13,7 @@ import {
|
||||
NgbDatepickerModule,
|
||||
NgbDropdownModule,
|
||||
} from '@ng-bootstrap/ng-bootstrap'
|
||||
import { NgSelectModule } from '@ng-select/ng-select'
|
||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||
import { Subject, Subscription } from 'rxjs'
|
||||
import { debounceTime } from 'rxjs/operators'
|
||||
@@ -32,10 +33,14 @@ export interface DateSelection {
|
||||
}
|
||||
|
||||
export enum RelativeDate {
|
||||
WITHIN_1_WEEK = 0,
|
||||
WITHIN_1_MONTH = 1,
|
||||
WITHIN_3_MONTHS = 2,
|
||||
WITHIN_1_YEAR = 3,
|
||||
WITHIN_1_WEEK = 1,
|
||||
WITHIN_1_MONTH = 2,
|
||||
WITHIN_3_MONTHS = 3,
|
||||
WITHIN_1_YEAR = 4,
|
||||
THIS_YEAR = 5,
|
||||
THIS_MONTH = 6,
|
||||
TODAY = 7,
|
||||
YESTERDAY = 8,
|
||||
}
|
||||
|
||||
@Component({
|
||||
@@ -49,6 +54,7 @@ export enum RelativeDate {
|
||||
NgxBootstrapIconsModule,
|
||||
NgbDatepickerModule,
|
||||
NgbDropdownModule,
|
||||
NgSelectModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
NgClass,
|
||||
@@ -82,44 +88,64 @@ export class DatesDropdownComponent implements OnInit, OnDestroy {
|
||||
name: $localize`Within 1 year`,
|
||||
date: new Date().setFullYear(new Date().getFullYear() - 1),
|
||||
},
|
||||
{
|
||||
id: RelativeDate.THIS_YEAR,
|
||||
name: $localize`This year`,
|
||||
date: new Date('1/1/' + new Date().getFullYear()),
|
||||
},
|
||||
{
|
||||
id: RelativeDate.THIS_MONTH,
|
||||
name: $localize`This month`,
|
||||
date: new Date().setDate(1),
|
||||
},
|
||||
{
|
||||
id: RelativeDate.TODAY,
|
||||
name: $localize`Today`,
|
||||
date: new Date().setHours(0, 0, 0, 0),
|
||||
},
|
||||
{
|
||||
id: RelativeDate.YESTERDAY,
|
||||
name: $localize`Yesterday`,
|
||||
date: new Date().setDate(new Date().getDate() - 1),
|
||||
},
|
||||
]
|
||||
|
||||
datePlaceHolder: string
|
||||
|
||||
// created
|
||||
@Input()
|
||||
createdDateTo: string
|
||||
createdDateTo: string = null
|
||||
|
||||
@Output()
|
||||
createdDateToChange = new EventEmitter<string>()
|
||||
|
||||
@Input()
|
||||
createdDateFrom: string
|
||||
createdDateFrom: string = null
|
||||
|
||||
@Output()
|
||||
createdDateFromChange = new EventEmitter<string>()
|
||||
|
||||
@Input()
|
||||
createdRelativeDate: RelativeDate
|
||||
createdRelativeDate: RelativeDate = null
|
||||
|
||||
@Output()
|
||||
createdRelativeDateChange = new EventEmitter<number>()
|
||||
|
||||
// added
|
||||
@Input()
|
||||
addedDateTo: string
|
||||
addedDateTo: string = null
|
||||
|
||||
@Output()
|
||||
addedDateToChange = new EventEmitter<string>()
|
||||
|
||||
@Input()
|
||||
addedDateFrom: string
|
||||
addedDateFrom: string = null
|
||||
|
||||
@Output()
|
||||
addedDateFromChange = new EventEmitter<string>()
|
||||
|
||||
@Input()
|
||||
addedRelativeDate: RelativeDate
|
||||
addedRelativeDate: RelativeDate = null
|
||||
|
||||
@Output()
|
||||
addedRelativeDateChange = new EventEmitter<number>()
|
||||
@@ -133,6 +159,9 @@ export class DatesDropdownComponent implements OnInit, OnDestroy {
|
||||
@Input()
|
||||
disabled: boolean = false
|
||||
|
||||
@Input()
|
||||
placement: string = 'bottom-start'
|
||||
|
||||
public readonly today: string = new Date().toISOString().split('T')[0]
|
||||
|
||||
get isActive(): boolean {
|
||||
@@ -172,17 +201,17 @@ export class DatesDropdownComponent implements OnInit, OnDestroy {
|
||||
this.onChange()
|
||||
}
|
||||
|
||||
setCreatedRelativeDate(rd: RelativeDate) {
|
||||
onSetCreatedRelativeDate(rd: { id: number; name: string; date: number }) {
|
||||
// createdRelativeDate is set by ngModel
|
||||
this.createdDateTo = null
|
||||
this.createdDateFrom = null
|
||||
this.createdRelativeDate = this.createdRelativeDate == rd ? null : rd
|
||||
this.onChange()
|
||||
}
|
||||
|
||||
setAddedRelativeDate(rd: RelativeDate) {
|
||||
onSetAddedRelativeDate(rd: { id: number; name: string; date: number }) {
|
||||
// addedRelativeDate is set by ngModel
|
||||
this.addedDateTo = null
|
||||
this.addedDateFrom = null
|
||||
this.addedRelativeDate = this.addedRelativeDate == rd ? null : rd
|
||||
this.onChange()
|
||||
}
|
||||
|
||||
@@ -224,6 +253,11 @@ export class DatesDropdownComponent implements OnInit, OnDestroy {
|
||||
this.onChange()
|
||||
}
|
||||
|
||||
clearCreatedRelativeDate() {
|
||||
this.createdRelativeDate = null
|
||||
this.onChange()
|
||||
}
|
||||
|
||||
clearAddedTo() {
|
||||
this.addedDateTo = null
|
||||
this.onChange()
|
||||
@@ -234,6 +268,11 @@ export class DatesDropdownComponent implements OnInit, OnDestroy {
|
||||
this.onChange()
|
||||
}
|
||||
|
||||
clearAddedRelativeDate() {
|
||||
this.addedRelativeDate = null
|
||||
this.onChange()
|
||||
}
|
||||
|
||||
// prevent chars other than numbers and separators
|
||||
onKeyPress(event: KeyboardEvent) {
|
||||
if ('Enter' !== event.key && !/[0-9,\.\/-]+/.test(event.key)) {
|
||||
|
@@ -93,6 +93,7 @@
|
||||
}
|
||||
<pngx-dates-dropdown class="flex-fill fade" [class.show]="show"
|
||||
title="Dates" i18n-title
|
||||
placement="bottom-end"
|
||||
(datesSet)="updateRules()"
|
||||
[(createdDateTo)]="dateCreatedTo"
|
||||
[(createdDateFrom)]="dateCreatedFrom"
|
||||
|
@@ -96,7 +96,10 @@ import {
|
||||
import { environment } from 'src/environments/environment'
|
||||
import { ClearableBadgeComponent } from '../../common/clearable-badge/clearable-badge.component'
|
||||
import { CustomFieldsQueryDropdownComponent } from '../../common/custom-fields-query-dropdown/custom-fields-query-dropdown.component'
|
||||
import { DatesDropdownComponent } from '../../common/dates-dropdown/dates-dropdown.component'
|
||||
import {
|
||||
DatesDropdownComponent,
|
||||
RelativeDate,
|
||||
} from '../../common/dates-dropdown/dates-dropdown.component'
|
||||
import {
|
||||
FilterableDropdownComponent,
|
||||
Intersection,
|
||||
@@ -422,7 +425,7 @@ describe('FilterEditorComponent', () => {
|
||||
value: 'created:[-1 week to now]',
|
||||
},
|
||||
]
|
||||
expect(component.dateCreatedRelativeDate).toEqual(0) // RELATIVE_DATE_QUERYSTRINGS['-1 week to now']
|
||||
expect(component.dateCreatedRelativeDate).toEqual(1) // RELATIVE_DATE_QUERYSTRINGS['-1 week to now']
|
||||
expect(component.textFilter).toBeNull()
|
||||
}))
|
||||
|
||||
@@ -434,7 +437,7 @@ describe('FilterEditorComponent', () => {
|
||||
value: 'added:[-1 week to now]',
|
||||
},
|
||||
]
|
||||
expect(component.dateAddedRelativeDate).toEqual(0) // RELATIVE_DATE_QUERYSTRINGS['-1 week to now']
|
||||
expect(component.dateAddedRelativeDate).toEqual(1) // RELATIVE_DATE_QUERYSTRINGS['-1 week to now']
|
||||
expect(component.textFilter).toBeNull()
|
||||
}))
|
||||
|
||||
@@ -1587,10 +1590,8 @@ describe('FilterEditorComponent', () => {
|
||||
const dateCreatedDropdown = fixture.debugElement.queryAll(
|
||||
By.directive(DatesDropdownComponent)
|
||||
)[0]
|
||||
const dateCreatedBeforeRelativeButton = dateCreatedDropdown.queryAll(
|
||||
By.css('button')
|
||||
)[1]
|
||||
dateCreatedBeforeRelativeButton.triggerEventHandler('click')
|
||||
component.dateCreatedRelativeDate = RelativeDate.WITHIN_1_WEEK
|
||||
dateCreatedDropdown.triggerEventHandler('datesSet')
|
||||
fixture.detectChanges()
|
||||
tick(400)
|
||||
expect(component.filterRules).toEqual([
|
||||
@@ -1606,10 +1607,8 @@ describe('FilterEditorComponent', () => {
|
||||
const dateCreatedDropdown = fixture.debugElement.queryAll(
|
||||
By.directive(DatesDropdownComponent)
|
||||
)[0]
|
||||
const dateCreatedBeforeRelativeButton = dateCreatedDropdown.queryAll(
|
||||
By.css('button')
|
||||
)[1]
|
||||
dateCreatedBeforeRelativeButton.triggerEventHandler('click')
|
||||
component.dateCreatedRelativeDate = RelativeDate.WITHIN_1_WEEK
|
||||
dateCreatedDropdown.triggerEventHandler('datesSet')
|
||||
fixture.detectChanges()
|
||||
tick(400)
|
||||
expect(component.filterRules).toEqual([
|
||||
@@ -1692,16 +1691,14 @@ describe('FilterEditorComponent', () => {
|
||||
const datesDropdown = fixture.debugElement.query(
|
||||
By.directive(DatesDropdownComponent)
|
||||
)
|
||||
const dateCreatedBeforeRelativeButton = datesDropdown.queryAll(
|
||||
By.css('button')
|
||||
)[1]
|
||||
dateCreatedBeforeRelativeButton.triggerEventHandler('click')
|
||||
component.dateAddedRelativeDate = RelativeDate.WITHIN_1_WEEK
|
||||
datesDropdown.triggerEventHandler('datesSet')
|
||||
fixture.detectChanges()
|
||||
tick(400)
|
||||
expect(component.filterRules).toEqual([
|
||||
{
|
||||
rule_type: FILTER_FULLTEXT_QUERY,
|
||||
value: 'created:[-1 week to now]',
|
||||
value: 'added:[-1 week to now]',
|
||||
},
|
||||
])
|
||||
}))
|
||||
@@ -1711,16 +1708,14 @@ describe('FilterEditorComponent', () => {
|
||||
const datesDropdown = fixture.debugElement.query(
|
||||
By.directive(DatesDropdownComponent)
|
||||
)
|
||||
const dateCreatedBeforeRelativeButton = datesDropdown.queryAll(
|
||||
By.css('button')
|
||||
)[1]
|
||||
dateCreatedBeforeRelativeButton.triggerEventHandler('click')
|
||||
component.dateAddedRelativeDate = RelativeDate.WITHIN_1_WEEK
|
||||
datesDropdown.triggerEventHandler('datesSet')
|
||||
fixture.detectChanges()
|
||||
tick(400)
|
||||
expect(component.filterRules).toEqual([
|
||||
{
|
||||
rule_type: FILTER_FULLTEXT_QUERY,
|
||||
value: 'foo,created:[-1 week to now]',
|
||||
value: 'foo,added:[-1 week to now]',
|
||||
},
|
||||
])
|
||||
}))
|
||||
|
@@ -135,24 +135,44 @@ const TEXT_FILTER_MODIFIER_NOTNULL = 'not null'
|
||||
const TEXT_FILTER_MODIFIER_GT = 'greater'
|
||||
const TEXT_FILTER_MODIFIER_LT = 'less'
|
||||
|
||||
const RELATIVE_DATE_QUERY_REGEXP_CREATED = /created:\[([^\]]+)\]/g
|
||||
const RELATIVE_DATE_QUERY_REGEXP_ADDED = /added:\[([^\]]+)\]/g
|
||||
const RELATIVE_DATE_QUERY_REGEXP_CREATED = /created:[\["]([^\]]+)[\]"]/g
|
||||
const RELATIVE_DATE_QUERY_REGEXP_ADDED = /added:[\["]([^\]]+)[\]"]/g
|
||||
const RELATIVE_DATE_QUERYSTRINGS = [
|
||||
{
|
||||
relativeDate: RelativeDate.WITHIN_1_WEEK,
|
||||
dateQuery: '-1 week to now',
|
||||
isRange: true,
|
||||
},
|
||||
{
|
||||
relativeDate: RelativeDate.WITHIN_1_MONTH,
|
||||
dateQuery: '-1 month to now',
|
||||
isRange: true,
|
||||
},
|
||||
{
|
||||
relativeDate: RelativeDate.WITHIN_3_MONTHS,
|
||||
dateQuery: '-3 month to now',
|
||||
isRange: true,
|
||||
},
|
||||
{
|
||||
relativeDate: RelativeDate.WITHIN_1_YEAR,
|
||||
dateQuery: '-1 year to now',
|
||||
isRange: true,
|
||||
},
|
||||
{
|
||||
relativeDate: RelativeDate.THIS_YEAR,
|
||||
dateQuery: 'this year',
|
||||
},
|
||||
{
|
||||
relativeDate: RelativeDate.THIS_MONTH,
|
||||
dateQuery: 'this month',
|
||||
},
|
||||
{
|
||||
relativeDate: RelativeDate.TODAY,
|
||||
dateQuery: 'today',
|
||||
},
|
||||
{
|
||||
relativeDate: RelativeDate.YESTERDAY,
|
||||
dateQuery: 'yesterday',
|
||||
},
|
||||
]
|
||||
|
||||
@@ -907,12 +927,11 @@ export class FilterEditorComponent
|
||||
|
||||
let existingRuleArgs = existingRule?.value.split(',')
|
||||
if (this.dateCreatedRelativeDate !== null) {
|
||||
const rd = RELATIVE_DATE_QUERYSTRINGS.find(
|
||||
(qS) => qS.relativeDate == this.dateCreatedRelativeDate
|
||||
)
|
||||
queryArgs.push(
|
||||
`created:[${
|
||||
RELATIVE_DATE_QUERYSTRINGS.find(
|
||||
(qS) => qS.relativeDate == this.dateCreatedRelativeDate
|
||||
).dateQuery
|
||||
}]`
|
||||
`created:${rd.isRange ? `[${rd.dateQuery}]` : `"${rd.dateQuery}"`}`
|
||||
)
|
||||
if (existingRule) {
|
||||
queryArgs = existingRuleArgs
|
||||
@@ -921,12 +940,11 @@ export class FilterEditorComponent
|
||||
}
|
||||
}
|
||||
if (this.dateAddedRelativeDate !== null) {
|
||||
const rd = RELATIVE_DATE_QUERYSTRINGS.find(
|
||||
(qS) => qS.relativeDate == this.dateAddedRelativeDate
|
||||
)
|
||||
queryArgs.push(
|
||||
`added:[${
|
||||
RELATIVE_DATE_QUERYSTRINGS.find(
|
||||
(qS) => qS.relativeDate == this.dateAddedRelativeDate
|
||||
).dateQuery
|
||||
}]`
|
||||
`added:${rd.isRange ? `[${rd.dateQuery}]` : `"${rd.dateQuery}"`}`
|
||||
)
|
||||
if (existingRule) {
|
||||
queryArgs = existingRuleArgs
|
||||
|
Reference in New Issue
Block a user