From 53eb287b0a6455362661e7a6ee94f0b32f27d3fc Mon Sep 17 00:00:00 2001 From: Leon Hoppe Date: Fri, 30 Aug 2024 21:09:12 +0200 Subject: [PATCH] finished time recording --- angular.json | 5 +- .../analysis.page.html} | 0 .../analysis.page.scss} | 0 .../analysis.page.ts} | 6 +- src/app/app.component.spec.ts | 16 --- .../settings.page.html} | 0 .../settings.page.scss} | 0 .../settings.page.ts} | 6 +- src/app/tab1/tab1.page.html | 17 --- src/app/tab1/tab1.page.spec.ts | 18 --- src/app/tab1/tab1.page.ts | 14 -- src/app/tab2/tab2.page.spec.ts | 18 --- src/app/tab3/tab3.page.scss | 0 src/app/tab3/tab3.page.spec.ts | 18 --- src/app/tabs/tabs.page.html | 18 +-- src/app/tabs/tabs.page.spec.ts | 26 ---- src/app/tabs/tabs.page.ts | 4 +- src/app/tabs/tabs.routes.ts | 14 +- src/app/time/time.page.html | 78 +++++++++++ src/app/time/time.page.scss | 121 ++++++++++++++++++ src/app/time/time.page.ts | 118 +++++++++++++++++ src/assets/shapes.svg | 1 - src/index.html | 2 +- src/models/timeEntry.ts | 6 + 24 files changed, 352 insertions(+), 154 deletions(-) rename src/app/{tab2/tab2.page.html => analysis/analysis.page.html} (100%) rename src/app/{tab1/tab1.page.scss => analysis/analysis.page.scss} (100%) rename src/app/{tab2/tab2.page.ts => analysis/analysis.page.ts} (79%) delete mode 100644 src/app/app.component.spec.ts rename src/app/{tab3/tab3.page.html => settings/settings.page.html} (100%) rename src/app/{tab2/tab2.page.scss => settings/settings.page.scss} (100%) rename src/app/{tab3/tab3.page.ts => settings/settings.page.ts} (79%) delete mode 100644 src/app/tab1/tab1.page.html delete mode 100644 src/app/tab1/tab1.page.spec.ts delete mode 100644 src/app/tab1/tab1.page.ts delete mode 100644 src/app/tab2/tab2.page.spec.ts delete mode 100644 src/app/tab3/tab3.page.scss delete mode 100644 src/app/tab3/tab3.page.spec.ts delete mode 100644 src/app/tabs/tabs.page.spec.ts create mode 100644 src/app/time/time.page.html create mode 100644 src/app/time/time.page.scss create mode 100644 src/app/time/time.page.ts delete mode 100644 src/assets/shapes.svg create mode 100644 src/models/timeEntry.ts diff --git a/angular.json b/angular.json index 454190c..e523c20 100644 --- a/angular.json +++ b/angular.json @@ -126,7 +126,10 @@ } }, "cli": { - "schematicCollections": ["@ionic/angular-toolkit"] + "schematicCollections": [ + "@ionic/angular-toolkit" + ], + "analytics": false }, "schematics": { "@ionic/angular-toolkit:component": { diff --git a/src/app/tab2/tab2.page.html b/src/app/analysis/analysis.page.html similarity index 100% rename from src/app/tab2/tab2.page.html rename to src/app/analysis/analysis.page.html diff --git a/src/app/tab1/tab1.page.scss b/src/app/analysis/analysis.page.scss similarity index 100% rename from src/app/tab1/tab1.page.scss rename to src/app/analysis/analysis.page.scss diff --git a/src/app/tab2/tab2.page.ts b/src/app/analysis/analysis.page.ts similarity index 79% rename from src/app/tab2/tab2.page.ts rename to src/app/analysis/analysis.page.ts index 6fd1fdd..6d8e213 100644 --- a/src/app/tab2/tab2.page.ts +++ b/src/app/analysis/analysis.page.ts @@ -4,12 +4,12 @@ import { ExploreContainerComponent } from '../explore-container/explore-containe @Component({ selector: 'app-tab2', - templateUrl: 'tab2.page.html', - styleUrls: ['tab2.page.scss'], + templateUrl: 'analysis.page.html', + styleUrls: ['analysis.page.scss'], standalone: true, imports: [IonHeader, IonToolbar, IonTitle, IonContent, ExploreContainerComponent] }) -export class Tab2Page { +export class AnalysisPage { constructor() {} diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts deleted file mode 100644 index 118209f..0000000 --- a/src/app/app.component.spec.ts +++ /dev/null @@ -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(); - }); -}); diff --git a/src/app/tab3/tab3.page.html b/src/app/settings/settings.page.html similarity index 100% rename from src/app/tab3/tab3.page.html rename to src/app/settings/settings.page.html diff --git a/src/app/tab2/tab2.page.scss b/src/app/settings/settings.page.scss similarity index 100% rename from src/app/tab2/tab2.page.scss rename to src/app/settings/settings.page.scss diff --git a/src/app/tab3/tab3.page.ts b/src/app/settings/settings.page.ts similarity index 79% rename from src/app/tab3/tab3.page.ts rename to src/app/settings/settings.page.ts index 5e29647..0cca1d1 100644 --- a/src/app/tab3/tab3.page.ts +++ b/src/app/settings/settings.page.ts @@ -4,11 +4,11 @@ import { ExploreContainerComponent } from '../explore-container/explore-containe @Component({ selector: 'app-tab3', - templateUrl: 'tab3.page.html', - styleUrls: ['tab3.page.scss'], + templateUrl: 'settings.page.html', + styleUrls: ['settings.page.scss'], standalone: true, imports: [IonHeader, IonToolbar, IonTitle, IonContent, ExploreContainerComponent], }) -export class Tab3Page { +export class SettingsPage { constructor() {} } diff --git a/src/app/tab1/tab1.page.html b/src/app/tab1/tab1.page.html deleted file mode 100644 index 22e11e4..0000000 --- a/src/app/tab1/tab1.page.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - Tab 1 - - - - - - - - Tab 1 - - - - - diff --git a/src/app/tab1/tab1.page.spec.ts b/src/app/tab1/tab1.page.spec.ts deleted file mode 100644 index fcc00f9..0000000 --- a/src/app/tab1/tab1.page.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { Tab1Page } from './tab1.page'; - -describe('Tab1Page', () => { - let component: Tab1Page; - let fixture: ComponentFixture; - - beforeEach(async () => { - fixture = TestBed.createComponent(Tab1Page); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/app/tab1/tab1.page.ts b/src/app/tab1/tab1.page.ts deleted file mode 100644 index 125fdf9..0000000 --- a/src/app/tab1/tab1.page.ts +++ /dev/null @@ -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() {} -} diff --git a/src/app/tab2/tab2.page.spec.ts b/src/app/tab2/tab2.page.spec.ts deleted file mode 100644 index 92c0cac..0000000 --- a/src/app/tab2/tab2.page.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { Tab2Page } from './tab2.page'; - -describe('Tab2Page', () => { - let component: Tab2Page; - let fixture: ComponentFixture; - - beforeEach(async () => { - fixture = TestBed.createComponent(Tab2Page); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/app/tab3/tab3.page.scss b/src/app/tab3/tab3.page.scss deleted file mode 100644 index e69de29..0000000 diff --git a/src/app/tab3/tab3.page.spec.ts b/src/app/tab3/tab3.page.spec.ts deleted file mode 100644 index a03da99..0000000 --- a/src/app/tab3/tab3.page.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { Tab3Page } from './tab3.page'; - -describe('Tab3Page', () => { - let component: Tab3Page; - let fixture: ComponentFixture; - - beforeEach(async () => { - fixture = TestBed.createComponent(Tab3Page); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/app/tabs/tabs.page.html b/src/app/tabs/tabs.page.html index 0f38384..02f7742 100644 --- a/src/app/tabs/tabs.page.html +++ b/src/app/tabs/tabs.page.html @@ -1,18 +1,18 @@ - - - Tab 1 + + + Erfassen - - - Tab 2 + + + Analyse - - - Tab 3 + + + Einstellungen diff --git a/src/app/tabs/tabs.page.spec.ts b/src/app/tabs/tabs.page.spec.ts deleted file mode 100644 index 2ebc97e..0000000 --- a/src/app/tabs/tabs.page.spec.ts +++ /dev/null @@ -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; - - 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(); - }); -}); diff --git a/src/app/tabs/tabs.page.ts b/src/app/tabs/tabs.page.ts index 62fd0c0..e88d620 100644 --- a/src/app/tabs/tabs.page.ts +++ b/src/app/tabs/tabs.page.ts @@ -1,7 +1,7 @@ import { Component, EnvironmentInjector, inject } from '@angular/core'; import { IonTabs, IonTabBar, IonTabButton, IonIcon, IonLabel } from '@ionic/angular/standalone'; import { addIcons } from 'ionicons'; -import { triangle, ellipse, square } from 'ionicons/icons'; +import { time, pieChart, settings } from 'ionicons/icons'; @Component({ selector: 'app-tabs', @@ -14,6 +14,6 @@ export class TabsPage { public environmentInjector = inject(EnvironmentInjector); constructor() { - addIcons({ triangle, ellipse, square }); + addIcons({ time, pieChart, settings }); } } diff --git a/src/app/tabs/tabs.routes.ts b/src/app/tabs/tabs.routes.ts index 15ba9e5..00e8258 100644 --- a/src/app/tabs/tabs.routes.ts +++ b/src/app/tabs/tabs.routes.ts @@ -7,19 +7,19 @@ export const routes: Routes = [ component: TabsPage, children: [ { - path: 'tab1', + path: 'time', loadComponent: () => - import('../tab1/tab1.page').then((m) => m.Tab1Page), + import('../time/time.page').then((m) => m.TimePage), }, { - path: 'tab2', + path: 'analysis', loadComponent: () => - import('../tab2/tab2.page').then((m) => m.Tab2Page), + import('../analysis/analysis.page').then((m) => m.AnalysisPage), }, { - path: 'tab3', + path: 'settings', loadComponent: () => - import('../tab3/tab3.page').then((m) => m.Tab3Page), + import('../settings/settings.page').then((m) => m.SettingsPage), }, { path: '', @@ -30,7 +30,7 @@ export const routes: Routes = [ }, { path: '', - redirectTo: '/tabs/tab1', + redirectTo: '/tabs/time', pathMatch: 'full', }, ]; diff --git a/src/app/time/time.page.html b/src/app/time/time.page.html new file mode 100644 index 0000000..aa69f48 --- /dev/null +++ b/src/app/time/time.page.html @@ -0,0 +1,78 @@ + + + + Zeiterfassung + + + + + + + + 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 @@ - diff --git a/src/index.html b/src/index.html index 3b0aae1..c74d135 100644 --- a/src/index.html +++ b/src/index.html @@ -3,7 +3,7 @@ - Ionic App + Zeiterfassung diff --git a/src/models/timeEntry.ts b/src/models/timeEntry.ts new file mode 100644 index 0000000..ba137b2 --- /dev/null +++ b/src/models/timeEntry.ts @@ -0,0 +1,6 @@ +export interface TimeEntry { + registeredAt: Date; + type: TimeType; +} + +export type TimeType = 'login' | 'logout';