1
0
Fork 0
forked from Kispi/Core

feat(webapp): support old iPadOS version
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Simon Giesel 2023-07-17 10:23:26 +02:00
parent 7089d0397f
commit dc3c065d1a
12 changed files with 150 additions and 24 deletions

View file

@ -2,7 +2,7 @@
"name": "hgoe-sas", "name": "hgoe-sas",
"private": true, "private": true,
"author": "Simon Giesel", "author": "Simon Giesel",
"version": "1.0.0", "version": "1.1.0",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vue-tsc --noEmit && vite build", "build": "vue-tsc --noEmit && vite build",
@ -17,6 +17,7 @@
"@vueuse/core": "^10.2.1", "@vueuse/core": "^10.2.1",
"canvas-confetti": "^1.6.0", "canvas-confetti": "^1.6.0",
"daisyui": "^3.2.1", "daisyui": "^3.2.1",
"date-fns": "^2.30.0",
"pocketbase": "^0.15.3", "pocketbase": "^0.15.3",
"vue": "^3.3.4", "vue": "^3.3.4",
"vue-router": "4.2.4" "vue-router": "4.2.4"

View file

@ -17,6 +17,9 @@ dependencies:
daisyui: daisyui:
specifier: ^3.2.1 specifier: ^3.2.1
version: 3.2.1 version: 3.2.1
date-fns:
specifier: ^2.30.0
version: 2.30.0
pocketbase: pocketbase:
specifier: ^0.15.3 specifier: ^0.15.3
version: 0.15.3 version: 0.15.3
@ -111,6 +114,13 @@ packages:
dependencies: dependencies:
'@babel/types': 7.18.8 '@babel/types': 7.18.8
/@babel/runtime@7.22.6:
resolution: {integrity: sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==}
engines: {node: '>=6.9.0'}
dependencies:
regenerator-runtime: 0.13.11
dev: false
/@babel/types@7.18.8: /@babel/types@7.18.8:
resolution: {integrity: sha512-qwpdsmraq0aJ3osLJRApsc2ouSJCdnMeZwB0DhbtHAtRpZNZCdlbRnHIgcRKzdE1g0iOGg644fzjOBcdOz9cPw==} resolution: {integrity: sha512-qwpdsmraq0aJ3osLJRApsc2ouSJCdnMeZwB0DhbtHAtRpZNZCdlbRnHIgcRKzdE1g0iOGg644fzjOBcdOz9cPw==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
@ -1216,6 +1226,13 @@ packages:
- ts-node - ts-node
dev: false dev: false
/date-fns@2.30.0:
resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==}
engines: {node: '>=0.11'}
dependencies:
'@babel/runtime': 7.22.6
dev: false
/de-indent@1.0.2: /de-indent@1.0.2:
resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==}
dev: true dev: true
@ -2526,6 +2543,10 @@ packages:
dependencies: dependencies:
picomatch: 2.3.1 picomatch: 2.3.1
/regenerator-runtime@0.13.11:
resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==}
dev: false
/resolve-from@4.0.0: /resolve-from@4.0.0:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
engines: {node: '>=4'} engines: {node: '>=4'}

View file

@ -0,0 +1,92 @@
<template>
<Teleport to="body">
<input
:id="id"
type="checkbox"
class="modal-toggle"
@change="($event.target as HTMLInputElement).checked ? $emit('close') : null"
/>
<div class="modal">
<form
method="dialog"
class="modal-box"
:class="{
'bg-base-300': darker,
'w-auto': wAuto,
}"
>
<button
v-if="closeButton"
class="btn-ghost btn-sm btn-circle btn absolute right-2 top-2"
>
</button>
<h3
v-if="title"
class="text-lg font-bold"
>
{{ title }}
</h3>
<p class="py-4"><slot /></p>
<div class="modal-action">
<slot name="action" />
</div>
</form>
<form
method="dialog"
class="modal-backdrop"
>
<button>close</button>
</form>
</div>
</Teleport>
</template>
<script lang="ts" setup>
const props = defineProps({
id: {
type: String,
required: true,
},
title: {
type: String,
default: '',
},
darker: {
type: Boolean,
default: false,
},
closeButton: {
type: Boolean,
default: false,
},
wAuto: {
type: Boolean,
default: false,
},
});
function show(): void {
(document.getElementById(props.id) as HTMLInputElement).checked = true;
}
function close(): void {
(document.getElementById(props.id) as HTMLInputElement).checked = false;
}
function isOpen(): boolean {
return (document.getElementById(props.id) as HTMLInputElement).checked;
}
defineEmits(['close']);
defineExpose({
show,
close,
isOpen,
});
</script>
<style lang="scss" scoped>
</style>

