added pwa + dienstreise support
This commit is contained in:
@@ -29,10 +29,13 @@
|
||||
"glob": "**/*",
|
||||
"input": "src/assets",
|
||||
"output": "assets"
|
||||
}
|
||||
},
|
||||
"src/manifest.webmanifest"
|
||||
],
|
||||
"styles": ["src/global.scss", "src/theme/variables.scss"],
|
||||
"scripts": []
|
||||
"scripts": [],
|
||||
"serviceWorker": true,
|
||||
"ngswConfigPath": "ngsw-config.json"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
|
||||
29
ngsw-config.json
Normal file
29
ngsw-config.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/service-worker/config/schema.json",
|
||||
"index": "/index.html",
|
||||
"assetGroups": [
|
||||
{
|
||||
"name": "app",
|
||||
"installMode": "prefetch",
|
||||
"resources": {
|
||||
"files": [
|
||||
"/favicon.ico",
|
||||
"/index.html",
|
||||
"/manifest.webmanifest",
|
||||
"/*.css",
|
||||
"/*.js"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "assets",
|
||||
"installMode": "lazy",
|
||||
"updateMode": "prefetch",
|
||||
"resources": {
|
||||
"files": [
|
||||
"/**/*.(svg|cur|jpg|jpeg|png|apng|webp|avif|gif|otf|ttf|woff|woff2)"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
19
package-lock.json
generated
19
package-lock.json
generated
@@ -16,6 +16,7 @@
|
||||
"@angular/platform-browser": "^18.0.0",
|
||||
"@angular/platform-browser-dynamic": "^18.0.0",
|
||||
"@angular/router": "^18.0.0",
|
||||
"@angular/service-worker": "^18.0.0",
|
||||
"@capacitor/app": "6.0.1",
|
||||
"@capacitor/core": "6.1.2",
|
||||
"@capacitor/haptics": "6.0.1",
|
||||
@@ -648,6 +649,24 @@
|
||||
"rxjs": "^6.5.3 || ^7.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/service-worker": {
|
||||
"version": "18.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@angular/service-worker/-/service-worker-18.2.2.tgz",
|
||||
"integrity": "sha512-az0v0gNkAjOQ4DThDWfNJv2DkH63B4Vj/WnXd8pbY/C7Be6w3S1mN2y9vJClWAzUH/GSLQHnOrZJfnZtTc8M0w==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
"bin": {
|
||||
"ngsw-config": "ngsw-config.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.19.1 || ^20.11.1 || >=22.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/common": "18.2.2",
|
||||
"@angular/core": "18.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz",
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
"@angular/platform-browser": "^18.0.0",
|
||||
"@angular/platform-browser-dynamic": "^18.0.0",
|
||||
"@angular/router": "^18.0.0",
|
||||
"@angular/service-worker": "^18.0.0",
|
||||
"@capacitor/app": "6.0.1",
|
||||
"@capacitor/core": "6.1.2",
|
||||
"@capacitor/haptics": "6.0.1",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content [fullscreen]="true" [scrollY]="false">
|
||||
<ion-content [fullscreen]="true">
|
||||
<ion-header collapse="condense">
|
||||
<ion-toolbar>
|
||||
<ion-title size="large">Zeiterfassung</ion-title>
|
||||
@@ -27,7 +27,7 @@
|
||||
<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>
|
||||
<ion-label class="type">{{getTypeText(entry.type)}}</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>
|
||||
@@ -37,7 +37,7 @@
|
||||
|
||||
<div class="button-container">
|
||||
<ion-button (click)="addEntry()">{{currentAction === 'login' ? "Einstempeln" : "Ausstempeln"}}</ion-button>
|
||||
<ion-button shape="round" id="open-modal">
|
||||
<ion-button shape="round" id="open-modal" class="icon-button">
|
||||
<ion-icon slot="icon-only" name="add"></ion-icon>
|
||||
</ion-button>
|
||||
</div>
|
||||
@@ -70,6 +70,8 @@
|
||||
<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-option value="start-drive">Dienstreise starten</ion-select-option>
|
||||
<ion-select-option value="end-drive">Dienstreise beenden</ion-select-option>
|
||||
</ion-select>
|
||||
</ion-item>
|
||||
</ion-content>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.button-container {
|
||||
position: absolute;
|
||||
bottom: 75px;
|
||||
position: fixed;
|
||||
bottom: 25px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
@@ -13,6 +13,7 @@
|
||||
flex-direction: column;
|
||||
gap: 65px;
|
||||
margin: 20px;
|
||||
padding-bottom: 75px;
|
||||
|
||||
.entry {
|
||||
--inner-border-width: 0 0 0 0;
|
||||
|
||||
@@ -57,23 +57,45 @@ export class TimePage {
|
||||
return this.data.filter(entry => entry.registeredAt.getDay() === today);
|
||||
}
|
||||
|
||||
public getTypeText(type: TimeType): string {
|
||||
switch (type) {
|
||||
case "login":
|
||||
return "Eingestempelt";
|
||||
|
||||
case "logout":
|
||||
return "Ausgestempelt";
|
||||
|
||||
case "start-drive":
|
||||
return "Dienstreise gestartet";
|
||||
|
||||
case "end-drive":
|
||||
return "Dienstreise beendet";
|
||||
}
|
||||
}
|
||||
|
||||
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 ";
|
||||
|
||||
let text = entry1.type === 'login' ? "Arbeit " : "Pause ";
|
||||
if (entry1.type === 'start-drive' && entry2.type === 'end-drive') {
|
||||
text = "Dienstreise";
|
||||
}
|
||||
|
||||
return text + `(${date.toLocaleTimeString()})`;
|
||||
}
|
||||
|
||||
public addEntry(): void {
|
||||
const animateIndex = this.shouldAnimate.length;
|
||||
this.shouldAnimate.push(true)
|
||||
setTimeout(() => this.shouldAnimate[this.shouldAnimate.length - 1] = false, 5000);
|
||||
setTimeout(() => this.shouldAnimate[animateIndex] = false, 2000);
|
||||
|
||||
this.data.push({
|
||||
registeredAt: new Date(Date.now()),
|
||||
type: this.currentAction
|
||||
});
|
||||
this.saveData();
|
||||
this.currentAction = this.currentAction === 'login' ? 'logout' : 'login';
|
||||
this.updateCurrentAction();
|
||||
}
|
||||
|
||||
public removeEntry(index: number): void {
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 930 B After Width: | Height: | Size: 14 KiB |
@@ -35,3 +35,7 @@
|
||||
/* @import "@ionic/angular/css/palettes/dark.always.css"; */
|
||||
/* @import "@ionic/angular/css/palettes/dark.class.css"; */
|
||||
@import '@ionic/angular/css/palettes/dark.system.css';
|
||||
|
||||
.ios .icon-button {
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
@@ -17,10 +17,13 @@
|
||||
<!-- add to homescreen for ios -->
|
||||
<meta name="apple-mobile-web-app-capable" content="yes"/>
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black"/>
|
||||
<link rel="manifest" href="manifest.webmanifest">
|
||||
<meta name="theme-color" content="#1976d2">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
<noscript>Please enable JavaScript to continue using this application.</noscript>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
@@ -4,11 +4,16 @@ import { IonicRouteStrategy, provideIonicAngular } from '@ionic/angular/standalo
|
||||
|
||||
import { routes } from './app/app.routes';
|
||||
import { AppComponent } from './app/app.component';
|
||||
import { isDevMode } from '@angular/core';
|
||||
import { provideServiceWorker } from '@angular/service-worker';
|
||||
|
||||
bootstrapApplication(AppComponent, {
|
||||
providers: [
|
||||
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
|
||||
provideIonicAngular(),
|
||||
provideRouter(routes, withPreloading(PreloadAllModules)),
|
||||
provideRouter(routes, withPreloading(PreloadAllModules)), provideServiceWorker('ngsw-worker.js', {
|
||||
enabled: !isDevMode(),
|
||||
registrationStrategy: 'registerWhenStable:30000'
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
17
src/manifest.webmanifest
Normal file
17
src/manifest.webmanifest
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "Zeiterfassung",
|
||||
"short_name": "Zeiterfassung",
|
||||
"theme_color": "#121212",
|
||||
"background_color": "#121212",
|
||||
"display": "standalone",
|
||||
"scope": "./",
|
||||
"start_url": "./",
|
||||
"icons": [
|
||||
{
|
||||
"src": "assets/icon/favicon.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable any"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -3,4 +3,4 @@ export interface TimeEntry {
|
||||
type: TimeType;
|
||||
}
|
||||
|
||||
export type TimeType = 'login' | 'logout';
|
||||
export type TimeType = 'login' | 'logout' | 'start-drive' | 'end-drive';
|
||||
|
||||
Reference in New Issue
Block a user