finished time recording
This commit is contained in:
@@ -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() {}
|
||||
|
||||
@@ -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({
|
||||
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() {}
|
||||
}
|
||||
@@ -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-tab-bar slot="bottom">
|
||||
<ion-tab-button tab="tab1" href="/tabs/tab1">
|
||||
<ion-icon aria-hidden="true" name="triangle"></ion-icon>
|
||||
<ion-label>Tab 1</ion-label>
|
||||
<ion-tab-button tab="time" href="/time">
|
||||
<ion-icon aria-hidden="true" name="time"></ion-icon>
|
||||
<ion-label>Erfassen</ion-label>
|
||||
</ion-tab-button>
|
||||
|
||||
<ion-tab-button tab="tab2" href="/tabs/tab2">
|
||||
<ion-icon aria-hidden="true" name="ellipse"></ion-icon>
|
||||
<ion-label>Tab 2</ion-label>
|
||||
<ion-tab-button tab="analysis" href="/analysis">
|
||||
<ion-icon aria-hidden="true" name="pie-chart"></ion-icon>
|
||||
<ion-label>Analyse</ion-label>
|
||||
</ion-tab-button>
|
||||
|
||||
<ion-tab-button tab="tab3" href="/tabs/tab3">
|
||||
<ion-icon aria-hidden="true" name="square"></ion-icon>
|
||||
<ion-label>Tab 3</ion-label>
|
||||
<ion-tab-button tab="settings" href="/settings">
|
||||
<ion-icon aria-hidden="true" name="settings"></ion-icon>
|
||||
<ion-label>Einstellungen</ion-label>
|
||||
</ion-tab-button>
|
||||
</ion-tab-bar>
|
||||
</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 { 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 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
];
|
||||
|
||||
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>
|
||||
<meta charset="utf-8" />
|
||||
<title>Ionic App</title>
|
||||
<title>Zeiterfassung</title>
|
||||
|
||||
<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