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"> <table class="table w-full table-zebra">
<thead class="sticky top-0"> <thead class="sticky top-0">
<tr> <tr>
<th v-for="(_, key) of data[0]">{{ key }}</th> <th v-for="header in tableHeaders">{{ header.title }}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-for="entries in data"> <tr v-for="entry in data">
<td <td
v-for="(entry, _, key) of entries" v-for="header in tableHeaders"
:class="key >= 6 ? 'text-center' : null" :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 <button
v-if="key == 6" v-if="header.type === TableHeaderType.BUTTON_CONTACT"
class="btn btn-sm btn-ghost p-1" class="btn btn-sm btn-ghost p-1"
> >
<DocumentTextIcon <DocumentTextIcon class="h-6 w-6" />
class="h-6 w-6"
/>
</button> </button>
<button <button
v-if="key == 7" v-if="header.type === TableHeaderType.BUTTON_ACCOUNT"
class="btn btn-sm btn-ghost p-1" class="btn btn-sm btn-ghost p-1"
> >
<CurrencyDollarIcon <CurrencyDollarIcon class="h-6 w-6" />
class="h-6 w-6"
/>
</button> </button>
</td> </td>
</tr> </tr>
@ -36,17 +43,40 @@
</div> </div>
</template> </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'; import { CurrencyDollarIcon, DocumentTextIcon } from '@heroicons/vue/outline';
defineProps({ defineProps({
tableHeaders: {
type: Array as PropType<{ title: string, key: string, type: TableHeaderType }[]>,
required: true,
},
data: { 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, required: true,
}, },
}); });
</script> </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> </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 { AppwriteService } from './appwrite.service';
import { IAccount } from '../../interfaces/account.interface'; import { IAccount } from '../../interfaces/account.interface';
import { Databases, Models, Query } from 'appwrite';
import { ITransaction } from '../../interfaces/transaction.interface'; import { ITransaction } from '../../interfaces/transaction.interface';
const DATABASE_ID = '62dfd5787755e7ed9fcd'; const DATABASE_ID = '62dfd5787755e7ed9fcd';
const ACCOUNTS_COLLECTION_ID = '62dfd6ca06197f9baa86';
const TRANSACTIONS_COLLECTION_ID = '62dfd81b7671727b27cd'; const TRANSACTIONS_COLLECTION_ID = '62dfd81b7671727b27cd';
const DEPOSIT_LABEL = 'Bargeldeinzahlung'; const DEPOSIT_LABEL = 'Bargeldeinzahlung';
const SALARY_LABEL = 'Gehalt'; const SALARY_LABEL = 'Gehalt';
@ -14,35 +14,30 @@ const sdk = AppwriteService.getSDK();
export const BankService = { export const BankService = {
async getAccountDetails(accountNumber: string): Promise<IAccount> { async getAccountDetails(accountNumber: string): Promise<IAccount> {
const database = new Databases(sdk, DATABASE_ID); const database = new Databases(sdk, DATABASE_ID);
const accountDocument = await database.listDocuments<IAccount & Models.Document>( const account = await AccountService.getAccount(accountNumber);
ACCOUNTS_COLLECTION_ID, [Query.equal('accountNumber', accountNumber)], 1);
const transactionDocuments = await database.listDocuments<ITransaction & Models.Document>( const transactionDocuments = await database.listDocuments<ITransaction & Models.Document>(
TRANSACTIONS_COLLECTION_ID, [Query.equal('accountNumber', accountNumber)], 100, 0, undefined, undefined, ['$createdAt'], ['DESC']); TRANSACTIONS_COLLECTION_ID, [Query.equal('accountNumber', accountNumber)], 100, 0, undefined, undefined, ['$createdAt'], ['DESC']);
const account: IAccount = { return {
accountNumber: accountDocument.documents[0].accountNumber, accountNumber: account.accountNumber,
firstName: accountDocument.documents[0].firstName, firstName: account.firstName,
lastName: accountDocument.documents[0].lastName, lastName: account.lastName,
balance: accountDocument.documents[0].balance, balance: account.balance,
transactions: transactionDocuments.documents.map(transaction => ({ transactions: transactionDocuments.documents.map(transaction => ({
label: transaction.label, label: transaction.label,
amount: transaction.amount, amount: transaction.amount,
date: new Date(transaction.$createdAt * 1000), date: new Date(transaction.$createdAt * 1000),
})), })),
lastCheckIn: account.lastCheckIn,
}; };
return account;
}, },
async addTransaction(accountNumber: string, amount: number, type: TransactionType): Promise<ITransaction> { async addTransaction(accountNumber: string, amount: number, type: TransactionType): Promise<ITransaction> {
const database = new Databases(sdk, DATABASE_ID); 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()', { const transactionDocument = await database.createDocument<ITransaction & Models.Document>(TRANSACTIONS_COLLECTION_ID, 'unique()', {
accountNumber: accountNumber, accountNumber: accountNumber,
label: type === TransactionType.DEPOSIT ? DEPOSIT_LABEL : (type === TransactionType.SALARY ? SALARY_LABEL : WITHDRAW_LABEL), label: type === TransactionType.DEPOSIT ? DEPOSIT_LABEL : (type === TransactionType.SALARY ? SALARY_LABEL : WITHDRAW_LABEL),
amount: (type === TransactionType.WITHDRAW ? -amount : amount), amount: (type === TransactionType.WITHDRAW ? -amount : amount),
}); });
await database.updateDocument<IAccount & Models.Document>(ACCOUNTS_COLLECTION_ID, accountDocument.documents[0].$id, { await AccountService.updateBalance(accountNumber, type === TransactionType.WITHDRAW ? -amount : amount);
balance: accountDocument.documents[0].balance + (type === TransactionType.WITHDRAW ? -amount : amount),
});
return { label: transactionDocument.label, amount: transactionDocument.amount, date: new Date(transactionDocument.$createdAt * 1000) }; 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 { 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', { return date.toLocaleDateString('de-DE', {
day: '2-digit', day: '2-digit',
month: '2-digit', month: '2-digit',
@ -9,4 +15,17 @@ export class DateService {
second: '2-digit', 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" :transactions="accountDetails.transactions"
/> />
</div> </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 <MoleculeInputModal
:id="depositModalId" :id="depositModalId"
title="Einzahlung" title="Einzahlung"
@ -62,15 +62,15 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ITransaction } from '../../interfaces/transaction.interface'; import { onMounted, ref } from 'vue';
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 { onKeyStroke } from '@vueuse/core'; import { onKeyStroke } from '@vueuse/core';
import { BankService, TransactionType } from '../services/bank.service'; 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 { 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'; const DEMO_ACCOUNT_ID = '0000123456';

View file

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

View file

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