Merge branch 'feature/moba' into 'main'

Resolve "Unterscheidung zwischen mobA und vor Ort"

Closes #1

See merge request leon.hoppe/WorkTime!1
This commit was merged in pull request #11.
This commit is contained in:
2025-02-26 18:46:12 +00:00
5 changed files with 42 additions and 15 deletions

View File

@@ -4,6 +4,7 @@ stages:
- install - install
- lint - lint
- build - build
- publish
install: install:
stage: install stage: install
@@ -36,7 +37,8 @@ build:
- npm run build - npm run build
artifacts: artifacts:
paths: paths:
- $CI_PROJECT_DIR/dist - $CI_PROJECT_DIR/www
expire_in: 10 minutes
cache: cache:
key: key:
files: files:
@@ -44,3 +46,16 @@ build:
paths: paths:
- node_modules - node_modules
policy: pull policy: pull
publish:
stage: publish
script:
- export VERSION=$(echo $CI_COMMIT_TAG | sed 's/^v//')
- docker login -u leon.hoppe -p ${CI_REGISTRY_PASSWORD} registry.leon-hoppe.de
- docker build -t registry.leon-hoppe.de/leon.hoppe/worktime:$VERSION -t registry.leon-hoppe.de/leon.hoppe/worktime:latest .
- docker push registry.leon-hoppe.de/leon.hoppe/worktime:$VERSION
- docker push registry.leon-hoppe.de/leon.hoppe/worktime:latest
only:
- tags
dependencies:
- build

View File

