1
0
Fork 0
forked from Kispi/Core

feat(core): add employer portal with mock data
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Simon Giesel 2023-06-30 16:18:07 +02:00
parent c94bf147f5
commit 3e1437300f
13 changed files with 300 additions and 184 deletions

View file

@ -162,88 +162,6 @@
"deleteRule": null, "deleteRule": null,
"options": {} "options": {}
}, },
{
"id": "74ftooxenpeq14b",
"name": "accounts",
"type": "base",
"system": false,
"schema": [
{
"id": "as1gvc1r",
"name": "accountNumber",
"type": "text",
"system": false,
"required": true,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"id": "vxeq20lk",
"name": "firstName",
"type": "text",
"system": false,
"required": true,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"id": "bsinpdk7",
"name": "lastName",
"type": "text",
"system": false,
"required": true,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"id": "syfivtki",
"name": "lastCheckIn",
"type": "date",
"system": false,
"required": false,
"options": {
"min": "",
"max": ""
}
},
{
"id": "pkoynx7h",
"name": "personalData",
"type": "relation",
"system": false,
"required": false,
"options": {
"collectionId": "p0gixtx6jwria0d",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": 1,
"displayFields": [
"firstNameParent",
"lastNameParent",
"email"
]
}
}
],
"indexes": [
"CREATE UNIQUE INDEX `idx_mPdvrAQ` ON `accounts` (`accountNumber`)"
],
"listRule": "",
"viewRule": null,
"createRule": null,
"updateRule": "@request.data.id = null && @request.data.accountNumber = null && @request.data.firstName = null && @request.data.lastName = null && @request.data.created = null && @request.data.updated = null",
"deleteRule": null,
"options": {}
},
{ {
"id": "c3zz98wpbn7m6zw", "id": "c3zz98wpbn7m6zw",
"name": "accountsList", "name": "accountsList",
@ -358,5 +276,147 @@
"updateRule": null, "updateRule": null,
"deleteRule": null, "deleteRule": null,
"options": {} "options": {}
},
{
"id": "s854d2w72fvyl54",
"name": "companies",
"type": "auth",
"system": false,
"schema": [],
"indexes": [],
"listRule": null,
"viewRule": null,
"createRule": null,
"updateRule": null,
"deleteRule": null,
"options": {
"allowEmailAuth": true,
"allowOAuth2Auth": true,
"allowUsernameAuth": true,
"exceptEmailDomains": null,
"manageRule": null,
"minPasswordLength": 8,
"onlyEmailDomains": null,
"requireEmail": false
}
},
{
"id": "74ftooxenpeq14b",
"name": "accounts",
"type": "base",
"system": false,
"schema": [
{
"id": "as1gvc1r",
"name": "accountNumber",
"type": "text",
"system": false,
"required": true,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"id": "vxeq20lk",
"name": "firstName",
"type": "text",
"system": false,
"required": true,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"id": "bsinpdk7",
"name": "lastName",
"type": "text",
"system": false,
"required": true,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"id": "syfivtki",
"name": "lastCheckIn",
"type": "date",
"system": false,
"required": false,
"options": {
"min": "",
"max": ""
}
},
{
"id": "pkoynx7h",
"name": "personalData",
"type": "relation",
"system": false,
"required": false,
"options": {
"collectionId": "p0gixtx6jwria0d",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": 1,
"displayFields": [
"firstNameParent",
"lastNameParent",
"email"
]
}
},
{
"id": "gwrzizpu",
"name": "company",
"type": "relation",
"system": false,
"required": true,
"options": {
"collectionId": "s854d2w72fvyl54",
"cascadeDelete": false,
"minSelect": null,
"maxSelect": 1,
"displayFields": []
}
},
{
"id": "rtvjzpxw",
"name": "shift",
"type": "text",
"system": false,
"required": true,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"id": "ma5ckyk1",
"name": "wageFactor",
"type": "number",
"system": false,
"required": true,
"options": {
"min": 0,
"max": null
}
}
],
"indexes": [
"CREATE UNIQUE INDEX `idx_mPdvrAQ` ON `accounts` (`accountNumber`)"
],
"listRule": "",
"viewRule": null,
"createRule": null,
"updateRule": "(@request.data.id = null && @request.data.accountNumber = null && @request.data.firstName = null && @request.data.lastName = null && @request.data.created = null && @request.data.updated = null && @request.data.company = null && @request.data.shift = null && @request.data.wageFactor = null) || (@request.data.id = null && @request.data.accountNumber = null && @request.data.firstName = null && @request.data.lastName = null && @request.data.created = null && @request.data.updated = null && @request.data.company = null && @request.data.shift = null && @request.auth.id = company.id)",
"deleteRule": null,
"options": {}
} }
] ]

