finished time recording
This commit is contained in:
@@ -126,7 +126,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"cli": {
|
"cli": {
|
||||||
"schematicCollections": ["@ionic/angular-toolkit"]
|
"schematicCollections": [
|
||||||
|
"@ionic/angular-toolkit"
|
||||||
|
],
|
||||||
|
"analytics": false
|
||||||
},
|
},
|
||||||
"schematics": {
|
"schematics": {
|
||||||
"@ionic/angular-toolkit:component": {
|
"@ionic/angular-toolkit:component": {
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ import { ExploreContainerComponent } from '../explore-container/explore-containe
|
|||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-tab2',
|
selector: 'app-tab2',
|
||||||
templateUrl: 'tab2.page.html',
|
templateUrl: 'analysis.page.html',
|
||||||
styleUrls: ['tab2.page.scss'],
|
styleUrls: ['analysis.page.scss'],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [IonHeader, IonToolbar, IonTitle, IonContent, ExploreContainerComponent]
|
imports: [IonHeader, IonToolbar, IonTitle, IonContent, ExploreContainerComponent]
|
||||||
})
|
})
|
||||||
export class Tab2Page {
|
export class AnalysisPage {
|
||||||
|
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import { TestBed } from '@angular/core/testing';
|
|
||||||
import { provideRouter } from '@angular/router';
|
|
||||||
import { AppComponent } from './app.component';
|
|
||||||
|
|
||||||
describe('AppComponent', () => {
|
|
||||||
it('should create the app', async () => {
|
|
||||||
await TestBed.configureTestingModule({
|
|
||||||
imports: [AppComponent],
|
|
||||||
providers: [provideRouter([])]
|
|
||||||
}).compileComponents();
|
|
||||||
|
|
||||||
const fixture = TestBed.createComponent(AppComponent);
|
|
||||||
const app = fixture.componentInstance;
|
|
||||||
expect(app).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -4,11 +4,11 @@ import { ExploreContainerComponent } from '../explore-container/explore-containe
|
|||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-tab3',
|
selector: 'app-tab3',
|
||||||
templateUrl: 'tab3.page.html',
|
templateUrl: 'settings.page.html',
|
||||||
styleUrls: ['tab3.page.scss'],
|
styleUrls: ['settings.page.scss'],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [IonHeader, IonToolbar, IonTitle, IonContent, ExploreContainerComponent],
|
imports: [IonHeader, IonToolbar, IonTitle, IonContent, ExploreContainerComponent],
|
||||||
})
|
})
|
||||||
export class Tab3Page {
|
export class SettingsPage {
|
||||||
constructor() {}
|
constructor() {}
|
||||||
}
|
}
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
<ion-header [translucent]="true">
|
|
||||||
<ion-toolbar>
|
|
||||||
<ion-title>
|
|
||||||
Tab 1
|
|
||||||
</ion-title>
|
|
||||||
</ion-toolbar>
|
|
||||||
</ion-header>
|
|
||||||
|
|
||||||
<ion-content [fullscreen]="true">
|
|
||||||
<ion-header collapse="condense">
|
|
||||||
<ion-toolbar>
|
|
||||||
<ion-title size="large">Tab 1</ion-title>
|
|
||||||
</ion-toolbar>
|
|
||||||
</ion-header>
|
|
||||||
|
|
||||||
<app-explore-container name="Tab 1 page"></app-explore-container>
|
|
||||||
</ion-content>
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { Tab1Page } from './tab1.page';
|
|
||||||
|
|
||||||
describe('Tab1Page', () => {
|
|
||||||
let component: Tab1Page;
|
|
||||||
let fixture: ComponentFixture<Tab1Page>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
fixture = TestBed.createComponent(Tab1Page);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
import { Component } from '@angular/core';
|
|
||||||
import { IonHeader, IonToolbar, IonTitle, IonContent } from '@ionic/angular/standalone';
|
|
||||||
import { ExploreContainerComponent } from '../explore-container/explore-container.component';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-tab1',
|
|
||||||
templateUrl: 'tab1.page.html',
|
|
||||||
styleUrls: ['tab1.page.scss'],
|
|
||||||
standalone: true,
|
|
||||||
imports: [IonHeader, IonToolbar, IonTitle, IonContent, ExploreContainerComponent],
|
|
||||||
})
|
|
||||||
export class Tab1Page {
|
|
||||||
constructor() {}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { Tab2Page } from './tab2.page';
|
|
||||||
|
|
||||||
describe('Tab2Page', () => {
|
|
||||||
let component: Tab2Page;
|
|
||||||
let fixture: ComponentFixture<Tab2Page>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
fixture = TestBed.createComponent(Tab2Page);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { Tab3Page } from './tab3.page';
|
|
||||||
|
|
||||||
describe('Tab3Page', () => {
|
|
||||||
let component: Tab3Page;
|
|
||||||
let fixture: ComponentFixture<Tab3Page>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
fixture = TestBed.createComponent(Tab3Page);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,18 +1,18 @@
|
|||||||
<ion-tabs>
|
<ion-tabs>
|
||||||
<ion-tab-bar slot="bottom">
|
<ion-tab-bar slot="bottom">
|
||||||
<ion-tab-button tab="tab1" href="/tabs/tab1">
|
<ion-tab-button tab="time" href="/time">
|
||||||
<ion-icon aria-hidden="true" name="triangle"></ion-icon>
|
<ion-icon aria-hidden="true" name="time"></ion-icon>
|
||||||
<ion-label>Tab 1</ion-label>
|
<ion-label>Erfassen</ion-label>
|
||||||
</ion-tab-button>
|
</ion-tab-button>
|
||||||
|
|
||||||
<ion-tab-button tab="tab2" href="/tabs/tab2">
|
<ion-tab-button tab="analysis" href="/analysis">
|
||||||
<ion-icon aria-hidden="true" name="ellipse"></ion-icon>
|
<ion-icon aria-hidden="true" name="pie-chart"></ion-icon>
|
||||||
<ion-label>Tab 2</ion-label>
|
<ion-label>Analyse</ion-label>
|
||||||
</ion-tab-button>
|
</ion-tab-button>
|
||||||
|
|
||||||
<ion-tab-button tab="tab3" href="/tabs/tab3">
|
<ion-tab-button tab="settings" href="/settings">
|
||||||
<ion-icon aria-hidden="true" name="square"></ion-icon>
|
<ion-icon aria-hidden="true" name="settings"></ion-icon>
|
||||||
<ion-label>Tab 3</ion-label>
|
<ion-label>Einstellungen</ion-label>
|
||||||
</ion-tab-button>
|
</ion-tab-button>
|
||||||
</ion-tab-bar>
|
</ion-tab-bar>
|
||||||
</ion-tabs>
|
</ion-tabs>
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
import { provideRouter } from '@angular/router';
|
|
||||||
|
|
||||||
import { TabsPage } from './tabs.page';
|
|
||||||
|
|
||||||
describe('TabsPage', () => {
|
|
||||||
let component: TabsPage;
|
|
||||||
let fixture: ComponentFixture<TabsPage>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await TestBed.configureTestingModule({
|
|
||||||
imports: [TabsPage],
|
|
||||||
providers: [provideRouter([])]
|
|
||||||
}).compileComponents();
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
fixture = TestBed.createComponent(TabsPage);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Component, EnvironmentInjector, inject } from '@angular/core';
|
import { Component, EnvironmentInjector, inject } from '@angular/core';
|
||||||
import { IonTabs, IonTabBar, IonTabButton, IonIcon, IonLabel } from '@ionic/angular/standalone';
|
import { IonTabs, IonTabBar, IonTabButton, IonIcon, IonLabel } from '@ionic/angular/standalone';
|
||||||
import { addIcons } from 'ionicons';
|
import { addIcons } from 'ionicons';
|
||||||
import { triangle, ellipse, square } from 'ionicons/icons';
|
import { time, pieChart, settings } from 'ionicons/icons';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-tabs',
|
selector: 'app-tabs',
|
||||||
@@ -14,6 +14,6 @@ export class TabsPage {
|
|||||||
public environmentInjector = inject(EnvironmentInjector);
|
public environmentInjector = inject(EnvironmentInjector);
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
addIcons({ triangle, ellipse, square });
|
addIcons({ time, pieChart, settings });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,19 +7,19 @@ export const routes: Routes = [
|
|||||||
component: TabsPage,
|
component: TabsPage,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: 'tab1',
|
path: 'time',
|
||||||
loadComponent: () =>
|
loadComponent: () =>
|
||||||
import('../tab1/tab1.page').then((m) => m.Tab1Page),
|
import('../time/time.page').then((m) => m.TimePage),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'tab2',
|
path: 'analysis',
|
||||||
loadComponent: () =>
|
loadComponent: () =>
|
||||||
import('../tab2/tab2.page').then((m) => m.Tab2Page),
|
import('../analysis/analysis.page').then((m) => m.AnalysisPage),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'tab3',
|
path: 'settings',
|
||||||
loadComponent: () =>
|
loadComponent: () =>
|
||||||
import('../tab3/tab3.page').then((m) => m.Tab3Page),
|
import('../settings/settings.page').then((m) => m.SettingsPage),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
@@ -30,7 +30,7 @@ export const routes: Routes = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
redirectTo: '/tabs/tab1',
|
redirectTo: '/tabs/time',
|
||||||
pathMatch: 'full',
|
pathMatch: 'full',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
78
src/app/time/time.page.html
Normal file
78
src/app/time/time.page.html
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
<ion-header [translucent]="true">
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>
|
||||||
|
Zeiterfassung
|
||||||
|
</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-content [fullscreen]="true" [scrollY]="false">
|
||||||
|
<ion-header collapse="condense">
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title size="large">Zeiterfassung</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<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"></ion-datetime>
|
||||||
|
</ng-template>
|
||||||
|
</ion-modal>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<section class="time-entries">
|
||||||
|
<ion-item class="entry" *ngFor="let entry of getEntriesOfToday(); let index = index" (click)="removeEntry(index)" [ngClass]="{'animate': shouldAnimate[index]}">
|
||||||
|
<div class="circle"></div>
|
||||||
|
<ion-label class="type">{{entry.type === 'login' ? "Eingestempelt" : "Ausgestempelt"}}</ion-label>
|
||||||
|
<span class="time">{{entry.registeredAt.toLocaleTimeString()}}</span>
|
||||||
|
<span *ngIf="index !== 0" class="between">
|
||||||
|
<span class="between-content">{{generateSeparatorText(data[index - 1], entry)}}</span>
|
||||||
|
</span>
|
||||||
|
</ion-item>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="button-container">
|
||||||
|
<ion-button (click)="addEntry()">{{currentAction === 'login' ? "Einstempeln" : "Ausstempeln"}}</ion-button>
|
||||||
|
<ion-button shape="round" id="open-modal">
|
||||||
|
<ion-icon slot="icon-only" name="add"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ion-modal trigger="open-modal" (willDismiss)="modal?.dismiss(null, 'cancel')" #createModal>
|
||||||
|
<ng-template>
|
||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-buttons slot="start">
|
||||||
|
<ion-button (click)="modal?.dismiss(null, 'cancel')">Cancel</ion-button>
|
||||||
|
</ion-buttons>
|
||||||
|
<ion-title>Welcome</ion-title>
|
||||||
|
<ion-buttons slot="end">
|
||||||
|
<ion-button [strong]="true" (click)="addModalEntry()">Confirm</ion-button>
|
||||||
|
</ion-buttons>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
<ion-content class="ion-padding">
|
||||||
|
<ion-item>
|
||||||
|
<ion-label>Uhrzeit</ion-label>
|
||||||
|
<ion-datetime-button datetime="datetime"></ion-datetime-button>
|
||||||
|
|
||||||
|
<ion-modal [keepContentsMounted]="true">
|
||||||
|
<ng-template>
|
||||||
|
<ion-datetime id="datetime" presentation="time" [(ngModel)]="modalDate"></ion-datetime>
|
||||||
|
</ng-template>
|
||||||
|
</ion-modal>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item>
|
||||||
|
<ion-select label="Stempeltyp" value="login" [(ngModel)]="modalMode">
|
||||||
|
<ion-select-option value="login">Einstempeln</ion-select-option>
|
||||||
|
<ion-select-option value="logout">Ausstempeln</ion-select-option>
|
||||||
|
</ion-select>
|
||||||
|
</ion-item>
|
||||||
|
</ion-content>
|
||||||
|
</ng-template>
|
||||||
|
</ion-modal>
|
||||||
|
</ion-content>
|
||||||
121
src/app/time/time.page.scss
Normal file
121
src/app/time/time.page.scss
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
.button-container {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 75px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-entries {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 65px;
|
||||||
|
margin: 20px;
|
||||||
|
|
||||||
|
.entry {
|
||||||
|
--inner-border-width: 0 0 0 0;
|
||||||
|
position: relative;
|
||||||
|
overflow: visible;
|
||||||
|
line-height: 15px;
|
||||||
|
z-index: 0;
|
||||||
|
|
||||||
|
&.animate {
|
||||||
|
opacity: 0;
|
||||||
|
animation: fade-in 200ms ease-in-out forwards;
|
||||||
|
|
||||||
|
.between::before {
|
||||||
|
transform: scaleX(0);
|
||||||
|
animation: line-in-horizontal 200ms ease-in-out 1000ms forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.between-content {
|
||||||
|
opacity: 0;
|
||||||
|
animation: fade-in 500ms ease-in-out 1200ms forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle::after {
|
||||||
|
transform: scaleY(0);
|
||||||
|
animation: line-in 500ms ease-in-out 500ms forwards;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.between {
|
||||||
|
position: absolute;
|
||||||
|
overflow: visible;
|
||||||
|
left: 45px;
|
||||||
|
bottom: calc(100% + 25px);
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
height: 2px;
|
||||||
|
width: 20px;
|
||||||
|
background-color: var(--color);
|
||||||
|
top: calc(50% - 1px);
|
||||||
|
left: -37px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.type {
|
||||||
|
padding-left: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle {
|
||||||
|
background-color: var(--color);
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 15px;
|
||||||
|
height: 15px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
background-color: var(--color);
|
||||||
|
width: 2px;
|
||||||
|
height: 100px;
|
||||||
|
bottom: 100%;
|
||||||
|
left: calc(50% - 1px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:first-of-type .circle::after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fade-in {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes line-in {
|
||||||
|
0% {
|
||||||
|
transform-origin: top;
|
||||||
|
transform: scaleY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform-origin: top;
|
||||||
|
transform: scaleY(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes line-in-horizontal {
|
||||||
|
0% {
|
||||||
|
transform-origin: left;
|
||||||
|
transform: scaleX(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform-origin: left;
|
||||||
|
transform: scaleX(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
118
src/app/time/time.page.ts
Normal file
118
src/app/time/time.page.ts
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
import {Component, ViewChild} from '@angular/core';
|
||||||
|
import {
|
||||||
|
IonHeader,
|
||||||
|
IonToolbar,
|
||||||
|
IonTitle,
|
||||||
|
IonContent,
|
||||||
|
IonButton,
|
||||||
|
IonList,
|
||||||
|
IonItem,
|
||||||
|
IonLabel, IonIcon, IonModal, IonButtons, IonInput, IonDatetime, IonDatetimeButton, IonSelect, IonSelectOption
|
||||||
|
} from '@ionic/angular/standalone';
|
||||||
|
import { ExploreContainerComponent } from '../explore-container/explore-container.component';
|
||||||
|
import {TimeEntry, TimeType} from "../../models/timeEntry";
|
||||||
|
import {NgClass, NgForOf, NgIf} from "@angular/common";
|
||||||
|
import {addIcons} from "ionicons";
|
||||||
|
import {add} from "ionicons/icons";
|
||||||
|
import {FormsModule} from "@angular/forms";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-tab1',
|
||||||
|
templateUrl: 'time.page.html',
|
||||||
|
styleUrls: ['time.page.scss'],
|
||||||
|
standalone: true,
|
||||||
|
imports: [IonHeader, IonToolbar, IonTitle, IonContent, ExploreContainerComponent, NgForOf, IonButton, IonList, IonItem, IonLabel, NgIf, NgClass, IonIcon, IonModal, IonButtons, IonInput, IonDatetime, IonDatetimeButton, IonSelect, IonSelectOption, FormsModule],
|
||||||
|
})
|
||||||
|
export class TimePage {
|
||||||
|
public data: TimeEntry[] = [];
|
||||||
|
public shouldAnimate: boolean[] = [];
|
||||||
|
public currentAction: TimeType = 'login';
|
||||||
|
@ViewChild('createModal') modal: IonModal | undefined;
|
||||||
|
|
||||||
|
public modalDate: any;
|
||||||
|
public modalMode: TimeType = 'login';
|
||||||
|
public currentDate: any;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
const savedData = localStorage.getItem("time-data");
|
||||||
|
|
||||||
|
if (savedData != null) {
|
||||||
|
this.data = JSON.parse(savedData as string);
|
||||||
|
|
||||||
|
for (let entry of this.data) {
|
||||||
|
entry.registeredAt = new Date(entry.registeredAt);
|
||||||
|
entry.registeredAt.toLocaleTimeString();
|
||||||
|
|
||||||
|
this.shouldAnimate.push(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateCurrentAction();
|
||||||
|
}
|
||||||
|
|
||||||
|
addIcons({add});
|
||||||
|
}
|
||||||
|
|
||||||
|
public getEntriesOfToday(): TimeEntry[] {
|
||||||
|
const today = new Date(this.currentDate || Date.now()).getDay();
|
||||||
|
return this.data.filter(entry => entry.registeredAt.getDay() === today);
|
||||||
|
}
|
||||||
|
|
||||||
|
public generateSeparatorText(entry1: TimeEntry, entry2: TimeEntry): string {
|
||||||
|
const difference = +entry2.registeredAt.getTime() - +entry1.registeredAt.getTime() - 3600000;
|
||||||
|
const date = new Date(difference);
|
||||||
|
const text = entry1.type === 'login' ? "Arbeit " : "Pause ";
|
||||||
|
return text + `(${date.toLocaleTimeString()})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
public addEntry(): void {
|
||||||
|
this.shouldAnimate.push(true)
|
||||||
|
setTimeout(() => this.shouldAnimate[this.shouldAnimate.length - 1] = false, 5000);
|
||||||
|
|
||||||
|
this.data.push({
|
||||||
|
registeredAt: new Date(Date.now()),
|
||||||
|
type: this.currentAction
|
||||||
|
});
|
||||||
|
this.saveData();
|
||||||
|
this.currentAction = this.currentAction === 'login' ? 'logout' : 'login';
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeEntry(index: number): void {
|
||||||
|
this.shouldAnimate.splice(index, 1);
|
||||||
|
this.data.splice(index, 1);
|
||||||
|
this.saveData();
|
||||||
|
this.updateCurrentAction();
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateCurrentAction(): void {
|
||||||
|
if (this.data.length == 0) {
|
||||||
|
this.currentAction = 'login';
|
||||||
|
}else {
|
||||||
|
this.currentAction = this.data[this.data.length - 1].type === 'login' ? 'logout' : 'login';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private saveData(): void {
|
||||||
|
localStorage.setItem("time-data", JSON.stringify(this.data));
|
||||||
|
}
|
||||||
|
|
||||||
|
public addModalEntry(): void {
|
||||||
|
const date = new Date(this.modalDate);
|
||||||
|
date.setSeconds(0);
|
||||||
|
|
||||||
|
this.data.push({
|
||||||
|
registeredAt: date,
|
||||||
|
type: this.modalMode
|
||||||
|
});
|
||||||
|
this.data.sort((a: TimeEntry, b: TimeEntry) => {
|
||||||
|
return a.registeredAt.getTime() - b.registeredAt.getTime();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.shouldAnimate = [];
|
||||||
|
for (let i = 0; i < this.data.length; i++) {
|
||||||
|
this.shouldAnimate.push(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.saveData();
|
||||||
|
this.modal?.dismiss(null, 'submit');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1 +0,0 @@
|
|||||||
<svg width="350" height="140" xmlns="http://www.w3.org/2000/svg" style="background:#f6f7f9"><g fill="none" fill-rule="evenodd"><path fill="#F04141" style="mix-blend-mode:multiply" d="M61.905-34.23l96.194 54.51-66.982 54.512L22 34.887z"/><circle fill="#10DC60" style="mix-blend-mode:multiply" cx="155.5" cy="135.5" r="57.5"/><path fill="#3880FF" style="mix-blend-mode:multiply" d="M208.538 9.513l84.417 15.392L223.93 93.93z"/><path fill="#FFCE00" style="mix-blend-mode:multiply" d="M268.625 106.557l46.332-26.75 46.332 26.75v53.5l-46.332 26.75-46.332-26.75z"/><circle fill="#7044FF" style="mix-blend-mode:multiply" cx="299.5" cy="9.5" r="38.5"/><rect fill="#11D3EA" style="mix-blend-mode:multiply" transform="rotate(-60 148.47 37.886)" x="143.372" y="-7.056" width="10.196" height="89.884" rx="5.098"/><path d="M-25.389 74.253l84.86 8.107c5.498.525 9.53 5.407 9.004 10.905a10 10 0 0 1-.057.477l-12.36 85.671a10.002 10.002 0 0 1-11.634 8.42l-86.351-15.226c-5.44-.959-9.07-6.145-8.112-11.584l13.851-78.551a10 10 0 0 1 10.799-8.219z" fill="#7044FF" style="mix-blend-mode:multiply"/><circle fill="#0CD1E8" style="mix-blend-mode:multiply" cx="273.5" cy="106.5" r="20.5"/></g></svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<title>Ionic App</title>
|
<title>Zeiterfassung</title>
|
||||||
|
|
||||||
<base href="/" />
|
<base href="/" />
|
||||||
|
|
||||||
|
|||||||
6
src/models/timeEntry.ts
Normal file
6
src/models/timeEntry.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export interface TimeEntry {
|
||||||
|
registeredAt: Date;
|
||||||
|
type: TimeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TimeType = 'login' | 'logout';
|
||||||
Reference in New Issue
Block a user