+
+
+ Zeiterfassung
+
+
+
+
+ Tag
+
+
+
+
+
+
+
+
+
+
+
+
+ {{entry.type === 'login' ? "Eingestempelt" : "Ausgestempelt"}}
+ {{entry.registeredAt.toLocaleTimeString()}}
+
+ {{generateSeparatorText(data[index - 1], entry)}}
+
+
+
+
+
+ {{currentAction === 'login' ? "Einstempeln" : "Ausstempeln"}}
+
+
+
+
+
+
+
+
+
+
+ Cancel
+
+ Welcome
+
+ Confirm
+
+
+
+
+
+ Uhrzeit
+
+
+
+
+
+
+
+
+
+
+ Einstempeln
+ Ausstempeln
+
+
+
+
+
+
diff --git a/src/app/time/time.page.scss b/src/app/time/time.page.scss
new file mode 100644
index 0000000..30736af
--- /dev/null
+++ b/src/app/time/time.page.scss
@@ -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);
+ }
+}
diff --git a/src/app/time/time.page.ts b/src/app/time/time.page.ts
new file mode 100644
index 0000000..f9e5e0c
--- /dev/null
+++ b/src/app/time/time.page.ts
@@ -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');
+ }
+}
diff --git a/src/assets/shapes.svg b/src/assets/shapes.svg
deleted file mode 100644
index d370b4d..0000000
--- a/src/assets/shapes.svg
+++ /dev/null
@@ -1 +0,0 @@
-