Implement editing of transactions #23

Merged
jacob1123 merged 22 commits from edit-transaction into master 2022-02-25 23:35:12 +01:00
18 changed files with 352 additions and 152 deletions

View File

@ -130,7 +130,7 @@ func (q *Queries) GetAccountsWithBalance(ctx context.Context, budgetID uuid.UUID
}
const searchAccounts = `-- name: SearchAccounts :many
SELECT accounts.id, accounts.budget_id, accounts.name, true as is_account FROM accounts
SELECT accounts.id, accounts.budget_id, accounts.name, 'account' as type FROM accounts
WHERE accounts.budget_id = $1
AND accounts.name LIKE $2
ORDER BY accounts.name
@ -145,7 +145,7 @@ type SearchAccountsRow struct {
ID uuid.UUID
BudgetID uuid.UUID
Name string
IsAccount bool
Type interface{}
}
func (q *Queries) SearchAccounts(ctx context.Context, arg SearchAccountsParams) ([]SearchAccountsRow, error) {
@ -161,7 +161,7 @@ func (q *Queries) SearchAccounts(ctx context.Context, arg SearchAccountsParams)
&i.ID,
&i.BudgetID,
&i.Name,
&i.IsAccount,
&i.Type,
); err != nil {
return nil, err
}

View File

@ -118,7 +118,8 @@ func (q *Queries) GetCategoryGroups(ctx context.Context, budgetID uuid.UUID) ([]
}
const searchCategories = `-- name: SearchCategories :many
SELECT CONCAT(category_groups.name, ' : ', categories.name) as name, categories.id FROM categories
SELECT CONCAT(category_groups.name, ' : ', categories.name) as name, categories.id, 'category' as type
FROM categories
INNER JOIN category_groups ON categories.category_group_id = category_groups.id
WHERE category_groups.budget_id = $1
AND categories.name LIKE $2
@ -133,6 +134,7 @@ type SearchCategoriesParams struct {
type SearchCategoriesRow struct {
Name interface{}
ID uuid.UUID
Type interface{}
}
func (q *Queries) SearchCategories(ctx context.Context, arg SearchCategoriesParams) ([]SearchCategoriesRow, error) {
@ -144,7 +146,7 @@ func (q *Queries) SearchCategories(ctx context.Context, arg SearchCategoriesPara
var items []SearchCategoriesRow
for rows.Next() {
var i SearchCategoriesRow
if err := rows.Scan(&i.Name, &i.ID); err != nil {
if err := rows.Scan(&i.Name, &i.ID, &i.Type); err != nil {
return nil, err
}
items = append(items, i)

View File

@ -58,7 +58,7 @@ func (q *Queries) GetPayees(ctx context.Context, budgetID uuid.UUID) ([]Payee, e
}
const searchPayees = `-- name: SearchPayees :many
SELECT payees.id, payees.budget_id, payees.name FROM payees
SELECT payees.id, payees.budget_id, payees.name, 'payee' as type FROM payees
WHERE payees.budget_id = $1
AND payees.name LIKE $2
ORDER BY payees.name
@ -69,16 +69,28 @@ type SearchPayeesParams struct {
Search string
}
func (q *Queries) SearchPayees(ctx context.Context, arg SearchPayeesParams) ([]Payee, error) {
type SearchPayeesRow struct {
ID uuid.UUID
BudgetID uuid.UUID
Name string
Type interface{}
}
func (q *Queries) SearchPayees(ctx context.Context, arg SearchPayeesParams) ([]SearchPayeesRow, error) {
rows, err := q.db.QueryContext(ctx, searchPayees, arg.BudgetID, arg.Search)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Payee
var items []SearchPayeesRow
for rows.Next() {
var i Payee
if err := rows.Scan(&i.ID, &i.BudgetID, &i.Name); err != nil {
var i SearchPayeesRow
if err := rows.Scan(
&i.ID,
&i.BudgetID,
&i.Name,
&i.Type,
); err != nil {
return nil, err
}
items = append(items, i)

View File

@ -22,7 +22,7 @@ GROUP BY accounts.id, accounts.name
ORDER BY accounts.name;
-- name: SearchAccounts :many
SELECT accounts.id, accounts.budget_id, accounts.name, true as is_account FROM accounts
SELECT accounts.id, accounts.budget_id, accounts.name, 'account' as type FROM accounts
WHERE accounts.budget_id = @budget_id
AND accounts.name LIKE @search
ORDER BY accounts.name;

View File

@ -21,7 +21,8 @@ WHERE category_groups.budget_id = $1
ORDER BY category_groups.name, categories.name;
-- name: SearchCategories :many
SELECT CONCAT(category_groups.name, ' : ', categories.name) as name, categories.id FROM categories
SELECT CONCAT(category_groups.name, ' : ', categories.name) as name, categories.id, 'category' as type
FROM categories
INNER JOIN category_groups ON categories.category_group_id = category_groups.id
WHERE category_groups.budget_id = @budget_id
AND categories.name LIKE @search

View File

@ -10,7 +10,7 @@ WHERE payees.budget_id = $1
ORDER BY name;
-- name: SearchPayees :many
SELECT payees.* FROM payees
SELECT payees.*, 'payee' as type FROM payees
WHERE payees.budget_id = @budget_id
AND payees.name LIKE @search
ORDER BY payees.name;

View File

@ -13,10 +13,9 @@ UPDATE transactions
SET date = $1,
memo = $2,
amount = $3,
account_id = $4,
payee_id = $5,
category_id = $6
WHERE id = $7;
payee_id = $4,
category_id = $5
WHERE id = $6;
-- name: DeleteTransaction :exec
DELETE FROM transactions
@ -25,7 +24,7 @@ WHERE id = $1;
-- name: GetAllTransactionsForBudget :many
SELECT transactions.id, transactions.date, transactions.memo,
transactions.amount, transactions.group_id, transactions.status,
accounts.name as account,
accounts.name as account, transactions.payee_id, transactions.category_id,
COALESCE(payees.name, '') as payee,
COALESCE(category_groups.name, '') as category_group,
COALESCE(categories.name, '') as category,
@ -47,7 +46,7 @@ ORDER BY transactions.date DESC;
-- name: GetTransactionsForAccount :many
SELECT transactions.id, transactions.date, transactions.memo,
transactions.amount, transactions.group_id, transactions.status,
accounts.name as account,
accounts.name as account, transactions.payee_id, transactions.category_id,
COALESCE(payees.name, '') as payee,
COALESCE(category_groups.name, '') as category_group,
COALESCE(categories.name, '') as category,

View File

@ -83,7 +83,7 @@ func (q *Queries) DeleteTransaction(ctx context.Context, id uuid.UUID) error {
const getAllTransactionsForBudget = `-- name: GetAllTransactionsForBudget :many
SELECT transactions.id, transactions.date, transactions.memo,
transactions.amount, transactions.group_id, transactions.status,
accounts.name as account,
accounts.name as account, transactions.payee_id, transactions.category_id,
COALESCE(payees.name, '') as payee,
COALESCE(category_groups.name, '') as category_group,
COALESCE(categories.name, '') as category,
@ -111,6 +111,8 @@ type GetAllTransactionsForBudgetRow struct {
GroupID uuid.NullUUID
Status TransactionStatus
Account string
PayeeID uuid.NullUUID
CategoryID uuid.NullUUID
Payee string
CategoryGroup string
Category string
@ -134,6 +136,8 @@ func (q *Queries) GetAllTransactionsForBudget(ctx context.Context, budgetID uuid
&i.GroupID,
&i.Status,
&i.Account,
&i.PayeeID,
&i.CategoryID,
&i.Payee,
&i.CategoryGroup,
&i.Category,
@ -211,7 +215,7 @@ func (q *Queries) GetTransactionsByMonthAndCategory(ctx context.Context, budgetI
const getTransactionsForAccount = `-- name: GetTransactionsForAccount :many
SELECT transactions.id, transactions.date, transactions.memo,
transactions.amount, transactions.group_id, transactions.status,
accounts.name as account,
accounts.name as account, transactions.payee_id, transactions.category_id,
COALESCE(payees.name, '') as payee,
COALESCE(category_groups.name, '') as category_group,
COALESCE(categories.name, '') as category,
@ -240,6 +244,8 @@ type GetTransactionsForAccountRow struct {
GroupID uuid.NullUUID
Status TransactionStatus
Account string
PayeeID uuid.NullUUID
CategoryID uuid.NullUUID
Payee string
CategoryGroup string
Category string
@ -263,6 +269,8 @@ func (q *Queries) GetTransactionsForAccount(ctx context.Context, accountID uuid.
&i.GroupID,
&i.Status,
&i.Account,
&i.PayeeID,
&i.CategoryID,
&i.Payee,
&i.CategoryGroup,
&i.Category,
@ -286,17 +294,15 @@ UPDATE transactions
SET date = $1,
memo = $2,
amount = $3,
account_id = $4,
payee_id = $5,
category_id = $6
WHERE id = $7
payee_id = $4,
category_id = $5
WHERE id = $6
`
type UpdateTransactionParams struct {
Date time.Time
Memo string
Amount numeric.Numeric
AccountID uuid.UUID
PayeeID uuid.NullUUID
CategoryID uuid.NullUUID
ID uuid.UUID
@ -307,7 +313,6 @@ func (q *Queries) UpdateTransaction(ctx context.Context, arg UpdateTransactionPa
arg.Date,
arg.Memo,
arg.Amount,
arg.AccountID,
arg.PayeeID,
arg.CategoryID,
arg.ID,

View File

@ -17,12 +17,9 @@ type NewTransactionPayload struct {
Payee struct {
ID uuid.NullUUID
Name string
IsAccount bool
Type string
} `json:"payee"`
Category struct {
ID uuid.NullUUID
Name string
} `json:"category"`
CategoryID uuid.NullUUID `json:"categoryId"`
Memo string `json:"memo"`
Amount string `json:"amount"`
BudgetID uuid.UUID `json:"budgetId"`
@ -44,26 +41,27 @@ func (h *Handler) newTransaction(c *gin.Context) {
return
}
transactionID := c.Param("transactionid")
if transactionID != "" {
h.UpdateTransaction(payload, amount, transactionID, c)
return
}
newTransaction := postgres.CreateTransactionParams{
Memo: payload.Memo,
Date: time.Time(payload.Date),
Amount: amount,
Status: postgres.TransactionStatus(payload.State),
CategoryID: payload.CategoryID,
AccountID: payload.AccountID,
}
if payload.Payee.IsAccount {
newTransaction.GroupID = uuid.NullUUID{UUID: uuid.New(), Valid: true}
newTransaction.Amount = amount.Neg()
newTransaction.AccountID = payload.Payee.ID.UUID
newTransaction.CategoryID = uuid.NullUUID{}
_, err = h.Service.CreateTransaction(c.Request.Context(), newTransaction)
if payload.Payee.Type == "account" {
err := h.CreateTransferForOtherAccount(newTransaction, amount, payload, c)
if err != nil {
c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("create transfer transaction: %w", err))
c.AbortWithError(http.StatusInternalServerError, err)
return
}
newTransaction.Amount = amount
} else {
payeeID, err := GetPayeeID(c.Request.Context(), payload, h)
if err != nil {
@ -72,17 +70,54 @@ func (h *Handler) newTransaction(c *gin.Context) {
newTransaction.PayeeID = payeeID
}
newTransaction.CategoryID = payload.Category.ID
newTransaction.AccountID = payload.AccountID
transaction, err := h.Service.CreateTransaction(c.Request.Context(), newTransaction)
if err != nil {
c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("create transaction: %w", err))
return
}
c.JSON(http.StatusOK, transaction)
}
func (h *Handler) UpdateTransaction(payload NewTransactionPayload, amount numeric.Numeric, transactionID string, c *gin.Context) {
transactionUUID := uuid.MustParse(transactionID)
if amount.IsZero() {
err := h.Service.DeleteTransaction(c.Request.Context(), transactionUUID)
if err != nil {
c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("delete transaction: %w", err))
}
return
}
editTransaction := postgres.UpdateTransactionParams{
Memo: payload.Memo,
Date: time.Time(payload.Date),
Amount: amount,
PayeeID: payload.Payee.ID,
CategoryID: payload.CategoryID,
ID: transactionUUID,
}
err := h.Service.UpdateTransaction(c.Request.Context(), editTransaction)
if err != nil {
c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("edit transaction: %w", err))
}
}
func (h *Handler) CreateTransferForOtherAccount(newTransaction postgres.CreateTransactionParams, amount numeric.Numeric, payload NewTransactionPayload, c *gin.Context) error {
newTransaction.GroupID = uuid.NullUUID{UUID: uuid.New(), Valid: true}
newTransaction.Amount = amount.Neg()
newTransaction.AccountID = payload.Payee.ID.UUID
// transfer does not need category. Either it's account is off-budget or no category was supplied.
newTransaction.CategoryID = uuid.NullUUID{}
_, err := h.Service.CreateTransaction(c.Request.Context(), newTransaction)
if err != nil {
return fmt.Errorf("create transfer transaction: %w", err)
}
return nil
}
func GetPayeeID(context context.Context, payload NewTransactionPayload, h *Handler) (uuid.NullUUID, error) {
payeeID := payload.Payee.ID
if payeeID.Valid {

View File

@ -1,43 +1,38 @@
<script lang="ts" setup>
import { defineComponent, PropType, ref, watch } from "vue"
import { ref, watch } from "vue"
import { GET } from "../api";
import { useBudgetsStore } from "../stores/budget";
export interface Suggestion {
ID: string
Name: string
}
interface Data {
Selected: Suggestion | undefined
SearchQuery: String
Suggestions: Suggestion[]
Type: string
}
const props = defineProps<{
modelValue: Suggestion | undefined,
type: String
text: String,
id: String | undefined,
model: String,
type?: string | undefined,
}>();
const Selected = ref<Suggestion | undefined>(props.modelValue || undefined);
const SearchQuery = ref(props.modelValue?.Name || "");
const SearchQuery = ref(props.text || "");
const Suggestions = ref<Array<Suggestion>>([]);
const emit = defineEmits(["update:modelValue"]);
const emit = defineEmits(["update:id", "update:text", "update:type"]);
watch(SearchQuery, () => {
load(SearchQuery.value);
});
function saveTransaction(e: MouseEvent) {
e.preventDefault();
};
function load(text: String) {
emit('update:modelValue', { ID: null, Name: text });
emit('update:id', null);
emit('update:text', text);
emit('update:type', undefined);
if (text == "") {
Suggestions.value = [];
return;
}
const budgetStore = useBudgetsStore();
GET("/budget/" + budgetStore.CurrentBudgetID + "/autocomplete/" + props.type + "?s=" + text)
GET("/budget/" + budgetStore.CurrentBudgetID + "/autocomplete/" + props.model + "?s=" + text)
.then(x => x.json())
.then(x => {
let suggestions = x || [];
@ -56,13 +51,13 @@ function keypress(e: KeyboardEvent) {
const currentIndex = inputElements.indexOf(el);
const nextElement = inputElements[currentIndex < inputElements.length - 1 ? currentIndex + 1 : 0];
(<HTMLInputElement>nextElement).focus();
}
};
function selectElement(element: Suggestion) {
Selected.value = element;
emit('update:id', element.ID);
emit('update:text', element.Name);
emit('update:type', element.Type);
Suggestions.value = [];
emit('update:modelValue', element);
};
function select(e: MouseEvent) {
const target = (<HTMLInputElement>e.target);
@ -74,8 +69,9 @@ function select(e: MouseEvent) {
selectElement(selected);
};
function clear() {
Selected.value = undefined;
emit('update:modelValue', { ID: null, Name: SearchQuery.value });
emit('update:id', null);
emit('update:text', SearchQuery.value);
emit('update:type', undefined);
};
</script>
@ -84,10 +80,10 @@ function clear() {
<input
class="border-b-2 border-black"
@keypress="keypress"
v-if="Selected == undefined"
v-if="id == undefined"
v-model="SearchQuery"
/>
<span @click="clear" v-if="Selected != undefined" class="bg-gray-300">{{ Selected.Name }}</span>
<span @click="clear" v-if="id != undefined" class="bg-gray-300">{{ text }}</span>
<div v-if="Suggestions.length > 0" class="absolute bg-gray-400 w-64 p-2">
<span
v-for="suggestion in Suggestions"

View File

@ -0,0 +1,33 @@
<script lang="ts" setup>
const props = defineProps(["modelValue"]);
const emit = defineEmits(['update:modelValue']);
function dateToYYYYMMDD(d: Date) : string {
// alternative implementations in https://stackoverflow.com/q/23593052/1850609
//return new Date(d.getTime() - (d.getTimezoneOffset() * 60 * 1000)).toISOString().split('T')[0];
return d.toISOString().split('T')[0];
}
function updateValue(event: Event) {
const target = event.target as HTMLInputElement;
emit('update:modelValue', target.valueAsDate);
}
function selectAll(event: FocusEvent) {
// Workaround for Safari bug
// http://stackoverflow.com/questions/1269722/selecting-text-on-focus-using-jquery-not-working-in-safari-and-chrome
setTimeout(function () {
const target = event.target as HTMLInputElement;
target.select()
}, 0)
}
</script>
<template>
<input
type="date"
ref="input"
v-bind:value="dateToYYYYMMDD(modelValue)"
@input="updateValue"
@focus="selectAll"
/>
</template>

View File

@ -0,0 +1,60 @@
<script lang="ts" setup>
import { computed, ref } from "vue";
import Autocomplete from './Autocomplete.vue'
import { useAccountStore } from '../stores/budget-account'
import DateInput from "./DateInput.vue";
const props = defineProps<{
transactionid: string
}>()
const accountStore = useAccountStore();
const TX = accountStore.Transactions.get(props.transactionid)!;
const payeeType = ref<string|undefined>(undefined);
const payload = computed(() => JSON.stringify({
date: TX.Date.toISOString().split("T")[0],
payee: {
Name: TX.Payee,
ID: TX.PayeeID,
Type: payeeType.value,
},
categoryId: TX.CategoryID,
memo: TX.Memo,
amount: TX.Amount.toString(),
state: "Uncleared"
}));
function saveTransaction(e: MouseEvent) {
e.preventDefault();
accountStore.editTransaction(TX.ID, payload.value);
}
</script>
<template>
<tr>
<td style="width: 90px;" class="text-sm">
<DateInput class="border-b-2 border-black" v-model="TX.Date" />
</td>
<td style="max-width: 150px;">
<Autocomplete v-model:text="TX.Payee" v-model:id="TX.PayeeID" v-model:type="payeeType" model="payees" />
</td>
<td style="max-width: 200px;">
<Autocomplete v-model:text="TX.Category" v-model:id="TX.CategoryID" model="categories" />
</td>
<td>
<input class="block w-full border-b-2 border-black" type="text" v-model="TX.Memo" />
</td>
<td style="width: 80px;" class="text-right">
<input
class="text-right block w-full border-b-2 border-black"
type="currency"
v-model="TX.Amount"
/>
</td>
<td style="width: 20px;">
<input type="submit" @click="saveTransaction" value="Save" />
</td>
<td style="width: 20px;"></td>
</tr>
</template>

View File

@ -1,27 +1,43 @@
<script lang="ts" setup>
import { computed, ref } from "vue";
import Autocomplete, { Suggestion } from '../components/Autocomplete.vue'
import { useAccountStore } from '../stores/budget-account'
import { Transaction, useAccountStore } from '../stores/budget-account'
import DateInput from "./DateInput.vue";
const props = defineProps<{
budgetid: string
accountid: string
}>()
const TransactionDate = ref(new Date().toISOString().substring(0, 10));
const Payee = ref<Suggestion | undefined>(undefined);
const Category = ref<Suggestion | undefined>(undefined);
const Memo = ref("");
const Amount = ref("0");
const TX = ref<Transaction>({
Date: new Date(),
Memo: "",
Amount: 0,
Payee: "",
PayeeID: undefined,
Category: "",
CategoryID: undefined,
CategoryGroup: "",
GroupID: "",
ID: "",
Status: "Uncleared",
TransferAccount: "",
});
const payeeType = ref<string|undefined>(undefined);
const payload = computed(() => JSON.stringify({
budgetId: props.budgetid,
accountId: props.accountid,
date: TransactionDate.value,
payee: Payee.value,
category: Category.value,
memo: Memo.value,
amount: Amount.value,
date: TX.value.Date.toISOString().split("T")[0],
payee: {
Name: TX.value.Payee,
ID: TX.value.PayeeID,
Type: payeeType.value,
},
categoryId: TX.value.CategoryID,
memo: TX.value.Memo,
amount: TX.value.Amount.toString(),
state: "Uncleared"
}));
@ -35,22 +51,22 @@ function saveTransaction(e: MouseEvent) {
<template>
<tr>
<td style="width: 90px;" class="text-sm">
<input class="border-b-2 border-black" type="date" v-model="TransactionDate" />
<DateInput class="border-b-2 border-black" v-model="TX.Date" />
</td>
<td style="max-width: 150px;">
<Autocomplete v-model="Payee" type="payees" />
<Autocomplete v-model:text="TX.Payee" v-model:id="TX.PayeeID" v-model:type="payeeType" model="payees" />
</td>
<td style="max-width: 200px;">
<Autocomplete v-model="Category" type="categories" />
<Autocomplete v-model:text="TX.Category" v-model:id="TX.CategoryID" model="categories" />
</td>
<td>
<input class="block w-full border-b-2 border-black" type="text" v-model="Memo" />
<input class="block w-full border-b-2 border-black" type="text" v-model="TX.Memo" />
</td>
<td style="width: 80px;" class="text-right">
<input
class="text-right block w-full border-b-2 border-black"
type="currency"
v-model="Amount"
v-model="TX.Amount"
/>
</td>
<td style="width: 20px;">

View File

@ -1,24 +1,28 @@
<script lang="ts" setup>
import { computed } from "vue";
import { computed, ref } from "vue";
import { useBudgetsStore } from "../stores/budget";
import { Transaction } from "../stores/budget-account";
import Currency from "./Currency.vue";
import TransactionEditRow from "./TransactionEditRow.vue";
import { formatDate } from "../date";
const props = defineProps<{
transaction: Transaction,
index: number,
}>();
const edit = ref(false);
const CurrentBudgetID = computed(()=> useBudgetsStore().CurrentBudgetID);
</script>
<template>
<tr class="{{transaction.Date.After now ? 'future' : ''}}"
<tr v-if="!edit" class="{{new Date(transaction.Date) > new Date() ? 'future' : ''}}"
:class="[index % 6 < 3 ? 'bg-gray-300' : 'bg-gray-100']">
<!--:class="[index % 6 < 3 ? index % 6 === 1 ? 'bg-gray-400' : 'bg-gray-300' : index % 6 !== 4 ? 'bg-gray-100' : '']">-->
<td style="width: 90px;">{{ transaction.Date.substring(0, 10) }}</td>
<td style="max-width: 150px;">{{ transaction.TransferAccount ? "Transfer : " + transaction.TransferAccount : transaction.Payee }}</td>
<td style="max-width: 200px;">
<td>{{ formatDate(transaction.Date) }}</td>
<td>{{ transaction.TransferAccount ? "Transfer : " + transaction.TransferAccount : transaction.Payee }}</td>
<td>
{{ transaction.CategoryGroup ? transaction.CategoryGroup + " : " + transaction.Category : "" }}
</td>
<td>
@ -29,11 +33,12 @@ const CurrentBudgetID = computed(()=> useBudgetsStore().CurrentBudgetID);
<td>
<Currency class="block" :value="transaction.Amount" />
</td>
<td style="width: 20px;">
<td>
{{ transaction.Status == "Reconciled" ? "✔" : (transaction.Status == "Uncleared" ? "" : "*") }}
</td>
<td style="width: 20px;">{{ transaction.GroupID ? "☀" : "" }}</td>
<td class="text-right">{{ transaction.GroupID ? "☀" : "" }}<a @click="edit = true;"></a></td>
</tr>
<TransactionEditRow v-if="edit" :transactionid="transaction.ID" />
</template>
<style>

7
web/src/date.ts Normal file
View File

@ -0,0 +1,7 @@
export function formatDate(date: Date): string {
return date.toLocaleDateString(undefined, { // you can use undefined as first argument
year: "numeric",
month: "2-digit",
day: "2-digit",
});
}

View File

@ -0,0 +1,44 @@
<script lang="ts" setup>
import { computed, ref } from 'vue';
import Modal from '../components/Modal.vue';
import { useAccountStore } from '../stores/budget-account';
const accountStore = useAccountStore();
const CurrentAccount = computed(() => accountStore.CurrentAccount);
const accountName = ref("");
const accountOnBudget = ref(true);
function editAccount(e : any) {
accountStore.EditAccount(CurrentAccount.value?.ID ?? "", accountName.value, accountOnBudget.value);
}
function openEditAccount(e : any) {
accountName.value = CurrentAccount.value?.Name ?? "";
accountOnBudget.value = CurrentAccount.value?.OnBudget ?? true;
}
</script>
<template>
<Modal button-text="Edit Account" @open="openEditAccount" @submit="editAccount">
<template v-slot:placeholder></template>
<div class="mt-2 px-7 py-3">
<input
class="border-2"
type="text"
v-model="accountName"
placeholder="Account name"
required
/>
</div>
<div class="mt-2 px-7 py-3">
<input
class="border-2"
type="checkbox"
v-model="accountOnBudget"
required
/>
<label>On Budget</label>
</div>
</Modal>
</template>

View File

@ -4,7 +4,7 @@ import Currency from "../components/Currency.vue";
import TransactionRow from "../components/TransactionRow.vue";
import TransactionInputRow from "../components/TransactionInputRow.vue";
import { useAccountStore } from "../stores/budget-account";
import Modal from "../components/Modal.vue";
import EditAccount from "../dialogs/EditAccount.vue";
const props = defineProps<{
budgetid: string
@ -15,42 +15,11 @@ const accountStore = useAccountStore();
const CurrentAccount = computed(() => accountStore.CurrentAccount);
const TransactionsList = computed(() => accountStore.TransactionsList);
const accountName = ref("");
const accountOnBudget = ref(true);
function editAccount(e : any) {
accountStore.EditAccount(CurrentAccount.value?.ID ?? "", accountName.value, accountOnBudget.value);
}
function openEditAccount(e : any) {
accountName.value = CurrentAccount.value?.Name ?? "";
accountOnBudget.value = CurrentAccount.value?.OnBudget ?? true;
}
</script>
<template>
<h1 class="inline">{{ CurrentAccount?.Name }}</h1>
<Modal button-text="Edit Account" @open="openEditAccount" @submit="editAccount">
<template v-slot:placeholder></template>
<div class="mt-2 px-7 py-3">
<input
class="border-2"
type="text"
v-model="accountName"
placeholder="Account name"
required
/>
</div>
<div class="mt-2 px-7 py-3">
<input
class="border-2"
type="checkbox"
v-model="accountOnBudget"
required
/>
<label>On Budget</label>
</div>
</Modal>
<EditAccount />
<p>
Current Balance:
@ -64,7 +33,7 @@ function openEditAccount(e : any) {
<td>Memo</td>
<td class="text-right">Amount</td>
<td style="width: 20px;"></td>
<td style="width: 20px;"></td>
<td style="width: 40px;"></td>
</tr>
<TransactionInputRow :budgetid="budgetid" :accountid="accountid" />
<TransactionRow

View File

@ -8,20 +8,22 @@ interface State {
CurrentAccountID: string | null,
Categories: Map<string, Category>,
Months: Map<number, Map<number, Map<string, Category>>>,
Transactions: any[],
Transactions: Map<string, Transaction>,
Assignments: []
}
export interface Transaction {
ID: string,
Date: string,
Date: Date,
TransferAccount: string,
CategoryGroup: string,
Category: string,
CategoryID: string | undefined,
Memo: string,
Status: string,
GroupID: string,
Payee: string,
PayeeID: string | undefined,
Amount: number,
}
@ -30,6 +32,7 @@ export interface Account {
Name: string
OnBudget: boolean
Balance: number
Transactions: string[]
}
export interface Category {
@ -48,7 +51,7 @@ export const useAccountStore = defineStore("budget/account", {
CurrentAccountID: null,
Months: new Map<number, Map<number, Map<string, Category>>>(),
Categories: new Map<string, Category>(),
Transactions: [],
Transactions: new Map<string, Transaction>(),
Assignments: []
}),
getters: {
@ -100,8 +103,10 @@ export const useAccountStore = defineStore("budget/account", {
OffBudgetAccountsBalance(state): number {
return this.OffBudgetAccounts.reduce((prev, curr) => prev + Number(curr.Balance), 0);
},
TransactionsList(state) {
return (state.Transactions || []);
TransactionsList(state) : Transaction[] {
return this.CurrentAccount!.Transactions.map(x => {
return this.Transactions.get(x)!
});
}
},
actions: {
@ -110,16 +115,22 @@ export const useAccountStore = defineStore("budget/account", {
return
this.CurrentAccountID = accountid;
if (this.CurrentAccount == undefined)
const account = this.CurrentAccount;
if (account == undefined)
return
useSessionStore().setTitle(this.CurrentAccount.Name);
await this.FetchAccount(accountid);
useSessionStore().setTitle(account.Name);
await this.FetchAccount(account);
},
async FetchAccount(accountid: string) {
const result = await GET("/account/" + accountid + "/transactions");
async FetchAccount(account: Account) {
const result = await GET("/account/" + account.ID + "/transactions");
const response = await result.json();
this.Transactions = response.Transactions;
account.Transactions = [];
for (const transaction of response.Transactions) {
transaction.Date = new Date(transaction.Date);
this.Transactions.set(transaction.ID, transaction);
account.Transactions.push(transaction.ID);
}
},
async FetchMonthBudget(budgetid: string, year: number, month: number) {
const result = await GET("/budget/" + budgetid + "/" + year + "/" + month);
@ -151,7 +162,12 @@ export const useAccountStore = defineStore("budget/account", {
async saveTransaction(payload: string) {
const result = await POST("/transaction/new", payload);
const response = await result.json();
this.Transactions.unshift(response);
this.CurrentAccount?.Transactions.unshift(response);
},
async editTransaction(transactionid : string, payload: string) {
const result = await POST("/transaction/" + transactionid, payload);
const response = await result.json();
this.CurrentAccount?.Transactions.unshift(response);
}
}