View file

@ -30,7 +30,7 @@
{{ DateService.toShortString(entry[header.key]) }} {{ DateService.toShortString(entry[header.key]) }}
</span> </span>
<span v-else-if="header.type === TableHeaderType.DATETIME"> <span v-else-if="header.type === TableHeaderType.DATETIME">
{{ entry[header.key] ? DateService.toString(new Date(entry[header.key])): 'N/A' }} {{ entry[header.key] ? DateService.toString(parseISO(entry[header.key])): 'N/A' }}
</span> </span>
<span v-else-if="header.type === TableHeaderType.CURRENCY"> <span v-else-if="header.type === TableHeaderType.CURRENCY">
{{ CurrencyService.toString(entry[header.key]) }} {{ CurrencyService.toString(entry[header.key]) }}
@ -104,6 +104,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { PropType, onMounted, ref } from 'vue'; import { PropType, onMounted, ref } from 'vue';
import { parseISO } from 'date-fns';
import { SettingsResponse } from '../../types/pocketbase.types'; import { SettingsResponse } from '../../types/pocketbase.types';
import { DateService } from '../../services/date.service'; import { DateService } from '../../services/date.service';
import { CurrencyService } from '../../services/currency.service'; import { CurrencyService } from '../../services/currency.service';

View file

@ -43,7 +43,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from 'vue'; import { ref } from 'vue';
import { ArrowRightOnRectangleIcon, XCircleIcon } from '@heroicons/vue/24/outline'; import { ArrowRightOnRectangleIcon, XCircleIcon } from '@heroicons/vue/24/outline';
import AtomModal from '../atoms/AtomModal.vue'; import AtomModal from '../atoms/AtomLegacyModal.vue';
import AtomInput from '../atoms/AtomInput.vue'; import AtomInput from '../atoms/AtomInput.vue';
const modal = ref<InstanceType<typeof AtomModal>>(); const modal = ref<InstanceType<typeof AtomModal>>();

View file

@ -12,7 +12,7 @@
<div class="flex items-center"> <div class="flex items-center">
<div> <div>
<div class="font-bold">{{ transaction.label }}</div> <div class="font-bold">{{ transaction.label }}</div>
<div>{{ DateService.toString(new Date(transaction.created)) }}</div> <div>{{ DateService.toString(parseISO(transaction.created)) }}</div>
</div> </div>
</div> </div>
</td> </td>
@ -35,6 +35,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { PropType } from 'vue'; import { PropType } from 'vue';
import { parseISO } from 'date-fns';
import { CurrencyService } from '../../services/currency.service'; import { CurrencyService } from '../../services/currency.service';
import { DateService } from '../../services/date.service'; import { DateService } from '../../services/date.service';
import { TransactionsResponse } from '../../types/pocketbase.types'; import { TransactionsResponse } from '../../types/pocketbase.types';

View file

@ -18,7 +18,7 @@
</button> </button>
<button <button
class="btn gap-2" class="btn gap-2"
@click="$emit('confirm')" @click.prevent="$emit('confirm')"
> >
<CheckCircleIcon class="h-6 w-6" /> <CheckCircleIcon class="h-6 w-6" />
Bestätigen Bestätigen
@ -30,7 +30,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from 'vue'; import { ref } from 'vue';
import { CheckCircleIcon, XCircleIcon } from '@heroicons/vue/24/outline'; import { CheckCircleIcon, XCircleIcon } from '@heroicons/vue/24/outline';
import AtomModal from '../atoms/AtomModal.vue'; import AtomModal from '../atoms/AtomLegacyModal.vue';
const modal = ref<InstanceType<typeof AtomModal>>(); const modal = ref<InstanceType<typeof AtomModal>>();

View file

