created analysis tab
This commit is contained in:
23
package-lock.json
generated
23
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "WorkTime",
|
"name": "WorkTime",
|
||||||
"version": "0.0.1",
|
"version": "0.1.3",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "WorkTime",
|
"name": "WorkTime",
|
||||||
"version": "0.0.1",
|
"version": "0.1.3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "^18.0.0",
|
"@angular/animations": "^18.0.0",
|
||||||
"@angular/common": "^18.0.0",
|
"@angular/common": "^18.0.0",
|
||||||
@@ -23,6 +23,7 @@
|
|||||||
"@capacitor/keyboard": "6.0.2",
|
"@capacitor/keyboard": "6.0.2",
|
||||||
"@capacitor/status-bar": "6.0.1",
|
"@capacitor/status-bar": "6.0.1",
|
||||||
"@ionic/angular": "^8.0.0",
|
"@ionic/angular": "^8.0.0",
|
||||||
|
"chart.js": "^4.4.6",
|
||||||
"ionicons": "^7.2.1",
|
"ionicons": "^7.2.1",
|
||||||
"rxjs": "~7.8.0",
|
"rxjs": "~7.8.0",
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
@@ -4285,6 +4286,12 @@
|
|||||||
"tslib": "2"
|
"tslib": "2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@kurkle/color": {
|
||||||
|
"version": "0.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz",
|
||||||
|
"integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@leichtgewicht/ip-codec": {
|
"node_modules/@leichtgewicht/ip-codec": {
|
||||||
"version": "2.0.5",
|
"version": "2.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz",
|
||||||
@@ -6839,6 +6846,18 @@
|
|||||||
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
|
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/chart.js": {
|
||||||
|
"version": "4.4.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.6.tgz",
|
||||||
|
"integrity": "sha512-8Y406zevUPbbIBA/HRk33khEmQPk5+cxeflWE/2rx1NJsjVWMPw/9mSP9rxHP5eqi6LNoPBVMfZHxbwLSgldYA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@kurkle/color": "^0.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"pnpm": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/chokidar": {
|
"node_modules/chokidar": {
|
||||||
"version": "3.6.0",
|
"version": "3.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "WorkTime",
|
"name": "WorkTime",
|
||||||
"version": "0.1.3",
|
"version": "0.2.1",
|
||||||
"author": "Ionic Framework",
|
"author": "Ionic Framework",
|
||||||
"homepage": "https://ionicframework.com/",
|
"homepage": "https://ionicframework.com/",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -28,6 +28,7 @@
|
|||||||
"@capacitor/keyboard": "6.0.2",
|
"@capacitor/keyboard": "6.0.2",
|
||||||
"@capacitor/status-bar": "6.0.1",
|
"@capacitor/status-bar": "6.0.1",
|
||||||
"@ionic/angular": "^8.0.0",
|
"@ionic/angular": "^8.0.0",
|
||||||
|
"chart.js": "^4.4.6",
|
||||||
"ionicons": "^7.2.1",
|
"ionicons": "^7.2.1",
|
||||||
"rxjs": "~7.8.0",
|
"rxjs": "~7.8.0",
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<ion-header [translucent]="true">
|
<ion-header [translucent]="true">
|
||||||
<ion-toolbar>
|
<ion-toolbar>
|
||||||
<ion-title>
|
<ion-title>
|
||||||
Tab 2
|
Analyse
|
||||||
</ion-title>
|
</ion-title>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
@@ -9,9 +9,63 @@
|
|||||||
<ion-content [fullscreen]="true">
|
<ion-content [fullscreen]="true">
|
||||||
<ion-header collapse="condense">
|
<ion-header collapse="condense">
|
||||||
<ion-toolbar>
|
<ion-toolbar>
|
||||||
<ion-title size="large">Tab 2</ion-title>
|
<ion-title size="large">Analyse</ion-title>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
<app-explore-container name="Tab 2 page"></app-explore-container>
|
<ion-item>
|
||||||
|
<ion-label>Tag</ion-label>
|
||||||
|
<ion-datetime-button datetime="current-datetime"></ion-datetime-button>
|
||||||
|
|
||||||
|
<ion-modal [keepContentsMounted]="true">
|
||||||
|
<ng-template>
|
||||||
|
<ion-datetime id="current-datetime" presentation="date" [(ngModel)]="currentDate" (ionChange)="updateCurrentData()"></ion-datetime>
|
||||||
|
</ng-template>
|
||||||
|
</ion-modal>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-card>
|
||||||
|
<ion-card-header>
|
||||||
|
<ion-card-title>Noch zu arbeiten</ion-card-title>
|
||||||
|
<ion-card-subtitle *ngIf="combinedWorkTime > (maxWorkTime + desOverTime)">Tagessoll erreicht!</ion-card-subtitle>
|
||||||
|
</ion-card-header>
|
||||||
|
|
||||||
|
<ion-card-content>
|
||||||
|
<canvas #chart></canvas>
|
||||||
|
</ion-card-content>
|
||||||
|
</ion-card>
|
||||||
|
|
||||||
|
<ion-card>
|
||||||
|
<ion-card-header>
|
||||||
|
<ion-card-title>Auswertung</ion-card-title>
|
||||||
|
</ion-card-header>
|
||||||
|
|
||||||
|
<ion-card-content>
|
||||||
|
<ion-list>
|
||||||
|
<ion-item>
|
||||||
|
<ion-icon aria-hidden="true" name="briefcase" slot="start"></ion-icon>
|
||||||
|
<ion-label>
|
||||||
|
{{formatTime(workTime)}} von {{formatTime(maxWorkTime)}} <span *ngIf="driveTime > 0">(+{{formatTime(driveTime)}} Reisezeit)</span>
|
||||||
|
<ion-progress-bar class="work-progress" [value]="workTime / maxWorkTime" [buffer]="(workTime + driveTime) / maxWorkTime" />
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item>
|
||||||
|
<ion-icon aria-hidden="true" name="pizza" slot="start"></ion-icon>
|
||||||
|
<ion-label>
|
||||||
|
{{formatTime(pauseTime)}} von {{formatTime(maxPauseTime)}}
|
||||||
|
<ion-progress-bar *ngIf="pauseTime <= maxPauseTime" [value]="pauseTime / maxPauseTime" />
|
||||||
|
<ion-progress-bar *ngIf="pauseTime > maxPauseTime" value="1" color="warning" />
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item>
|
||||||
|
<ion-icon aria-hidden="true" name="card" slot="start"></ion-icon>
|
||||||
|
<ion-label>
|
||||||
|
{{formatTime(Math.max(combinedWorkTime - maxWorkTime, 0))}} von {{formatTime(desOverTime)}} ({{formatTime(maxOverTime)}} maximal)
|
||||||
|
<ion-progress-bar *ngIf="combinedWorkTime - maxWorkTime <= maxOverTime" class="work-progress" id="overtime" [value]="(combinedWorkTime - maxWorkTime) / maxOverTime" [buffer]="desOverTime / maxOverTime" />
|
||||||
|
<ion-progress-bar *ngIf="combinedWorkTime - maxWorkTime > maxOverTime" value="1" color="danger" />
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
</ion-list>
|
||||||
|
</ion-card-content>
|
||||||
|
</ion-card>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
.work-progress {
|
||||||
|
&::part(stream) {
|
||||||
|
animation: none;
|
||||||
|
-webkit-animation: none;
|
||||||
|
background-image: radial-gradient(ellipse at center, var(--background) 0%, var(--background) 30%, var(--background) 30%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::part(track) {
|
||||||
|
background-color: var(--ion-color-warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#overtime::part(track) {
|
||||||
|
background-color: var(--ion-color-tertiary-shade);
|
||||||
|
}
|
||||||
|
|
||||||
|
ion-card-content ion-item {
|
||||||
|
--background: unset;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,16 +1,194 @@
|
|||||||
import { Component } from '@angular/core';
|
import {Component, ElementRef, ViewChild} from '@angular/core';
|
||||||
import { IonHeader, IonToolbar, IonTitle, IonContent } from '@ionic/angular/standalone';
|
import {
|
||||||
|
IonHeader,
|
||||||
|
IonToolbar,
|
||||||
|
IonTitle,
|
||||||
|
IonContent,
|
||||||
|
IonItem,
|
||||||
|
IonLabel,
|
||||||
|
IonDatetimeButton,
|
||||||
|
IonModal,
|
||||||
|
IonDatetime,
|
||||||
|
IonCard,
|
||||||
|
IonCardHeader,
|
||||||
|
IonCardTitle,
|
||||||
|
IonCardContent,
|
||||||
|
IonCardSubtitle, IonList, IonIcon, IonProgressBar
|
||||||
|
} from '@ionic/angular/standalone';
|
||||||
import { ExploreContainerComponent } from '../explore-container/explore-container.component';
|
import { ExploreContainerComponent } from '../explore-container/explore-container.component';
|
||||||
|
import {FormsModule} from "@angular/forms";
|
||||||
|
import {TimeEntry} from "../../models/timeEntry";
|
||||||
|
import {TimeService} from "../../services/time.service";
|
||||||
|
import {Chart} from "chart.js/auto";
|
||||||
|
import {addIcons} from "ionicons";
|
||||||
|
import {briefcase, card, pizza} from "ionicons/icons";
|
||||||
|
import {NgIf} from "@angular/common";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-tab2',
|
selector: 'app-tab2',
|
||||||
templateUrl: 'analysis.page.html',
|
templateUrl: 'analysis.page.html',
|
||||||
styleUrls: ['analysis.page.scss'],
|
styleUrls: ['analysis.page.scss'],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [IonHeader, IonToolbar, IonTitle, IonContent, ExploreContainerComponent]
|
imports: [IonHeader, IonToolbar, IonTitle, IonContent, ExploreContainerComponent, IonItem, IonLabel, IonDatetimeButton, IonModal, IonDatetime, FormsModule, IonCard, IonCardHeader, IonCardTitle, IonCardContent, IonCardSubtitle, IonList, IonIcon, IonProgressBar, NgIf]
|
||||||
})
|
})
|
||||||
export class AnalysisPage {
|
export class AnalysisPage {
|
||||||
|
public currentDate: any;
|
||||||
|
public timeData: TimeEntry[] = [];
|
||||||
|
|
||||||
constructor() {}
|
public workTime: number = 0;
|
||||||
|
public pauseTime: number = 0;
|
||||||
|
public driveTime: number = 0;
|
||||||
|
public combinedWorkTime: number = 0;
|
||||||
|
|
||||||
|
public maxWorkTime: number = 420;
|
||||||
|
public maxPauseTime: number = 30;
|
||||||
|
public maxOverTime: number = 60;
|
||||||
|
public desOverTime: number = 30;
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
@ViewChild('chart') chartRef: ElementRef;
|
||||||
|
private chart: any;
|
||||||
|
|
||||||
|
constructor(private time: TimeService) {
|
||||||
|
addIcons({briefcase, pizza, card})
|
||||||
|
}
|
||||||
|
|
||||||
|
ionViewDidEnter() {
|
||||||
|
this.updateCurrentData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public updateCurrentData() {
|
||||||
|
this.timeData = this.time.getEntries(this.currentDate);
|
||||||
|
this.workTime = 0;
|
||||||
|
this.pauseTime = 0;
|
||||||
|
this.driveTime = 0;
|
||||||
|
this.combinedWorkTime = 0;
|
||||||
|
|
||||||
|
if (this.timeData.length < 2) {
|
||||||
|
this.showEmptyChart();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 1; i < this.timeData.length; i++) {
|
||||||
|
const start = this.timeData[i - 1];
|
||||||
|
const end = this.timeData[i];
|
||||||
|
const diff = this.time.calculateTimespanInMinutes(start, end);
|
||||||
|
|
||||||
|
if (start.type == 'start-drive' && end.type == 'end-drive') {
|
||||||
|
this.driveTime += diff;
|
||||||
|
}
|
||||||
|
else if (start.type === 'login') {
|
||||||
|
this.workTime += diff;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.pauseTime += diff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.combinedWorkTime = this.workTime + this.driveTime;
|
||||||
|
|
||||||
|
if (this.combinedWorkTime < 360) {
|
||||||
|
this.maxPauseTime = 0;
|
||||||
|
}
|
||||||
|
if (this.combinedWorkTime >= 360) { // 6h
|
||||||
|
this.maxPauseTime = 30;
|
||||||
|
}
|
||||||
|
if (this.combinedWorkTime >= 540) { // 9h
|
||||||
|
this.maxPauseTime = 45;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateChart();
|
||||||
|
}
|
||||||
|
|
||||||
|
public updateChart() {
|
||||||
|
const style = getComputedStyle(document.body);
|
||||||
|
const textColor = style.getPropertyValue('--ion-text-color');
|
||||||
|
const workColor = style.getPropertyValue('--ion-color-primary');
|
||||||
|
const driveColor = style.getPropertyValue('--ion-color-secondary');
|
||||||
|
const remainColor = style.getPropertyValue('--ion-card-background');
|
||||||
|
const overColor = style.getPropertyValue('--ion-color-tertiary');
|
||||||
|
const overColorWarn = style.getPropertyValue('--ion-color-warning');
|
||||||
|
|
||||||
|
let overData: number[] = [];
|
||||||
|
let overLabels: string[] = [];
|
||||||
|
let overColors: string[] = [];
|
||||||
|
|
||||||
|
if (this.combinedWorkTime > this.maxWorkTime) {
|
||||||
|
const overTime = this.combinedWorkTime - this.maxWorkTime;
|
||||||
|
const overPercentage = overTime / this.desOverTime;
|
||||||
|
|
||||||
|
overData.push(this.combinedWorkTime * overPercentage);
|
||||||
|
overLabels.push('Überstunden');
|
||||||
|
overColors.push(overPercentage > 1 ? overColorWarn : overColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.chart?.destroy();
|
||||||
|
Chart.defaults.color = textColor;
|
||||||
|
this.chart = new Chart(this.chartRef.nativeElement, {
|
||||||
|
type: 'doughnut',
|
||||||
|
data: {
|
||||||
|
labels: [
|
||||||
|
...overLabels,
|
||||||
|
'Arbeitszeit',
|
||||||
|
'Dienstreise'
|
||||||
|
],
|
||||||
|
datasets: [{
|
||||||
|
label: 'Zeit',
|
||||||
|
data: [...overData, this.workTime, this.driveTime, Math.max(this.maxWorkTime - this.combinedWorkTime, 0)],
|
||||||
|
backgroundColor: [
|
||||||
|
...overColors,
|
||||||
|
workColor,
|
||||||
|
driveColor,
|
||||||
|
remainColor
|
||||||
|
],
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
events: [],
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
display: this.driveTime > 0 || this.combinedWorkTime > this.maxWorkTime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public showEmptyChart() {
|
||||||
|
const style = getComputedStyle(document.body);
|
||||||
|
const remainColor = style.getPropertyValue('--ion-card-background');
|
||||||
|
|
||||||
|
this.chart?.destroy();
|
||||||
|
this.chart = new Chart(this.chartRef.nativeElement, {
|
||||||
|
type: 'doughnut',
|
||||||
|
data: {
|
||||||
|
labels: [],
|
||||||
|
datasets: [{
|
||||||
|
label: 'Zeit',
|
||||||
|
data: [100],
|
||||||
|
backgroundColor: [
|
||||||
|
remainColor
|
||||||
|
],
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
events: [],
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
display: this.driveTime > 0 || this.combinedWorkTime > this.maxWorkTime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public formatTime(time: number): string {
|
||||||
|
const hours = Math.floor(time / 60);
|
||||||
|
const minutes = time % 60;
|
||||||
|
|
||||||
|
let result = hours < 10 ? "0" + hours + ":" : hours.toString() + ":";
|
||||||
|
result += minutes < 10 ? "0" + minutes : minutes.toString();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected readonly Math = Math;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<ion-label>Erfassen</ion-label>
|
<ion-label>Erfassen</ion-label>
|
||||||
</ion-tab-button>
|
</ion-tab-button>
|
||||||
|
|
||||||
<ion-tab-button tab="analysis" href="/analysis" disabled>
|
<ion-tab-button tab="analysis" href="/analysis">
|
||||||
<ion-icon aria-hidden="true" name="pie-chart"></ion-icon>
|
<ion-icon aria-hidden="true" name="pie-chart"></ion-icon>
|
||||||
<ion-label>Analyse</ion-label>
|
<ion-label>Analyse</ion-label>
|
||||||
</ion-tab-button>
|
</ion-tab-button>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {NgClass, NgForOf, NgIf} from "@angular/common";
|
|||||||
import {addIcons} from "ionicons";
|
import {addIcons} from "ionicons";
|
||||||
import {add} from "ionicons/icons";
|
import {add} from "ionicons/icons";
|
||||||
import {FormsModule} from "@angular/forms";
|
import {FormsModule} from "@angular/forms";
|
||||||
|
import {TimeService} from "../../services/time.service";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-tab1',
|
selector: 'app-tab1',
|
||||||
@@ -33,22 +34,15 @@ export class TimePage {
|
|||||||
public modalDate: any;
|
public modalDate: any;
|
||||||
public currentDate: any;
|
public currentDate: any;
|
||||||
|
|
||||||
constructor() {
|
constructor(private timeService: TimeService) {
|
||||||
const savedData = localStorage.getItem("time-data");
|
this.data = timeService.loadEntries();
|
||||||
|
|
||||||
if (savedData != null) {
|
for (let i = 0; i < this.data.length; i++) {
|
||||||
this.data = JSON.parse(savedData as string);
|
this.shouldAnimate.push(false);
|
||||||
|
|
||||||
for (let entry of this.data) {
|
|
||||||
entry.registeredAt = new Date(entry.registeredAt);
|
|
||||||
entry.registeredAt.toLocaleTimeString();
|
|
||||||
|
|
||||||
this.shouldAnimate.push(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateCurrentAction();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.updateCurrentAction();
|
||||||
|
|
||||||
addIcons({add});
|
addIcons({add});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,18 +74,15 @@ export class TimePage {
|
|||||||
text = "Dienstreise ";
|
text = "Dienstreise ";
|
||||||
}
|
}
|
||||||
|
|
||||||
return text + `(${this.calculateTimespan(entry1.registeredAt, entry2.registeredAt)})`;
|
return text + `(${this.calculateTimespan(entry1, entry2)})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private calculateTimespan(start: Date, end: Date): string {
|
private calculateTimespan(start: TimeEntry, end: TimeEntry): string {
|
||||||
const startSeconds: number = (start.getHours() * 3600) + (start.getMinutes() * 60) + start.getSeconds();
|
const time = this.timeService.calculateTimespanInMinutes(start, end);
|
||||||
const endSeconds: number = (end.getHours() * 3600) + (end.getMinutes() * 60) + end.getSeconds();
|
const hours = Math.floor(time / 60);
|
||||||
|
const minutes = time % 60;
|
||||||
|
|
||||||
const difference = endSeconds - startSeconds;
|
return this.formatEntry(hours, minutes);
|
||||||
const diffHours = Math.floor(difference / 3600.00);
|
|
||||||
const diffMinutes = Math.floor((difference % 3600) / 60.00);
|
|
||||||
|
|
||||||
return this.formatEntry(diffHours, diffMinutes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public formatEntry(hours: number, minutes: number): string {
|
public formatEntry(hours: number, minutes: number): string {
|
||||||
@@ -165,7 +156,7 @@ export class TimePage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private saveData(): void {
|
private saveData(): void {
|
||||||
localStorage.setItem("time-data", JSON.stringify(this.data));
|
this.timeService.saveEntries(this.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public openModal(): void {
|
public openModal(): void {
|
||||||
|
|||||||
46
src/services/time.service.ts
Normal file
46
src/services/time.service.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import {TimeEntry} from "../models/timeEntry";
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class TimeService {
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
public calculateTimespanInMinutes(start: TimeEntry, end: TimeEntry): number {
|
||||||
|
const startSeconds: number = (start.registeredAt.getHours() * 3600) + (start.registeredAt.getMinutes() * 60) + start.registeredAt.getSeconds();
|
||||||
|
const endSeconds: number = (end.registeredAt.getHours() * 3600) + (end.registeredAt.getMinutes() * 60) + end.registeredAt.getSeconds();
|
||||||
|
|
||||||
|
const difference = endSeconds - startSeconds;
|
||||||
|
const diffHours = Math.floor(difference / 3600.00);
|
||||||
|
const diffMinutes = Math.floor((difference % 3600) / 60.00);
|
||||||
|
|
||||||
|
return diffMinutes + (diffHours * 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getEntries(day: any): TimeEntry[] {
|
||||||
|
const today = new Date(day || Date.now()).toLocaleDateString();
|
||||||
|
return this.loadEntries().filter(entry => entry.registeredAt.toLocaleDateString() === today);
|
||||||
|
}
|
||||||
|
|
||||||
|
public loadEntries(): TimeEntry[] {
|
||||||
|
const savedData = localStorage.getItem("time-data");
|
||||||
|
|
||||||
|
if (savedData != null) {
|
||||||
|
const data = JSON.parse(savedData as string) as TimeEntry[];
|
||||||
|
|
||||||
|
for (let entry of data) {
|
||||||
|
entry.registeredAt = new Date(entry.registeredAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public saveEntries(entries: TimeEntry[]): void {
|
||||||
|
localStorage.setItem("time-data", JSON.stringify(entries));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user