Shortens data stored in CODE128. Increases length of initials to accommodate UVA computing ids. Removes old home component and syncs data on Scan Barcode screen.

This commit is contained in:
Aaron Louie 2020-11-16 16:38:55 -05:00
parent e2841e3352
commit 82974d2d7a
19 changed files with 110 additions and 143 deletions

View File

@ -1,11 +1,22 @@
import {formatDate} from '@angular/common';
export const createQrCodeValue = (barCode: string, initials: string, dateCreated: Date, locationId: string): string => {
export const createQrCodeValue = (
barCode: string,
initials: string,
dateCreated: Date,
locationId: string,
delimiter = '-',
barcodeType: string
): string => {
const is1D = barcodeType === 'code128';
const locId = is1D ? locationId.slice(2, 4) : locationId;
const dateFormat = is1D ? 'yyMMdd' : 'yyyyMMddHHmm';
const valArray = [
barCode,
initials.toUpperCase(),
formatDate(dateCreated, 'yyyyMMddHHmm', 'en-us'),
locationId,
formatDate(dateCreated, dateFormat, 'en-us'),
locId,
];
return valArray.join('-');
return valArray.join(delimiter);
};

View File

@ -22,7 +22,6 @@ import {FormlyMaterialModule} from '@ngx-formly/material';
import {routes} from './app-routing.module';
import {AppComponent} from './app.component';
import {FooterComponent} from './footer/footer.component';
import {HomeComponent} from './home/home.component';
import {NavbarComponent} from './navbar/navbar.component';
import {MockEnvironment} from './testing/environment.mock';
@ -38,7 +37,6 @@ describe('Router: App', () => {
declarations: [
AppComponent,
FooterComponent,
HomeComponent,
NavbarComponent,
],
imports: [

View File

@ -2,7 +2,6 @@ import {NgModule} from '@angular/core';
import {RouterModule, Routes} from '@angular/router';
import {ThisEnvironment} from '../environments/environment.injectable';
import {CountComponent} from './count/count.component';
import {HomeComponent} from './home/home.component';
import {PrintComponent} from './print/print.component';
import {SampleComponent} from './sample/sample.component';
import {SettingsComponent} from './settings/settings.component';

View File

@ -21,7 +21,6 @@ import {AppComponent} from './app.component';
import {BarcodeSvgDirective} from './barcode-svg/barcode-svg.directive';
import {CountComponent} from './count/count.component';
import {FooterComponent} from './footer/footer.component';
import {HomeComponent} from './home/home.component';
import {CircleQRcodeDoubleComponent} from './label-layout/formats/circle-qrcode-double/circle-qrcode-double.component';
import {CircleQRcodeSingleComponent} from './label-layout/formats/circle-qrcode-single/circle-qrcode-single.component';
import {RectangleCode128Component} from './label-layout/formats/rectangle-code128/rectangle-code128.component';
@ -60,7 +59,6 @@ export function getBaseHref(platformLocation: PlatformLocation): string {
CircleQRcodeSingleComponent,
CountComponent,
FooterComponent,
HomeComponent,
LabelLayoutComponent,
LoadingComponent,
NavbarComponent,

View File

@ -22,6 +22,7 @@ export const labelLayouts = {
id: 'rectangle_code128',
pageWidth: 54,
pageHeight: 34,
delimiter: '',
}),
rectangle_datamatrix: new LabelLayout({
name: '2in x 1.25in Rectangular Label - DataMatrix',
@ -41,11 +42,11 @@ export const defaultOptions: AppDefaultsOptions = {
dateDisplayFormat: 'MM/dd/yyyy, hh:mm aa', // Format for dates when displayed to user.
dateEncodedFormat: 'yyyyMMddHHmm', // Format for dates when encoded in IDs for database records.
initialsLength: 6,
initialsRegExp: /^[a-zA-Z]{2,6}$/,
initialsRegExp: /^[a-zA-Z0-9]{2,6}$/,
labelLayout: labelLayouts.circle_qrcode_single, // Which label layout to use for printing. Can be overridden by user setting.
lineCountRegExp: /^[\d]{4}-[\d]{12}$/, // ID format for Line Count records.
locationId: '0000', // Default location ID. Can be overridden by user setting.
locationIdRegExp: /^[\d]{4}$/, // ID format for Line Count records.
locationIdRegExp: /^[\d]{1,4}$/, // ID format for Line Count records.
numCopies: 1, // Default number of copies of labels to print.
// Can be overridden by user setting.
qrCodeRegExp: /^[\d]{9}-[a-zA-Z]+-[\d]{12}-[\d]{4}$/, // ID format for QR Code records.

View File

@ -1,27 +0,0 @@
<div
class="full-height bg-primary"
fxLayout="column"
fxLayoutAlign="center center"
fxLayoutGap="40px"
>
<div
fxLayout="column"
fxLayoutAlign="center center"
fxLayoutGap="40px"
>
<button
id="nav_sample"
mat-flat-button
color="accent"
class="btn-xl"
routerLink="/sample"
autofocus
>New saliva sample</button>
<button
id="nav_count"
mat-flat-button
class="btn-xl"
routerLink="/count"
>Submit occupancy count</button>
</div>
</div>

View File

@ -1,3 +0,0 @@
.btn-xl {
width: 100%;
}

View File

@ -1,41 +0,0 @@
import {APP_BASE_HREF} from '@angular/common';
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
import {async, ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {ApiService} from '../services/api.service';
import {CacheService} from '../services/cache.service';
import {MockEnvironment} from '../testing/environment.mock';
import { HomeComponent } from './home.component';
describe('HomeComponent', () => {
let component: HomeComponent;
let fixture: ComponentFixture<HomeComponent>;
let httpMock: HttpTestingController;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [ HomeComponent ],
imports: [
HttpClientTestingModule,
],
providers: [
ApiService,
CacheService,
{provide: 'APP_ENVIRONMENT', useClass: MockEnvironment},
{provide: APP_BASE_HREF, useValue: '/'},
],
})
.compileComponents();
}));
beforeEach(() => {
httpMock = TestBed.inject(HttpTestingController);
fixture = TestBed.createComponent(HomeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -1,41 +0,0 @@
import {Component, OnInit} from '@angular/core';
import {ApiService} from '../services/api.service';
import {CacheService} from '../services/cache.service';
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {
constructor(
private cacheService: CacheService,
private apiService: ApiService,
) {
}
ngOnInit(): void {
const cachedRecords = this.cacheService.getRecords();
let numSuccess = 0;
if (cachedRecords && cachedRecords.length > 0) {
cachedRecords.forEach(r => {
this.apiService.addSample(r).subscribe(() => {
numSuccess++;
console.log('cachedRecords', cachedRecords);
console.log('numSuccess', numSuccess);
if (numSuccess === cachedRecords.length) {
console.log('Cache cleared.');
this.cacheService.clearCache();
}
}, error => {
console.log('Cannot connect to server. Cache not cleared.');
});
});
} else {
console.log('No cached records to upload.');
}
}
}

View File

@ -35,10 +35,10 @@
<g
appBarcodeSvg
[format]="'code128'"
[height]="10"
[height]="40"
[value]="sample.barcode"
[width]="47"
transform="scale(0.345)"/>
[width]="188"
transform="scale(0.08625)"/>
</g>
<use xlink:href="#up_arrow" x="2" y="27.5" />
</g>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -35,7 +35,9 @@ export class LabelLayoutComponent implements OnInit {
this.sample.student_id,
this.sample.initials,
this.sample.date,
this.sample.location
this.sample.location,
this.settings.labelLayout.delimiter,
this.settings.labelLayout.barcodeType,
);
this.sample.barcode = this.barcodeValue;

View File

@ -7,6 +7,7 @@ export interface LayoutOptions {
pageWidth?: number;
numCols?: number;
numCopies?: number;
delimiter?: string;
}
export class LabelLayout {
@ -18,6 +19,7 @@ export class LabelLayout {
pageWidth = 32;
numCols = 1;
numCopies = 1;
delimiter = '-';
constructor(private options: LayoutOptions) {
if (options) {

View File

@ -77,7 +77,9 @@ export class PrintComponent implements AfterViewInit {
this.barCode,
this.initials,
this.dateCreated,
this.settings.locationId
this.settings.locationId,
this.settings.labelLayout.delimiter,
this.settings.labelLayout.barcodeType,
);
const newSample: Sample = {

View File

@ -24,7 +24,7 @@
<mat-error *ngIf="barCodeFormControl.hasError('pattern')">Please enter exactly 9 or 14 digits.</mat-error>
</mat-form-field>
<mat-form-field class="initials-input" fxFlex="95%">
<mat-label>Initials</mat-label>
<mat-label>Initials or 6-character UVA Computing ID</mat-label>
<input
#initialsInput="matInput"
matInput

View File

@ -1,4 +1,7 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import {APP_BASE_HREF} from '@angular/common';
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {ReactiveFormsModule} from '@angular/forms';
import {MatButtonModule} from '@angular/material/button';
import {MatCardModule} from '@angular/material/card';
import {MatFormFieldModule} from '@angular/material/form-field';
@ -6,33 +9,46 @@ import {MatIconModule} from '@angular/material/icon';
import {MatInputModule} from '@angular/material/input';
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
import {Router} from '@angular/router';
import { SampleComponent } from './sample.component';
import {ApiService} from '../services/api.service';
import {CacheService} from '../services/cache.service';
import {SettingsService} from '../services/settings.service';
import {MockEnvironment} from '../testing/environment.mock';
import {SampleComponent} from './sample.component';
describe('SampleComponent', () => {
let component: SampleComponent;
let fixture: ComponentFixture<SampleComponent>;
const mockRouter = {navigate: jasmine.createSpy('navigate')};
let httpMock: HttpTestingController;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ SampleComponent ],
declarations: [
SampleComponent
],
imports: [
HttpClientTestingModule,
MatButtonModule,
MatCardModule,
MatFormFieldModule,
MatIconModule,
MatInputModule,
NoopAnimationsModule,
ReactiveFormsModule,
],
providers: [
{provide: 'APP_ENVIRONMENT', useClass: MockEnvironment},
{provide: APP_BASE_HREF, useValue: '/'},
{provide: Router, useValue: mockRouter},
ApiService,
CacheService,
SettingsService,
]
})
.compileComponents();
}).compileComponents();
});
beforeEach(() => {
httpMock = TestBed.inject(HttpTestingController);
fixture = TestBed.createComponent(SampleComponent);
component = fixture.componentInstance;
fixture.detectChanges();

View File

@ -1,9 +1,13 @@
import {AfterViewInit, ChangeDetectorRef, Component, ViewChild} from '@angular/core';
import {APP_BASE_HREF} from '@angular/common';
import {AfterViewInit, ChangeDetectorRef, Component, Inject, OnInit, ViewChild} from '@angular/core';
import {FormControl, Validators} from '@angular/forms';
import {MatButton} from '@angular/material/button';
import {MatInput} from '@angular/material/input';
import {Params, Router} from '@angular/router';
import {AppDefaults} from '../models/appDefaults.interface';
import {AppEnvironment} from '../models/appEnvironment.interface';
import {ApiService} from '../services/api.service';
import {CacheService} from '../services/cache.service';
import {SettingsService} from '../services/settings.service';
@ -12,7 +16,7 @@ import {SettingsService} from '../services/settings.service';
templateUrl: './sample.component.html',
styleUrls: ['./sample.component.scss']
})
export class SampleComponent implements AfterViewInit {
export class SampleComponent implements OnInit, AfterViewInit {
settings: AppDefaults;
barCodeFormControl: FormControl;
initialsFormControl: FormControl;
@ -24,6 +28,8 @@ export class SampleComponent implements AfterViewInit {
private router: Router,
private changeDetector: ChangeDetectorRef,
private settingsService: SettingsService,
private cacheService: CacheService,
private apiService: ApiService
) {
this.settings = this.settingsService.getSettings();
this.barCodeFormControl = new FormControl('', [
@ -65,6 +71,29 @@ export class SampleComponent implements AfterViewInit {
return !(this.barCodeFormControl.valid && this.initialsFormControl.valid);
}
ngOnInit(): void {
const cachedRecords = this.cacheService.getRecords();
let numSuccess = 0;
if (cachedRecords && cachedRecords.length > 0) {
cachedRecords.forEach(r => {
this.apiService.addSample(r).subscribe(() => {
numSuccess++;
console.log('cachedRecords', cachedRecords);
console.log('numSuccess', numSuccess);
if (numSuccess === cachedRecords.length) {
console.log('Cache cleared.');
this.cacheService.clearCache();
}
}, error => {
console.log('Cannot connect to server. Cache not cleared.');
});
});
} else {
console.log('No cached records to upload.');
}
}
ngAfterViewInit() {
this.barCodeInput.focus();
this.changeDetector.detectChanges();

View File

@ -15,12 +15,12 @@
<input
#locationIdInput="matInput"
matInput
type="text"
type="number"
[formControl]="locationIdFormControl"
(keyup.enter)="save()"
>
<mat-error *ngIf="locationIdFormControl.hasError('required')">This field is required.</mat-error>
<mat-error *ngIf="locationIdFormControl.hasError('pattern')">Please enter exactly 4 digits.</mat-error>
<mat-error *ngIf="locationIdFormControl.hasError('pattern')">Please enter a number between 1 and 4 digits long.</mat-error>
</mat-form-field>
<mat-form-field class="label-layout-select" fxFlex="95%">
<mat-label>Label layout</mat-label>
@ -39,7 +39,7 @@
{{layout.name}}
</div>
<div fxFlex="50%">
<app-circle-qrcode-single *ngIf="layout.id === 'circle_qrcode_single'" [sample]="fakeSample"></app-circle-qrcode-single>
<app-circle-qrcode-single *ngIf="layout.id === 'circle_qrcode_single'" [sample]="sampleForLayout(layout)"></app-circle-qrcode-single>
<app-circle-qrcode-double *ngIf="layout.id === 'circle_qrcode_double'" [sample]="fakeSample"></app-circle-qrcode-double>
<app-rectangle-code128 *ngIf="layout.id === 'rectangle_code128'" [sample]="fakeSample"></app-rectangle-code128>
<app-rectangle-datamatrix *ngIf="layout.id === 'rectangle_datamatrix'" [sample]="fakeSample"></app-rectangle-datamatrix>

View File

@ -8,6 +8,7 @@ import {AppDefaults} from '../models/appDefaults.interface';
import {LabelLayout} from '../models/labelLayout.interface';
import {Sample} from '../models/sample.interface';
import {SettingsService} from '../services/settings.service';
import createClone from 'rfdc';
@Component({
selector: 'app-settings',
@ -70,7 +71,7 @@ export class SettingsComponent implements AfterViewInit {
this.settingsService.saveSettings({
labelLayout: labelLayouts[this.labelLayoutFormControl.value],
numCopies: this.numCopiesFormControl.value,
locationId: this.locationIdFormControl.value,
locationId: this.locationIdFormControl.value.toString().padStart(4, '0'),
});
this.router.navigate(['/']);
}
@ -81,7 +82,7 @@ export class SettingsComponent implements AfterViewInit {
this.fakeSample = {
barcode: '',
student_id: '123456789',
initials: 'ABCDE',
initials: 'ABC9Z',
date: new Date(),
location: this.settings.locationId,
};
@ -90,7 +91,9 @@ export class SettingsComponent implements AfterViewInit {
this.fakeSample.student_id,
this.fakeSample.initials,
this.fakeSample.date,
this.fakeSample.location
this.fakeSample.location,
'-',
'datamatrix',
);
this.fakeSample.barcode = this.fakeBarcodeValue;
@ -99,4 +102,18 @@ export class SettingsComponent implements AfterViewInit {
selectLabelLayout(layout: LabelLayout) {
this.labelLayoutFormControl.patchValue(layout);
}
sampleForLayout(layout: LabelLayout) {
const sample: Sample = createClone()(this.fakeSample);
sample.barcode = createQrCodeValue(
sample.student_id,
sample.initials,
sample.date,
sample.location,
layout.delimiter,
layout.barcodeType,
);
return sample;
}
}

View File

@ -7,4 +7,8 @@ export class MockEnvironment implements AppEnvironment {
api = 'apiRoot';
title = 'Mock Title';
googleAnalyticsKey = 'SOME_KEY';
constructor() {
console.log('MockEnvironment constructor');
}
}