View file

@ -21,6 +21,7 @@
"vue-router": "4.2.2" "vue-router": "4.2.2"
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/container-queries": "^0.1.1",
"@tailwindcss/typography": "^0.5.9", "@tailwindcss/typography": "^0.5.9",
"@types/canvas-confetti": "^1.6.0", "@types/canvas-confetti": "^1.6.0",
"@typescript-eslint/eslint-plugin": "^5.60.1", "@typescript-eslint/eslint-plugin": "^5.60.1",

View file

@ -28,6 +28,9 @@ dependencies:
version: 4.2.2(vue@3.3.4) version: 4.2.2(vue@3.3.4)
devDependencies: devDependencies:
'@tailwindcss/container-queries':
specifier: ^0.1.1
version: 0.1.1(tailwindcss@3.3.2)
'@tailwindcss/typography': '@tailwindcss/typography':
specifier: ^0.5.9 specifier: ^0.5.9
version: 0.5.9(tailwindcss@3.3.2) version: 0.5.9(tailwindcss@3.3.2)
@ -460,6 +463,14 @@ packages:
dev: true dev: true
optional: true optional: true
/@tailwindcss/container-queries@0.1.1(tailwindcss@3.3.2):
resolution: {integrity: sha512-p18dswChx6WnTSaJCSGx6lTmrGzNNvm2FtXmiO6AuA1V4U5REyoqwmT6kgAsIMdjo07QdAfYXHJ4hnMtfHzWgA==}
peerDependencies:
tailwindcss: '>=3.2.0'
dependencies:
tailwindcss: 3.3.2
dev: true
/@tailwindcss/typography@0.5.9(tailwindcss@3.3.2): /@tailwindcss/typography@0.5.9(tailwindcss@3.3.2):
resolution: {integrity: sha512-t8Sg3DyynFysV9f4JDOVISGsjazNb48AeIYQwcL+Bsq5uf4RYL75C1giZ43KISjeDGBaTN3Kxh7Xj/vRSMJUUg==} resolution: {integrity: sha512-t8Sg3DyynFysV9f4JDOVISGsjazNb48AeIYQwcL+Bsq5uf4RYL75C1giZ43KISjeDGBaTN3Kxh7Xj/vRSMJUUg==}
peerDependencies: peerDependencies:

View file

@ -1,6 +1,7 @@
<template> <template>
<div class="relative"> <div class="relative">
<AtomInput <AtomInput
ref="input"
v-model="modelValue" v-model="modelValue"
type="number" type="number"
class="pr-[5.5rem] text-right" class="pr-[5.5rem] text-right"

View file

