1
0
Fork 0
forked from Kispi/Core

feat(webapp): add backend for data view

This commit is contained in:
Simon Giesel 2022-07-26 23:38:22 +02:00
parent 4915be99b8
commit 5ba74d198a
10 changed files with 208 additions and 198 deletions

View file

@ -3,31 +3,38 @@
<table class="table w-full table-zebra">
<thead class="sticky top-0">
<tr>
<th v-for="(_, key) of data[0]">{{ key }}</th>
<th v-for="header in tableHeaders">{{ header.title }}</th>
</tr>
</thead>
<tbody>
<tr v-for="entries in data">
<tr v-for="entry in data">
<td
v-for="(entry, _, key) of entries"
:class="key >= 6 ? 'text-center' : null"
v-for="header in tableHeaders"
:class="[TableHeaderType.BUTTON_ACCOUNT, TableHeaderType.BUTTON_CONTACT].includes(header.type) ? 'text-center' : null"
>
<span v-if="key < 6">{{ entry }}</span>
<span v-if="header.type === TableHeaderType.STRING">
{{ entry[header.key] }}
</span>
<span v-else-if="header.type === TableHeaderType.DATE">
{{ DateService.toShortString(entry[header.key]) }}
</span>
<span v-else-if="header.type === TableHeaderType.DATETIME">
{{ DateService.toString(entry[header.key]) }}
</span>
<span v-else-if="header.type === TableHeaderType.CURRENCY">
{{ CurrencyService.toString(entry[header.key]) }}
</span>
<button
v-if="key == 6"
v-if="header.type === TableHeaderType.BUTTON_CONTACT"
class="btn btn-sm btn-ghost p-1"
>
<DocumentTextIcon
class="h-6 w-6"
/>
<DocumentTextIcon class="h-6 w-6" />
</button>
<button
v-if="key == 7"
v-if="header.type === TableHeaderType.BUTTON_ACCOUNT"
class="btn btn-sm btn-ghost p-1"
>
<CurrencyDollarIcon
class="h-6 w-6"
/>
<CurrencyDollarIcon class="h-6 w-6" />
</button>
</td>
</tr>
@ -36,17 +43,40 @@
</div>
</template>
<script lang="ts" setup>import { PropType } from 'vue';
<script lang="ts" setup>
import { PropType } from 'vue';
import { CurrencyService } from '../services/currency.service';
import { DateService } from '../services/date.service';
import { CurrencyDollarIcon, DocumentTextIcon } from '@heroicons/vue/outline';
defineProps({
tableHeaders: {
type: Array as PropType<{ title: string, key: string, type: TableHeaderType }[]>,
required: true,
},
data: {
type: Array as PropType<object[]>,
// Explicit allow any types as this is a dynamic list of data
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type: Array as PropType<any[]>,
required: true,
},
});
</script>
<style lang="scss" scoped>
<script lang="ts">
export enum TableHeaderType {
STRING,
DATE,
CURRENCY,
DATETIME,
BUTTON_CONTACT,
BUTTON_ACCOUNT,
}
</script>
<style lang="scss" scoped>
.table th:first-child {
position: inherit;
}
</style>

View file

@ -0,0 +1,42 @@
import { Databases, Models, Query } from 'appwrite';
import { AppwriteService } from './appwrite.service';
import { IAccount } from '../../interfaces/account.interface';
const DATABASE_ID = '62dfd5787755e7ed9fcd';
const ACCOUNTS_COLLECTION_ID = '62dfd6ca06197f9baa86';
const sdk = AppwriteService.getSDK();
const database = new Databases(sdk, DATABASE_ID);
export const AccountService = {
async getAccount(accountNumber: string): Promise<IAccount & Models.Document> {
const accountDocument = await database.listDocuments<IAccount & Models.Document>(
ACCOUNTS_COLLECTION_ID, [Query.equal('accountNumber', accountNumber)], 1);
return accountDocument.documents[0];
},
async updateBalance(accountNumber: string, balance: number): Promise<IAccount & Models.Document> {
const account = await this.getAccount(accountNumber);
await database.updateDocument<IAccount & Models.Document>(ACCOUNTS_COLLECTION_ID, account.$id, {
balance: account.balance + balance,
});
return account;
},
async getAllAccounts(): Promise<IAccount[]> {
let accountDocuments: Models.DocumentList<IAccount & Models.Document>,
offset = 0;
const accounts: IAccount[] = [];
do {
accountDocuments = await database.listDocuments<IAccount & Models.Document>(
ACCOUNTS_COLLECTION_ID, [], 100, offset);
accounts.push(...accountDocuments.documents.map(account => ({
accountNumber: account.accountNumber,
firstName: account.firstName,
lastName: account.lastName,
balance: account.balance,
transactions: [],
lastCheckIn: account.lastCheckIn,
})));
offset += 100;
} while(accountDocuments.total > offset);
return accounts;
},
};