@@ -76,7 +76,8 @@ export class AnalysisPage {
const lastEntry = this.timeData[this.timeData.length - 1]; const lastEntry = this.timeData[this.timeData.length - 1];
const diff = this.time.calculateTimespanInMinutes(lastEntry, { const diff = this.time.calculateTimespanInMinutes(lastEntry, {
type: undefined, type: undefined,
registeredAt: new Date(Date.now()) registeredAt: new Date(Date.now()),
isMoba: false
}); });
if (lastEntry.type == "login") { if (lastEntry.type == "login") {

View File

@@ -27,6 +27,7 @@
<section class="time-entries"> <section class="time-entries">
<ion-item class="entry" *ngFor="let entry of getEntriesOfToday(); let index = index" (click)="removeEntry(entry, index)" [ngClass]="{'animate': shouldAnimate[index]}"> <ion-item class="entry" *ngFor="let entry of getEntriesOfToday(); let index = index" (click)="removeEntry(entry, index)" [ngClass]="{'animate': shouldAnimate[index]}">
<div class="circle"></div> <div class="circle"></div>
<ion-icon name="home" *ngIf="entry.isMoba" />
<ion-label class="type">{{getTypeText(entry.type)}}</ion-label> <ion-label class="type">{{getTypeText(entry.type)}}</ion-label>
<span class="time">{{formatEntry(entry.registeredAt.getHours(), entry.registeredAt.getMinutes())}}</span> <span class="time">{{formatEntry(entry.registeredAt.getHours(), entry.registeredAt.getMinutes())}}</span>
<ion-icon name="trash" *ngIf="isToday()" /> <ion-icon name="trash" *ngIf="isToday()" />
@@ -37,7 +38,10 @@
</section> </section>
<div class="button-container" *ngIf="isToday()"> <div class="button-container" *ngIf="isToday()">
<ion-button (click)="addEntry()">{{translateCurrentAction()}}</ion-button> <ion-button (click)="addEntry()">
<ion-icon name="home" *ngIf="currentlyMoba" style="margin-right: 0.5rem" />
{{translateCurrentAction()}}
</ion-button>
<ion-button shape="round" class="icon-button" (click)="openModal()"> <ion-button shape="round" class="icon-button" (click)="openModal()">
<ion-icon slot="icon-only" name="add"></ion-icon> <ion-icon slot="icon-only" name="add"></ion-icon>
</ion-button> </ion-button>
@@ -50,7 +54,6 @@
<ion-buttons slot="start"> <ion-buttons slot="start">
<ion-button (click)="modal?.dismiss(null, 'cancel')">Abbrechen</ion-button> <ion-button (click)="modal?.dismiss(null, 'cancel')">Abbrechen</ion-button>
</ion-buttons> </ion-buttons>
<ion-title>Welcome</ion-title>
<ion-buttons slot="end"> <ion-buttons slot="end">
<ion-button [strong]="true" (click)="addModalEntry()">Speichern</ion-button> <ion-button [strong]="true" (click)="addModalEntry()">Speichern</ion-button>
</ion-buttons> </ion-buttons>
@@ -75,6 +78,9 @@
<ion-select-option value="end-drive">Dienstreise beenden</ion-select-option> <ion-select-option value="end-drive">Dienstreise beenden</ion-select-option>
</ion-select> </ion-select>
</ion-item> </ion-item>
<ion-item *ngIf="!currentAction.endsWith('-drive')">
<ion-checkbox [(ngModel)]="modalMoba">Mobieles Arbeiten</ion-checkbox>
</ion-item>
</ion-content> </ion-content>
</ng-template> </ng-template>
</ion-modal> </ion-modal>

View File

@@ -5,23 +5,21 @@ import {
IonTitle, IonTitle,
IonContent, IonContent,
IonButton, IonButton,
IonList,
IonItem, IonItem,
IonLabel, IonLabel,
IonIcon, IonIcon,
IonModal, IonModal,
IonButtons, IonButtons,
IonInput,
IonDatetime, IonDatetime,
IonDatetimeButton, IonDatetimeButton,
IonSelect, IonSelect,
IonSelectOption, IonSelectOption,
AlertController AlertController, ViewDidEnter, IonCheckbox
} from '@ionic/angular/standalone'; } from '@ionic/angular/standalone';
import {TimeEntry, TimeType} from "../../models/timeEntry"; import {TimeEntry, TimeType} from "../../models/timeEntry";
import {NgClass, NgForOf, NgIf} from "@angular/common"; import {NgClass, NgForOf, NgIf} from "@angular/common";
import {addIcons} from "ionicons"; import {addIcons} from "ionicons";
import {add, trash} from "ionicons/icons"; import {add, home, trash} from "ionicons/icons";
import {FormsModule} from "@angular/forms"; import {FormsModule} from "@angular/forms";
import {TimeService} from "../../services/time.service"; import {TimeService} from "../../services/time.service";
import {AppComponent} from "../app.component"; import {AppComponent} from "../app.component";
@@ -31,9 +29,9 @@ import {AppComponent} from "../app.component";
templateUrl: 'time.page.html', templateUrl: 'time.page.html',
styleUrls: ['time.page.scss'], styleUrls: ['time.page.scss'],
standalone: true, standalone: true,
imports: [IonHeader, IonToolbar, IonTitle, IonContent, NgForOf, IonButton, IonList, IonItem, IonLabel, NgIf, NgClass, IonIcon, IonModal, IonButtons, IonInput, IonDatetime, IonDatetimeButton, IonSelect, IonSelectOption, FormsModule], imports: [IonHeader, IonToolbar, IonTitle, IonContent, NgForOf, IonButton, IonItem, IonLabel, NgIf, NgClass, IonIcon, IonModal, IonButtons, IonDatetime, IonDatetimeButton, IonSelect, IonSelectOption, FormsModule, IonCheckbox],
}) })
export class TimePage { export class TimePage implements ViewDidEnter {
public data: TimeEntry[] = []; public data: TimeEntry[] = [];
public today: TimeEntry[] = []; public today: TimeEntry[] = [];
public shouldAnimate: boolean[] = []; public shouldAnimate: boolean[] = [];
@@ -42,6 +40,8 @@ export class TimePage {
public modalDate: any; public modalDate: any;
public currentDate: any; public currentDate: any;
public modalMoba: boolean;
public currentlyMoba: boolean;
constructor(private timeService: TimeService, private alerts: AlertController) { constructor(private timeService: TimeService, private alerts: AlertController) {
this.data = timeService.loadEntries(); this.data = timeService.loadEntries();
@@ -52,7 +52,7 @@ export class TimePage {
this.updateCurrentAction(); this.updateCurrentAction();
addIcons({add, trash}); addIcons({add, trash, home});
} }
ionViewDidEnter() { ionViewDidEnter() {
@@ -115,7 +115,8 @@ export class TimePage {
this.data.push({ this.data.push({
registeredAt: new Date(Date.now()), registeredAt: new Date(Date.now()),
type: this.currentAction type: this.currentAction,
isMoba: this.currentlyMoba
}); });
this.data.sort((a: TimeEntry, b: TimeEntry) => { this.data.sort((a: TimeEntry, b: TimeEntry) => {
return a.registeredAt.getTime() - b.registeredAt.getTime(); return a.registeredAt.getTime() - b.registeredAt.getTime();
@@ -156,9 +157,9 @@ export class TimePage {
if (this.data.length == 0) { if (this.data.length == 0) {
this.currentAction = 'login'; this.currentAction = 'login';
}else { }else {
const lastAction = this.data[this.data.length - 1].type; const lastEntry = this.data[this.data.length - 1]
switch (lastAction) { switch (lastEntry.type) {
case "start-drive": case "start-drive":
this.currentAction = 'end-drive'; this.currentAction = 'end-drive';
break; break;
@@ -176,6 +177,8 @@ export class TimePage {
this.currentAction = 'login'; this.currentAction = 'login';
break; break;
} }
this.currentlyMoba = lastEntry.isMoba && lastEntry.type == "login";
} }
} }
@@ -215,7 +218,8 @@ export class TimePage {
this.data.push({ this.data.push({
registeredAt: date, registeredAt: date,
type: action || this.currentAction type: action || this.currentAction,
isMoba: this.modalMoba
}); });
this.data.sort((a: TimeEntry, b: TimeEntry) => { this.data.sort((a: TimeEntry, b: TimeEntry) => {
return a.registeredAt.getTime() - b.registeredAt.getTime(); return a.registeredAt.getTime() - b.registeredAt.getTime();

View File

@@ -1,6 +1,7 @@
export interface TimeEntry { export interface TimeEntry {
registeredAt: Date; registeredAt: Date;
type: TimeType; type: TimeType;
isMoba: boolean;
} }
export type TimeType = 'login' | 'logout' | 'start-drive' | 'end-drive'; export type TimeType = 'login' | 'logout' | 'start-drive' | 'end-drive';