forked from Kispi/Core
feat(webapp): support old iPadOS version
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
7089d0397f
commit
dc3c065d1a
12 changed files with 150 additions and 24 deletions
|
@ -2,7 +2,7 @@
|
|||
"name": "hgoe-sas",
|
||||
"private": true,
|
||||
"author": "Simon Giesel",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vue-tsc --noEmit && vite build",
|
||||
|
@ -17,6 +17,7 @@
|
|||
"@vueuse/core": "^10.2.1",
|
||||
"canvas-confetti": "^1.6.0",
|
||||
"daisyui": "^3.2.1",
|
||||
"date-fns": "^2.30.0",
|
||||
"pocketbase": "^0.15.3",
|
||||
"vue": "^3.3.4",
|
||||
"vue-router": "4.2.4"
|
||||
|
|
|
@ -17,6 +17,9 @@ dependencies:
|
|||
daisyui:
|
||||
specifier: ^3.2.1
|
||||
version: 3.2.1
|
||||
date-fns:
|
||||
specifier: ^2.30.0
|
||||
version: 2.30.0
|
||||
pocketbase:
|
||||
specifier: ^0.15.3
|
||||
version: 0.15.3
|
||||
|
@ -111,6 +114,13 @@ packages:
|
|||
dependencies:
|
||||
'@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:
|
||||
resolution: {integrity: sha512-qwpdsmraq0aJ3osLJRApsc2ouSJCdnMeZwB0DhbtHAtRpZNZCdlbRnHIgcRKzdE1g0iOGg644fzjOBcdOz9cPw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
@ -1216,6 +1226,13 @@ packages:
|
|||
- ts-node
|
||||
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:
|
||||
resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==}
|
||||
dev: true
|
||||
|
@ -2526,6 +2543,10 @@ packages:
|
|||
dependencies:
|
||||
picomatch: 2.3.1
|
||||
|
||||
/regenerator-runtime@0.13.11:
|
||||
resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==}
|
||||
dev: false
|
||||
|
||||
/resolve-from@4.0.0:
|
||||
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
||||
engines: {node: '>=4'}
|
||||
|
|
92
webapp/src/components/atoms/AtomLegacyModal.vue
Normal file
92
webapp/src/components/atoms/AtomLegacyModal.vue
Normal 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>
|
|
@ -30,7 +30,7 @@
|
|||
{{ DateService.toShortString(entry[header.key]) }}
|
||||
</span>
|
||||
<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 v-else-if="header.type === TableHeaderType.CURRENCY">
|
||||
{{ CurrencyService.toString(entry[header.key]) }}
|
||||
|
@ -104,6 +104,7 @@
|
|||
|
||||
<script lang="ts" setup>
|
||||
import { PropType, onMounted, ref } from 'vue';
|
||||
import { parseISO } from 'date-fns';
|
||||
import { SettingsResponse } from '../../types/pocketbase.types';
|
||||
import { DateService } from '../../services/date.service';
|
||||
import { CurrencyService } from '../../services/currency.service';
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
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';
|
||||
|
||||
const modal = ref<InstanceType<typeof AtomModal>>();
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
<div class="flex items-center">
|
||||
<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>
|
||||
</td>
|
||||
|
@ -35,6 +35,7 @@
|
|||
|
||||
<script lang="ts" setup>
|
||||
import { PropType } from 'vue';
|
||||
import { parseISO } from 'date-fns';
|
||||
import { CurrencyService } from '../../services/currency.service';
|
||||
import { DateService } from '../../services/date.service';
|
||||
import { TransactionsResponse } from '../../types/pocketbase.types';
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
</button>
|
||||
<button
|
||||
class="btn gap-2"
|
||||
@click="$emit('confirm')"
|
||||
@click.prevent="$emit('confirm')"
|
||||
>
|
||||
<CheckCircleIcon class="h-6 w-6" />
|
||||
Bestätigen
|
||||
|
@ -30,7 +30,7 @@
|
|||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
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>>();
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { onKeyStroke, promiseTimeout } from '@vueuse/core';
|
||||
import { parseISO } from 'date-fns';
|
||||
import { AccountService } from '../../services/account.service';
|
||||
import { DateService } from '../../services/date.service';
|
||||
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 {
|
||||
const account = await AccountService.checkIn(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);
|
||||
message.value = DEFAULT_MESSAGE;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
|
|
|
@ -71,10 +71,13 @@
|
|||
<div class="stat-desc">in Spätschicht</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
|
||||
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()"
|
||||
>
|
||||
<SunIcon class="h-6 w-6" />
|
||||
|
@ -82,7 +85,7 @@
|
|||
</button>
|
||||
<button
|
||||
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()"
|
||||
>
|
||||
<MoonIcon class="h-6 w-6" />
|
||||
|
@ -91,14 +94,14 @@
|
|||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div
|
||||
v-if="DateService.isToday(new Date(company.earlyShiftPayed))"
|
||||
v-if="DateService.isToday(parseISO(company.earlyShiftPayed))"
|
||||
class="alert alert-info"
|
||||
>
|
||||
<InformationCircleIcon class="h-6 w-6" />
|
||||
Die Frühschicht wurde heute bereits bezahlt.
|
||||
</div>
|
||||
<div
|
||||
v-if="DateService.isToday(new Date(company.lateShiftPayed))"
|
||||
v-if="DateService.isToday(parseISO(company.lateShiftPayed))"
|
||||
class="alert alert-info"
|
||||
>
|
||||
<InformationCircleIcon class="h-6 w-6" />
|
||||
|
@ -131,14 +134,7 @@
|
|||
id="verify-modal"
|
||||
ref="verifyModal"
|
||||
title="Lohnauszahlung bestätigen"
|
||||
@confirm="async () => {
|
||||
verifyModal?.close();
|
||||
if(wageType) {
|
||||
await CompanyService.payWage(wageType);
|
||||
}
|
||||
wageType = undefined;
|
||||
init();
|
||||
}"
|
||||
@confirm="confirmWage"
|
||||
>
|
||||
<div class="flex flex-col gap-1">
|
||||
<p>Lohn wirklich für die {{ wageType == Shifts.EARLY ? 'Frühschicht' : 'Spätschicht' }} auszahlen?</p>
|
||||
|
@ -166,6 +162,7 @@
|
|||
<script lang="ts" setup>
|
||||
import { onMounted, onUnmounted, ref, watch } from 'vue';
|
||||
import { RecordSubscription, UnsubscribeFunc } from 'pocketbase';
|
||||
import { parseISO } from 'date-fns';
|
||||
import { AccountsResponse, CompaniesResponse, SettingsResponse, TransactionsResponse } from '../../types/pocketbase.types';
|
||||
import { AccountService } from '../../services/account.service';
|
||||
import { BankService } from '../../services/bank.service';
|
||||
|
@ -324,6 +321,15 @@ function logout() {
|
|||
getData();
|
||||
}
|
||||
|
||||
async function confirmWage() {
|
||||
verifyModal.value?.close();
|
||||
if(wageType.value) {
|
||||
await CompanyService.payWage(wageType.value);
|
||||
}
|
||||
wageType.value = undefined;
|
||||
init();
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
accountsSubscription.value?.();
|
||||
});
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { RecordSubscription, UnsubscribeFunc } from 'pocketbase';
|
||||
import { parseISO } from 'date-fns';
|
||||
import { AccountsListResponse, AccountsRecord, AccountsResponse, Collections } from '../types/pocketbase.types';
|
||||
import { DateService } from './date.service';
|
||||
import { PocketbaseService } from './pocketbase.service';
|
||||
|
@ -34,7 +35,7 @@ export class AccountService {
|
|||
}
|
||||
public static async getMissingStudents(): Promise<AccountsResponse[]> {
|
||||
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(
|
||||
|
|
|
@ -40,7 +40,7 @@ export class CompanyService {
|
|||
}
|
||||
public static async getCompany(): Promise<CompaniesResponse> {
|
||||
if(this.isAuthenticated()) {
|
||||
return API.authStore.model as unknown as CompaniesResponse;
|
||||
return COLLECTION.getFirstListItem(`id="${API.authStore.model?.id}"`);
|
||||
}
|
||||
throw new Error('Not authenticated');
|
||||
}
|
||||
|
@ -72,7 +72,6 @@ export class CompanyService {
|
|||
promises.push(BankService.addTransaction(
|
||||
account.id,
|
||||
(((account.wageFactor * settings.minWage) * (1 - (settings.incomeTax / 100)))) / 100,
|
||||
// 300 - 1 * -
|
||||
`Lohn für ${shift} bei ${company.name}`,
|
||||
));
|
||||
taxes += (account.wageFactor * settings.minWage) * (settings.incomeTax / 100);
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { parseISO } from 'date-fns';
|
||||
|
||||
let serverTime: Date;
|
||||
|
||||
export class DateService {
|
||||
|
@ -27,6 +29,7 @@ export class DateService {
|
|||
}
|
||||
public static isToday(date: Date): boolean {
|
||||
this.getServerTime();
|
||||
console.log('isToday', date, serverTime);
|
||||
if(!serverTime) { return false; }
|
||||
return date.getDate() === serverTime.getDate() &&
|
||||
date.getMonth() === serverTime.getMonth() &&
|
||||
|
@ -37,7 +40,7 @@ export class DateService {
|
|||
}
|
||||
public static async getServerTime(): Promise<Date> {
|
||||
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
|
||||
(import.meta as any).env.MODE === 'development' ? 'http://127.0.0.1:8090/api/time' : '/api/time',
|
||||
)).text());
|
||||
|
|
Loading…
Reference in a new issue