E2EE Group Finance Management
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

229 lines
6.2 KiB

<style>
#member-debitors {
display: flex;
flex-direction: column;
flex-wrap: wrap;
justify-content: center;
gap: 0.25em;
}
#member-debitors div {
border: 1px solid var(--c3);
border-radius: 5px;
display: flex;
justify-content: space-between;
flex-grow: 1;
}
#member-debitors label {
flex-grow: 2;
font-size: 0.8em;
padding: 0.5em;
margin: 0;
color: var(--c4);
}
input[type="checkbox"] {
margin: 0.25em;
}
</style>
<script lang="typescript">
import { onMount, createEventDispatcher } from "svelte";
// Type Imports
import type { Member } from "@app/types/member.type";
// Class Imports
import { MemberTransaction, Transaction } from "@app/classes/transaction.classes";
import { toast } from "@app/stores";
const dispatch = createEventDispatcher();
export let members: Array<Member>;
export let currencyFormatter: Intl.NumberFormat;
export let defaultCreditor: string;
export let existingTransaction: Transaction | undefined;
let actionText = "Create";
let title: string;
let date: string;
let creditorId: string = defaultCreditor;
let totalAmount = 0.0;
let selectedDebitors: Array<string> = [];
$: splitDebitorAmounts = splitTotal(selectedDebitors, totalAmount);
// Currently this allows for only ONE creditor but has room
// for expansion in future
function createCreditorTransactions(): Array<MemberTransaction> {
const transactions: Array<MemberTransaction> = [];
if (creditorId) {
const member: Member | undefined = members.find(
e => e.id === creditorId,
);
if (member == undefined) {
throw `Couldn't find an entry for MemberId ${creditorId}!`;
}
const transaction: MemberTransaction = {
id: member.id,
name: member.name,
amount: totalAmount * 100,
};
transactions.push(transaction);
}
return transactions;
}
// Split the total amount between all debitors within 2 decimal places
function splitTotal(debitors: Array<string>, total: number) {
const transactionParts: { [id: string]: number } = {};
let remainingTotal: number = total;
let remainingDevider: number = debitors.length;
debitors.forEach(memberId => {
const partAmount =
Math.round((remainingTotal * 100) / remainingDevider) / 100;
remainingTotal -= partAmount;
remainingDevider -= 1;
transactionParts[memberId] = partAmount;
});
return transactionParts;
}
function createDebitorTransactions(): Array<MemberTransaction> {
const transactions: Array<MemberTransaction> = [];
selectedDebitors.forEach(memberId => {
const member: Member | undefined = members.find(e => e.id === memberId);
if (member == undefined) {
throw `Couldn't find an entry for MemberId: ${memberId}!`;
}
const newTransaction: MemberTransaction = {
id: member.id,
name: member.name,
amount: Math.floor(splitDebitorAmounts[member.id] * 100),
};
transactions.push(newTransaction);
});
return transactions;
}
function uuidv4() {
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, c => {
const r = (Math.random() * 16) | 0;
const v = c == "x" ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
}
function handleSubmit() {
if (!title) {
toast.show("Transaction requires a name!", "warning");
return;
}
const creditorTransactions: Array<MemberTransaction> = createCreditorTransactions();
const debitorTransactions: Array<MemberTransaction> = createDebitorTransactions();
if (debitorTransactions.length <= 0) {
toast.show("At least one debitor must be set!", "warning");
return;
}
if (creditorTransactions.length <= 0) {
toast.show("At least one creditor must be set!", "warning");
return;
}
let createdAt = new Date().getTime().toString();
let uuid = uuidv4();
const currency = "EUR";
if (existingTransaction != undefined) {
createdAt = date;
uuid = existingTransaction.id;
}
const newTransaction = new Transaction(
uuid,
createdAt,
currency,
title,
creditorTransactions,
debitorTransactions,
);
dispatch("Submitted", {
transaction: newTransaction,
});
}
onMount(() => {
if (existingTransaction != undefined) {
actionText = "Edit";
title = existingTransaction.title;
date = existingTransaction.date;
totalAmount = existingTransaction.total / 100;
creditorId = existingTransaction.credits[0].id;
selectedDebitors = existingTransaction.debits.map(x => x.id);
}
});
</script>
<form on:submit|preventDefault="{handleSubmit}">
<h1>{actionText} Transaction</h1>
<label for="title">Title</label>
<input bind:value="{title}" id="title" placeholder="Movies" />
<label for="amount">Amount</label>
<input
bind:value="{totalAmount}"
id="amount"
placeholder="Transaction Amount"
type="number"
min="0"
step="0.01"
/>
<label for="creditor">Creditor</label>
<select bind:value="{creditorId}" id="creditor">
{#each members as member (member.id)}
<option value="{member.id}">{member.name}</option>
{/each}
</select>
<!-- <label for="member-debitors">Debitors</label> -->
<fieldset id="member-debitors">
<legend>Debitors</legend>
{#each members as member (member.id)}
<div>
{#if selectedDebitors.includes(member.id)}
<label for="{`check-${member.name}`}"
>{member.name} - {currencyFormatter.format(
splitDebitorAmounts[member.id],
)}</label
>
{:else}
<label for="{`check-${member.name}`}">{member.name}</label>
{/if}
<input
type="checkbox"
id="{`check-${member.name}`}"
bind:group="{selectedDebitors}"
value="{member.id}"
/>
</div>
{/each}
</fieldset>
<button type="submit">{actionText}</button>
</form>