changed status, added restoring samples and changed password guidelines
This commit is contained in:
		@@ -1,18 +1,18 @@
 | 
			
		||||
<rb-full-header>
 | 
			
		||||
  <nav *rbMainNavItems>
 | 
			
		||||
    <a routerLink="/home" routerLinkActive="active" rbLoadingLink>Home</a>
 | 
			
		||||
    <a routerLink="/samples" routerLinkActive="active" rbLoadingLink *ngIf="loginService.isLoggedIn">Samples</a>
 | 
			
		||||
    <a routerLink="/templates" routerLinkActive="active" rbLoadingLink *ngIf="loginService.isLevel.dev">
 | 
			
		||||
    <a routerLink="/samples" routerLinkActive="active" rbLoadingLink *ngIf="login.isLoggedIn">Samples</a>
 | 
			
		||||
    <a routerLink="/templates" routerLinkActive="active" rbLoadingLink *ngIf="login.isLevel.dev">
 | 
			
		||||
      Templates
 | 
			
		||||
    </a>
 | 
			
		||||
    <a routerLink="/users" routerLinkActive="active" rbLoadingLink *ngIf="loginService.isLevel.admin">Users</a>
 | 
			
		||||
    <a routerLink="/users" routerLinkActive="active" rbLoadingLink *ngIf="login.isLevel.admin">Users</a>
 | 
			
		||||
    <a routerLink="/documentation" routerLinkActive="active" rbLoadingLink>Documentation</a>
 | 
			
		||||
  </nav>
 | 
			
		||||
 | 
			
		||||
  <ng-container *ngIf="loginService.isLoggedIn">
 | 
			
		||||
  <ng-container *ngIf="login.isLoggedIn">
 | 
			
		||||
    <nav *rbActionNavItems>
 | 
			
		||||
      <a href="javascript:"  [rbPopover]="userPopover" [anchor]="popoverAnchor">
 | 
			
		||||
        {{loginService.username}} <span class="rb-ic rb-ic-my-brand-frame" #popoverAnchor></span></a>
 | 
			
		||||
        {{login.username}} <span class="rb-ic rb-ic-my-brand-frame" #popoverAnchor></span></a>
 | 
			
		||||
    </nav>
 | 
			
		||||
    <ng-template #userPopover let-close="close">
 | 
			
		||||
      <div class="spacing">
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@ export class AppComponent {
 | 
			
		||||
  bugReport = {do: '', work: ''};
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    public loginService: LoginService,
 | 
			
		||||
    public login: LoginService,
 | 
			
		||||
    private router: Router
 | 
			
		||||
  ) {
 | 
			
		||||
  }
 | 
			
		||||