@ -14,6 +14,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from 'vue'; import { ref } from 'vue';
import { onKeyStroke, promiseTimeout } from '@vueuse/core'; import { onKeyStroke, promiseTimeout } from '@vueuse/core';
import { parseISO } from 'date-fns';
import { AccountService } from '../../services/account.service'; import { AccountService } from '../../services/account.service';
import { DateService } from '../../services/date.service'; import { DateService } from '../../services/date.service';
import { XCircleIcon } from '@heroicons/vue/24/outline'; import { XCircleIcon } from '@heroicons/vue/24/outline';
@ -31,7 +32,7 @@ onKeyStroke(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'Enter'], async (
try { try {
const account = await AccountService.checkIn(accountId.value); const account = await AccountService.checkIn(accountId.value);
accountId.value = ''; accountId.value = '';
message.value = `${account.firstName} ${account.lastName} um ${DateService.toTime(new Date(account.lastCheckIn))}`; message.value = `${account.firstName} ${account.lastName} um ${DateService.toTime(parseISO(account.lastCheckIn))}`;
await promiseTimeout(5000); await promiseTimeout(5000);
message.value = DEFAULT_MESSAGE; message.value = DEFAULT_MESSAGE;
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any

View file

@ -71,10 +71,13 @@
<div class="stat-desc">in Spätschicht</div> <div class="stat-desc">in Spätschicht</div>
</div> </div>
</div> </div>
<div class="flex items-center justify-center gap-4"> <div
v-if="company"
class="flex items-center justify-center gap-4"
>
<button <button
class="btn-primary btn" class="btn-primary btn"
:class="{'btn-disabled': (getBalance() < getTotalCostsByShifts(Shifts.EARLY) || DateService.isToday(new Date(company.earlyShiftPayed)))}" :class="{'btn-disabled': (getBalance() < getTotalCostsByShifts(Shifts.EARLY) || DateService.isToday(parseISO(company.earlyShiftPayed)))}"
@click="wageType = Shifts.EARLY; verifyModal?.show()" @click="wageType = Shifts.EARLY; verifyModal?.show()"
> >
<SunIcon class="h-6 w-6" /> <SunIcon class="h-6 w-6" />
@ -82,7 +85,7 @@
</button> </button>
<button <button
class="btn-primary btn" class="btn-primary btn"
:class="{'btn-disabled': (getBalance() < getTotalCostsByShifts(Shifts.LATE) || DateService.isToday(new Date(company.lateShiftPayed)))}" :class="{'btn-disabled': (getBalance() < getTotalCostsByShifts(Shifts.LATE) || DateService.isToday(parseISO(company.lateShiftPayed)))}"
@click="wageType = Shifts.LATE; verifyModal?.show()" @click="wageType = Shifts.LATE; verifyModal?.show()"
> >
<MoonIcon class="h-6 w-6" /> <MoonIcon class="h-6 w-6" />
@ -91,14 +94,14 @@
</div> </div>
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<div <div
v-if="DateService.isToday(new Date(company.earlyShiftPayed))" v-if="DateService.isToday(parseISO(company.earlyShiftPayed))"
class="alert alert-info" class="alert alert-info"
> >
<InformationCircleIcon class="h-6 w-6" /> <InformationCircleIcon class="h-6 w-6" />
Die Frühschicht wurde heute bereits bezahlt. Die Frühschicht wurde heute bereits bezahlt.
</div> </div>
<div <div
v-if="DateService.isToday(new Date(company.lateShiftPayed))" v-if="DateService.isToday(parseISO(company.lateShiftPayed))"
class="alert alert-info" class="alert alert-info"
> >
<InformationCircleIcon class="h-6 w-6" /> <InformationCircleIcon class="h-6 w-6" />
@ -131,14 +134,7 @@
id="verify-modal" id="verify-modal"
ref="verifyModal" ref="verifyModal"
title="Lohnauszahlung bestätigen" title="Lohnauszahlung bestätigen"
@confirm="async () => { @confirm="confirmWage"
verifyModal?.close();
if(wageType) {
await CompanyService.payWage(wageType);
}
wageType = undefined;
init();
}"
> >
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<p>Lohn wirklich für die {{ wageType == Shifts.EARLY ? 'Frühschicht' : 'Spätschicht' }} auszahlen?</p> <p>Lohn wirklich für die {{ wageType == Shifts.EARLY ? 'Frühschicht' : 'Spätschicht' }} auszahlen?</p>
@ -166,6 +162,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, onUnmounted, ref, watch } from 'vue'; import { onMounted, onUnmounted, ref, watch } from 'vue';
import { RecordSubscription, UnsubscribeFunc } from 'pocketbase'; import { RecordSubscription, UnsubscribeFunc } from 'pocketbase';
import { parseISO } from 'date-fns';
import { AccountsResponse, CompaniesResponse, SettingsResponse, TransactionsResponse } from '../../types/pocketbase.types'; import { AccountsResponse, CompaniesResponse, SettingsResponse, TransactionsResponse } from '../../types/pocketbase.types';
import { AccountService } from '../../services/account.service'; import { AccountService } from '../../services/account.service';
import { BankService } from '../../services/bank.service'; import { BankService } from '../../services/bank.service';
@ -324,6 +321,15 @@ function logout() {
getData(); getData();
} }
async function confirmWage() {
verifyModal.value?.close();
if(wageType.value) {
await CompanyService.payWage(wageType.value);
}
wageType.value = undefined;
init();
}
onUnmounted(() => { onUnmounted(() => {
accountsSubscription.value?.(); accountsSubscription.value?.();
}); });

