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 => { | ||||
|       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,25 +44,8 @@ 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: true, error: ''}; | ||||
|   } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 VLE2FE
					VLE2FE