@ -1,5 +1,8 @@
<template> <template>
<div class="h-full overflow-x-auto rounded-lg"> <div
v-if="settings"
class="h-full overflow-x-auto rounded-lg"
>
<table class="table-pin-rows table-zebra table"> <table class="table-pin-rows table-zebra table">
<thead class="uppercase"> <thead class="uppercase">
<tr> <tr>
@ -39,6 +42,17 @@
> >
<CurrencyDollarIcon class="h-6 w-6" /> <CurrencyDollarIcon class="h-6 w-6" />
</RouterLink> </RouterLink>
<span
v-if="header.type === TableHeaderType.WAGE"
:for="contactModalId"
class="flex items-center gap-2"
@click="$emit('open-contact-modal', entry.id)"
>
<span>{{ CurrencyService.toString(entry[header.key] * settings.minWage) }} / h</span>
<div class="btn-ghost btn-sm btn p-1">
<PencilSquareIcon class="h-5 w-5" />
</div>
</span>
</td> </td>
</tr> </tr>
</tbody> </tbody>
@ -47,10 +61,14 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { PropType } from 'vue'; import { PropType, onMounted, ref } from 'vue';
import { DateService } from '../../services/date.service'; import { DateService } from '../../services/date.service';
import { CurrencyService } from '../../services/currency.service'; import { CurrencyService } from '../../services/currency.service';
import { CurrencyDollarIcon, DocumentTextIcon } from '@heroicons/vue/24/outline'; import { CurrencyDollarIcon, DocumentTextIcon, PencilSquareIcon } from '@heroicons/vue/24/outline';
import { SettingsService } from '../../services/settings.service';
import { SettingsResponse } from '../../types/pocketbase.types';
const settings = ref<SettingsResponse>();
defineProps({ defineProps({
tableHeaders: { tableHeaders: {
@ -70,6 +88,10 @@ defineProps({
}, },
}); });
onMounted(async () => {
settings.value = await SettingsService.getSettings();
});
defineEmits(['open-contact-modal']); defineEmits(['open-contact-modal']);
</script> </script>
@ -81,6 +103,7 @@ export enum TableHeaderType {
DATETIME, DATETIME,
BUTTON_CONTACT, BUTTON_CONTACT,
BUTTON_ACCOUNT, BUTTON_ACCOUNT,
WAGE,
} }
</script> </script>

View file

@ -7,7 +7,7 @@
<div class="flex place-items-center justify-between"> <div class="flex place-items-center justify-between">
<span>{{ inputLabel }}</span> <span>{{ inputLabel }}</span>
<AtomCurrencyInput <AtomCurrencyInput
ref="input" v-model="amount"
class="w-4/12" class="w-4/12"
@keyup.enter="handleSubmit()" @keyup.enter="handleSubmit()"
@keyup.esc="abortButton.click()" @keyup.esc="abortButton.click()"
@ -39,7 +39,7 @@ import { CheckCircleIcon, XCircleIcon } from '@heroicons/vue/24/outline';
import AtomCurrencyInput from '../atoms/AtomCurrencyInput.vue'; import AtomCurrencyInput from '../atoms/AtomCurrencyInput.vue';
import AtomModal from '../atoms/AtomModal.vue'; import AtomModal from '../atoms/AtomModal.vue';
const input = ref(); const amount = ref();
const abortButton = ref(); const abortButton = ref();
defineProps({ defineProps({
@ -66,16 +66,16 @@ const emit = defineEmits(['submit']);
function handleOpen(event: {id: string, open: boolean}) { function handleOpen(event: {id: string, open: boolean}) {
if(event.open) { if(event.open) {
// This is (currently) the only method to focus a input field inside a custom component. // This is (currently) the only method to focus a input field inside a custom component.
input.value.$refs.input.$refs.input.value = ''; // input.value.$el.$refs.input.$refs.input.value = '';
input.value.$refs.input.$refs.input.focus(); // input.value.$el.$refs.input.$refs.input.focus();
} }
} }
function handleSubmit() { function handleSubmit() {
if(input.value.$refs.input.$refs.input.value <= 0 || input.value.$refs.input.$refs.input.value > 100) { if(amount.value <= 0) {
return; return;
} }
emit('submit', input.value.$refs.input.$refs.input.value); emit('submit', amount.value);
abortButton.value.click(); abortButton.value.click();
} }
</script> </script>

View file

@ -30,19 +30,24 @@ const initAccounts = ref<AccountsListResponse<number, string>[]>([]);
const searchQuery = ref(''); const searchQuery = ref('');
const tableHeaders = [ const tableHeaders = [
{ {
title: 'Name', title: 'Vorname',
key: 'name', key: 'firstName',
type: TableHeaderType.STRING,
},
{
title: 'Nachname',
key: 'lastName',
type: TableHeaderType.STRING, type: TableHeaderType.STRING,
}, },
{ {
title: 'Schicht', title: 'Schicht',
key: 'lastCheckIn', key: 'shift',
type: TableHeaderType.DATETIME, type: TableHeaderType.STRING,
}, },
{ {
title: 'Lohn', title: 'Lohn',
key: 'balance', key: 'wageFactor',
type: TableHeaderType.CURRENCY, type: TableHeaderType.WAGE,
}, },
]; ];
@ -54,7 +59,7 @@ useEventBus<boolean>('isAuthenticated').on(state => {
}); });
async function getData() { async function getData() {
initAccounts.value = await AccountService.getAccounts(); initAccounts.value = await AccountService.getAccountsByCompanyId('c09ncb3l2pi6i0u');
accounts.value = initAccounts.value; accounts.value = initAccounts.value;
} }

View file

@ -1,5 +1,6 @@
<template> <template>
<OrganismAuthWrapper class="flex h-full flex-wrap gap-4 p-6"> <div class="@container">
<OrganismAuthWrapper class="flex h-full flex-wrap justify-center gap-4 p-6 @[832px]:justify-normal">
<AtomCard class="w-96"> <AtomCard class="w-96">
<template #title> <template #title>
<WrenchScrewdriverIcon class="h-6 w-6" />Einstellungen <WrenchScrewdriverIcon class="h-6 w-6" />Einstellungen
@ -84,6 +85,7 @@
</div> </div>
</AtomCard> </AtomCard>
</OrganismAuthWrapper> </OrganismAuthWrapper>
</div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -115,9 +117,10 @@ const settingsSaveError = ref<boolean>(false);
onMounted(async () => { onMounted(async () => {
settings.value = await SettingsService.getSettings(); settings.value = await SettingsService.getSettings();
minWage.value = settings.value.minWage; minWage.value = settings.value.minWage / 100;
incomeTax.value = settings.value.incomeTax; incomeTax.value = settings.value.incomeTax;
maxWageFactor.value = settings.value.maxWageFactor; maxWageFactor.value = settings.value.maxWageFactor;
radioUrl.value = settings.value.radioUrl;
}); });
async function saveSettings() { async function saveSettings() {

View file

@ -1,4 +1,3 @@
import { RecordSubscription, UnsubscribeFunc } from 'pocketbase'; import { RecordSubscription, UnsubscribeFunc } from 'pocketbase';
import { AccountsDataResponse, AccountsListResponse, AccountsResponse, Collections } from '../types/pocketbase.types'; import { AccountsDataResponse, AccountsListResponse, AccountsResponse, Collections } from '../types/pocketbase.types';
import { PocketbaseService } from './pocketbase.service'; import { PocketbaseService } from './pocketbase.service';
@ -20,6 +19,9 @@ export class AccountService {
public static getAccountDetails(id: string): Promise<AccountsResponse<{personalData: AccountsDataResponse}>> { public static getAccountDetails(id: string): Promise<AccountsResponse<{personalData: AccountsDataResponse}>> {
return COLLECTION.getOne(id, { expand: 'personalData' }); return COLLECTION.getOne(id, { expand: 'personalData' });
} }
public static getAccountsByCompanyId(companyId: string): Promise<AccountsListResponse<number, string>[]> {
return COLLECTION.getFullList({ filter: `company.id = "${companyId}"` });
}
public static async subscribeToAccountChanges( public static async subscribeToAccountChanges(
callback: (data: RecordSubscription<AccountsResponse>)=> void, callback: (data: RecordSubscription<AccountsResponse>)=> void,
): Promise<UnsubscribeFunc> { ): Promise<UnsubscribeFunc> {

View file

@ -3,12 +3,12 @@ export class CurrencyService {
return `${(value / 100).toLocaleString('de-DE', { return `${(value / 100).toLocaleString('de-DE', {
minimumFractionDigits: 2, minimumFractionDigits: 2,
maximumFractionDigits: 2, maximumFractionDigits: 2,
})} Öro`; })} Batzen`;
} }
public static toSignedString(value: number): string { public static toSignedString(value: number): string {
return `${(value / 100) > 0 ? '+' : ''}${(value / 100).toLocaleString('de', { return `${(value / 100) > 0 ? '+' : ''}${(value / 100).toLocaleString('de', {
minimumFractionDigits: 2, minimumFractionDigits: 2,
maximumFractionDigits: 2, maximumFractionDigits: 2,
})} Öro`; })} Batzen`;
} }
} }

View file

@ -8,6 +8,7 @@ export class SettingsService {
return COLLECTION.getFirstListItem('', { sort: '-created' }); return COLLECTION.getFirstListItem('', { sort: '-created' });
} }
public static async setSettings(settings: SettingsRecord): Promise<SettingsResponse> { public static async setSettings(settings: SettingsRecord): Promise<SettingsResponse> {
settings.minWage = settings.minWage * 100;
return COLLECTION.create(settings); return COLLECTION.create(settings);
} }
} }

View file

@ -6,6 +6,7 @@ export enum Collections {
Accounts = "accounts", Accounts = "accounts",
AccountsData = "accountsData", AccountsData = "accountsData",
AccountsList = "accountsList", AccountsList = "accountsList",
Companies = "companies",
Settings = "settings", Settings = "settings",
Transactions = "transactions", Transactions = "transactions",
} }
@ -40,6 +41,9 @@ export type AccountsRecord = {
lastName: string lastName: string
lastCheckIn?: IsoDateString lastCheckIn?: IsoDateString
personalData?: RecordIdString personalData?: RecordIdString
company: RecordIdString
shift: string
wageFactor: number
} }
export type AccountsDataRecord = { export type AccountsDataRecord = {
@ -60,6 +64,8 @@ export type AccountsListRecord<Tbalance = unknown, Tname = unknown> = {
balance?: null | Tbalance balance?: null | Tbalance
} }
export type CompaniesRecord = never
export type SettingsRecord = { export type SettingsRecord = {
minWage: number minWage: number
incomeTax: number incomeTax: number
@ -77,6 +83,7 @@ export type TransactionsRecord = {
export type AccountsResponse<Texpand = unknown> = Required<AccountsRecord> & BaseSystemFields<Texpand> export type AccountsResponse<Texpand = unknown> = Required<AccountsRecord> & BaseSystemFields<Texpand>
export type AccountsDataResponse<Texpand = unknown> = Required<AccountsDataRecord> & BaseSystemFields<Texpand> export type AccountsDataResponse<Texpand = unknown> = Required<AccountsDataRecord> & BaseSystemFields<Texpand>
export type AccountsListResponse<Tbalance = unknown, Tname = unknown, Texpand = unknown> = Required<AccountsListRecord<Tbalance, Tname>> & BaseSystemFields<Texpand> export type AccountsListResponse<Tbalance = unknown, Tname = unknown, Texpand = unknown> = Required<AccountsListRecord<Tbalance, Tname>> & BaseSystemFields<Texpand>
export type CompaniesResponse<Texpand = unknown> = Required<CompaniesRecord> & AuthSystemFields<Texpand>
export type SettingsResponse<Texpand = unknown> = Required<SettingsRecord> & BaseSystemFields<Texpand> export type SettingsResponse<Texpand = unknown> = Required<SettingsRecord> & BaseSystemFields<Texpand>
export type TransactionsResponse<Texpand = unknown> = Required<TransactionsRecord> & BaseSystemFields<Texpand> export type TransactionsResponse<Texpand = unknown> = Required<TransactionsRecord> & BaseSystemFields<Texpand>
@ -86,6 +93,7 @@ export type CollectionRecords = {
accounts: AccountsRecord accounts: AccountsRecord
accountsData: AccountsDataRecord accountsData: AccountsDataRecord
accountsList: AccountsListRecord accountsList: AccountsListRecord
companies: CompaniesRecord
settings: SettingsRecord settings: SettingsRecord
transactions: TransactionsRecord transactions: TransactionsRecord
} }
@ -94,6 +102,7 @@ export type CollectionResponses = {
accounts: AccountsResponse accounts: AccountsResponse
accountsData: AccountsDataResponse accountsData: AccountsDataResponse
accountsList: AccountsListResponse accountsList: AccountsListResponse
companies: CompaniesResponse
settings: SettingsResponse settings: SettingsResponse
transactions: TransactionsResponse transactions: TransactionsResponse
} }

View file

@ -6,7 +6,7 @@ module.exports = {
theme: { theme: {
extend: {}, extend: {},
}, },
plugins: [require('@tailwindcss/typography'), require('daisyui')], plugins: [require('@tailwindcss/typography'), require('daisyui'), require('@tailwindcss/container-queries')],
daisyui: { daisyui: {
logs: false, logs: false,
themes: [ themes: [