View file

@ -1,4 +1,5 @@
import { RecordSubscription, UnsubscribeFunc } from 'pocketbase'; import { RecordSubscription, UnsubscribeFunc } from 'pocketbase';
import { parseISO } from 'date-fns';
import { AccountsListResponse, AccountsRecord, AccountsResponse, Collections } from '../types/pocketbase.types'; import { AccountsListResponse, AccountsRecord, AccountsResponse, Collections } from '../types/pocketbase.types';
import { DateService } from './date.service'; import { DateService } from './date.service';
import { PocketbaseService } from './pocketbase.service'; import { PocketbaseService } from './pocketbase.service';
@ -34,7 +35,7 @@ export class AccountService {
} }
public static async getMissingStudents(): Promise<AccountsResponse[]> { public static async getMissingStudents(): Promise<AccountsResponse[]> {
return ((await COLLECTION.getFullList<AccountsResponse>()).filter( return ((await COLLECTION.getFullList<AccountsResponse>()).filter(
(account: AccountsResponse) => !DateService.isToday(new Date(account.lastCheckIn)), (account: AccountsResponse) => !DateService.isToday(parseISO(account.lastCheckIn)),
)); ));
} }
public static async subscribeToAccountChanges( public static async subscribeToAccountChanges(

View file

@ -40,7 +40,7 @@ export class CompanyService {
} }
public static async getCompany(): Promise<CompaniesResponse> { public static async getCompany(): Promise<CompaniesResponse> {
if(this.isAuthenticated()) { if(this.isAuthenticated()) {
return API.authStore.model as unknown as CompaniesResponse; return COLLECTION.getFirstListItem(`id="${API.authStore.model?.id}"`);
} }
throw new Error('Not authenticated'); throw new Error('Not authenticated');
} }
@ -72,7 +72,6 @@ export class CompanyService {
promises.push(BankService.addTransaction( promises.push(BankService.addTransaction(
account.id, account.id,
(((account.wageFactor * settings.minWage) * (1 - (settings.incomeTax / 100)))) / 100, (((account.wageFactor * settings.minWage) * (1 - (settings.incomeTax / 100)))) / 100,
// 300 - 1 * -
`Lohn für ${shift} bei ${company.name}`, `Lohn für ${shift} bei ${company.name}`,
)); ));
taxes += (account.wageFactor * settings.minWage) * (settings.incomeTax / 100); taxes += (account.wageFactor * settings.minWage) * (settings.incomeTax / 100);

View file

@ -1,3 +1,5 @@
import { parseISO } from 'date-fns';
let serverTime: Date; let serverTime: Date;
export class DateService { export class DateService {
@ -27,6 +29,7 @@ export class DateService {
} }
public static isToday(date: Date): boolean { public static isToday(date: Date): boolean {
this.getServerTime(); this.getServerTime();
console.log('isToday', date, serverTime);
if(!serverTime) { return false; } if(!serverTime) { return false; }
return date.getDate() === serverTime.getDate() && return date.getDate() === serverTime.getDate() &&
date.getMonth() === serverTime.getMonth() && date.getMonth() === serverTime.getMonth() &&
@ -37,7 +40,7 @@ export class DateService {
} }
public static async getServerTime(): Promise<Date> { public static async getServerTime(): Promise<Date> {
if(!serverTime || this.isOlderThanAMinute(serverTime)) { if(!serverTime || this.isOlderThanAMinute(serverTime)) {
serverTime = new Date(await (await fetch( serverTime = parseISO(await (await fetch(
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
(import.meta as any).env.MODE === 'development' ? 'http://127.0.0.1:8090/api/time' : '/api/time', (import.meta as any).env.MODE === 'development' ? 'http://127.0.0.1:8090/api/time' : '/api/time',
)).text()); )).text());