mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-11-03 03:16:10 -06:00 
			
		
		
		
	Merge branch 'dev' into feature-more-like-this
This commit is contained in:
		
							
								
								
									
										8
									
								
								src-ui/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										8
									
								
								src-ui/package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -2056,6 +2056,14 @@
 | 
			
		||||
        "tslib": "^2.0.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "@ng-select/ng-select": {
 | 
			
		||||
      "version": "5.0.9",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@ng-select/ng-select/-/ng-select-5.0.9.tgz",
 | 
			
		||||
      "integrity": "sha512-YZeSAiS8/Nx/eHZJPmOOYL8YmcvSq+dr1P8WIrsKmRA7mueorBpPc5xlUj+nLQbpLtsiQvdWDQspf/ykOvD/lA==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "tslib": "^2.0.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "@ngtools/webpack": {
 | 
			
		||||
      "version": "10.2.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-10.2.0.tgz",
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,7 @@
 | 
			
		||||
    "@angular/platform-browser-dynamic": "~10.1.5",
 | 
			
		||||
    "@angular/router": "~10.1.5",
 | 
			
		||||
    "@ng-bootstrap/ng-bootstrap": "^8.0.0",
 | 
			
		||||
    "@ng-select/ng-select": "^5.0.9",
 | 
			
		||||
    "bootstrap": "^4.5.0",
 | 
			
		||||
    "ng-bootstrap": "^1.6.3",
 | 
			
		||||
    "ng2-pdf-viewer": "^6.3.2",
 | 
			
		||||
 
 | 
			
		||||
@@ -54,6 +54,7 @@ import { FileSizePipe } from './pipes/file-size.pipe';
 | 
			
		||||
import { FilterPipe } from './pipes/filter.pipe';
 | 
			
		||||
import { DocumentTitlePipe } from './pipes/document-title.pipe';
 | 
			
		||||
import { MetadataCollapseComponent } from './components/document-detail/metadata-collapse/metadata-collapse.component';
 | 
			
		||||
import { NgSelectModule } from '@ng-select/ng-select';
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
  declarations: [
 | 
			
		||||
@@ -110,7 +111,8 @@ import { MetadataCollapseComponent } from './components/document-detail/metadata
 | 
			
		||||
    ReactiveFormsModule,
 | 
			
		||||
    NgxFileDropModule,
 | 
			
		||||
    InfiniteScrollModule,
 | 
			
		||||
    PdfViewerModule
 | 
			
		||||
    PdfViewerModule,
 | 
			
		||||
    NgSelectModule
 | 
			
		||||
  ],
 | 
			
		||||
  providers: [
 | 
			
		||||
    DatePipe,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,15 @@
 | 
			
		||||
<div class="form-group">
 | 
			
		||||
<div class="form-group paperless-input-select">
 | 
			
		||||
  <label [for]="inputId">{{title}}</label>
 | 
			
		||||
  <div [class.input-group]="showPlusButton()">
 | 
			
		||||
    <select class="form-control" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)" (blur)="onTouched()"
 | 
			
		||||
      [disabled]="disabled" [style.color]="textColor" [style.background]="backgroundColor">
 | 
			
		||||
      <option *ngIf="allowNull" [ngValue]="null" class="form-control">---</option>
 | 
			
		||||
      <option *ngFor="let i of items" [ngValue]="i.id" class="form-control">{{i.name}}</option>
 | 
			
		||||
    </select>
 | 
			
		||||
    <ng-select name="correspondent" [(ngModel)]="value"
 | 
			
		||||
      [disabled]="disabled"
 | 
			
		||||
      [style.color]="textColor"
 | 
			
		||||
      [style.background]="backgroundColor"
 | 
			
		||||
      (change)="onChange(value)"
 | 
			
		||||
      (blur)="onTouched()">
 | 
			
		||||
      <ng-option *ngFor="let i of items" [value]="i.id">{{i.name}}</ng-option>
 | 
			
		||||
    </ng-select>
 | 
			
		||||
 | 
			
		||||
    <div *ngIf="showPlusButton()" class="input-group-append">
 | 
			
		||||
      <button class="btn btn-outline-secondary" type="button" (click)="createNew.emit()">
 | 
			
		||||
        <svg class="buttonicon" fill="currentColor">
 | 
			
		||||
@@ -15,4 +19,4 @@
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
  <small *ngIf="hint" class="form-text text-muted">{{hint}}</small>
 | 
			
		||||
</div>
 | 
			
		||||
</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1 @@
 | 
			
		||||
// styles for ng-select child are in styles.scss
 | 
			
		||||
 
 | 
			
		||||
@@ -1,30 +1,41 @@
 | 
			
		||||
<div class="form-group">
 | 
			
		||||
  <label for="exampleFormControlTextarea1">Tags</label>
 | 
			
		||||
<div class="form-group paperless-input-select paperless-input-tags">
 | 
			
		||||
  <label for="tags">Tags</label>
 | 
			
		||||
 | 
			
		||||
  <div class="input-group">
 | 
			
		||||
    <div class="form-control tags-form-control" id="tags">
 | 
			
		||||
      <app-tag class="mr-2" *ngFor="let id of displayValue" [tag]="getTag(id)" (click)="removeTag(id)"></app-tag>
 | 
			
		||||
    </div>
 | 
			
		||||
  <div class="input-group flex-nowrap">
 | 
			
		||||
    <ng-select name="tags" [items]="tags" bindLabel="name" bindValue="id" [(ngModel)]="displayValue"
 | 
			
		||||
      [multiple]="true"
 | 
			
		||||
      [closeOnSelect]="false"
 | 
			
		||||
      [disabled]="disabled"
 | 
			
		||||
      (change)="ngSelectChange()">
 | 
			
		||||
 | 
			
		||||
    <div class="input-group-append" ngbDropdown placement="top-right">
 | 
			
		||||
      <button class="btn btn-outline-secondary" type="button" ngbDropdownToggle></button>
 | 
			
		||||
      <div ngbDropdownMenu class="scrollable-menu shadow">
 | 
			
		||||
        <button type="button" *ngFor="let tag of tags" ngbDropdownItem (click)="addTag(tag.id)">
 | 
			
		||||
          <app-tag [tag]="tag"></app-tag>
 | 
			
		||||
        </button>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
      <ng-template ng-label-tmp let-item="item">
 | 
			
		||||
        <span class="tag-wrap tag-wrap-delete" (click)="removeTag(item.id)">
 | 
			
		||||
          <svg width="1.2em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
 | 
			
		||||
            <use xlink:href="assets/bootstrap-icons.svg#x"/>
 | 
			
		||||
          </svg>
 | 
			
		||||
          <app-tag style="background-color: none;" [tag]="getTag(item.id)"></app-tag>
 | 
			
		||||
        </span>
 | 
			
		||||
      </ng-template>
 | 
			
		||||
      <ng-template ng-option-tmp let-item="item" let-index="index" let-search="searchTerm">
 | 
			
		||||
        <div class="tag-wrap">
 | 
			
		||||
          <div class="selected-icon d-inline-block mr-1">
 | 
			
		||||
            <svg *ngIf="displayValue.includes(item.id)" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
 | 
			
		||||
              <use xlink:href="assets/bootstrap-icons.svg#check"/>
 | 
			
		||||
            </svg>
 | 
			
		||||
          </div>
 | 
			
		||||
          <app-tag class="mr-2" [tag]="getTag(item.id)"></app-tag>
 | 
			
		||||
        </div>
 | 
			
		||||
      </ng-template>
 | 
			
		||||
    </ng-select>
 | 
			
		||||
 | 
			
		||||
    <div class="input-group-append">
 | 
			
		||||
 | 
			
		||||
      <button class="btn btn-outline-secondary" type="button" (click)="createTag()">
 | 
			
		||||
        <svg class="buttonicon" fill="currentColor">
 | 
			
		||||
          <use xlink:href="assets/bootstrap-icons.svg#plus" />
 | 
			
		||||
        </svg>
 | 
			
		||||
      </button>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
  </div>
 | 
			
		||||
  <small class="form-text text-muted" *ngIf="hint">{{hint}}</small>
 | 
			
		||||
 | 
			
		||||
</div>
 | 
			
		||||
</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,12 @@
 | 
			
		||||
.tags-form-control {
 | 
			
		||||
  height: auto;
 | 
			
		||||
.selected-icon {
 | 
			
		||||
  min-width: 1em;
 | 
			
		||||
  min-height: 1em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tag-wrap {
 | 
			
		||||
  font-size: 1rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.scrollable-menu {
 | 
			
		||||
  height: auto;
 | 
			
		||||
  max-height: 300px;
 | 
			
		||||
  overflow-x: hidden;
 | 
			
		||||
}
 | 
			
		||||
.tag-wrap-delete {
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@ export class TagsComponent implements OnInit, ControlValueAccessor {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  onChange = (newValue: number[]) => {};
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  onTouched = () => {};
 | 
			
		||||
 | 
			
		||||
  writeValue(newValue: number[]): void {
 | 
			
		||||
@@ -66,29 +66,28 @@ export class TagsComponent implements OnInit, ControlValueAccessor {
 | 
			
		||||
  removeTag(id) {
 | 
			
		||||
    let index = this.displayValue.indexOf(id)
 | 
			
		||||
    if (index > -1) {
 | 
			
		||||
      this.displayValue.splice(index, 1)
 | 
			
		||||
      let oldValue = this.displayValue
 | 
			
		||||
      oldValue.splice(index, 1)
 | 
			
		||||
      this.displayValue = [...oldValue]
 | 
			
		||||
      this.onChange(this.displayValue)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  addTag(id) {
 | 
			
		||||
    let index = this.displayValue.indexOf(id)
 | 
			
		||||
    if (index == -1) {
 | 
			
		||||
      this.displayValue.push(id)
 | 
			
		||||
      this.onChange(this.displayValue)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  createTag() {
 | 
			
		||||
    var modal = this.modalService.open(TagEditDialogComponent, {backdrop: 'static'})
 | 
			
		||||
    modal.componentInstance.dialogMode = 'create'
 | 
			
		||||
    modal.componentInstance.success.subscribe(newTag => {
 | 
			
		||||
      this.tagService.listAll().subscribe(tags => {
 | 
			
		||||
        this.tags = tags.results
 | 
			
		||||
        this.addTag(newTag.id)
 | 
			
		||||
        this.displayValue = [...this.displayValue, newTag.id]
 | 
			
		||||
        this.onChange(this.displayValue)
 | 
			
		||||
      })
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngSelectChange() {
 | 
			
		||||
    this.value = this.displayValue
 | 
			
		||||
    this.onChange(this.displayValue)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -58,9 +58,9 @@
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <app-input-date-time titleDate="Date created" formControlName="created"></app-input-date-time>
 | 
			
		||||
                        <app-input-select [items]="correspondents" title="Correspondent" formControlName="correspondent"
 | 
			
		||||
                            allowNull="true" (createNew)="createCorrespondent()"></app-input-select>
 | 
			
		||||
                            (createNew)="createCorrespondent()"></app-input-select>
 | 
			
		||||
                        <app-input-select [items]="documentTypes" title="Document type" formControlName="document_type"
 | 
			
		||||
                            allowNull="true" (createNew)="createDocumentType()"></app-input-select>
 | 
			
		||||
                            (createNew)="createDocumentType()"></app-input-select>
 | 
			
		||||
                        <app-input-tags formControlName="tags" title="Tags"></app-input-tags>
 | 
			
		||||
 | 
			
		||||
                    </ng-template>
 | 
			
		||||
 
 | 
			
		||||
@@ -4,38 +4,39 @@
 | 
			
		||||
  </button>
 | 
			
		||||
  <div class="dropdown-menu date-filter shadow pt-0" ngbDropdownMenu attr.aria-labelledby="dropdown{{title}}">
 | 
			
		||||
    <div class="list-group list-group-flush">
 | 
			
		||||
        <button class="list-group-item small list-goup list-group-item-action d-flex p-2 pl-3" (click)="clear()">Clear</button>
 | 
			
		||||
        <button *ngFor="let range of [7, 30, 'month', 'year']" class="list-group-item small list-goup list-group-item-action d-flex p-2 pl-3" role="menuitem" (click)="setDateQuickFilter(range)">
 | 
			
		||||
          <ng-container *ngIf="isStringRange(range)">This </ng-container>
 | 
			
		||||
          {{ range }}
 | 
			
		||||
          <ng-container *ngIf="!isStringRange(range)"> days</ng-container>
 | 
			
		||||
        <button *ngFor="let qf of quickFilters" class="list-group-item small list-goup list-group-item-action d-flex p-2 pl-3" role="menuitem" (click)="setDateQuickFilter(qf.id)">
 | 
			
		||||
          {{qf.name}}
 | 
			
		||||
        </button>
 | 
			
		||||
        <div class="list-group-item d-flex flex-column align-items-start" role="menuitem">
 | 
			
		||||
          <div>Before</div>
 | 
			
		||||
 | 
			
		||||
          <div class="mb-2 d-flex flex-row w-100 justify-content-between small">
 | 
			
		||||
            <div>After</div>
 | 
			
		||||
            <a *ngIf="dateAfter" class="btn btn-link p-0 m-0" (click)="clearAfter()">
 | 
			
		||||
              <svg width="0.8em" height="0.8em" viewBox="0 0 16 16" class="bi bi-x" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
 | 
			
		||||
                <path fill-rule="evenodd" d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z" />
 | 
			
		||||
              </svg>
 | 
			
		||||
              <small>Clear</small>
 | 
			
		||||
            </a>
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
          <div class="input-group input-group-sm">
 | 
			
		||||
            <input class="form-control" type="text" placeholder="yyyy-mm-dd" name="before" [(ngModel)]="_dateBefore" [maxDate]="this._maxDate" ngbDatepicker (dateSelect)="onBeforeSelected($event)" #dpBefore="ngbDatepicker">
 | 
			
		||||
            <div class="input-group-append">
 | 
			
		||||
              <button class="btn btn-outline-secondary btn-sm" (click)="dpBefore.toggle()" type="button">
 | 
			
		||||
                <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-calendar-date" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
 | 
			
		||||
                  <path fill-rule="evenodd" d="M3.5 0a.5.5 0 0 1 .5.5V1h8V.5a.5.5 0 0 1 1 0V1h1a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2h1V.5a.5.5 0 0 1 .5-.5zM1 4v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V4H1z"/>
 | 
			
		||||
                  <path d="M6.445 11.688V6.354h-.633A12.6 12.6 0 0 0 4.5 7.16v.695c.375-.257.969-.62 1.258-.777h.012v4.61h.675zm1.188-1.305c.047.64.594 1.406 1.703 1.406 1.258 0 2-1.066 2-2.871 0-1.934-.781-2.668-1.953-2.668-.926 0-1.797.672-1.797 1.809 0 1.16.824 1.77 1.676 1.77.746 0 1.23-.376 1.383-.79h.027c-.004 1.316-.461 2.164-1.305 2.164-.664 0-1.008-.45-1.05-.82h-.684zm2.953-2.317c0 .696-.559 1.18-1.184 1.18-.601 0-1.144-.383-1.144-1.2 0-.823.582-1.21 1.168-1.21.633 0 1.16.398 1.16 1.23z"/>
 | 
			
		||||
                </svg>
 | 
			
		||||
              </button>
 | 
			
		||||
            </div>
 | 
			
		||||
            <input type="date" class="form-control" id="date_after" [(ngModel)]="dateAfter" (change)="onChangeDebounce()">
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="list-group-item d-flex flex-column align-items-start" role="menuitem">
 | 
			
		||||
          <div>After</div>
 | 
			
		||||
 | 
			
		||||
          <div class="mb-2 d-flex flex-row w-100 justify-content-between small">
 | 
			
		||||
            <div>Before</div>
 | 
			
		||||
            <a *ngIf="dateBefore" class="btn btn-link p-0 m-0" (click)="clearBefore()">
 | 
			
		||||
              <svg width="0.8em" height="0.8em" viewBox="0 0 16 16" class="bi bi-x" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
 | 
			
		||||
                <path fill-rule="evenodd" d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z" />
 | 
			
		||||
              </svg>
 | 
			
		||||
              <small>Clear</small>
 | 
			
		||||
            </a>
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
          <div class="input-group input-group-sm">
 | 
			
		||||
            <input class="form-control form-control-sm" type="text" placeholder="yyyy-mm-dd" name="after" [(ngModel)]="_dateAfter" [maxDate]="this._maxDate" ngbDatepicker (dateSelect)="onAfterSelected($event)" #dpAfter="ngbDatepicker">
 | 
			
		||||
            <div class="input-group-append">
 | 
			
		||||
              <button class="btn btn-outline-secondary btn-sm" (click)="dpAfter.toggle()" type="button">
 | 
			
		||||
                <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-calendar-date" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
 | 
			
		||||
                  <path fill-rule="evenodd" d="M3.5 0a.5.5 0 0 1 .5.5V1h8V.5a.5.5 0 0 1 1 0V1h1a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2h1V.5a.5.5 0 0 1 .5-.5zM1 4v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V4H1z"/>
 | 
			
		||||
                  <path d="M6.445 11.688V6.354h-.633A12.6 12.6 0 0 0 4.5 7.16v.695c.375-.257.969-.62 1.258-.777h.012v4.61h.675zm1.188-1.305c.047.64.594 1.406 1.703 1.406 1.258 0 2-1.066 2-2.871 0-1.934-.781-2.668-1.953-2.668-.926 0-1.797.672-1.797 1.809 0 1.16.824 1.77 1.676 1.77.746 0 1.23-.376 1.383-.79h.027c-.004 1.316-.461 2.164-1.305 2.164-.664 0-1.008-.45-1.05-.82h-.684zm2.953-2.317c0 .696-.559 1.18-1.184 1.18-.601 0-1.144-.383-1.144-1.2 0-.823.582-1.21 1.168-1.21.633 0 1.16.398 1.16 1.23z"/>
 | 
			
		||||
                </svg>
 | 
			
		||||
              </button>
 | 
			
		||||
            </div>
 | 
			
		||||
            <input type="date" class="form-control" id="date_before" [(ngModel)]="dateBefore" (change)="onChangeDebounce()">
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,24 +1,37 @@
 | 
			
		||||
import { Component, EventEmitter, Input, Output, ElementRef, ViewChild, SimpleChange } from '@angular/core';
 | 
			
		||||
import { NgbDate, NgbDateStruct, NgbDatepicker } from '@ng-bootstrap/ng-bootstrap';
 | 
			
		||||
import { formatDate } from '@angular/common';
 | 
			
		||||
import { Component, EventEmitter, Input, Output, OnInit, OnDestroy } from '@angular/core';
 | 
			
		||||
import { Subject, Subscription } from 'rxjs';
 | 
			
		||||
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
 | 
			
		||||
 | 
			
		||||
export interface DateSelection {
 | 
			
		||||
  before?: NgbDateStruct
 | 
			
		||||
  after?: NgbDateStruct
 | 
			
		||||
  before?: string
 | 
			
		||||
  after?: string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const FILTER_LAST_7_DAYS = 0
 | 
			
		||||
const FILTER_LAST_MONTH = 1
 | 
			
		||||
const FILTER_LAST_3_MONTHS = 2
 | 
			
		||||
const FILTER_LAST_YEAR = 3
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-filter-dropdown-date',
 | 
			
		||||
  templateUrl: './filter-dropdown-date.component.html',
 | 
			
		||||
  styleUrls: ['./filter-dropdown-date.component.scss']
 | 
			
		||||
})
 | 
			
		||||
export class FilterDropdownDateComponent {
 | 
			
		||||
export class FilterDropdownDateComponent implements OnInit, OnDestroy {
 | 
			
		||||
 | 
			
		||||
  quickFilters = [
 | 
			
		||||
    {id: FILTER_LAST_7_DAYS, name: "Last 7 days"}, 
 | 
			
		||||
    {id: FILTER_LAST_MONTH, name: "Last month"},
 | 
			
		||||
    {id: FILTER_LAST_3_MONTHS, name: "Last 3 months"},
 | 
			
		||||
    {id: FILTER_LAST_YEAR, name: "Last year"}
 | 
			
		||||
  ]
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  dateBefore: NgbDateStruct
 | 
			
		||||
  dateBefore: string
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  dateAfter: NgbDateStruct
 | 
			
		||||
  dateAfter: string
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  title: string
 | 
			
		||||
@@ -26,87 +39,65 @@ export class FilterDropdownDateComponent {
 | 
			
		||||
  @Output()
 | 
			
		||||
  datesSet = new EventEmitter<DateSelection>()
 | 
			
		||||
 | 
			
		||||
  @ViewChild('dpAfter') dpAfter: NgbDatepicker
 | 
			
		||||
  @ViewChild('dpBefore') dpBefore: NgbDatepicker
 | 
			
		||||
  private datesSetDebounce$ = new Subject()
 | 
			
		||||
 | 
			
		||||
  _dateBefore: NgbDateStruct
 | 
			
		||||
  _dateAfter: NgbDateStruct
 | 
			
		||||
 | 
			
		||||
  get _maxDate(): NgbDate {
 | 
			
		||||
    let date = new Date()
 | 
			
		||||
    return NgbDate.from({year: date.getFullYear(), month: date.getMonth() + 1, day: date.getDate()})
 | 
			
		||||
  private sub: Subscription
 | 
			
		||||
  
 | 
			
		||||
  ngOnInit() {
 | 
			
		||||
    this.sub = this.datesSetDebounce$.pipe(
 | 
			
		||||
      debounceTime(400)
 | 
			
		||||
    ).subscribe(() => {
 | 
			
		||||
      this.onChange()
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  isStringRange(range: any) {
 | 
			
		||||
    return typeof range == 'string'
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnChanges(changes: SimpleChange) {
 | 
			
		||||
    // this is a hacky workaround perhaps because of https://github.com/angular/angular/issues/11097
 | 
			
		||||
    let dateString: string = ''
 | 
			
		||||
    let dateAfterChange: SimpleChange
 | 
			
		||||
    let dateBeforeChange: SimpleChange
 | 
			
		||||
    if (changes) {
 | 
			
		||||
      dateAfterChange = changes['dateAfter']
 | 
			
		||||
      dateBeforeChange = changes['dateBefore']
 | 
			
		||||
  ngOnDestroy() {
 | 
			
		||||
    if (this.sub) {
 | 
			
		||||
      this.sub.unsubscribe()
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    if (this.dpBefore && this.dpAfter) {
 | 
			
		||||
      let dpAfterElRef: ElementRef = this.dpAfter['_elRef']
 | 
			
		||||
      let dpBeforeElRef: ElementRef = this.dpBefore['_elRef']
 | 
			
		||||
  setDateQuickFilter(qf: number) {
 | 
			
		||||
    this.dateBefore = null
 | 
			
		||||
    let date = new Date()
 | 
			
		||||
    switch (qf) {
 | 
			
		||||
      case FILTER_LAST_7_DAYS:
 | 
			
		||||
        date.setDate(date.getDate() - 7)
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      if (dateAfterChange && dateAfterChange.currentValue) {
 | 
			
		||||
        let dateAfterDate = dateAfterChange.currentValue as NgbDateStruct
 | 
			
		||||
        dateString = `${dateAfterDate.year}-${dateAfterDate.month.toString().padStart(2,'0')}-${dateAfterDate.day.toString().padStart(2,'0')}`
 | 
			
		||||
        dpAfterElRef.nativeElement.value = dateString
 | 
			
		||||
      } else if (dateBeforeChange && dateBeforeChange.currentValue) {
 | 
			
		||||
        let dateBeforeDate = dateBeforeChange.currentValue as NgbDateStruct
 | 
			
		||||
        dateString = `${dateBeforeDate.year}-${dateBeforeDate.month.toString().padStart(2,'0')}-${dateBeforeDate.day.toString().padStart(2,'0')}`
 | 
			
		||||
        dpBeforeElRef.nativeElement.value = dateString
 | 
			
		||||
      } else {
 | 
			
		||||
        dpAfterElRef.nativeElement.value = dateString
 | 
			
		||||
        dpBeforeElRef.nativeElement.value = dateString
 | 
			
		||||
      case FILTER_LAST_MONTH:
 | 
			
		||||
        date.setMonth(date.getMonth() - 1)
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case FILTER_LAST_3_MONTHS:
 | 
			
		||||
        date.setMonth(date.getMonth() - 3)
 | 
			
		||||
        break
 | 
			
		||||
 | 
			
		||||
      case FILTER_LAST_YEAR:
 | 
			
		||||
        date.setFullYear(date.getFullYear() - 1)
 | 
			
		||||
        break
 | 
			
		||||
  
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    this.dateAfter = formatDate(date, 'yyyy-MM-dd', "en-us", "UTC")
 | 
			
		||||
    this.onChange()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setDateQuickFilter(range: any) {
 | 
			
		||||
    let date = new Date()
 | 
			
		||||
    let newDate: NgbDateStruct = { year: date.getFullYear(), month: date.getMonth() + 1, day: date.getDate() }
 | 
			
		||||
    switch (typeof range) {
 | 
			
		||||
      case 'number':
 | 
			
		||||
        date.setDate(date.getDate() - range)
 | 
			
		||||
        newDate.year = date.getFullYear()
 | 
			
		||||
        newDate.month = date.getMonth() + 1
 | 
			
		||||
        newDate.day = date.getDate()
 | 
			
		||||
        break
 | 
			
		||||
 | 
			
		||||
      case 'string':
 | 
			
		||||
        newDate.day = 1
 | 
			
		||||
        if (range == 'year') newDate.month = 1
 | 
			
		||||
        break
 | 
			
		||||
 | 
			
		||||
      default:
 | 
			
		||||
        break
 | 
			
		||||
    }
 | 
			
		||||
    this._dateAfter = newDate
 | 
			
		||||
    this._dateBefore = null
 | 
			
		||||
    this.datesSet.emit({after: newDate, before: null})
 | 
			
		||||
  onChange() {
 | 
			
		||||
    this.datesSet.emit({after: this.dateAfter, before: this.dateBefore})
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onBeforeSelected(date: NgbDateStruct) {
 | 
			
		||||
    this._dateBefore = date
 | 
			
		||||
    this.datesSet.emit({after: this._dateAfter, before: date})
 | 
			
		||||
  onChangeDebounce() {
 | 
			
		||||
    this.datesSetDebounce$.next({after: this.dateAfter, before: this.dateBefore})
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onAfterSelected(date: NgbDateStruct) {
 | 
			
		||||
    this._dateAfter = date
 | 
			
		||||
    this.datesSet.emit({after: date, before: this._dateBefore})
 | 
			
		||||
  clearBefore() {
 | 
			
		||||
    this.dateBefore = null;
 | 
			
		||||
    this.onChange()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  clear() {
 | 
			
		||||
    this._dateBefore = null
 | 
			
		||||
    this._dateAfter = null
 | 
			
		||||
    this.datesSet.emit({after: null, before: null})
 | 
			
		||||
  clearAfter() {
 | 
			
		||||
    this.dateAfter = null;
 | 
			
		||||
    this.onChange()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -179,54 +179,53 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
 | 
			
		||||
    this.applyFilters()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get dateCreatedBefore(): NgbDateStruct {
 | 
			
		||||
  get dateCreatedBefore(): string {
 | 
			
		||||
    let createdBeforeRule: FilterRule = this.filterRules.find(fr => fr.rule_type == FILTER_CREATED_BEFORE)
 | 
			
		||||
    return createdBeforeRule ? this.dateParser.parse(createdBeforeRule.value) : null
 | 
			
		||||
    return createdBeforeRule ? createdBeforeRule.value : null
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get dateCreatedAfter(): NgbDateStruct {
 | 
			
		||||
  get dateCreatedAfter(): string {
 | 
			
		||||
    let createdAfterRule: FilterRule = this.filterRules.find(fr => fr.rule_type == FILTER_CREATED_AFTER)
 | 
			
		||||
    return createdAfterRule ? this.dateParser.parse(createdAfterRule.value) : null
 | 
			
		||||
    return createdAfterRule ? createdAfterRule.value : null
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get dateAddedBefore(): NgbDateStruct {
 | 
			
		||||
  get dateAddedBefore(): string {
 | 
			
		||||
    let addedBeforeRule: FilterRule = this.filterRules.find(fr => fr.rule_type == FILTER_ADDED_BEFORE)
 | 
			
		||||
    return addedBeforeRule ? this.dateParser.parse(addedBeforeRule.value) : null
 | 
			
		||||
    return addedBeforeRule ? addedBeforeRule.value : null
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get dateAddedAfter(): NgbDateStruct {
 | 
			
		||||
  get dateAddedAfter(): string {
 | 
			
		||||
    let addedAfterRule: FilterRule = this.filterRules.find(fr => fr.rule_type == FILTER_ADDED_AFTER)
 | 
			
		||||
    return addedAfterRule ? this.dateParser.parse(addedAfterRule.value) : null
 | 
			
		||||
    return addedAfterRule ? addedAfterRule.value : null
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setDateCreatedBefore(date?: NgbDateStruct) {
 | 
			
		||||
  setDateCreatedBefore(date?: string) {
 | 
			
		||||
    if (date) this.setDateFilter(date, FILTER_CREATED_BEFORE)
 | 
			
		||||
    else this.clearDateFilter(FILTER_CREATED_BEFORE)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setDateCreatedAfter(date?: NgbDateStruct) {
 | 
			
		||||
  setDateCreatedAfter(date?: string) {
 | 
			
		||||
    if (date) this.setDateFilter(date, FILTER_CREATED_AFTER)
 | 
			
		||||
    else this.clearDateFilter(FILTER_CREATED_AFTER)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setDateAddedBefore(date?: NgbDateStruct) {
 | 
			
		||||
  setDateAddedBefore(date?: string) {
 | 
			
		||||
    if (date) this.setDateFilter(date, FILTER_ADDED_BEFORE)
 | 
			
		||||
    else this.clearDateFilter(FILTER_ADDED_BEFORE)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setDateAddedAfter(date?: NgbDateStruct) {
 | 
			
		||||
  setDateAddedAfter(date?: string) {
 | 
			
		||||
    if (date) this.setDateFilter(date, FILTER_ADDED_AFTER)
 | 
			
		||||
    else this.clearDateFilter(FILTER_ADDED_AFTER)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setDateFilter(date: NgbDateStruct, dateRuleTypeID: number) {
 | 
			
		||||
  setDateFilter(date: string, dateRuleTypeID: number) {
 | 
			
		||||
    let existingRule = this.filterRules.find(rule => rule.rule_type == dateRuleTypeID)
 | 
			
		||||
    let newValue = this.dateParser.format(date)
 | 
			
		||||
 | 
			
		||||
    if (existingRule) {
 | 
			
		||||
      existingRule.value = newValue
 | 
			
		||||
      existingRule.value = date
 | 
			
		||||
    } else {
 | 
			
		||||
      this.filterRules.push({rule_type: dateRuleTypeID, value: newValue})
 | 
			
		||||
      this.filterRules.push({rule_type: dateRuleTypeID, value: date})
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
@import "theme";
 | 
			
		||||
 | 
			
		||||
@import "node_modules/bootstrap/scss/bootstrap";
 | 
			
		||||
 | 
			
		||||
@import "~@ng-select/ng-select/themes/default.theme.css";
 | 
			
		||||
 | 
			
		||||
.toolbaricon {
 | 
			
		||||
  width: 1.2em;
 | 
			
		||||
@@ -20,7 +19,7 @@
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
body {
 | 
			
		||||
  font-size: .875rem;
 | 
			
		||||
  font-size: 0.875rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.form-control-dark {
 | 
			
		||||
@@ -65,4 +64,39 @@ body {
 | 
			
		||||
  display: block;
 | 
			
		||||
  background-size: 1rem;
 | 
			
		||||
  float: right;
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.paperless-input-select {
 | 
			
		||||
  .ng-select {
 | 
			
		||||
    position: relative;
 | 
			
		||||
    flex: 1 1 auto;
 | 
			
		||||
    margin-bottom: 0;
 | 
			
		||||
    min-height: calc(1.5em + 0.75rem + 5px);
 | 
			
		||||
    line-height: 1.5;
 | 
			
		||||
 | 
			
		||||
    .ng-select-container {
 | 
			
		||||
      height: 100%;
 | 
			
		||||
      border-top-right-radius: 0;
 | 
			
		||||
      border-bottom-right-radius: 0;
 | 
			
		||||
 | 
			
		||||
      .ng-value-container .ng-input {
 | 
			
		||||
        top: 10px;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .ng-dropdown-panel .ng-dropdown-panel-items .ng-option.ng-option-selected,
 | 
			
		||||
    .ng-dropdown-panel .ng-dropdown-panel-items .ng-option.ng-option-selected.ng-option-marked {
 | 
			
		||||
      background: none;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.paperless-input-tags {
 | 
			
		||||
  .ng-select.ng-select-multiple .ng-select-container .ng-value-container .ng-value {
 | 
			
		||||
    background-color: transparent;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .ng-select.ng-select-multiple .ng-select-container .ng-value-container {
 | 
			
		||||
    padding-top: 1px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user