mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-11-03 03:16:10 -06:00 
			
		
		
		
	modular dashboard
This commit is contained in:
		@@ -41,6 +41,10 @@ import { TagsComponent } from './components/common/input/tags/tags.component';
 | 
			
		||||
import { SortableDirective } from './directives/sortable.directive';
 | 
			
		||||
import { CookieService } from 'ngx-cookie-service';
 | 
			
		||||
import { CsrfInterceptor } from './interceptors/csrf.interceptor';
 | 
			
		||||
import { SavedViewWidgetComponent } from './components/dashboard/widgets/saved-view-widget/saved-view-widget.component';
 | 
			
		||||
import { StatisticsWidgetComponent } from './components/dashboard/widgets/statistics-widget/statistics-widget.component';
 | 
			
		||||
import { UploadFileWidgetComponent } from './components/dashboard/widgets/upload-file-widget/upload-file-widget.component';
 | 
			
		||||
import { WidgetFrameComponent } from './components/dashboard/widgets/widget-frame/widget-frame.component';
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
  declarations: [
 | 
			
		||||
@@ -74,7 +78,11 @@ import { CsrfInterceptor } from './interceptors/csrf.interceptor';
 | 
			
		||||
    SaveViewConfigDialogComponent,
 | 
			
		||||
    DateTimeComponent,
 | 
			
		||||
    TagsComponent,
 | 
			
		||||
    SortableDirective
 | 
			
		||||
    SortableDirective,
 | 
			
		||||
    SavedViewWidgetComponent,
 | 
			
		||||
    StatisticsWidgetComponent,
 | 
			
		||||
    UploadFileWidgetComponent,
 | 
			
		||||
    WidgetFrameComponent
 | 
			
		||||
  ],
 | 
			
		||||
  imports: [
 | 
			
		||||
    BrowserModule,
 | 
			
		||||
 
 | 
			
		||||
@@ -4,77 +4,22 @@
 | 
			
		||||
 | 
			
		||||
<div class='row'>
 | 
			
		||||
  <div class="col-lg">
 | 
			
		||||
    <ng-container *ngFor="let v of savedDashboardViews">
 | 
			
		||||
 | 
			
		||||
      <div class="card mb-3 shadow">
 | 
			
		||||
        <div class="card-header">
 | 
			
		||||
          <h5 class="card-title mb-0">{{v.viewConfig.title}}</h5>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="card-body text-dark">
 | 
			
		||||
 | 
			
		||||
          <table class="table table-sm table-hover table-borderless">
 | 
			
		||||
            <thead>
 | 
			
		||||
              <tr>
 | 
			
		||||
                <th>Created</th>
 | 
			
		||||
                <th scope="col">Title</th>
 | 
			
		||||
              </tr>
 | 
			
		||||
            </thead>
 | 
			
		||||
            <tbody>
 | 
			
		||||
              <tr *ngFor="let doc of v.documents" routerLink="/documents/{{doc.id}}">
 | 
			
		||||
                <td>{{doc.created | date}}</td>
 | 
			
		||||
                <td>{{doc.title}}<app-tag [tag]="t" *ngFor="let t of doc.tags" class="ml-1"></app-tag>
 | 
			
		||||
              </tr>
 | 
			
		||||
            </tbody>
 | 
			
		||||
          </table>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </ng-container>
 | 
			
		||||
    <ng-container *ngIf="savedDashboardViews.length == 0">
 | 
			
		||||
      <div class="card mb-3 shadow">
 | 
			
		||||
        <div class="card-header">
 | 
			
		||||
          <h5 class="card-title mb-0">Saved views</h5>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="card-body text-dark">
 | 
			
		||||
          <p class="card-text">This space is reserved to display your saved views. Go to your documents and save a view
 | 
			
		||||
            to have it displayed
 | 
			
		||||
            here!</p>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    <app-widget-frame title="Saved views" *ngIf="savedViews.length == 0">
 | 
			
		||||
      <p class="card-text">This space is reserved to display your saved views. Go to your documents and save a view
 | 
			
		||||
        to have it displayed
 | 
			
		||||
        here!</p>
 | 
			
		||||
    </app-widget-frame>
 | 
			
		||||
 | 
			
		||||
    <ng-container *ngFor="let v of savedViews">
 | 
			
		||||
      <app-saved-view-widget [savedView]="v"></app-saved-view-widget>
 | 
			
		||||
    </ng-container>
 | 
			
		||||
 | 
			
		||||
  </div>
 | 
			
		||||
  <div class="col-lg">
 | 
			
		||||
 | 
			
		||||
    <div class="card mb-3 shadow">
 | 
			
		||||
      <div class="card-header">
 | 
			
		||||
        <h5 class="card-title mb-0">Statistics</h5>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="card-body text-dark">
 | 
			
		||||
        <p class="card-text">Documents in inbox: {{statistics.documents_inbox}}</p>
 | 
			
		||||
        <p class="card-text">Total documents: {{statistics.documents_total}}</p>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <app-statistics-widget></app-statistics-widget>
 | 
			
		||||
 | 
			
		||||
    <div class="card mb-3 shadow">
 | 
			
		||||
      <div class="card-header">
 | 
			
		||||
        <h5 class="card-title mb-0">Upload new documents</h5>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="card-body text-dark">
 | 
			
		||||
        <form>
 | 
			
		||||
          <ngx-file-drop 
 | 
			
		||||
            dropZoneLabel="Drop documents here or" (onFileDrop)="dropped($event)"
 | 
			
		||||
            (onFileOver)="fileOver($event)" (onFileLeave)="fileLeave($event)"
 | 
			
		||||
            dropZoneClassName="bg-light card"
 | 
			
		||||
            multiple="true"
 | 
			
		||||
            contentClassName="justify-content-center d-flex align-items-center p-5"
 | 
			
		||||
            [showBrowseBtn]=true
 | 
			
		||||
            browseBtnClassName="btn btn-sm btn-outline-primary ml-2">
 | 
			
		||||
 | 
			
		||||
          </ngx-file-drop>
 | 
			
		||||
        </form>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <app-upload-file-widget></app-upload-file-widget>
 | 
			
		||||
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
@@ -1,16 +1,6 @@
 | 
			
		||||
import { HttpClient } from '@angular/common/http';
 | 
			
		||||
import { Component, OnInit } from '@angular/core';
 | 
			
		||||
import { FileSystemFileEntry, NgxFileDropEntry } from 'ngx-file-drop';
 | 
			
		||||
import { Observable } from 'rxjs';
 | 
			
		||||
import { DocumentService } from 'src/app/services/rest/document.service';
 | 
			
		||||
import { SavedViewConfigService } from 'src/app/services/saved-view-config.service';
 | 
			
		||||
import { Toast, ToastService } from 'src/app/services/toast.service';
 | 
			
		||||
import { environment } from 'src/environments/environment';
 | 
			
		||||
 | 
			
		||||
export interface Statistics {
 | 
			
		||||
  documents_total?: number
 | 
			
		||||
  documents_inbox?: number
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-dashboard',
 | 
			
		||||
@@ -19,53 +9,14 @@ export interface Statistics {
 | 
			
		||||
})
 | 
			
		||||
export class DashboardComponent implements OnInit {
 | 
			
		||||
 | 
			
		||||
  constructor(private documentService: DocumentService, private toastService: ToastService,
 | 
			
		||||
    public savedViewConfigService: SavedViewConfigService, private http: HttpClient) { }
 | 
			
		||||
  constructor(
 | 
			
		||||
    public savedViewConfigService: SavedViewConfigService) { }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  savedDashboardViews = []
 | 
			
		||||
  statistics: Statistics = {}
 | 
			
		||||
  savedViews = []
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    this.savedViewConfigService.getDashboardConfigs().forEach(config => {
 | 
			
		||||
      this.documentService.list(1,10,config.sortField,config.sortDirection,config.filterRules).subscribe(result => {
 | 
			
		||||
        this.savedDashboardViews.push({viewConfig: config, documents: result.results})
 | 
			
		||||
      })
 | 
			
		||||
    })
 | 
			
		||||
    this.getStatistics().subscribe(statistics => {
 | 
			
		||||
      this.statistics = statistics
 | 
			
		||||
    })
 | 
			
		||||
    this.savedViews = this.savedViewConfigService.getDashboardConfigs()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getStatistics(): Observable<Statistics> {
 | 
			
		||||
    return this.http.get(`${environment.apiBaseUrl}statistics/`)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  public fileOver(event){
 | 
			
		||||
    console.log(event);
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
  public fileLeave(event){
 | 
			
		||||
    console.log(event);
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
  public dropped(files: NgxFileDropEntry[]) {
 | 
			
		||||
    for (const droppedFile of files) {
 | 
			
		||||
      if (droppedFile.fileEntry.isFile) {
 | 
			
		||||
        const fileEntry = droppedFile.fileEntry as FileSystemFileEntry;
 | 
			
		||||
        console.log(fileEntry)
 | 
			
		||||
        fileEntry.file((file: File) => {
 | 
			
		||||
          console.log(file)
 | 
			
		||||
          const formData = new FormData()
 | 
			
		||||
          formData.append('document', file, file.name)
 | 
			
		||||
          this.documentService.uploadDocument(formData).subscribe(result => {
 | 
			
		||||
            this.toastService.showToast(Toast.make("Information", "The document has been uploaded and will be processed by the consumer shortly."))
 | 
			
		||||
          }, error => {
 | 
			
		||||
            this.toastService.showToast(Toast.makeError("An error has occured while uploading the document. Sorry!"))
 | 
			
		||||
          })
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,18 @@
 | 
			
		||||
<app-widget-frame [title]="savedView.title">
 | 
			
		||||
 | 
			
		||||
  <table class="table table-sm table-hover table-borderless">
 | 
			
		||||
    <thead>
 | 
			
		||||
      <tr>
 | 
			
		||||
        <th>Created</th>
 | 
			
		||||
        <th scope="col">Title</th>
 | 
			
		||||
      </tr>
 | 
			
		||||
    </thead>
 | 
			
		||||
    <tbody>
 | 
			
		||||
      <tr *ngFor="let doc of documents" routerLink="/documents/{{doc.id}}">
 | 
			
		||||
        <td>{{doc.created | date}}</td>
 | 
			
		||||
        <td>{{doc.title}}<app-tag [tag]="t" *ngFor="let t of doc.tags" class="ml-1"></app-tag>
 | 
			
		||||
      </tr>
 | 
			
		||||
    </tbody>
 | 
			
		||||
  </table>
 | 
			
		||||
 | 
			
		||||
</app-widget-frame>
 | 
			
		||||
@@ -0,0 +1,25 @@
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
 | 
			
		||||
import { SavedViewWidgetComponent } from './saved-view-widget.component';
 | 
			
		||||
 | 
			
		||||
describe('SavedViewWidgetComponent', () => {
 | 
			
		||||
  let component: SavedViewWidgetComponent;
 | 
			
		||||
  let fixture: ComponentFixture<SavedViewWidgetComponent>;
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ SavedViewWidgetComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(SavedViewWidgetComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
@@ -0,0 +1,26 @@
 | 
			
		||||
import { Component, Input, OnInit } from '@angular/core';
 | 
			
		||||
import { PaperlessDocument } from 'src/app/data/paperless-document';
 | 
			
		||||
import { SavedViewConfig } from 'src/app/data/saved-view-config';
 | 
			
		||||
import { DocumentService } from 'src/app/services/rest/document.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-saved-view-widget',
 | 
			
		||||
  templateUrl: './saved-view-widget.component.html',
 | 
			
		||||
  styleUrls: ['./saved-view-widget.component.scss']
 | 
			
		||||
})
 | 
			
		||||
export class SavedViewWidgetComponent implements OnInit {
 | 
			
		||||
 | 
			
		||||
  constructor(private documentService: DocumentService) { }
 | 
			
		||||
  
 | 
			
		||||
  @Input()
 | 
			
		||||
  savedView: SavedViewConfig
 | 
			
		||||
 | 
			
		||||
  documents: PaperlessDocument[] = []
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    this.documentService.list(1,10,this.savedView.sortField,this.savedView.sortDirection,this.savedView.filterRules).subscribe(result => {
 | 
			
		||||
      this.documents = result.results
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,4 @@
 | 
			
		||||
<app-widget-frame title="Statistics">
 | 
			
		||||
  <p class="card-text">Documents in inbox: {{statistics.documents_inbox}}</p>
 | 
			
		||||
  <p class="card-text">Total documents: {{statistics.documents_total}}</p>
 | 
			
		||||
</app-widget-frame>
 | 
			
		||||
@@ -0,0 +1,25 @@
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
 | 
			
		||||
import { StatisticsWidgetComponent } from './statistics-widget.component';
 | 
			
		||||
 | 
			
		||||
describe('StatisticsWidgetComponent', () => {
 | 
			
		||||
  let component: StatisticsWidgetComponent;
 | 
			
		||||
  let fixture: ComponentFixture<StatisticsWidgetComponent>;
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ StatisticsWidgetComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(StatisticsWidgetComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
@@ -0,0 +1,33 @@
 | 
			
		||||
import { HttpClient } from '@angular/common/http';
 | 
			
		||||
import { Component, OnInit } from '@angular/core';
 | 
			
		||||
import { Observable } from 'rxjs';
 | 
			
		||||
import { environment } from 'src/environments/environment';
 | 
			
		||||
 | 
			
		||||
export interface Statistics {
 | 
			
		||||
  documents_total?: number
 | 
			
		||||
  documents_inbox?: number
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-statistics-widget',
 | 
			
		||||
  templateUrl: './statistics-widget.component.html',
 | 
			
		||||
  styleUrls: ['./statistics-widget.component.scss']
 | 
			
		||||
})
 | 
			
		||||
export class StatisticsWidgetComponent implements OnInit {
 | 
			
		||||
 | 
			
		||||
  constructor(private http: HttpClient) { }
 | 
			
		||||
 | 
			
		||||
  statistics: Statistics = {}
 | 
			
		||||
 | 
			
		||||
  getStatistics(): Observable<Statistics> {
 | 
			
		||||
    return this.http.get(`${environment.apiBaseUrl}statistics/`)
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    this.getStatistics().subscribe(statistics => {
 | 
			
		||||
      this.statistics = statistics
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,15 @@
 | 
			
		||||
<app-widget-frame title="Upload new documents">
 | 
			
		||||
 | 
			
		||||
  <form>
 | 
			
		||||
    <ngx-file-drop 
 | 
			
		||||
      dropZoneLabel="Drop documents here or" (onFileDrop)="dropped($event)"
 | 
			
		||||
      (onFileOver)="fileOver($event)" (onFileLeave)="fileLeave($event)"
 | 
			
		||||
      dropZoneClassName="bg-light card"
 | 
			
		||||
      multiple="true"
 | 
			
		||||
      contentClassName="justify-content-center d-flex align-items-center p-5"
 | 
			
		||||
      [showBrowseBtn]=true
 | 
			
		||||
      browseBtnClassName="btn btn-sm btn-outline-primary ml-2">
 | 
			
		||||
 | 
			
		||||
    </ngx-file-drop>
 | 
			
		||||
  </form>
 | 
			
		||||
</app-widget-frame>
 | 
			
		||||
@@ -0,0 +1,25 @@
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
 | 
			
		||||
import { UploadFileWidgetComponent } from './upload-file-widget.component';
 | 
			
		||||
 | 
			
		||||
describe('UploadFileWidgetComponent', () => {
 | 
			
		||||
  let component: UploadFileWidgetComponent;
 | 
			
		||||
  let fixture: ComponentFixture<UploadFileWidgetComponent>;
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ UploadFileWidgetComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(UploadFileWidgetComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
@@ -0,0 +1,44 @@
 | 
			
		||||
import { Component, OnInit } from '@angular/core';
 | 
			
		||||
import { FileSystemFileEntry, NgxFileDropEntry } from 'ngx-file-drop';
 | 
			
		||||
import { DocumentService } from 'src/app/services/rest/document.service';
 | 
			
		||||
import { Toast, ToastService } from 'src/app/services/toast.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-upload-file-widget',
 | 
			
		||||
  templateUrl: './upload-file-widget.component.html',
 | 
			
		||||
  styleUrls: ['./upload-file-widget.component.scss']
 | 
			
		||||
})
 | 
			
		||||
export class UploadFileWidgetComponent implements OnInit {
 | 
			
		||||
 | 
			
		||||
  constructor(private documentService: DocumentService, private toastService: ToastService) { }
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public fileOver(event){
 | 
			
		||||
    console.log(event);
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
  public fileLeave(event){
 | 
			
		||||
    console.log(event);
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
  public dropped(files: NgxFileDropEntry[]) {
 | 
			
		||||
    for (const droppedFile of files) {
 | 
			
		||||
      if (droppedFile.fileEntry.isFile) {
 | 
			
		||||
        const fileEntry = droppedFile.fileEntry as FileSystemFileEntry;
 | 
			
		||||
        console.log(fileEntry)
 | 
			
		||||
        fileEntry.file((file: File) => {
 | 
			
		||||
          console.log(file)
 | 
			
		||||
          const formData = new FormData()
 | 
			
		||||
          formData.append('document', file, file.name)
 | 
			
		||||
          this.documentService.uploadDocument(formData).subscribe(result => {
 | 
			
		||||
            this.toastService.showToast(Toast.make("Information", "The document has been uploaded and will be processed by the consumer shortly."))
 | 
			
		||||
          }, error => {
 | 
			
		||||
            this.toastService.showToast(Toast.makeError("An error has occured while uploading the document. Sorry!"))
 | 
			
		||||
          })
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,8 @@
 | 
			
		||||
<div class="card mb-3 shadow">
 | 
			
		||||
  <div class="card-header">
 | 
			
		||||
    <h5 class="card-title mb-0">{{title}}</h5>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div class="card-body text-dark">
 | 
			
		||||
    <ng-content></ng-content>
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
@@ -0,0 +1,25 @@
 | 
			
		||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
 | 
			
		||||
import { WidgetFrameComponent } from './widget-frame.component';
 | 
			
		||||
 | 
			
		||||
describe('WidgetFrameComponent', () => {
 | 
			
		||||
  let component: WidgetFrameComponent;
 | 
			
		||||
  let fixture: ComponentFixture<WidgetFrameComponent>;
 | 
			
		||||
 | 
			
		||||
  beforeEach(async () => {
 | 
			
		||||
    await TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ WidgetFrameComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(WidgetFrameComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
@@ -0,0 +1,18 @@
 | 
			
		||||
import { Component, Input, OnInit } from '@angular/core';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-widget-frame',
 | 
			
		||||
  templateUrl: './widget-frame.component.html',
 | 
			
		||||
  styleUrls: ['./widget-frame.component.scss']
 | 
			
		||||
})
 | 
			
		||||
export class WidgetFrameComponent implements OnInit {
 | 
			
		||||
 | 
			
		||||
  constructor() { }
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  title: string
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user