├── src
├── app
│ ├── app.component.html
│ ├── shared
│ │ ├── footer.component.html
│ │ ├── footer.component.ts
│ │ ├── aboutus.component.ts
│ │ ├── dropzone
│ │ │ ├── fileupload.component.css
│ │ │ ├── dropzone.directive.ts
│ │ │ ├── filesize.pipe.ts
│ │ │ ├── fileupload.component.html
│ │ │ └── fileupload.component.ts
│ │ ├── header.component.ts
│ │ ├── signup.component.ts
│ │ ├── aboutus.component.html
│ │ ├── login.component.ts
│ │ ├── signup.component.html
│ │ ├── login.component.html
│ │ ├── header.component.html
│ │ └── custom.material.module.ts
│ ├── app.component.ts
│ ├── services
│ │ ├── auth-guard.service.ts
│ │ └── backend.service.ts
│ ├── app-routing.module.ts
│ ├── app.component.spec.ts
│ ├── app.module.ts
│ └── product
│ │ ├── attendance.component.ts
│ │ └── attendance.component.html
├── assets
│ ├── favicon.ico
│ ├── icons
│ │ ├── favicon.ico
│ │ ├── add.svg
│ │ ├── menu.svg
│ │ ├── equalizer.svg
│ │ ├── dashboard-black.svg
│ │ ├── delete.svg
│ │ ├── code.svg
│ │ ├── book.svg
│ │ ├── clear.svg
│ │ ├── email.svg
│ │ ├── person.svg
│ │ ├── security.svg
│ │ ├── radio_off.svg
│ │ ├── event.svg
│ │ ├── create.svg
│ │ ├── edit.svg
│ │ ├── more_vert.svg
│ │ ├── cloud.svg
│ │ ├── place.svg
│ │ ├── vpn.svg
│ │ ├── radio_on.svg
│ │ ├── star.svg
│ │ ├── business.svg
│ │ ├── backspace.svg
│ │ ├── cached.svg
│ │ ├── account_circle.svg
│ │ ├── search.svg
│ │ ├── phone.svg
│ │ ├── help.svg
│ │ ├── group.svg
│ │ ├── track_changes.svg
│ │ ├── bars.svg
│ │ ├── new.svg
│ │ ├── linkedin.svg
│ │ ├── fb.svg
│ │ ├── drop_down.svg
│ │ ├── github.svg
│ │ ├── language.svg
│ │ ├── salary.svg
│ │ └── home.svg
│ └── styles.css
├── tsconfig.app.json
├── tsconfig.spec.json
├── tslint.json
├── browserslist
├── main.ts
├── index.html
├── environments
│ ├── environment.prod.ts
│ └── environment.ts
├── test.ts
├── karma.conf.js
└── polyfills.ts
├── tsconfig.json
├── LICENSE
├── package.json
├── tslint.json
├── angular.json
└── README.md
/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/assets/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AmitXShukla/Mobile-Picture-GPS-Tracking-Attendance-APP/HEAD/src/assets/favicon.ico
--------------------------------------------------------------------------------
/src/assets/icons/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AmitXShukla/Mobile-Picture-GPS-Tracking-Attendance-APP/HEAD/src/assets/icons/favicon.ico
--------------------------------------------------------------------------------
/src/app/shared/footer.component.html:
--------------------------------------------------------------------------------
1 |
@copyright 2019 Amit.LA https://github.com/AmitXShukla
--------------------------------------------------------------------------------
/src/assets/icons/add.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/assets/icons/menu.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-root',
5 | templateUrl: './app.component.html'
6 | })
7 | export class AppComponent {
8 | title = 'client';
9 | }
10 |
--------------------------------------------------------------------------------
/src/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/app",
5 | "types": []
6 | },
7 | "exclude": [
8 | "test.ts",
9 | "**/*.spec.ts"
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/src/assets/icons/equalizer.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/assets/icons/dashboard-black.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/assets/icons/delete.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/assets/icons/code.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/assets/icons/book.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/assets/icons/clear.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/assets/icons/email.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/assets/icons/person.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/assets/icons/security.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/assets/icons/radio_off.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/assets/icons/event.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/app/shared/footer.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-footer',
5 | templateUrl: './footer.component.html'
6 | })
7 | export class FooterComponent implements OnInit {
8 |
9 | constructor() { }
10 |
11 | ngOnInit() {
12 | }
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/src/app/shared/aboutus.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-aboutus',
5 | templateUrl: './aboutus.component.html'
6 | })
7 | export class AboutusComponent implements OnInit {
8 |
9 | constructor() { }
10 |
11 | ngOnInit() {
12 | }
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/src/assets/icons/create.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/assets/icons/edit.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/assets/icons/more_vert.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/assets/icons/cloud.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/assets/icons/place.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/assets/icons/vpn.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/spec",
5 | "types": [
6 | "jasmine",
7 | "node"
8 | ]
9 | },
10 | "files": [
11 | "test.ts",
12 | "polyfills.ts"
13 | ],
14 | "include": [
15 | "**/*.spec.ts",
16 | "**/*.d.ts"
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/src/assets/icons/radio_on.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/assets/icons/star.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/assets/icons/business.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/assets/icons/backspace.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tslint.json",
3 | "rules": {
4 | "directive-selector": [
5 | true,
6 | "attribute",
7 | "app",
8 | "camelCase"
9 | ],
10 | "component-selector": [
11 | true,
12 | "element",
13 | "app",
14 | "kebab-case"
15 | ]
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/assets/icons/cached.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/assets/icons/account_circle.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/assets/icons/search.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/assets/icons/phone.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/browserslist:
--------------------------------------------------------------------------------
1 | # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers
2 | # For additional information regarding the format and rule options, please see:
3 | # https://github.com/browserslist/browserslist#queries
4 | #
5 | # For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed
6 |
7 | > 0.5%
8 | last 2 versions
9 | Firefox ESR
10 | not dead
11 | not IE 9-11
--------------------------------------------------------------------------------
/src/assets/icons/help.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { enableProdMode } from '@angular/core';
2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
3 | import 'hammerjs';
4 | import { AppModule } from './app/app.module';
5 | import { environment } from './environments/environment';
6 |
7 | if (environment.production) {
8 | enableProdMode();
9 | }
10 |
11 | platformBrowserDynamic().bootstrapModule(AppModule)
12 | .catch(err => console.error(err));
13 |
--------------------------------------------------------------------------------
/src/assets/icons/group.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Mobile Picture, GPS Tracking Attendance App
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/app/shared/dropzone/fileupload.component.css:
--------------------------------------------------------------------------------
1 | .dropzone {
2 | display: flex;
3 | align-items: center;
4 | justify-content: center;
5 | flex-direction: column;
6 | height: 300px;
7 | border: 2px dashed #f16624;
8 | border-radius: 5px;
9 | background: white;
10 | margin: 10px 0;
11 | &.hovering {
12 | border: 2px solid #f16624;
13 | color: #dadada !important;
14 | }
15 | }
16 | progress::-webkit-progress-value {
17 | transition: width 0.1s ease;
18 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "baseUrl": "./",
5 | "outDir": "./dist/out-tsc",
6 | "sourceMap": true,
7 | "declaration": false,
8 | "module": "es2015",
9 | "moduleResolution": "node",
10 | "emitDecoratorMetadata": true,
11 | "experimentalDecorators": true,
12 | "importHelpers": true,
13 | "target": "es5",
14 | "typeRoots": [
15 | "node_modules/@types"
16 | ],
17 | "lib": [
18 | "es2018",
19 | "dom"
20 | ]
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/assets/icons/track_changes.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/assets/icons/bars.svg:
--------------------------------------------------------------------------------
1 |
2 | bars-line
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true,
3 | firebase: {
4 | apiKey: "xxxx",
5 | authDomain: "xxxx",
6 | databaseURL: "xxxx",
7 | projectId: "xxxx",
8 | storageBucket: "xxxx",
9 | messagingSenderId: "xxxx",
10 | },
11 | emailAPI: 'xxxx',
12 | database: 'firebase',
13 | social: {
14 | fblink: 'https://www.facebook.com/elishconsulting',
15 | linkedin: 'https://www.linkedin.com/in/ashuklax/',
16 | github: 'https://github.com/AmitXShukla',
17 | emailId: 'mailto://amit@elishconsulting.com'
18 | },
19 | socialAuthEnabled: true,
20 | googleMapsKey: 'xxxx'
21 | };
--------------------------------------------------------------------------------
/src/assets/icons/new.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/assets/icons/linkedin.svg:
--------------------------------------------------------------------------------
1 | LinkedIn icon
--------------------------------------------------------------------------------
/src/app/services/auth-guard.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from "@angular/core";
2 | import { CanActivate, Router } from '@angular/router';
3 | import { BackendService } from './backend.service';
4 |
5 | @Injectable({
6 | providedIn: 'root'
7 | })
8 |
9 | export class AuthGuardService implements CanActivate {
10 |
11 | constructor (private _backendService: BackendService, private _router: Router) { }
12 | async canActivate(): Promise {
13 | const authenticatedUser = await this._backendService.getUser();
14 | const authenticated = !!authenticatedUser;
15 | if (!authenticated) {
16 | this._router.navigate(['/login']);
17 | }
18 | return authenticated;
19 | }
20 | }
--------------------------------------------------------------------------------
/src/test.ts:
--------------------------------------------------------------------------------
1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files
2 |
3 | import 'zone.js/dist/zone-testing';
4 | import { getTestBed } from '@angular/core/testing';
5 | import {
6 | BrowserDynamicTestingModule,
7 | platformBrowserDynamicTesting
8 | } from '@angular/platform-browser-dynamic/testing';
9 |
10 | declare const require: any;
11 |
12 | // First, initialize the Angular testing environment.
13 | getTestBed().initTestEnvironment(
14 | BrowserDynamicTestingModule,
15 | platformBrowserDynamicTesting()
16 | );
17 | // Then we find all the tests.
18 | const context = require.context('./', true, /\.spec\.ts$/);
19 | // And load the modules.
20 | context.keys().map(context);
21 |
--------------------------------------------------------------------------------
/src/assets/icons/fb.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/app/shared/header.component.ts:
--------------------------------------------------------------------------------
1 | import {Component, Input } from '@angular/core';
2 | import { HttpClient, HttpHeaders } from '@angular/common/http';
3 |
4 | @Component({
5 | selector: 'app-header',
6 | templateUrl: './header.component.html'
7 | })
8 |
9 | export class HeaderComponent {
10 | @Input() imageUrl: string;
11 | @Input() pageTitle: string;
12 | emailSent = false;
13 | selectedValue;
14 | formShowing = false;
15 | constructor(private _http: HttpClient) {
16 | }
17 | onSubmit(filter) {
18 | const message = 'name=' + JSON.stringify(filter);
19 | const httpOptions = { headers: new HttpHeaders({ 'Content-Type': 'application/json' }) };
20 | this._http.post('http://elisheducation.com/contact-form-app.php', message, httpOptions).subscribe((data) => {
21 | this.emailSent = true;
22 | });
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/assets/icons/drop_down.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/assets/icons/github.svg:
--------------------------------------------------------------------------------
1 | GitHub icon
--------------------------------------------------------------------------------
/src/app/shared/dropzone/dropzone.directive.ts:
--------------------------------------------------------------------------------
1 | import { Directive, HostListener, Output, EventEmitter } from '@angular/core';
2 |
3 | @Directive({
4 | selector: '[appDropZone]'
5 | })
6 |
7 | export class DropZoneDirective {
8 | @Output() dropped = new EventEmitter();
9 | @Output() hovered = new EventEmitter();
10 |
11 | constructor() { }
12 |
13 | @HostListener('drop', ['event'])
14 | onDrop($event) {
15 | $event.preventDefault();
16 | this.dropped.emit($event.dataTransfer.files);
17 | this.hovered.emit(false);
18 | }
19 |
20 | @HostListener('dragOver', ['event'])
21 | ondragover($event) {
22 | $event.preventDefault();
23 | this.hovered.emit(true);
24 | }
25 | @HostListener('dragleave', ['$event'])
26 | onDragLeave($event) {
27 | $event.preventDefault();
28 | this.hovered.emit(false);
29 | }
30 | }
--------------------------------------------------------------------------------
/src/app/shared/dropzone/filesize.pipe.ts:
--------------------------------------------------------------------------------
1 | import { Pipe, PipeTransform } from '@angular/core';
2 | const FILE_SIZE_UNITS = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
3 | const FILE_SIZE_UNITS_LONG = ['Bytes', 'Kilobytes', 'Megabytes', 'Gigabytes', 'Pettabytes', 'Exabytes', 'Zettabytes', 'Yottabytes'];
4 | @Pipe({
5 | name: 'fileSize'
6 | })
7 | export class FileSizePipe implements PipeTransform {
8 | transform(sizeInBytes: number, longForm: boolean): string {
9 | const units = longForm
10 | ? FILE_SIZE_UNITS_LONG
11 | : FILE_SIZE_UNITS;
12 | let power = Math.round(Math.log(sizeInBytes) / Math.log(1024));
13 | power = Math.min(power, units.length - 1);
14 | const size = sizeInBytes / Math.pow(1024, power); // size in new units
15 | const formattedSize = Math.round(size * 100) / 100; // keep up to 2 decimals
16 | const unit = units[power];
17 | return size ? `${formattedSize} ${unit}` : '0';
18 | }
19 | }
--------------------------------------------------------------------------------
/src/app/app-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { Routes, RouterModule } from '@angular/router';
3 | import { AboutusComponent } from './shared/aboutus.component';
4 | import { LoginComponent } from './shared/login.component';
5 | import { SignupComponent } from './shared/signup.component';
6 | import { AttendanceComponent } from './product/attendance.component';
7 | import { AuthGuardService } from './services/auth-guard.service';
8 |
9 | const routes: Routes = [
10 | { path: '', redirectTo: '/aboutus', pathMatch: 'full' },
11 | { path: 'aboutus', component: AboutusComponent },
12 | { path: 'login', component: LoginComponent },
13 | { path: 'signup', component: SignupComponent },
14 | { path: 'attendance', component: AttendanceComponent, canActivate: [AuthGuardService] }
15 | ];
16 |
17 | @NgModule({
18 | imports: [RouterModule.forRoot(routes)],
19 | exports: [RouterModule]
20 | })
21 | export class AppRoutingModule { }
22 |
--------------------------------------------------------------------------------
/src/assets/icons/language.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/app/shared/signup.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { BackendService } from '../services/backend.service';
3 | import { Router } from '@angular/router';
4 |
5 | @Component({
6 | selector: 'app-signup',
7 | templateUrl: './signup.component.html'
8 | })
9 | export class SignupComponent implements OnInit {
10 | state: string = '';
11 | error: any;
12 | dataLoading: boolean = false;
13 | brokenNetwork = false;
14 | savedChanges = false;
15 |
16 | constructor(private _backendService: BackendService, private _router: Router) {
17 | }
18 | ngOnInit() {
19 | }
20 | routeLoginPage() {
21 | this._router.navigate(['/login']);
22 | }
23 |
24 | onSubmit(formData) {
25 | this.dataLoading = true;
26 | this._backendService.createUser(formData).then(
27 | (success) => {
28 | this.dataLoading = false;
29 | this.savedChanges = true;
30 | },
31 | (error) => {
32 | this.error = error;
33 | this.dataLoading = false;
34 | this.savedChanges = false;
35 | }
36 | );
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/1.0/config/configuration-file.html
3 |
4 | module.exports = function (config) {
5 | config.set({
6 | basePath: '',
7 | frameworks: ['jasmine', '@angular-devkit/build-angular'],
8 | plugins: [
9 | require('karma-jasmine'),
10 | require('karma-chrome-launcher'),
11 | require('karma-jasmine-html-reporter'),
12 | require('karma-coverage-istanbul-reporter'),
13 | require('@angular-devkit/build-angular/plugins/karma')
14 | ],
15 | client: {
16 | clearContext: false // leave Jasmine Spec Runner output visible in browser
17 | },
18 | coverageIstanbulReporter: {
19 | dir: require('path').join(__dirname, '../coverage'),
20 | reports: ['html', 'lcovonly', 'text-summary'],
21 | fixWebpackSourcePaths: true
22 | },
23 | reporters: ['progress', 'kjhtml'],
24 | port: 9876,
25 | colors: true,
26 | logLevel: config.LOG_INFO,
27 | autoWatch: true,
28 | browsers: ['Chrome'],
29 | singleRun: false
30 | });
31 | };
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Amit Shukla
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/app/app.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed, async } from '@angular/core/testing';
2 | import { RouterTestingModule } from '@angular/router/testing';
3 | import { AppComponent } from './app.component';
4 |
5 | describe('AppComponent', () => {
6 | beforeEach(async(() => {
7 | TestBed.configureTestingModule({
8 | imports: [
9 | RouterTestingModule
10 | ],
11 | declarations: [
12 | AppComponent
13 | ],
14 | }).compileComponents();
15 | }));
16 |
17 | it('should create the app', () => {
18 | const fixture = TestBed.createComponent(AppComponent);
19 | const app = fixture.debugElement.componentInstance;
20 | expect(app).toBeTruthy();
21 | });
22 |
23 | it(`should have as title 'client'`, () => {
24 | const fixture = TestBed.createComponent(AppComponent);
25 | const app = fixture.debugElement.componentInstance;
26 | expect(app.title).toEqual('client');
27 | });
28 |
29 | it('should render title in a h1 tag', () => {
30 | const fixture = TestBed.createComponent(AppComponent);
31 | fixture.detectChanges();
32 | const compiled = fixture.debugElement.nativeElement;
33 | expect(compiled.querySelector('h1').textContent).toContain('Welcome to client!');
34 | });
35 | });
36 |
--------------------------------------------------------------------------------
/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | // This file can be replaced during build by using the `fileReplacements` array.
2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
3 | // The list of file replacements can be found in `angular.json`.
4 |
5 | export const environment = {
6 | production: false,
7 | firebase: {
8 | apiKey: "xxxx",
9 | authDomain: "xxxx",
10 | databaseURL: "xxxx",
11 | projectId: "xxxx",
12 | storageBucket: "xxxx",
13 | messagingSenderId: "xxxx",
14 | },
15 | emailAPI: 'xxxx',
16 | database: 'firebase',
17 | social: {
18 | fblink: 'https://www.facebook.com/elishconsulting',
19 | linkedin: 'https://www.linkedin.com/in/ashuklax/',
20 | github: 'https://github.com/AmitXShukla',
21 | emailId: 'mailto://amit@elishconsulting.com'
22 | },
23 | socialAuthEnabled: true,
24 | googleMapsKey: 'xxxx'
25 | };
26 |
27 | /*
28 | * For easier debugging in development mode, you can import the following file
29 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
30 | *
31 | * This import should be commented out in production mode because it will have a negative impact
32 | * on performance if an error is thrown.
33 | */
34 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI.
35 |
--------------------------------------------------------------------------------
/src/app/shared/aboutus.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Mobile Picture, GPS Tracking Attendance App
5 | A Mobile GPS, Picture Attendance App for storing Employee, Asset Picture Attendance with GPS Locations.
6 |
7 |
8 | Store all Employee and/or Assets attendance records electronically.
9 | 1. Paperless Records for Attendance Regiter
10 | 2. Save Pictures and Live GPS Locations
11 | 3. Online and/or Offline (delayed capture) App
12 | 4. One App for multiple platforms (iOS, Android, Desktop, Cloud etc.)
13 | 5. Unlimited Storage (only limited to server/database hosting).
14 | 6. Store and Access millions of records instantly.
15 | 7. Paperless and Mobile on-premise App.
16 | 8. Instant access to ALL historical records at anytime.
17 | 9. No Thumb expressions, Picture identification or card punching or GPS devices to carry except smart phone.
18 | 10. Auto Face Recognition (Pro version only).
19 |
20 | send an email to info@elishconsulting.com for Pro version enquiries.
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "client",
3 | "version": "0.0.0",
4 | "scripts": {
5 | "ng": "ng",
6 | "start": "ng serve",
7 | "build": "ng build",
8 | "test": "ng test",
9 | "lint": "ng lint",
10 | "e2e": "ng e2e"
11 | },
12 | "private": true,
13 | "dependencies": {
14 | "@agm/core": "^1.0.0-beta.5",
15 | "@angular/animations": "^7.1.4",
16 | "@angular/cdk": "^7.3.1",
17 | "@angular/common": "~7.1.0",
18 | "@angular/compiler": "~7.1.0",
19 | "@angular/core": "~7.1.0",
20 | "@angular/fire": "^5.1.1",
21 | "@angular/forms": "~7.1.0",
22 | "@angular/material": "^7.3.1",
23 | "@angular/platform-browser": "~7.1.0",
24 | "@angular/platform-browser-dynamic": "~7.1.0",
25 | "@angular/router": "~7.1.0",
26 | "core-js": "^2.5.4",
27 | "firebase": "^5.8.2",
28 | "hammerjs": "^2.0.8",
29 | "rxjs": "~6.3.3",
30 | "tslib": "^1.9.0",
31 | "zone.js": "~0.8.26"
32 | },
33 | "devDependencies": {
34 | "@angular-devkit/build-angular": "~0.11.0",
35 | "@angular/cli": "~7.1.4",
36 | "@angular/compiler-cli": "~7.1.0",
37 | "@angular/language-service": "~7.1.0",
38 | "@types/node": "~8.9.4",
39 | "@types/jasmine": "~2.8.8",
40 | "@types/jasminewd2": "~2.0.3",
41 | "codelyzer": "~4.5.0",
42 | "jasmine-core": "~2.99.1",
43 | "jasmine-spec-reporter": "~4.2.1",
44 | "karma": "~3.1.1",
45 | "karma-chrome-launcher": "~2.2.0",
46 | "karma-coverage-istanbul-reporter": "~2.0.1",
47 | "karma-jasmine": "~1.1.2",
48 | "karma-jasmine-html-reporter": "^0.2.2",
49 | "protractor": "~5.4.0",
50 | "ts-node": "~7.0.0",
51 | "tslint": "~5.11.0",
52 | "typescript": "~3.1.6"
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/app/shared/login.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, AfterViewInit } from '@angular/core';
2 | import { AngularFireAuth } from '@angular/fire/auth';
3 | import { environment } from '../../environments/environment';
4 | import { BackendService } from '../services/backend.service';
5 |
6 | @Component({
7 | selector: 'app-login',
8 | templateUrl: './login.component.html'
9 | })
10 | export class LoginComponent implements OnInit, AfterViewInit {
11 | socialAuth: boolean = false; // show Google and FB Sign in only when social auth is enabled
12 | error: any;
13 | dataLoading: boolean = false;
14 | brokenNetwork = false;
15 |
16 | constructor(public afAuth: AngularFireAuth, private _backendService: BackendService) { }
17 |
18 | ngOnInit() {
19 | this.socialAuth = environment.socialAuthEnabled;
20 | }
21 | ngAfterViewInit() {
22 | if (!localStorage.getItem("uid")) {
23 | this.afAuth.user.subscribe(res => {
24 | if (res) {
25 | window.localStorage.setItem("uid", res.uid);
26 | window.localStorage.setItem("displayName", res.displayName);
27 | window.localStorage.setItem("email", res.email);
28 | window.localStorage.setItem("picture", res.photoURL);
29 | }
30 | });
31 | }
32 | }
33 | login(loginType, formData?) {
34 | this._backendService.login(loginType, formData).catch(
35 | (err) => {
36 | this.error = err;
37 | });
38 | }
39 | logout() {
40 | window.localStorage.removeItem("uid");
41 | window.localStorage.removeItem("displayName");
42 | window.localStorage.removeItem("email");
43 | window.localStorage.removeItem("picture");
44 | return this.afAuth.auth.signOut();
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/assets/icons/salary.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
8 |
9 |
10 |
11 |
13 |
14 |
19 |
21 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/app/shared/dropzone/fileupload.component.html:
--------------------------------------------------------------------------------
1 | Error occured.
2 |
3 |
6 |
7 | {{ pct | number }}%
8 |
9 |
Picture Attendance is saved.
10 |
11 |
17 |
File Upload
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | {{ snap.bytesTransferred | fileSize }} of {{ snap.totalBytes | fileSize }}
31 |
32 |
Results!
33 |
34 |
35 |
36 |
Pause
37 |
Cancel
38 |
Resume
39 |
--------------------------------------------------------------------------------
/src/app/shared/dropzone/fileupload.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input } from '@angular/core';
2 | import { AngularFireStorage, AngularFireUploadTask } from '@angular/fire/storage';
3 | import { Observable } from 'rxjs';
4 | import { finalize } from 'rxjs/operators';
5 | import { BackendService } from '../../services/backend.service';
6 |
7 | @Component({
8 | selector: 'app-fileupload',
9 | templateUrl: './fileupload.component.html',
10 | styleUrls: ['./fileupload.component.css']
11 | })
12 | export class FileUploadComponent {
13 | @Input() fileUrl: string;
14 | @Input() docId: string;
15 | task: AngularFireUploadTask;
16 | percentage: Observable;
17 | snapshot: Observable;
18 | downloadURL: Observable;
19 | isHovering: boolean;
20 | error: boolean = false;
21 |
22 | constructor(private _storage: AngularFireStorage, private _backendService: BackendService) { }
23 |
24 | toggleHover(event: boolean) {
25 | this.isHovering = event;
26 | }
27 |
28 | startUpload(event) {
29 | const file = event.item(0);
30 | const filePath = this.fileUrl + '/' + new Date().getTime();
31 | const fileRef = this._storage.ref(filePath);
32 | const task = this._storage.upload(filePath, file);
33 |
34 | // observe percentage changes
35 | this.percentage = task.percentageChanges();
36 | // update attendance doc
37 | task.snapshotChanges().pipe(
38 | finalize(() => this.downloadURL = fileRef.getDownloadURL()))
39 | .subscribe((res) => {
40 | if (res.bytesTransferred === res.totalBytes) {
41 | res.ref.getDownloadURL().then((val) => {
42 | return this._backendService.updateAttendance(val, this.docId);
43 | });
44 | }
45 | });
46 | }
47 |
48 | isActive(snapshot) {
49 | return snapshot.state === 'running' && snapshot.bytesTransferred < snapshot.totalBytes;
50 | }
51 | }
--------------------------------------------------------------------------------
/src/assets/icons/home.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
8 |
9 |
11 |
12 |
13 |
14 |
16 |
18 |
19 |
20 |
22 |
23 |
24 |
25 |
27 |
29 |
30 |
31 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { BrowserModule } from '@angular/platform-browser';
2 | import { NgModule } from '@angular/core';
3 | import { ElishCustomMaterialModule } from './shared/custom.material.module';
4 | import { HttpClientModule } from '@angular/common/http';
5 | import { FormsModule, ReactiveFormsModule } from '@angular/forms';
6 |
7 | import { AppRoutingModule } from './app-routing.module';
8 | import { AppComponent } from './app.component';
9 | import { AttendanceComponent } from './product/attendance.component';
10 | import { HeaderComponent } from './shared/header.component';
11 | import { FooterComponent } from './shared/footer.component';
12 | import { AboutusComponent } from './shared/aboutus.component';
13 | import { LoginComponent } from './shared/login.component';
14 | import { SignupComponent } from './shared/signup.component';
15 | // Angular Firebase settings
16 | import { AngularFireModule } from '@angular/fire';
17 | import { AngularFirestoreModule } from '@angular/fire/firestore';
18 | import { AngularFireStorageModule } from '@angular/fire/storage';
19 | import { AngularFireAuthModule } from '@angular/fire/auth';
20 | import { environment } from '../environments/environment';
21 | import { AgmCoreModule } from '@agm/core';
22 | import { FileUploadComponent } from './shared/dropzone/fileupload.component';
23 | import { DropZoneDirective } from './shared/dropzone/dropzone.directive';
24 | import { FileSizePipe } from './shared/dropzone/filesize.pipe';
25 |
26 | @NgModule({
27 | declarations: [
28 | AppComponent,
29 | AttendanceComponent,
30 | HeaderComponent,
31 | FooterComponent,
32 | AboutusComponent,
33 | LoginComponent,
34 | SignupComponent,
35 | FileUploadComponent,
36 | DropZoneDirective,
37 | FileSizePipe
38 | ],
39 | imports: [
40 | BrowserModule,
41 | AppRoutingModule,
42 | ElishCustomMaterialModule,
43 | HttpClientModule,
44 | FormsModule,
45 | ReactiveFormsModule,
46 | AgmCoreModule.forRoot({
47 | apiKey: environment.googleMapsKey
48 | }),
49 | AngularFireModule.initializeApp(environment.firebase, 'Attendance-APP'), // imports firebase/app needed for everything
50 | AngularFirestoreModule, // imports firebase/firestore, only needed for database features
51 | AngularFireAuthModule, // imports firebase/auth, only needed for auth features,
52 | AngularFireStorageModule // imports firebase/storage only needed for storage features
53 | ],
54 | providers: [],
55 | bootstrap: [AppComponent]
56 | })
57 | export class AppModule { }
58 |
--------------------------------------------------------------------------------
/src/app/shared/signup.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | A New userId is created. Plese login back with your emailid and password.
45 |
46 |
47 |
48 |
49 |
50 |
51 | Close
52 |
53 |
54 |
--------------------------------------------------------------------------------
/src/app/shared/login.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | dashboard Amit.LA
6 | Mobile Picture GPS Tracking Attendance App
7 |
8 |
9 | Welcome {{ user.displayName }} !!!
10 |
11 |
12 |
13 |
Logout
14 |
15 |
16 |
17 |
18 |
43 |
44 |
45 |
46 |
47 | Login With Facebook
48 |
49 |
50 | Login With Google
51 |
52 |
53 |
54 |
55 |
56 |
57 | No account?
58 | Create one with your email here
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/src/app/product/attendance.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, ViewChild } from '@angular/core';
2 | import { BackendService } from '../services/backend.service';
3 | import {MatPaginator, MatSort, MatTableDataSource} from '@angular/material';
4 |
5 | @Component({
6 | selector: 'app-attendance',
7 | templateUrl: './attendance.component.html'
8 | })
9 | export class AttendanceComponent implements OnInit {
10 | attendanceSaved: boolean = false;
11 | showCurrentLocation: boolean = false;
12 | showHistLocation: boolean = false;
13 | histLat;
14 | histLng;
15 | userData;
16 | dataSource: MatTableDataSource;
17 | @ViewChild(MatPaginator) paginator: MatPaginator;
18 | @ViewChild(MatSort) sort: MatSort;
19 | private displayedColumns = ['createdAt', 'data', 'id', 'path'];
20 | members;
21 | showFileUpload: boolean = false;
22 | attendanceDocId;
23 | picUrl;
24 |
25 | constructor(private _backendService: BackendService) { }
26 |
27 | ngOnInit() {
28 | this.userData = {
29 | email: localStorage.getItem("email"),
30 | uid: localStorage.getItem("uid"),
31 | gpsLoc : {
32 | "lat": null,
33 | "lng": null
34 | },
35 | dttm: new Date()
36 | };
37 | this.getUserLocation();
38 | this.dataSource = new MatTableDataSource(this.members);
39 | }
40 | onTabClick(event) {
41 | if (event.index === 1) {
42 | this.dataSource.paginator = this.paginator;
43 | this.dataSource.sort = this.sort;
44 | this.getHistLocation();
45 | }
46 | }
47 | getUserLocation() {
48 | const positionOption = { enableHighAccuracy: false, maximumAge: Infinity, timeout: 60000 };
49 | const gpsSunccuss = function (currentPosition) {
50 | // use gps position
51 | };
52 | const gpsFailed = function () {
53 | // use some 3rd party position solution(get position by your device ip)
54 | // getPositionBy3rdParty();
55 | };
56 |
57 | /// locate the user
58 | if (navigator.geolocation) {
59 | // navigator.geolocation.watchPosition((position => {
60 | navigator.geolocation.getCurrentPosition((position => {
61 | // call these two lines from another function at settime interval, only capture new GPS coords here through watchPosition
62 | this.userData.gpsLoc.lat = position.coords.latitude;
63 | this.userData.gpsLoc.lng = position.coords.longitude;
64 | return this.userData;
65 | }), gpsFailed, positionOption);
66 | }
67 | }
68 | setAttendance() {
69 | if (confirm("Are you sure want to Submit your attendance ?")) {
70 | this._backendService.setAttendance('NA', this.userData);
71 | this.attendanceSaved = true;
72 | }
73 | }
74 | deleteAttendance(docId) {
75 | if (confirm("Are you sure want to Delete your attendance ?")) {
76 | this._backendService.deleteAttendance(docId);
77 | }
78 | }
79 | getHistLocation() {
80 | this._backendService.getAttendance()
81 | .subscribe(members => {
82 | this.dataSource = new MatTableDataSource(members);
83 | this.dataSource.paginator = this.paginator;
84 | this.dataSource.sort = this.sort;
85 | });
86 | }
87 | applyFilter(filterValue: string) {
88 | this.dataSource.filter = filterValue.trim().toLowerCase();
89 |
90 | if (this.dataSource.paginator) {
91 | this.dataSource.paginator.firstPage();
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rulesDirectory": [
3 | "codelyzer"
4 | ],
5 | "rules": {
6 | "arrow-return-shorthand": true,
7 | "callable-types": true,
8 | "class-name": true,
9 | "comment-format": [
10 | true,
11 | "check-space"
12 | ],
13 | "curly": true,
14 | "deprecation": {
15 | "severity": "warn"
16 | },
17 | "eofline": false,
18 | "forin": true,
19 | "import-blacklist": [
20 | true,
21 | "rxjs/Rx"
22 | ],
23 | "import-spacing": true,
24 | "indent": [
25 | true,
26 | "spaces"
27 | ],
28 | "interface-over-type-literal": true,
29 | "label-position": true,
30 | "max-line-length": [
31 | true,
32 | 140
33 | ],
34 | "member-access": false,
35 | "member-ordering": [
36 | true,
37 | {
38 | "order": [
39 | "static-field",
40 | "instance-field",
41 | "static-method",
42 | "instance-method"
43 | ]
44 | }
45 | ],
46 | "no-arg": true,
47 | "no-bitwise": true,
48 | "no-console": [
49 | true,
50 | "debug",
51 | "info",
52 | "time",
53 | "timeEnd",
54 | "trace"
55 | ],
56 | "no-construct": true,
57 | "no-debugger": true,
58 | "no-duplicate-super": true,
59 | "no-empty": false,
60 | "no-empty-interface": true,
61 | "no-eval": true,
62 | "no-inferrable-types": [
63 | false,
64 | "ignore-params"
65 | ],
66 | "no-misused-new": true,
67 | "no-non-null-assertion": true,
68 | "no-redundant-jsdoc": true,
69 | "no-shadowed-variable": true,
70 | "no-string-literal": false,
71 | "no-string-throw": true,
72 | "no-switch-case-fall-through": true,
73 | "no-trailing-whitespace": true,
74 | "no-unnecessary-initializer": true,
75 | "no-unused-expression": true,
76 | "no-use-before-declare": true,
77 | "no-var-keyword": true,
78 | "object-literal-sort-keys": false,
79 | "one-line": [
80 | true,
81 | "check-open-brace",
82 | "check-catch",
83 | "check-else",
84 | "check-whitespace"
85 | ],
86 | "prefer-const": true,
87 | "quotemark": [
88 | false,
89 | "single"
90 | ],
91 | "radix": true,
92 | "semicolon": [
93 | true,
94 | "always"
95 | ],
96 | "triple-equals": [
97 | true,
98 | "allow-null-check"
99 | ],
100 | "typedef-whitespace": [
101 | true,
102 | {
103 | "call-signature": "nospace",
104 | "index-signature": "nospace",
105 | "parameter": "nospace",
106 | "property-declaration": "nospace",
107 | "variable-declaration": "nospace"
108 | }
109 | ],
110 | "unified-signatures": true,
111 | "variable-name": false,
112 | "whitespace": [
113 | true,
114 | "check-branch",
115 | "check-decl",
116 | "check-operator",
117 | "check-separator",
118 | "check-type"
119 | ],
120 | "no-output-on-prefix": true,
121 | "use-input-property-decorator": true,
122 | "use-output-property-decorator": true,
123 | "use-host-property-decorator": true,
124 | "no-input-rename": true,
125 | "no-output-rename": true,
126 | "use-life-cycle-interface": true,
127 | "use-pipe-transform-interface": true,
128 | "component-class-suffix": true,
129 | "directive-class-suffix": true
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/src/app/shared/header.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | menu
4 |
5 |
6 | home About us
7 | fingerprint Attendance
8 | account_circle Settings
9 |
10 | {{ imageUrl }}
11 | {{ pageTitle }}
12 | email
13 | account_circle
14 |
15 |
16 |
58 |
--------------------------------------------------------------------------------
/src/app/services/backend.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { AngularFireAuth } from '@angular/fire/auth';
3 | import { AngularFirestore, AngularFirestoreDocument } from '@angular/fire/firestore';
4 | import { auth } from 'firebase/app';
5 | import { environment } from '../../environments/environment';
6 | import { Observable } from 'rxjs';
7 | import { switchMap, take } from 'rxjs/operators';
8 |
9 | @Injectable({
10 | providedIn: 'root'
11 | })
12 | export class BackendService {
13 |
14 | constructor(public afAuth: AngularFireAuth, private _afs: AngularFirestore) { }
15 | // login page - login with FB/GOOGLE/EMAIL, if formData is passed, this means is user is using email/password login
16 | login(loginType, formData?) {
17 | if (formData) {
18 | return this.afAuth.auth.signInWithEmailAndPassword(formData.email, formData.password);
19 | } else {
20 | let loginMethod;
21 | if (loginType === 'FB') { loginMethod = new auth.FacebookAuthProvider(); }
22 | if (loginType === 'GOOGLE') { loginMethod = new auth.GoogleAuthProvider(); }
23 |
24 | return this.afAuth.auth.signInWithRedirect(loginMethod);
25 | }
26 | }
27 |
28 | // method to retreive firebase auth after login redirect
29 | redirectLogin() {
30 | return this.afAuth.auth.getRedirectResult();
31 | }
32 | createUser(formData) {
33 | if (environment.database === 'firebase') {
34 | return this.afAuth.auth.createUserWithEmailAndPassword(formData.value.email, formData.value.password);
35 | }
36 | if (environment.database === 'SQL') {
37 | // need to call SQL API here if a SQL Database is used
38 | }
39 | }
40 | getUser(): Promise {
41 | return this.afAuth.authState.pipe(take(1)).toPromise();
42 | }
43 | getAttendance(): Observable {
44 | return this._afs.collection('attendanceusers').doc(this.afAuth.auth.currentUser.uid).valueChanges().pipe(
45 | switchMap(val => this.getUserAttendance(val)));
46 | }
47 | getUserAttendance(val?) {
48 | if (val) {
49 | return this._afs.collection('attendance', ref =>
50 | ref.where('delete_flag', '==', 'N')
51 | .limit(20)
52 | .orderBy('updatedAt', "desc")).valueChanges();
53 | } else {
54 | return this._afs.collection('attendance', ref =>
55 | ref.where('delete_flag', '==', 'N')
56 | .where("author", "==", this.afAuth.auth.currentUser.uid)
57 | .limit(20)
58 | .orderBy('updatedAt', "desc")).valueChanges();
59 | }
60 | }
61 | setAttendance(filePath: string, userData) {
62 | const id = this._afs.createId();
63 | const item = { id, name };
64 | const docRef = this._afs.collection('attendance').doc(item.id);
65 | return docRef.set({
66 | path: filePath,
67 | updatedAt: this.timestamp,
68 | createdAt: this.timestamp,
69 | delete_flag: "N",
70 | author: userData.uid,
71 | id: item.id,
72 | data: userData
73 | });
74 | }
75 | updateAttendance(filePath: string, docId: string) {
76 | const timestamp = this.timestamp;
77 | const docRef = this._afs.collection('attendance').doc(docId);
78 | return docRef.update({
79 | path: filePath,
80 | updatedAt: this.timestamp
81 | });
82 | }
83 | deleteAttendance(docId) {
84 | this._afs.collection('attendance').doc(docId).delete();
85 | }
86 | // helper functions // get local or serverTimestamp
87 | get timestamp() {
88 | const d = new Date();
89 | return d;
90 | // return firebase.firestore.FieldValue.serverTimestamp();
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/polyfills.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This file includes polyfills needed by Angular and is loaded before the app.
3 | * You can add your own extra polyfills to this file.
4 | *
5 | * This file is divided into 2 sections:
6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main
8 | * file.
9 | *
10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
13 | *
14 | * Learn more in https://angular.io/guide/browser-support
15 | */
16 |
17 | /***************************************************************************************************
18 | * BROWSER POLYFILLS
19 | */
20 |
21 | /** IE9, IE10, IE11, and Chrome <55 requires all of the following polyfills.
22 | * This also includes Android Emulators with older versions of Chrome and Google Search/Googlebot
23 | */
24 |
25 | // import 'core-js/es6/symbol';
26 | // import 'core-js/es6/object';
27 | // import 'core-js/es6/function';
28 | // import 'core-js/es6/parse-int';
29 | // import 'core-js/es6/parse-float';
30 | // import 'core-js/es6/number';
31 | // import 'core-js/es6/math';
32 | // import 'core-js/es6/string';
33 | // import 'core-js/es6/date';
34 | // import 'core-js/es6/array';
35 | // import 'core-js/es6/regexp';
36 | // import 'core-js/es6/map';
37 | // import 'core-js/es6/weak-map';
38 | // import 'core-js/es6/set';
39 |
40 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */
41 | // import 'classlist.js'; // Run `npm install --save classlist.js`.
42 |
43 | /** IE10 and IE11 requires the following for the Reflect API. */
44 | // import 'core-js/es6/reflect';
45 |
46 | /**
47 | * Web Animations `@angular/platform-browser/animations`
48 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
49 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
50 | */
51 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`.
52 |
53 | /**
54 | * By default, zone.js will patch all possible macroTask and DomEvents
55 | * user can disable parts of macroTask/DomEvents patch by setting following flags
56 | * because those flags need to be set before `zone.js` being loaded, and webpack
57 | * will put import in the top of bundle, so user need to create a separate file
58 | * in this directory (for example: zone-flags.ts), and put the following flags
59 | * into that file, and then add the following code before importing zone.js.
60 | * import './zone-flags.ts';
61 | *
62 | * The flags allowed in zone-flags.ts are listed here.
63 | *
64 | * The following flags will work for all browsers.
65 | *
66 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
67 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
68 | * (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
69 | *
70 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
71 | * with the following flag, it will bypass `zone.js` patch for IE/Edge
72 | *
73 | * (window as any).__Zone_enable_cross_context_check = true;
74 | *
75 | */
76 |
77 | /***************************************************************************************************
78 | * Zone JS is required by default for Angular itself.
79 | */
80 | import 'zone.js/dist/zone'; // Included with Angular CLI.
81 |
82 |
83 | /***************************************************************************************************
84 | * APPLICATION IMPORTS
85 | */
86 |
--------------------------------------------------------------------------------
/angular.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "version": 1,
4 | "newProjectRoot": "projects",
5 | "projects": {
6 | "client": {
7 | "root": "",
8 | "sourceRoot": "src",
9 | "projectType": "application",
10 | "prefix": "app",
11 | "schematics": {},
12 | "architect": {
13 | "build": {
14 | "builder": "@angular-devkit/build-angular:browser",
15 | "options": {
16 | "outputPath": "dist/client",
17 | "index": "src/index.html",
18 | "main": "src/main.ts",
19 | "polyfills": "src/polyfills.ts",
20 | "tsConfig": "src/tsconfig.app.json",
21 | "assets": [
22 | "src/assets/favicon.ico",
23 | "src/assets"
24 | ],
25 | "styles": [
26 | "src/assets/styles.css"
27 | ],
28 | "scripts": []
29 | },
30 | "configurations": {
31 | "production": {
32 | "fileReplacements": [
33 | {
34 | "replace": "src/environments/environment.ts",
35 | "with": "src/environments/environment.prod.ts"
36 | }
37 | ],
38 | "optimization": true,
39 | "outputHashing": "all",
40 | "sourceMap": false,
41 | "extractCss": true,
42 | "namedChunks": false,
43 | "aot": true,
44 | "extractLicenses": true,
45 | "vendorChunk": false,
46 | "buildOptimizer": true,
47 | "budgets": [
48 | {
49 | "type": "initial",
50 | "maximumWarning": "2mb",
51 | "maximumError": "5mb"
52 | }
53 | ]
54 | }
55 | }
56 | },
57 | "serve": {
58 | "builder": "@angular-devkit/build-angular:dev-server",
59 | "options": {
60 | "browserTarget": "client:build"
61 | },
62 | "configurations": {
63 | "production": {
64 | "browserTarget": "client:build:production"
65 | }
66 | }
67 | },
68 | "extract-i18n": {
69 | "builder": "@angular-devkit/build-angular:extract-i18n",
70 | "options": {
71 | "browserTarget": "client:build"
72 | }
73 | },
74 | "test": {
75 | "builder": "@angular-devkit/build-angular:karma",
76 | "options": {
77 | "main": "src/test.ts",
78 | "polyfills": "src/polyfills.ts",
79 | "tsConfig": "src/tsconfig.spec.json",
80 | "karmaConfig": "src/karma.conf.js",
81 | "styles": [
82 | "src/styles.css"
83 | ],
84 | "scripts": [],
85 | "assets": [
86 | "src/favicon.ico",
87 | "src/assets"
88 | ]
89 | }
90 | },
91 | "lint": {
92 | "builder": "@angular-devkit/build-angular:tslint",
93 | "options": {
94 | "tsConfig": [
95 | "src/tsconfig.app.json",
96 | "src/tsconfig.spec.json"
97 | ],
98 | "exclude": [
99 | "**/node_modules/**"
100 | ]
101 | }
102 | }
103 | }
104 | },
105 | "client-e2e": {
106 | "root": "e2e/",
107 | "projectType": "application",
108 | "prefix": "",
109 | "architect": {
110 | "e2e": {
111 | "builder": "@angular-devkit/build-angular:protractor",
112 | "options": {
113 | "protractorConfig": "e2e/protractor.conf.js",
114 | "devServerTarget": "client:serve"
115 | },
116 | "configurations": {
117 | "production": {
118 | "devServerTarget": "client:serve:production"
119 | }
120 | }
121 | },
122 | "lint": {
123 | "builder": "@angular-devkit/build-angular:tslint",
124 | "options": {
125 | "tsConfig": "e2e/tsconfig.e2e.json",
126 | "exclude": [
127 | "**/node_modules/**"
128 | ]
129 | }
130 | }
131 | }
132 | }
133 | },
134 | "defaultProject": "client"
135 | }
--------------------------------------------------------------------------------
/src/assets/styles.css:
--------------------------------------------------------------------------------
1 | @import "~@angular/material/prebuilt-themes/indigo-pink.css";
2 |
3 | .small-headline {
4 | font-family: times, Times New Roman, times-roman, georgia, serif;
5 | font-size: 8;
6 | line-height: 40px;
7 | letter-spacing: -1px;
8 | color: #0088cc;
9 | }
10 | .middle-headline {
11 | font-family: times, Times New Roman, times-roman, georgia, serif;
12 | font-size: 28px;
13 | line-height: 40px;
14 | letter-spacing: -1px;
15 | color: #0088cc;
16 | }
17 | .large-headline {
18 | font-family: times, Times New Roman, times-roman, georgia, serif;
19 | font-size: 48px;
20 | line-height: 40px;
21 | letter-spacing: -1px;
22 | color: #0088cc;
23 | margin: 0 0 0 0;
24 | padding: 0 0 0 0;
25 | font-weight: 100;
26 | }
27 | .bodytext {
28 | font-family: Arial, Helvetica, sans-serif;
29 | font-size: 10;
30 | line-height: 35px;
31 | color: black;
32 | }
33 | .bodylabel {
34 | font-family: Arial, Helvetica, sans-serif;
35 | font-size: 10;
36 | line-height: 35px;
37 | color: darkslateblue;
38 | text-transform: uppercase;
39 | }
40 | .label-error {
41 | margin: 0 10px;
42 | font-family: Arial, Helvetica, sans-serif;
43 | font-size: 10;
44 | color: red
45 | }
46 | .label-go {
47 | margin: 0 10px;
48 | font-family: Arial, Helvetica, sans-serif;
49 | font-size: 10;
50 | color: green
51 | }
52 | .small-spacer {
53 | margin-left: 10px;
54 | }
55 | .med-spacer {
56 | margin-left: 70px;
57 | }
58 | .large-spacer {
59 | margin-left: 170px;
60 | }
61 | .label-hint {
62 | margin: 0 10px;
63 | font-family: Arial, Helvetica, sans-serif;
64 | font-size: 5;
65 | color: darkgray
66 | }
67 |
68 | .label-heading {
69 | margin: 0 10px;
70 | height: 60px;
71 | font-family: Arial, Helvetica, sans-serif;
72 | font-weight: bold;
73 | }
74 | .label-text {
75 | margin: 0 10px;
76 | height: 60px;
77 | font-family: Arial, Helvetica, sans-serif;
78 | }
79 | .body {
80 | background-color: white;
81 | color: #777777;
82 | font-family: Arial, Helvetica, sans-serif;
83 | font-size: 10;
84 | line-height: 22px;
85 | margin: 0;
86 | }
87 | .list-text {
88 | font-family: Courier;
89 | color:blue;
90 | font-size: 10;
91 | }
92 | .example-h2 {
93 | margin: 10px;
94 | }
95 | .example-margin {
96 | margin: 0 10px;
97 | }
98 | .example-section {
99 | display: flex;
100 | align-content: center;
101 | align-items: center;
102 | height: 60px;
103 | }
104 | .tab-margin {
105 | margin: 0 200px;
106 | }
107 | .col-md-7 {
108 | position: relative;
109 | min-height: 1px;
110 | padding-right: 15px;
111 | padding-left: 15px;
112 | }
113 | .lead {
114 | margin-bottom: 20px;
115 | font-size: 10;
116 | font-weight: 300;
117 | line-height: 1.4;
118 | }
119 | p {
120 | color: #777777;
121 | line-height: 24px;
122 | margin: 0 0 20px;
123 | }
124 | .heading {
125 | color:black !important;
126 | margin-bottom: 5px !important;
127 | font-family: "Open Sans", Arial, sans-serif;
128 | font-size: 10;
129 | }
130 | .blueheading {
131 | color: #0088cc !important;
132 | margin-bottom: 5px !important;
133 | font-family: "Open Sans", Arial, sans-serif;
134 | font-size: 16px;
135 | }
136 | .list-group-item {
137 | list-style-type: square;
138 | list-style-position: outside;
139 | list-style-image: none;
140 | }
141 | .list-group {
142 | list-style-type: square;
143 | list-style-position: outside;
144 | list-style-image: none;
145 | }
146 | .example-icon {
147 | padding: 0 14px;
148 | }
149 |
150 | .example-spacer {
151 | flex: 1 1 auto;
152 | }
153 |
154 | .example-card {
155 | color:white;
156 | background-color: black;
157 | }
158 | .example-button-row {
159 | display: flex;
160 | align-items: center;
161 | justify-content: space-around;
162 | }
163 | .sebm-google-map-container {
164 | height: 300px;
165 | }
166 | .tab-group {
167 | border: 1px solid #e8e8e8;
168 | }
169 | .tab-content {
170 | padding: 16px;
171 | }
172 | .example-form {
173 | min-width: 150px;
174 | max-width: 500px;
175 | width: 100%;
176 | }
177 | .example-full-width {
178 | width: 100%;
179 | display: flex;
180 | flex-direction: column;
181 | }
182 | .mat-chip {
183 | max-width: 200px;
184 | }
185 | .mat-card{
186 | height:auto;
187 | }
188 | .mat-card-content{
189 | height:auto;
190 | }
191 | .agm-map-container-inner { width: inherit; height: inherit; }
192 | .agm-map-content { display:none; }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ```diff
2 | - If you like this project, please consider giving it a star (*) and follow me at GitHub & YouTube.
3 | ```
4 | [ ](https://youtube.com/AmitShukla_AI)
5 | [ ](https://github.com/AmitXShukla)
6 | [ ](https://medium.com/@Amit_Shukla)
7 | [ ](https://twitter.com/ashuklax)
8 |
9 | Mobile Picture, GPS Tracking Attendance App
10 | A Mobile GPS, Picture Attendance App for storing Employee, Asset Picture Attendance with GPS Locations.
11 | Objective
12 | Store all Employee and/or Assets attendance records electronically.
13 | 1. Paperless Records for Attendance Regiter
14 | 2. Save Pictures and Live GPS Locations
15 | 3. Online and/or Offline (delayed capture) App
16 | 4. One App for multiple platforms (iOS, Android, Desktop, Cloud etc.)
17 | 5. Unlimited Storage (only limited to server/database hosting).
18 | 6. Store and Access millions of records instantly.
19 | 7. Paperless and Mobile on-premise App.
20 | 8. Instant access to ALL historical records at anytime.
21 | 9. No Thumb expressions, Picture identification or card punching or GPS devices to carry except smart phone.
22 | 10. Auto Face Recognition (Pro version only).
23 |
24 | send an email to info@elishconsulting.com for Pro version enquiries.
25 |
26 | Tools:
27 | front-end: Angular 7
28 | back-end: Google Firebase
29 | Pro Version: AI, Machine Learning Algorthim with Face Recognition
30 | Let's get started :-
31 |
32 | Before we start, Please make sure you have latest version of node js installed.
33 | Let's head out to https://nodejs.org/en/ and grab latest nodejs.
34 | Once you have nodejs installed, open command prompt/terminal window.
35 | $ node -v // make sure, this command comes back with a node version
36 | $ npm -v // make sure, this command comes back with a npm version
37 |
38 | How to Install NodeJS on ChromeOS
39 | # First run
40 | $ sudo apt-get update
41 | # and then if needed
42 | $ sudo apt-get install curl gnupg -y
43 | # or nodejs version 11
44 | $ curl -sL https://deb.nodesource.com/setup_11.x | sudo -E bash -
45 | $ sudo apt-get install -y nodejs
46 | Install Angular CLI
47 | $ npm install -g @angular/cli
48 | $ mkdir app
49 | $ cd app
50 | $ mkdir client
52 | $ ng new GPS-Pic-Attendance
53 | $ cd GPS-Pic-Attendance
54 | $ ng serve
55 |
56 |
57 | $ ng g c shared/aboutus --spec=false --flat=true
58 | $ ng g c shared/header --spec=false --flat=true
59 | $ ng g c shared/footer --spec=false --flat=true
60 | $ ng g c shared/login --spec=false --flat=true
61 | $ ng g c shared/signup --spec=false --flat=true
62 | $ ng g c product/attendance --spec=false --flat=true
63 |
64 | Firebase Database Rules
65 | service cloud.firestore {
66 | match /databases/{database}/documents {
67 |
68 | match /attendance/{document} {
69 | allow write: if isSignedIn();
70 | allow read, update: if isDocOwner() || isAdmin();
71 | }
72 |
73 | match /attendanceusers/{document} {
74 | allow read: if isSignedIn();
75 | }
76 | }
77 | // helper functions
78 | function isDocOwner(){
79 | // assuming document has a field author which is uid
80 | // Only the authenticated user who authored the document can read or write
81 | return request.auth.uid == resource.data.author;
82 | // This above read query will fail
83 | // The query fails even if the current user actually is the author of every story document.
84 | // The reason for this behavior is that when Cloud Firestore applies your security rules,
85 | // it evaluates the query against its potential result set,
86 | // not against the actual properties of documents in your database.
87 | // If a query could potentially include documents that violate your security rules,
88 | // the query will fail.
89 | // on your client app, make sure to include following
90 | // .where("author", "==", this.afAuth.auth.currentUser.uid)
91 | }
92 | function isSignedIn() {
93 | // check if user is signed in
94 | return request.auth.uid != null;
95 | }
96 | function isAdmin() {
97 | return get(/databases/$(database)/documents/attendanceusers/
98 | $(request.auth.uid)).data.isAdmin == true;
99 | }
100 |
101 | videos !!
102 | Setup Angular environment
103 | Setup Angular Materials
104 | Setup Firebase
105 | Setup Angular Routes and AuthGuard
106 | AngularFire and Firebase
107 | Social Auth
108 | Angular with Google Maps
109 | GPS Location
110 | Angular Firebase fileupload FireStorage
111 | Angualar Materal Data table tutorials
112 | Firebase persistence
113 | Using Google Maps with Angular 7
114 | Firebase query
115 | Firebase database rules
116 |
--------------------------------------------------------------------------------
/src/app/shared/custom.material.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { FormsModule, ReactiveFormsModule } from '@angular/forms';
3 |
4 | import {
5 | MatButtonModule,
6 | MatCheckboxModule,
7 | MatFormFieldModule,
8 | MatDialogModule,
9 | MatTabsModule,
10 | MatProgressSpinnerModule,
11 | MatMenuModule,
12 | MatIconModule,
13 | MatInputModule,
14 | MatSelectModule,
15 | MatToolbarModule,
16 | MatCardModule,
17 | MatChipsModule,
18 | MatListModule,
19 | MatTooltipModule,
20 | MatNativeDateModule,
21 | MatDatepickerModule,
22 | MatTableModule,
23 | MatPaginatorModule,
24 | MatProgressBarModule,
25 | MatSortModule,
26 | MatSnackBarModule,
27 | MatStepperModule,
28 | MatGridListModule,
29 | MatBadgeModule,
30 | MatExpansionModule,
31 | MatRadioModule
32 | } from '@angular/material';
33 | import {BrowserModule} from '@angular/platform-browser';
34 | import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
35 | import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
36 | import {DomSanitizer} from '@angular/platform-browser';
37 | import {MatIconRegistry} from '@angular/material';
38 | @NgModule({
39 | imports: [
40 | FormsModule,
41 | ReactiveFormsModule,
42 | BrowserAnimationsModule,
43 | MatButtonModule,
44 | MatCheckboxModule,
45 | MatFormFieldModule,
46 | MatDialogModule,
47 | MatTabsModule,
48 | MatProgressSpinnerModule,
49 | MatMenuModule,
50 | MatIconModule,
51 | MatInputModule,
52 | MatSelectModule,
53 | MatToolbarModule,
54 | MatCardModule,
55 | MatChipsModule,
56 | MatListModule,
57 | MatTooltipModule,
58 | MatNativeDateModule,
59 | MatDatepickerModule,
60 | MatTableModule,
61 | MatPaginatorModule,
62 | MatProgressBarModule,
63 | MatSortModule,
64 | MatSnackBarModule,
65 | MatStepperModule,
66 | MatGridListModule,
67 | MatBadgeModule,
68 | MatExpansionModule,
69 | MatRadioModule
70 | ],
71 | exports: [
72 | BrowserAnimationsModule,
73 | MatButtonModule,
74 | MatFormFieldModule,
75 | MatCheckboxModule,
76 | MatTabsModule,
77 | MatProgressSpinnerModule,
78 | MatMenuModule,
79 | MatIconModule,
80 | MatInputModule,
81 | MatSelectModule,
82 | MatToolbarModule,
83 | MatCardModule,
84 | MatChipsModule,
85 | MatListModule,
86 | MatTooltipModule,
87 | MatNativeDateModule,
88 | MatDatepickerModule,
89 | MatTableModule,
90 | MatPaginatorModule,
91 | MatProgressBarModule,
92 | MatSortModule,
93 | MatSnackBarModule,
94 | MatStepperModule,
95 | MatGridListModule,
96 | MatBadgeModule,
97 | MatExpansionModule,
98 | MatRadioModule
99 | ],
100 | declarations: []
101 | })
102 | export class ElishCustomMaterialModule {
103 | constructor(iconRegistry: MatIconRegistry, sanitizer: DomSanitizer) {
104 | iconRegistry.addSvgIcon(
105 | 'more_vert',
106 | sanitizer.bypassSecurityTrustResourceUrl('assets/icons/more_vert.svg'));
107 | iconRegistry.addSvgIcon(
108 | 'facebook',
109 | sanitizer.bypassSecurityTrustResourceUrl('assets/icons/fb.svg'));
110 | iconRegistry.addSvgIcon(
111 | 'linkedin',
112 | sanitizer.bypassSecurityTrustResourceUrl('assets/icons/linkedin.svg'));
113 | iconRegistry.addSvgIcon(
114 | 'github',
115 | sanitizer.bypassSecurityTrustResourceUrl('assets/icons/github.svg'));
116 | iconRegistry.addSvgIcon(
117 | 'menu',
118 | sanitizer.bypassSecurityTrustResourceUrl('assets/icons/menu.svg'));
119 | iconRegistry.addSvgIcon(
120 | 'dashboard-black',
121 | sanitizer.bypassSecurityTrustResourceUrl('assets/icons/dashboard-black.svg'));
122 | iconRegistry.addSvgIcon(
123 | 'star',
124 | sanitizer.bypassSecurityTrustResourceUrl('assets/icons/star.svg'));
125 | iconRegistry.addSvgIcon(
126 | 'phone',
127 | sanitizer.bypassSecurityTrustResourceUrl('assets/icons/phone.svg'));
128 | iconRegistry.addSvgIcon(
129 | 'email',
130 | sanitizer.bypassSecurityTrustResourceUrl('assets/icons/email.svg'));
131 | iconRegistry.addSvgIcon(
132 | 'account_circle',
133 | sanitizer.bypassSecurityTrustResourceUrl('assets/icons/account_circle.svg'));
134 | iconRegistry.addSvgIcon(
135 | 'event',
136 | sanitizer.bypassSecurityTrustResourceUrl('assets/icons/event.svg'));
137 | iconRegistry.addSvgIcon(
138 | 'track_changes',
139 | sanitizer.bypassSecurityTrustResourceUrl('assets/icons/track_changes.svg'));
140 | iconRegistry.addSvgIcon(
141 | 'language',
142 | sanitizer.bypassSecurityTrustResourceUrl('assets/icons/language.svg'));
143 | iconRegistry.addSvgIcon(
144 | 'search',
145 | sanitizer.bypassSecurityTrustResourceUrl('assets/icons/search.svg'));
146 | iconRegistry.addSvgIcon(
147 | 'book',
148 | sanitizer.bypassSecurityTrustResourceUrl('assets/icons/book.svg'));
149 | iconRegistry.addSvgIcon(
150 | 'business',
151 | sanitizer.bypassSecurityTrustResourceUrl('assets/icons/business.svg'));
152 | iconRegistry.addSvgIcon(
153 | 'place',
154 | sanitizer.bypassSecurityTrustResourceUrl('assets/icons/place.svg'));
155 | iconRegistry.addSvgIcon(
156 | 'code',
157 | sanitizer.bypassSecurityTrustResourceUrl('assets/icons/code.svg'));
158 | iconRegistry.addSvgIcon(
159 | 'help',
160 | sanitizer.bypassSecurityTrustResourceUrl('assets/icons/help.svg'));
161 | iconRegistry.addSvgIcon(
162 | 'clear',
163 | sanitizer.bypassSecurityTrustResourceUrl('assets/icons/clear.svg'));
164 | iconRegistry.addSvgIcon(
165 | 'vpn',
166 | sanitizer.bypassSecurityTrustResourceUrl('assets/icons/vpn.svg'));
167 | iconRegistry.addSvgIcon(
168 | 'new',
169 | sanitizer.bypassSecurityTrustResourceUrl('assets/icons/new.svg'));
170 | iconRegistry.addSvgIcon(
171 | 'cloud',
172 | sanitizer.bypassSecurityTrustResourceUrl('assets/icons/cloud.svg'));
173 | iconRegistry.addSvgIcon(
174 | 'backspace',
175 | sanitizer.bypassSecurityTrustResourceUrl('assets/icons/backspace.svg'));
176 | iconRegistry.addSvgIcon(
177 | 'add',
178 | sanitizer.bypassSecurityTrustResourceUrl('assets/icons/add.svg'));
179 | iconRegistry.addSvgIcon(
180 | 'edit',
181 | sanitizer.bypassSecurityTrustResourceUrl('assets/icons/edit.svg'));
182 | iconRegistry.addSvgIcon(
183 | 'delete',
184 | sanitizer.bypassSecurityTrustResourceUrl('assets/icons/delete.svg'));
185 | iconRegistry.addSvgIcon(
186 | 'cached',
187 | sanitizer.bypassSecurityTrustResourceUrl('assets/icons/cached.svg'));
188 | iconRegistry.addSvgIcon(
189 | 'create',
190 | sanitizer.bypassSecurityTrustResourceUrl('assets/icons/create.svg'));
191 | iconRegistry.addSvgIcon(
192 | 'employee',
193 | sanitizer.bypassSecurityTrustResourceUrl('assets/icons/person.svg'));
194 | iconRegistry.addSvgIcon(
195 | 'employer',
196 | sanitizer.bypassSecurityTrustResourceUrl('assets/icons/group.svg'));
197 | iconRegistry.addSvgIcon(
198 | 'home',
199 | sanitizer.bypassSecurityTrustResourceUrl('assets/icons/home.svg'));
200 | iconRegistry.addSvgIcon(
201 | 'equalizer',
202 | sanitizer.bypassSecurityTrustResourceUrl('assets/icons/equalizer.svg'));
203 | iconRegistry.addSvgIcon(
204 | 'security',
205 | sanitizer.bypassSecurityTrustResourceUrl('assets/icons/security.svg'));
206 | iconRegistry.addSvgIcon(
207 | 'radio_on',
208 | sanitizer.bypassSecurityTrustResourceUrl('assets/icons/radio_on.svg'));
209 | iconRegistry.addSvgIcon(
210 | 'radio_off',
211 | sanitizer.bypassSecurityTrustResourceUrl('assets/icons/radio_off.svg'));
212 | iconRegistry.addSvgIcon(
213 | 'salary',
214 | sanitizer.bypassSecurityTrustResourceUrl('assets/icons/salary.svg'));
215 | iconRegistry.addSvgIcon(
216 | 'drop_down',
217 | sanitizer.bypassSecurityTrustResourceUrl('assets/icons/drop_down.svg'));
218 | }
219 | }
220 |
--------------------------------------------------------------------------------
/src/app/product/attendance.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Submit
7 | GPS Attendance
8 |
9 | userId: {{ userData.email }}
10 | Dttm : {{ userData.dttm }}
11 | GPS :
12 |
13 |
14 | track_changes
15 |
16 |
17 |
18 |
19 | gps_off
20 | Please enable location services on your mobile device and restart/reload this app.
21 |
22 |
23 |
24 |
26 |
27 |
28 |
29 | clear
30 |
31 |
32 |
33 |
34 |
35 | Welcome!
36 | You are here!
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | gps_off
45 | Please enable location services on your mobile device and restart/reload this app.
46 |
47 |
48 |
49 |
50 | GPS Location Attendance is saved. For Picture Attendance and past results,
51 | visit History tab.
52 | Close
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | clear
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | clear
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 | clear
82 |
83 |
84 |
85 |
86 |
87 | Welcome!
88 | You are here!
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 | Dttm
99 |
100 | {{ row.createdAt.toDate() | date:'medium' }}
101 |
102 |
103 |
104 | email
105 | {{row.data.email}}
107 | track_changes
108 |
109 |
110 |
111 |
112 | Action
113 |
115 | delete
116 |
117 |
118 |
119 | Picture Attendance
120 |
121 |
122 |
123 | camera_roll
124 |
125 |
126 | photo_camera
127 |
128 |
129 |
130 |
131 | photo_camera
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
--------------------------------------------------------------------------------