@@ -31,7 +31,7 @@ export class AppComponent {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  logout() {
 | 
			
		||||
    this.loginService.logout();
 | 
			
		||||
    this.login.logout();
 | 
			
		||||
    this.router.navigate(['/']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -23,6 +23,7 @@ export class SampleModel extends BaseModel {
 | 
			
		||||
    sample_references: {sample_id: IdModel, relation: string}[],
 | 
			
		||||
    custom_fields: {[prop: string]: string}
 | 
			
		||||
  } = {comment: '', sample_references: [], custom_fields: {}};
 | 
			
		||||
  status = '';
 | 
			
		||||
  added: Date = null;
 | 
			
		||||
 | 
			
		||||
  deserialize(input: any): this {
 | 
			
		||||
 
 | 
			
		||||
@@ -6,9 +6,9 @@
 | 
			
		||||
  </a>
 | 
			
		||||
  <rb-icon-button *ngIf="validation" mode="secondary" icon="close" (click)="validation = false" class="validation-close"
 | 
			
		||||
                  iconOnly></rb-icon-button>
 | 
			
		||||
  <rb-icon-button *ngIf="login.isLevel.dev" [icon]="validation? 'checkmark' : 'clear-all'"
 | 
			
		||||
  <rb-icon-button *ngIf="login.isLevel.dev" [icon]="validation ? 'checkmark' : 'clear-all'"
 | 
			
		||||
                  mode="secondary" (click)="validate()">
 | 
			
		||||
    Validate
 | 
			
		||||
    {{validation ? 'Validate' : 'Validation'}}
 | 
			
		||||
  </rb-icon-button>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
@@ -19,13 +19,20 @@
 | 
			
		||||
      <div class="status-selection">
 | 
			
		||||
        <label class="label">Status</label>
 | 
			
		||||
        <rb-form-checkbox name="status-validated" [(ngModel)]="filters.status.validated"
 | 
			
		||||
                          [disabled]="!filters.status.new" (ngModelChange)="loadSamples({firstPage: true})">
 | 
			
		||||
                          [disabled]="!filters.status.new && !filters.status.deleted"
 | 
			
		||||
                          (ngModelChange)="loadSamples({firstPage: true})">
 | 
			
		||||
          validated
 | 
			
		||||
        </rb-form-checkbox>
 | 
			
		||||
        <rb-form-checkbox name="status-new" [(ngModel)]="filters.status.new" [disabled]="!filters.status.validated"
 | 
			
		||||
        <rb-form-checkbox name="status-new" [(ngModel)]="filters.status.new"
 | 
			
		||||
                          [disabled]="!filters.status.validated && !filters.status.deleted"
 | 
			
		||||
                          (ngModelChange)="loadSamples({firstPage: true})">
 | 
			
		||||
          new
 | 
			
		||||
        </rb-form-checkbox>
 | 
			
		||||
        <rb-form-checkbox name="status-deleted" [(ngModel)]="filters.status.deleted"
 | 
			
		||||
                          [disabled]="!filters.status.validated && !filters.status.new"
 | 
			
		||||
                          (ngModelChange)="loadSamples({firstPage: true})" *ngIf="login.isLevel.dev">
 | 
			
		||||
          deleted
 | 
			
		||||
        </rb-form-checkbox>
 | 
			
		||||
      </div>
 | 
			
		||||
      <rb-form-select name="pageSizeSelection" label="page size" [(ngModel)]="filters.pageSize" class="selection"
 | 
			
		||||
                      (ngModelChange)="loadSamples({firstPage: true})" #pageSizeSelection>
 | 
			
		||||
@@ -135,7 +142,8 @@
 | 
			
		||||
 | 
			
		||||
  <tr *ngFor="let sample of samples; index as i" class="clickable" (click)="sampleDetails(sample._id, sampleModal)">
 | 
			
		||||
    <td *ngIf="validation">
 | 
			
		||||
      <rb-form-checkbox [name]="'validate-' + i" (click)="stopPropagation($event)" [(ngModel)]="sample.validate">
 | 
			
		||||
      <rb-form-checkbox *ngIf="sample.status !== 'deleted'" [name]="'validate-' + i" (click)="stopPropagation($event)"
 | 
			
		||||
                        [(ngModel)]="sample.validate">
 | 
			
		||||
      </rb-form-checkbox>
 | 
			
		||||
    </td>
 | 
			
		||||
    <td *ngIf="isActiveKey['number']">{{sample.number}}</td>
 | 
			
		||||
@@ -151,12 +159,16 @@
 | 
			
		||||
    <td *ngIf="isActiveKey['batch']">{{sample.batch}}</td>
 | 
			
		||||
    <td *ngIf="isActiveKey['notes']">{{sample.notes | object: ['_id', 'sample_references']}}</td>
 | 
			
		||||
    <td *ngFor="let key of activeTemplateKeys.measurements">{{sample[key[1]] | exists: key[2]}}</td>
 | 
			
		||||
    <td *ngIf="isActiveKey['status']">{{sample.status}}</td>
 | 
			
		||||
    <td *ngIf="isActiveKey['added']">{{sample.added | date:'dd/MM/yy'}}</td>
 | 
			
		||||
    <td *ngIf="login.isLevel.write">
 | 
			
		||||
      <a [routerLink]="'/samples/edit/' + sample._id"
 | 
			
		||||
         *ngIf="login.isLevel.dev || (login.isLevel.write && sample.user_id === login.userId)">
 | 
			
		||||
        <span class="rb-ic rb-ic-edit"></span>
 | 
			
		||||
         *ngIf="sample.status !== 'deleted' &&
 | 
			
		||||
         (login.isLevel.dev || (login.isLevel.write && sample.user_id === login.userId))">
 | 
			
		||||
        <span class="rb-ic rb-ic-edit clickable"></span>
 | 
			
		||||
      </a>
 | 
			
		||||
      <span class="rb-ic rb-ic-undo clickable" *ngIf="sample.status === 'deleted' && login.isLevel.dev"
 | 
			
		||||
            (click)="restoreSample(sample._id, restoreConfirm, $event)"></span>
 | 
			
		||||
    </td>
 | 
			
		||||
  </tr>
 | 
			
		||||
</rb-table>
 | 
			
		||||
@@ -208,6 +220,13 @@
 | 
			
		||||
        <td>{{measurement.value}}</td>
 | 
			
		||||
      </tr>
 | 
			
		||||
      <tr><th>User</th><td>{{sampleDetailsSample.user}}</td></tr>
 | 
			
		||||
      <tr><th>Status</th><td>{{sampleDetailsSample.status}}</td></tr>
 | 
			
		||||
    </rb-table>
 | 
			
		||||
  </ng-template>
 | 
			
		||||
</ng-template>
 | 
			
		||||
 | 
			
		||||
<ng-template #restoreConfirm>
 | 
			
		||||
  <rb-dialog dialogTitle="Restore sample">
 | 
			
		||||
    Do you really want to restore this sample?
 | 
			
		||||
  </rb-dialog>
 | 
			
		||||
</ng-template>
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,7 @@ rb-table {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.rb-ic.rb-ic-edit {
 | 
			
		||||
 td .clickable.rb-ic {
 | 
			
		||||
  font-size: 1.1rem;
 | 
			
		||||
  color: $color-gray-silver-sand;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
 
 | 
			
		||||
@@ -39,13 +39,12 @@ export class SamplesComponent implements OnInit {
 | 
			
		||||
  totalSamples = 0;  // total number of samples
 | 
			
		||||
  csvUrl = '';  // store url separate so it only has to be generated when clicking the download button
 | 
			
		||||
  filters = {
 | 
			
		||||
    status: {new: true, validated: true},
 | 
			
		||||
    status: {new: true, validated: true, deleted: false},
 | 
			
		||||
    pageSize: 25,
 | 
			
		||||
    toPage: 0,
 | 
			
		||||
    sort: 'added-asc',
 | 
			
		||||
    filters: [
 | 
			
		||||
      {field: 'number', label: 'Number', active: false, autocomplete: [], mode: 'eq', values: ['']},
 | 
			
		||||
      {field: 'material.number', label: 'Material number', active: false, autocomplete: [], mode: 'eq', values: ['']},
 | 
			
		||||
      {field: 'material.name', label: 'Material name', active: false, autocomplete: [], mode: 'eq', values: ['']},
 | 
			
		||||
      {field: 'material.supplier', label: 'Supplier', active: false, autocomplete: [], mode: 'eq', values: ['']},
 | 
			
		||||
      {field: 'material.group', label: 'Material', active: false, autocomplete: [], mode: 'eq', values: ['']},
 | 
			
		||||
@@ -72,7 +71,8 @@ export class SamplesComponent implements OnInit {
 | 
			
		||||
    {id: 'color', label: 'Color', active: true, sortable: true},
 | 
			
		||||
    {id: 'batch', label: 'Batch', active: true, sortable: true},
 | 
			
		||||
    {id: 'notes', label: 'Notes', active: false, sortable: false},
 | 
			
		||||
    {id: 'added', label: 'Added', active: true, sortable: true},
 | 
			
		||||
    {id: 'status', label: 'Status', active: true, sortable: true},
 | 
			
		||||
    {id: 'added', label: 'Added', active: true, sortable: true}
 | 
			
		||||
  ];
 | 
			
		||||
  isActiveKey: {[key: string]: boolean} = {};
 | 
			
		||||
  activeKeys: KeyInterface[] = [];
 | 
			
		||||
@@ -97,18 +97,16 @@ export class SamplesComponent implements OnInit {
 | 
			
		||||
      this.filters.filters.find(e => e.field === 'color').autocomplete =
 | 
			
		||||
        [...new Set(this.d.arr.materials.reduce((s, e) => {s.push(...e.numbers.map(el => el.color)); return s; }, []))];
 | 
			
		||||
      this.loadSamples();
 | 
			
		||||
      console.log(this.d.id.materials);
 | 
			
		||||
    });
 | 
			
		||||
    this.d.load('materialSuppliers', () => {
 | 
			
		||||
      this.filters.filters.find(e => e.field === 'material.supplier').autocomplete = this.d.arr.materialSuppliers;
 | 
			
		||||
    });
 | 
			
		||||
    this.d.load('materialGroups', () => {
 | 
			
		||||
      console.log(this.d.arr.materialGroups);
 | 
			
		||||
      this.filters.filters.find(e => e.field === 'material.group').autocomplete = this.d.arr.materialGroups;
 | 
			
		||||
    });
 | 
			
		||||
    this.d.load('userKey');
 | 
			
		||||
    this.loadTemplateKeys('material', 'type');
 | 
			
		||||
    this.loadTemplateKeys('measurement', 'added');
 | 
			
		||||
    this.loadTemplateKeys('measurement', 'status');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  loadTemplateKeys(collection, insertBefore) {
 | 
			
		||||
@@ -185,9 +183,7 @@ export class SamplesComponent implements OnInit {
 | 
			
		||||
  }) {  // return url to fetch samples
 | 
			
		||||
    const additionalTableKeys = ['material_id', '_id', 'user_id'];  // keys which should always be added if export = false
 | 
			
		||||
    const query: string[] = [];
 | 
			
		||||
    query.push(
 | 
			
		||||
      'status=' + (this.filters.status.new && this.filters.status.validated ? 'all' : (this.filters.status.new ? 'new' : 'validated'))
 | 
			
		||||
    );
 | 
			
		||||
    query.push(...Object.keys(this.filters.status).filter(e => this.filters.status[e]).map(e => 'status[]=' + e));
 | 
			
		||||
    if (options.paging) {
 | 
			
		||||
      if (this.samples[0]) {  // do not include from-id when page size was changed
 | 
			
		||||
        if (!options.pagingOptions.firstPage) {
 | 
			
		||||
@@ -326,6 +322,7 @@ export class SamplesComponent implements OnInit {
 | 
			
		||||
          this.api.put('/sample/validate/' + sample._id);
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
      this.loadSamples();
 | 
			
		||||
      this.validation = false;
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
@@ -333,9 +330,25 @@ export class SamplesComponent implements OnInit {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  restoreSample(id, modal, event) {
 | 
			
		||||
    this.stopPropagation(event);
 | 
			
		||||
    this.modalService.open(modal).then(res => {
 | 
			
		||||
      if (res) {
 | 
			
		||||
        this.api.put('/sample/restore/' + id, {}, ignore => {
 | 
			
		||||
          this.samples.find(e => e._id === id).status = 'new';
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  selectAll(event) {
 | 
			
		||||
    this.samples.forEach(sample => {
 | 
			
		||||
      sample.validate = event.target.checked;
 | 
			
		||||
      if (sample.status !== 'deleted') {
 | 
			
		||||
        sample.validate = event.target.checked;
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        sample.validate = false;
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,7 @@ export class ValidationService {
 | 
			
		||||
    .max(128);
 | 
			
		||||
 | 
			
		||||
  private vPassword = Joi.string()
 | 
			
		||||
    .pattern(/^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[!"#%&'()*+,-.\/:;<=>?@[\]^_`{|}~])(?=\S+$)[a-zA-Z0-9!"#%&'()*+,\-.\/:;<=>?@[\]^_`{|}~]{8,}$/)
 | 
			
		||||
    .min(8)
 | 
			
		||||
    .max(128);
 | 
			
		||||
 | 
			
		||||
  constructor() { }
 | 
			
		||||
@@ -44,24 +44,7 @@ export class ValidationService {
 | 
			
		||||
  password(data) {
 | 
			
		||||
    const {ignore, error} = this.vPassword.validate(data);
 | 
			
		||||
    if (error) {
 | 
			
		||||
      if (Joi.string().min(8).validate(data).error) {
 | 
			
		||||
        return {ok: false, error: 'password must have at least 8 characters'};
 | 
			
		||||
      }
 | 
			
		||||
      else if (Joi.string().pattern(/[a-z]+/).validate(data).error) {
 | 
			
		||||
        return {ok: false, error: 'password must have at least one lowercase character'};
 | 
			
		||||
      }
 | 
			
		||||
      else if (Joi.string().pattern(/[A-Z]+/).validate(data).error) {
 | 
			
		||||
        return {ok: false, error: 'password must have at least one uppercase character'};
 | 
			
		||||
      }
 | 
			
		||||
      else if (Joi.string().pattern(/[0-9]+/).validate(data).error) {
 | 
			
		||||
        return {ok: false, error: 'password must have at least one number'};
 | 
			
		||||
      }
 | 
			
		||||
      else if (Joi.string().pattern(/[!"#%&'()*+,-.\/:;<=>?@[\]^_`{|}~]+/).validate(data).error) {
 | 
			
		||||
        return {ok: false, error: 'password must have at least one of the following characters !"#%&\'()*+,-.\\/:;<=>?@[]^_`{|}~'};
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        return {ok: false, error: 'password must only contain a-zA-Z0-9!"#%&\'()*+,-./:;<=>?@[]^_`{|}~'};
 | 
			
		||||
      }
 | 
			
		||||
      return {ok: false, error: 'password must have at least 8 characters'};
 | 
			
		||||
    }
 | 
			
		||||
    return {ok: true, error: ''};
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user