View file

@ -1,10 +1,10 @@
import { Databases, Models, Query } from 'appwrite';
import { AccountService } from './account.service';
import { AppwriteService } from './appwrite.service';
import { IAccount } from '../../interfaces/account.interface';
import { Databases, Models, Query } from 'appwrite';
import { ITransaction } from '../../interfaces/transaction.interface';
const DATABASE_ID = '62dfd5787755e7ed9fcd';
const ACCOUNTS_COLLECTION_ID = '62dfd6ca06197f9baa86';
const TRANSACTIONS_COLLECTION_ID = '62dfd81b7671727b27cd';
const DEPOSIT_LABEL = 'Bargeldeinzahlung';
const SALARY_LABEL = 'Gehalt';
@ -14,35 +14,30 @@ const sdk = AppwriteService.getSDK();
export const BankService = {
async getAccountDetails(accountNumber: string): Promise<IAccount> {
const database = new Databases(sdk, DATABASE_ID);
const accountDocument = await database.listDocuments<IAccount & Models.Document>(
ACCOUNTS_COLLECTION_ID, [Query.equal('accountNumber', accountNumber)], 1);
const account = await AccountService.getAccount(accountNumber);
const transactionDocuments = await database.listDocuments<ITransaction & Models.Document>(
TRANSACTIONS_COLLECTION_ID, [Query.equal('accountNumber', accountNumber)], 100, 0, undefined, undefined, ['$createdAt'], ['DESC']);
const account: IAccount = {
accountNumber: accountDocument.documents[0].accountNumber,
firstName: accountDocument.documents[0].firstName,
lastName: accountDocument.documents[0].lastName,
balance: accountDocument.documents[0].balance,
return {
accountNumber: account.accountNumber,
firstName: account.firstName,
lastName: account.lastName,
balance: account.balance,
transactions: transactionDocuments.documents.map(transaction => ({
label: transaction.label,
amount: transaction.amount,
date: new Date(transaction.$createdAt * 1000),
})),
lastCheckIn: account.lastCheckIn,
};
return account;
},
async addTransaction(accountNumber: string, amount: number, type: TransactionType): Promise<ITransaction> {
const database = new Databases(sdk, DATABASE_ID);
const accountDocument = await database.listDocuments<IAccount & Models.Document>(
ACCOUNTS_COLLECTION_ID, [Query.equal('accountNumber', accountNumber)], 1);
const transactionDocument = await database.createDocument<ITransaction & Models.Document>(TRANSACTIONS_COLLECTION_ID, 'unique()', {
accountNumber: accountNumber,
label: type === TransactionType.DEPOSIT ? DEPOSIT_LABEL : (type === TransactionType.SALARY ? SALARY_LABEL : WITHDRAW_LABEL),
amount: (type === TransactionType.WITHDRAW ? -amount : amount),
});
await database.updateDocument<IAccount & Models.Document>(ACCOUNTS_COLLECTION_ID, accountDocument.documents[0].$id, {
balance: accountDocument.documents[0].balance + (type === TransactionType.WITHDRAW ? -amount : amount),
});
await AccountService.updateBalance(accountNumber, type === TransactionType.WITHDRAW ? -amount : amount);
return { label: transactionDocument.label, amount: transactionDocument.amount, date: new Date(transactionDocument.$createdAt * 1000) };
},
};

View file

@ -0,0 +1,33 @@
import { Databases, Models } from 'appwrite';
import { AccountService } from './account.service';
import { AppwriteService } from './appwrite.service';
import { IAccountData } from '../../interfaces/account-data.interface';
import { IPersonalInformation } from '../../interfaces/personal-information.interface';
const DATABASE_ID = '62dfd5787755e7ed9fcd';
const PERSONAL_INFORMATION_COLLECTION_ID = '62e004abb0cdae53b483';
const sdk = AppwriteService.getSDK();
const database = new Databases(sdk, DATABASE_ID);
export const DataService = {
async getAllData(): Promise<IAccountData[]> {
const accounts = (await AccountService.getAllAccounts()) as IAccountData[];
let offset = 0,
personalInformationDocuments: Models.DocumentList<IPersonalInformation & Models.Document>;
do {
personalInformationDocuments = await database.listDocuments<IPersonalInformation & Models.Document>(
PERSONAL_INFORMATION_COLLECTION_ID, [], 100, offset);
personalInformationDocuments.documents.forEach(personalInformation => {
const account = accounts.find(account => account.accountNumber === personalInformation.accountNumber);
if(account) {
account.name = `${account.firstName} ${account.lastName}`;
account.birthday = personalInformation.birthday;
account.address = personalInformation.address;
}
},
);
offset += 100;
} while(personalInformationDocuments.total > offset);
return accounts;
},
};

View file

@ -1,5 +1,11 @@
export class DateService {
public static toString(date: Date): string {
public static toString(date: Date | number): string {
if(typeof date === 'number') {
date = new Date(date * 1000);
}
if(!date) {
return 'n/a';
}
return date.toLocaleDateString('de-DE', {
day: '2-digit',
month: '2-digit',
@ -9,4 +15,17 @@ export class DateService {
second: '2-digit',
});
}
public static toShortString(date: Date | number): string {
if(typeof date === 'number') {
date = new Date(date * 1000);
}
if(!date) {
return 'n/a';
}
return date.toLocaleDateString('de-DE', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
});
}
}

View file

@ -12,7 +12,7 @@
:transactions="accountDetails.transactions"
/>
</div>
<div class="fixed flex place-content-center lg:gap-32 gap-16 bg-base-100 bottom-0 lg:w-[calc(100%-16rem)] w-full p-5">
<div class="fixed flex place-content-center lg:gap-32 gap-16 bg-base-100 bottom-0 lg:w-[calc(100%-16rem)] p-5">
<MoleculeInputModal
:id="depositModalId"
title="Einzahlung"
@ -62,15 +62,15 @@
</template>
<script lang="ts" setup>
import { ITransaction } from '../../interfaces/transaction.interface';
import { CurrencyService } from '../services/currency.service';
import { CurrencyDollarIcon, ChevronDownIcon, ChevronUpIcon } from '@heroicons/vue/outline';
import MoleculeTransactionTable from '../molecules/MoleculeTransactionTable.vue';
import MoleculeInputModal from '../molecules/MoleculeInputModal.vue';
import { onMounted, ref } from 'vue';
import { onKeyStroke } from '@vueuse/core';
import { BankService, TransactionType } from '../services/bank.service';
import { onMounted, ref } from 'vue';
import { CurrencyService } from '../services/currency.service';
import { IAccount } from '../../interfaces/account.interface';
import { ITransaction } from '../../interfaces/transaction.interface';
import { CurrencyDollarIcon, ChevronDownIcon, ChevronUpIcon } from '@heroicons/vue/outline';
import MoleculeInputModal from '../molecules/MoleculeInputModal.vue';
import MoleculeTransactionTable from '../molecules/MoleculeTransactionTable.vue';
const DEMO_ACCOUNT_ID = '0000123456';

View file

@ -1,6 +1,10 @@
<template>
<div class="lg:p-6">
<MoleculeDataTable :data="data" />
<MoleculeDataTable
v-if="data"
:table-headers="tableHeaders"
:data="data"
/>
<MoleculeAddAccountModal :id="addAccountModalId" />
<label
class="btn btn-primary btn-lg btn-circle shadow-2xl fixed bottom-8 right-8"
@ -12,186 +16,60 @@
</template>
<script lang="ts" setup>
import MoleculeDataTable from '../molecules/MoleculeDataTable.vue';
import { onMounted, ref } from 'vue';
import { DataService } from '../services/data.service';
import { PlusIcon } from '@heroicons/vue/outline';
import MoleculeAddAccountModal from '../molecules/MoleculeAddAccountModal.vue';
import MoleculeDataTable, { TableHeaderType } from '../molecules/MoleculeDataTable.vue';
const data = ref();
const addAccountModalId = 'add-account-modal';
/* eslint-disable @typescript-eslint/naming-convention */
const data = [
const tableHeaders = [
{
'Kontonummer': '0000123456',
'Name': 'John Doe',
'Geburtsdatum': '13.11.1998',
'Kontostand': '150,00 Öro',
'Adresse': 'Test-Straße 1, 74613 Öhringen',
'Letzter': '12.07.2022 17:39',
'Kontakt': null,
'Trans.': null,
title: 'Kontonummer',
key: 'accountNumber',
type: TableHeaderType.STRING,
},
{
'Kontonummer': '0000123456',
'Name': 'John Doe',
'Geburtsdatum': '13.11.1998',
'Kontostand': '150,00 Öro',
'Adresse': 'Test-Straße 1, 74613 Öhringen',
'Letzter': '12.07.2022 17:39',
'Kontakt': null,
'Trans.': null,
title: 'Name',
key: 'name',
type: TableHeaderType.STRING,
},
{
'Kontonummer': '0000123456',
'Name': 'John Doe',
'Geburtsdatum': '13.11.1998',
'Kontostand': '150,00 Öro',
'Adresse': 'Test-Straße 1, 74613 Öhringen',
'Letzter': '12.07.2022 17:39',
'Kontakt': null,
'Trans.': null,
title: 'Geburtsdatum',
key: 'birthday',
type: TableHeaderType.DATE,
},
{
'Kontonummer': '0000123456',
'Name': 'John Doe',
'Geburtsdatum': '13.11.1998',
'Kontostand': '150,00 Öro',
'Adresse': 'Test-Straße 1, 74613 Öhringen',
'Letzter': '12.07.2022 17:39',
'Kontakt': null,
'Trans.': null,
title: 'Kontostand',
key: 'balance',
type: TableHeaderType.CURRENCY,
},
{
'Kontonummer': '0000123456',
'Name': 'John Doe',
'Geburtsdatum': '13.11.1998',
'Kontostand': '150,00 Öro',
'Adresse': 'Test-Straße 1, 74613 Öhringen',
'Letzter': '12.07.2022 17:39',
'Kontakt': null,
'Trans.': null,
title: 'Adresse',
key: 'address',
type: TableHeaderType.STRING,
},
{
'Kontonummer': '0000123456',
'Name': 'John Doe',
'Geburtsdatum': '13.11.1998',
'Kontostand': '150,00 Öro',
'Adresse': 'Test-Straße 1, 74613 Öhringen',
'Letzter': '12.07.2022 17:39',
'Kontakt': null,
'Trans.': null,
title: 'Letzter Login',
key: 'lastCheckIn',
type: TableHeaderType.DATETIME,
},
{
'Kontonummer': '0000123456',
'Name': 'John Doe',
'Geburtsdatum': '13.11.1998',
'Kontostand': '150,00 Öro',
'Adresse': 'Test-Straße 1, 74613 Öhringen',
'Letzter': '12.07.2022 17:39',
'Kontakt': null,
'Trans.': null,
title: 'Kontakt',
key: '',
type: TableHeaderType.BUTTON_CONTACT,
},
{
'Kontonummer': '0000123456',
'Name': 'John Doe',
'Geburtsdatum': '13.11.1998',
'Kontostand': '150,00 Öro',
'Adresse': 'Test-Straße 1, 74613 Öhringen',
'Letzter': '12.07.2022 17:39',
'Kontakt': null,
'Trans.': null,
title: 'Trans.',
key: '',
type: TableHeaderType.BUTTON_ACCOUNT,
},
{
'Kontonummer': '0000123456',
'Name': 'John Doe',
'Geburtsdatum': '13.11.1998',
'Kontostand': '150,00 Öro',
'Adresse': 'Test-Straße 1, 74613 Öhringen',
'Letzter': '12.07.2022 17:39',
'Kontakt': null,
'Trans.': null,
},
{
'Kontonummer': '0000123456',
'Name': 'John Doe',
'Geburtsdatum': '13.11.1998',
'Kontostand': '150,00 Öro',
'Adresse': 'Test-Straße 1, 74613 Öhringen',
'Letzter': '12.07.2022 17:39',
'Kontakt': null,
'Trans.': null,
},
{
'Kontonummer': '0000123456',
'Name': 'John Doe',
'Geburtsdatum': '13.11.1998',
'Kontostand': '150,00 Öro',
'Adresse': 'Test-Straße 1, 74613 Öhringen',
'Letzter': '12.07.2022 17:39',
'Kontakt': null,
'Trans.': null,
},
{
'Kontonummer': '0000123456',
'Name': 'John Doe',
'Geburtsdatum': '13.11.1998',
'Kontostand': '150,00 Öro',
'Adresse': 'Test-Straße 1, 74613 Öhringen',
'Letzter': '12.07.2022 17:39',
'Kontakt': null,
'Trans.': null,
},
{
'Kontonummer': '0000123456',
'Name': 'John Doe',
'Geburtsdatum': '13.11.1998',
'Kontostand': '150,00 Öro',
'Adresse': 'Test-Straße 1, 74613 Öhringen',
'Letzter': '12.07.2022 17:39',
'Kontakt': null,
'Trans.': null,
},
{
'Kontonummer': '0000123456',
'Name': 'John Doe',
'Geburtsdatum': '13.11.1998',
'Kontostand': '150,00 Öro',
'Adresse': 'Test-Straße 1, 74613 Öhringen',
'Letzter': '12.07.2022 17:39',
'Kontakt': null,
'Trans.': null,
},
{
'Kontonummer': '0000123456',
'Name': 'John Doe',
'Geburtsdatum': '13.11.1998',
'Kontostand': '150,00 Öro',
'Adresse': 'Test-Straße 1, 74613 Öhringen',
'Letzter': '12.07.2022 17:39',
'Kontakt': null,
'Trans.': null,
},
{
'Kontonummer': '0000123456',
'Name': 'John Doe',
'Geburtsdatum': '13.11.1998',
'Kontostand': '150,00 Öro',
'Adresse': 'Test-Straße 1, 74613 Öhringen',
'Letzter': '12.07.2022 17:39',
'Kontakt': null,
'Trans.': null,
},
{
'Kontonummer': '0000123456',
'Name': 'John Doe',
'Geburtsdatum': '13.11.1998',
'Kontostand': '150,00 Öro',
'Adresse': 'Test-Straße 1, 74613 Öhringen',
'Letzter': '12.07.2022 17:39',
'Kontakt': null,
'Trans.': null,
},
/* eslint-enable @typescript-eslint/naming-convention */
];
onMounted(async () => {
data.value = await DataService.getAllData();
});
</script>
<style lang="scss" scoped>

View file

@ -0,0 +1,7 @@
import { IAccount } from './account.interface';
export interface IAccountData extends IAccount {
name: string;
birthday: number;
address: string;
}

View file

@ -6,4 +6,5 @@ export interface IAccount {
lastName: string;
balance: number;
transactions: ITransaction[];
lastCheckIn: number;
}

View file

@ -0,0 +1,5 @@
export interface IPersonalInformation {
accountNumber: string;
birthday: number;
address: string;
}