Use spaces

This commit is contained in:
2022-03-15 12:52:23 +00:00
parent 61a534610f
commit d717ef1b4d
43 changed files with 1400 additions and 1400 deletions

View File

@ -5,229 +5,229 @@ import { useSessionStore } from "./session";
import { useTransactionsStore } from "./transactions";
interface State {
Accounts: Map<string, Account>;
CurrentAccountID: string | null;
Categories: Map<string, Category>;
Months: Map<number, Map<number, Map<string, Category>>>;
Assignments: [];
Accounts: Map<string, Account>;
CurrentAccountID: string | null;
Categories: Map<string, Category>;
Months: Map<number, Map<number, Map<string, Category>>>;
Assignments: [];
}
export interface Account {
ID: string;
Name: string;
OnBudget: boolean;
IsOpen: boolean;
ClearedBalance: number;
WorkingBalance: number;
ReconciledBalance: number;
Transactions: string[];
LastReconciled: NullDate;
ID: string;
Name: string;
OnBudget: boolean;
IsOpen: boolean;
ClearedBalance: number;
WorkingBalance: number;
ReconciledBalance: number;
Transactions: string[];
LastReconciled: NullDate;
}
interface NullDate {
Valid: boolean;
Time: Date;
Valid: boolean;
Time: Date;
}
export interface Category {
ID: string;
Group: string;
Name: string;
AvailableLastMonth: number;
Assigned: number;
Activity: number;
ID: string;
Group: string;
Name: string;
AvailableLastMonth: number;
Assigned: number;
Activity: number;
}
export const useAccountStore = defineStore("budget/account", {
state: (): State => ({
Accounts: new Map<string, Account>(),
CurrentAccountID: null,
Months: new Map<number, Map<number, Map<string, Category>>>(),
Categories: new Map<string, Category>(),
Assignments: [],
}),
getters: {
AccountsList(state) {
return [...state.Accounts.values()];
},
AllCategoriesForMonth: (state) => (year: number, month: number) => {
const yearMap = state.Months.get(year);
const monthMap = yearMap?.get(month);
return [...(monthMap?.values() || [])];
},
GetCategoryAvailable(state) {
return (category: Category): number => {
return (
category.AvailableLastMonth +
Number(category.Assigned) +
category.Activity
);
};
},
GetIncomeCategoryID(state) {
const budget = useBudgetsStore();
return budget.CurrentBudget?.IncomeCategoryID;
},
GetIncomeAvailable(state) {
return (year: number, month: number) => {
const IncomeCategoryID = this.GetIncomeCategoryID;
if (IncomeCategoryID == null) return 0;
state: (): State => ({
Accounts: new Map<string, Account>(),
CurrentAccountID: null,
Months: new Map<number, Map<number, Map<string, Category>>>(),
Categories: new Map<string, Category>(),
Assignments: [],
}),
getters: {
AccountsList(state) {
return [...state.Accounts.values()];
},
AllCategoriesForMonth: (state) => (year: number, month: number) => {
const yearMap = state.Months.get(year);
const monthMap = yearMap?.get(month);
return [...(monthMap?.values() || [])];
},
GetCategoryAvailable(state) {
return (category: Category): number => {
return (
category.AvailableLastMonth +
Number(category.Assigned) +
category.Activity
);
};
},
GetIncomeCategoryID(state) {
const budget = useBudgetsStore();
return budget.CurrentBudget?.IncomeCategoryID;
},
GetIncomeAvailable(state) {
return (year: number, month: number) => {
const IncomeCategoryID = this.GetIncomeCategoryID;
if (IncomeCategoryID == null) return 0;
const categories = this.AllCategoriesForMonth(year, month);
const category = categories.filter(
(x) => x.ID == IncomeCategoryID
)[0];
if (category == null) return 0;
return category.AvailableLastMonth;
};
},
CategoryGroupsForMonth(state) {
return (year: number, month: number) => {
const categories = this.AllCategoriesForMonth(year, month);
const categoryGroups = [];
let prev = undefined;
for (const category of categories) {
if (category.ID == this.GetIncomeCategoryID) continue;
const categories = this.AllCategoriesForMonth(year, month);
const category = categories.filter(
(x) => x.ID == IncomeCategoryID
)[0];
if (category == null) return 0;
return category.AvailableLastMonth;
};
},
CategoryGroupsForMonth(state) {
return (year: number, month: number) => {
const categories = this.AllCategoriesForMonth(year, month);
const categoryGroups = [];
let prev = undefined;
for (const category of categories) {
if (category.ID == this.GetIncomeCategoryID) continue;
if (prev == undefined || category.Group != prev.Name) {
prev = {
Name: category.Group,
Available: this.GetCategoryAvailable(category),
AvailableLastMonth: category.AvailableLastMonth,
Activity: category.Activity,
Assigned: category.Assigned,
};
categoryGroups.push({
...prev,
Expand: prev.Name != "Hidden Categories",
});
} else {
categoryGroups[categoryGroups.length - 1].Available +=
this.GetCategoryAvailable(category);
categoryGroups[
categoryGroups.length - 1
].AvailableLastMonth += category.AvailableLastMonth;
categoryGroups[categoryGroups.length - 1].Activity +=
category.Activity;
categoryGroups[categoryGroups.length - 1].Assigned +=
category.Assigned;
continue;
}
}
return categoryGroups;
};
},
CategoriesForMonthAndGroup(state) {
return (year: number, month: number, group: string) => {
const categories = this.AllCategoriesForMonth(year, month);
return categories.filter((x) => x.Group == group);
};
},
GetAccount(state) {
return (accountid: string) => {
return this.Accounts.get(accountid);
};
},
CurrentAccount(state): Account | undefined {
if (state.CurrentAccountID == null) return undefined;
if (prev == undefined || category.Group != prev.Name) {
prev = {
Name: category.Group,
Available: this.GetCategoryAvailable(category),
AvailableLastMonth: category.AvailableLastMonth,
Activity: category.Activity,
Assigned: category.Assigned,
};
categoryGroups.push({
...prev,
Expand: prev.Name != "Hidden Categories",
});
} else {
categoryGroups[categoryGroups.length - 1].Available +=
this.GetCategoryAvailable(category);
categoryGroups[
categoryGroups.length - 1
].AvailableLastMonth += category.AvailableLastMonth;
categoryGroups[categoryGroups.length - 1].Activity +=
category.Activity;
categoryGroups[categoryGroups.length - 1].Assigned +=
category.Assigned;
continue;
}
}
return categoryGroups;
};
},
CategoriesForMonthAndGroup(state) {
return (year: number, month: number, group: string) => {
const categories = this.AllCategoriesForMonth(year, month);
return categories.filter((x) => x.Group == group);
};
},
GetAccount(state) {
return (accountid: string) => {
return this.Accounts.get(accountid);
};
},
CurrentAccount(state): Account | undefined {
if (state.CurrentAccountID == null) return undefined;
return this.GetAccount(state.CurrentAccountID);
},
OnBudgetAccounts(state) {
return [...state.Accounts.values()].filter((x) => x.OnBudget);
},
OnBudgetAccountsBalance(state): number {
return this.OnBudgetAccounts.reduce(
(prev, curr) => prev + Number(curr.ClearedBalance),
0
);
},
OffBudgetAccounts(state) {
return [...state.Accounts.values()].filter((x) => !x.OnBudget);
},
OffBudgetAccountsBalance(state): number {
return this.OffBudgetAccounts.reduce(
(prev, curr) => prev + Number(curr.ClearedBalance),
0
);
},
},
actions: {
async SetCurrentAccount(budgetid: string, accountid: string) {
if (budgetid == null) return;
return this.GetAccount(state.CurrentAccountID);
},
OnBudgetAccounts(state) {
return [...state.Accounts.values()].filter((x) => x.OnBudget);
},
OnBudgetAccountsBalance(state): number {
return this.OnBudgetAccounts.reduce(
(prev, curr) => prev + Number(curr.ClearedBalance),
0
);
},
OffBudgetAccounts(state) {
return [...state.Accounts.values()].filter((x) => !x.OnBudget);
},
OffBudgetAccountsBalance(state): number {
return this.OffBudgetAccounts.reduce(
(prev, curr) => prev + Number(curr.ClearedBalance),
0
);
},
},
actions: {
async SetCurrentAccount(budgetid: string, accountid: string) {
if (budgetid == null) return;
this.CurrentAccountID = accountid;
this.CurrentAccountID = accountid;
if (accountid == null) return;
const account = this.GetAccount(accountid)!;
useSessionStore().setTitle(account.Name);
await this.FetchAccount(account);
},
async FetchAccount(account: Account) {
const result = await GET(
"/account/" + account.ID + "/transactions"
);
const response = await result.json();
const transactionsStore = useTransactionsStore();
const transactions = transactionsStore.AddTransactions(
response.Transactions
);
account.Transactions = transactions;
},
async FetchMonthBudget(budgetid: string, year: number, month: number) {
const result = await GET(
"/budget/" + budgetid + "/" + year + "/" + (month + 1)
);
const response = await result.json();
if (
response.Categories == undefined ||
response.Categories.length <= 0
)
return;
this.addCategoriesForMonth(year, month, response.Categories);
},
async EditAccount(
accountid: string,
name: string,
onBudget: boolean,
isOpen: boolean
) {
const result = await POST(
"/account/" + accountid,
JSON.stringify({
name: name,
onBudget: onBudget,
isOpen: isOpen,
})
);
const response = await result.json();
useBudgetsStore().MergeBudgetingData(response);
if (accountid == null) return;
const account = this.GetAccount(accountid)!;
useSessionStore().setTitle(account.Name);
await this.FetchAccount(account);
},
async FetchAccount(account: Account) {
const result = await GET(
"/account/" + account.ID + "/transactions"
);
const response = await result.json();
const transactionsStore = useTransactionsStore();
const transactions = transactionsStore.AddTransactions(
response.Transactions
);
account.Transactions = transactions;
},
async FetchMonthBudget(budgetid: string, year: number, month: number) {
const result = await GET(
"/budget/" + budgetid + "/" + year + "/" + (month + 1)
);
const response = await result.json();
if (
response.Categories == undefined ||
response.Categories.length <= 0
)
return;
this.addCategoriesForMonth(year, month, response.Categories);
},
async EditAccount(
accountid: string,
name: string,
onBudget: boolean,
isOpen: boolean
) {
const result = await POST(
"/account/" + accountid,
JSON.stringify({
name: name,
onBudget: onBudget,
isOpen: isOpen,
})
);
const response = await result.json();
useBudgetsStore().MergeBudgetingData(response);
if (!isOpen) {
this.Accounts.delete(accountid);
}
},
addCategoriesForMonth(
year: number,
month: number,
categories: Category[]
): void {
this.$patch((state) => {
const yearMap =
state.Months.get(year) ||
new Map<number, Map<string, Category>>();
const monthMap =
yearMap.get(month) || new Map<string, Category>();
for (const category of categories) {
monthMap.set(category.ID, category);
}
if (!isOpen) {
this.Accounts.delete(accountid);
}
},
addCategoriesForMonth(
year: number,
month: number,
categories: Category[]
): void {
this.$patch((state) => {
const yearMap =
state.Months.get(year) ||
new Map<number, Map<string, Category>>();
const monthMap =
yearMap.get(month) || new Map<string, Category>();
for (const category of categories) {
monthMap.set(category.ID, category);
}
yearMap.set(month, monthMap);
state.Months.set(year, yearMap);
});
},
logout() {
this.$reset();
},
},
yearMap.set(month, monthMap);
state.Months.set(year, yearMap);
});
},
logout() {
this.$reset();
},
},
});

View File

@ -4,67 +4,67 @@ import { useAccountStore } from "./budget-account";
import { Budget, useSessionStore } from "./session";
interface State {
CurrentBudgetID: string | null;
CurrentBudgetID: string | null;
}
export const useBudgetsStore = defineStore("budget", {
state: (): State => ({
CurrentBudgetID: null,
}),
getters: {
CurrentBudget(): Budget | undefined {
if (this.CurrentBudgetID == null) return undefined;
state: (): State => ({
CurrentBudgetID: null,
}),
getters: {
CurrentBudget(): Budget | undefined {
if (this.CurrentBudgetID == null) return undefined;
const sessionStore = useSessionStore();
return sessionStore.Budgets.get(this.CurrentBudgetID);
},
CurrentBudgetName(state): string {
return this.CurrentBudget?.Name ?? "";
},
},
actions: {
ImportYNAB(formData: FormData) {
return POST(
"/budget/" + this.CurrentBudgetID + "/import/ynab",
formData
);
},
async NewBudget(budgetName: string): Promise<void> {
const result = await POST(
"/budget/new",
JSON.stringify({ name: budgetName })
);
const response = await result.json();
const sessionStore = useSessionStore();
return sessionStore.Budgets.get(this.CurrentBudgetID);
},
CurrentBudgetName(state): string {
return this.CurrentBudget?.Name ?? "";
},
},
actions: {
ImportYNAB(formData: FormData) {
return POST(
"/budget/" + this.CurrentBudgetID + "/import/ynab",
formData
);
},
async NewBudget(budgetName: string): Promise<void> {
const result = await POST(
"/budget/new",
JSON.stringify({ name: budgetName })
);
const response = await result.json();
const sessionStore = useSessionStore();
sessionStore.Budgets.set(response.ID, response);
},
async SetCurrentBudget(budgetid: string): Promise<void> {
this.CurrentBudgetID = budgetid;
const sessionStore = useSessionStore();
sessionStore.Budgets.set(response.ID, response);
},
async SetCurrentBudget(budgetid: string): Promise<void> {
this.CurrentBudgetID = budgetid;
if (budgetid == null) return;
if (budgetid == null) return;
await this.FetchBudget(budgetid);
},
async FetchBudget(budgetid: string) {
const result = await GET("/budget/" + budgetid);
const response = await result.json();
this.MergeBudgetingData(response);
},
MergeBudgetingData(response: any) {
const accounts = useAccountStore();
for (const account of response.Accounts || []) {
const existingAccount = accounts.Accounts.get(account.ID);
account.Transactions = existingAccount?.Transactions ?? [];
if (account.LastReconciled.Valid)
account.LastReconciled.Time = new Date(
account.LastReconciled.Time
);
accounts.Accounts.set(account.ID, account);
}
for (const category of response.Categories || []) {
accounts.Categories.set(category.ID, category);
}
},
},
await this.FetchBudget(budgetid);
},
async FetchBudget(budgetid: string) {
const result = await GET("/budget/" + budgetid);
const response = await result.json();
this.MergeBudgetingData(response);
},
MergeBudgetingData(response: any) {
const accounts = useAccountStore();
for (const account of response.Accounts || []) {
const existingAccount = accounts.Accounts.get(account.ID);
account.Transactions = existingAccount?.Transactions ?? [];
if (account.LastReconciled.Valid)
account.LastReconciled.Time = new Date(
account.LastReconciled.Time
);
accounts.Accounts.set(account.ID, account);
}
for (const category of response.Categories || []) {
accounts.Categories.set(category.ID, category);
}
},
},
});

View File

@ -3,72 +3,72 @@ import { defineStore } from "pinia";
import { POST } from "../api";
interface State {
Session: Session | null;
Budgets: Map<string, Budget>;
Session: Session | null;
Budgets: Map<string, Budget>;
}
interface Session {
Token: string;
User: string;
Token: string;
User: string;
}
export interface Budget {
ID: string;
Name: string;
AvailableBalance: number;
IncomeCategoryID: string;
ID: string;
Name: string;
AvailableBalance: number;
IncomeCategoryID: string;
}
export const useSessionStore = defineStore("session", {
state: () => ({
Session: useStorage<Session | null>("session", null, undefined, {
serializer: StorageSerializers.object,
}),
Budgets: useStorage<Map<string, Budget>>(
"budgets",
new Map<string, Budget>(),
undefined,
{ serializer: StorageSerializers.map }
),
}),
getters: {
BudgetsList: (state) => [...state.Budgets.values()],
AuthHeaders: (state) => ({
Authorization: "Bearer " + state.Session?.Token,
}),
LoggedIn: (state) => state.Session != null,
},
actions: {
setTitle(title: string) {
document.title = "Budgeteer - " + title;
},
loginSuccess(x: any) {
this.Session = {
User: x.User,
Token: x.Token,
};
for (const budget of x.Budgets) {
this.Budgets.set(budget.ID, budget);
}
},
async login(login: any) {
const response = await POST("/user/login", JSON.stringify(login));
const result = await response.json();
this.loginSuccess(result);
return result;
},
async register(login: any) {
const response = await POST(
"/user/register",
JSON.stringify(login)
);
const result = await response.json();
this.loginSuccess(result);
return result;
},
logout() {
this.Session = null;
this.Budgets.clear();
},
},
state: () => ({
Session: useStorage<Session | null>("session", null, undefined, {
serializer: StorageSerializers.object,
}),
Budgets: useStorage<Map<string, Budget>>(
"budgets",
new Map<string, Budget>(),
undefined,
{ serializer: StorageSerializers.map }
),
}),
getters: {
BudgetsList: (state) => [...state.Budgets.values()],
AuthHeaders: (state) => ({
Authorization: "Bearer " + state.Session?.Token,
}),
LoggedIn: (state) => state.Session != null,
},
actions: {
setTitle(title: string) {
document.title = "Budgeteer - " + title;
},
loginSuccess(x: any) {
this.Session = {
User: x.User,
Token: x.Token,
};
for (const budget of x.Budgets) {
this.Budgets.set(budget.ID, budget);
}
},
async login(login: any) {
const response = await POST("/user/login", JSON.stringify(login));
const result = await response.json();
this.loginSuccess(result);
return result;
},
async register(login: any) {
const response = await POST(
"/user/register",
JSON.stringify(login)
);
const result = await response.json();
this.loginSuccess(result);
return result;
},
logout() {
this.Session = null;
this.Budgets.clear();
},
},
});

View File

@ -2,27 +2,27 @@ import { useStorage } from "@vueuse/core";
import { defineStore } from "pinia";
interface State {
Menu: MenuSettings;
Menu: MenuSettings;
}
interface MenuSettings {
Show: boolean | null;
Expand: boolean | null;
Show: boolean | null;
Expand: boolean | null;
}
export const useSettingsStore = defineStore("settings", {
state: () => ({
Menu: useStorage<MenuSettings>("settings", {
Show: null,
Expand: false,
}),
}),
actions: {
toggleMenu() {
this.Menu.Show = !this.Menu.Show;
},
toggleMenuSize() {
this.Menu.Expand = !this.Menu.Expand;
},
},
state: () => ({
Menu: useStorage<MenuSettings>("settings", {
Show: null,
Expand: false,
}),
}),
actions: {
toggleMenu() {
this.Menu.Show = !this.Menu.Show;
},
toggleMenuSize() {
this.Menu.Expand = !this.Menu.Expand;
},
},
});

View File

@ -3,108 +3,108 @@ import { POST } from "../api";
import { useAccountStore } from "./budget-account";
interface State {
Transactions: Map<string, Transaction>;
Reconciling: boolean;
Transactions: Map<string, Transaction>;
Reconciling: boolean;
}
export interface Transaction {
ID: 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;
Reconciled: boolean;
ID: 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;
Reconciled: boolean;
}
export const useTransactionsStore = defineStore("budget/transactions", {
state: (): State => ({
Transactions: new Map<string, Transaction>(),
Reconciling: false,
}),
getters: {
ReconcilingBalance(state): number {
const accountsStore = useAccountStore();
let reconciledBalance =
accountsStore.CurrentAccount!.ReconciledBalance;
for (const transaction of this.TransactionsList) {
if (transaction.Reconciled)
reconciledBalance += transaction.Amount;
}
return reconciledBalance;
},
TransactionsList(state): Transaction[] {
const accountsStore = useAccountStore();
return accountsStore.CurrentAccount!.Transactions.map((x) => {
return this.Transactions.get(x)!;
});
},
},
actions: {
AddTransactions(transactions: Array<Transaction>) {
const transactionIds = [] as Array<string>;
this.$patch(() => {
for (const transaction of transactions) {
transaction.Date = new Date(transaction.Date);
this.Transactions.set(transaction.ID, transaction);
transactionIds.push(transaction.ID);
}
});
return transactionIds;
},
SetReconciledForAllTransactions(value: boolean) {
for (const transaction of this.TransactionsList) {
if (transaction.Status == "Reconciled") continue;
state: (): State => ({
Transactions: new Map<string, Transaction>(),
Reconciling: false,
}),
getters: {
ReconcilingBalance(state): number {
const accountsStore = useAccountStore();
let reconciledBalance =
accountsStore.CurrentAccount!.ReconciledBalance;
for (const transaction of this.TransactionsList) {
if (transaction.Reconciled)
reconciledBalance += transaction.Amount;
}
return reconciledBalance;
},
TransactionsList(state): Transaction[] {
const accountsStore = useAccountStore();
return accountsStore.CurrentAccount!.Transactions.map((x) => {
return this.Transactions.get(x)!;
});
},
},
actions: {
AddTransactions(transactions: Array<Transaction>) {
const transactionIds = [] as Array<string>;
this.$patch(() => {
for (const transaction of transactions) {
transaction.Date = new Date(transaction.Date);
this.Transactions.set(transaction.ID, transaction);
transactionIds.push(transaction.ID);
}
});
return transactionIds;
},
SetReconciledForAllTransactions(value: boolean) {
for (const transaction of this.TransactionsList) {
if (transaction.Status == "Reconciled") continue;
transaction.Reconciled = value;
}
},
async SubmitReconcilation(reconciliationTransactionAmount: number) {
const accountsStore = useAccountStore();
const account = accountsStore.CurrentAccount!;
const reconciledTransactions = this.TransactionsList.filter(
(x) => x.Reconciled
);
for (const transaction of reconciledTransactions) {
account.ReconciledBalance += transaction.Amount;
transaction.Status = "Reconciled";
transaction.Reconciled = false;
}
const result = await POST(
"/account/" + accountsStore.CurrentAccountID + "/reconcile",
JSON.stringify({
transactionIDs: reconciledTransactions.map((x) => x.ID),
reconciliationTransactionAmount:
reconciliationTransactionAmount.toString(),
})
);
const response = await result.json();
const recTrans = response.ReconciliationTransaction;
if (recTrans) {
this.AddTransactions([recTrans]);
account.Transactions.unshift(recTrans.ID);
}
},
logout() {
this.$reset();
},
async saveTransaction(payload: string) {
const accountsStore = useAccountStore();
const result = await POST("/transaction/new", payload);
const response = (await result.json()) as Transaction;
this.AddTransactions([response]);
accountsStore.CurrentAccount?.Transactions.unshift(response.ID);
},
async editTransaction(transactionid: string, payload: string) {
const result = await POST("/transaction/" + transactionid, payload);
const response = (await result.json()) as Transaction;
this.AddTransactions([response]);
},
},
transaction.Reconciled = value;
}
},
async SubmitReconcilation(reconciliationTransactionAmount: number) {
const accountsStore = useAccountStore();
const account = accountsStore.CurrentAccount!;
const reconciledTransactions = this.TransactionsList.filter(
(x) => x.Reconciled
);
for (const transaction of reconciledTransactions) {
account.ReconciledBalance += transaction.Amount;
transaction.Status = "Reconciled";
transaction.Reconciled = false;
}
const result = await POST(
"/account/" + accountsStore.CurrentAccountID + "/reconcile",
JSON.stringify({
transactionIDs: reconciledTransactions.map((x) => x.ID),
reconciliationTransactionAmount:
reconciliationTransactionAmount.toString(),
})
);
const response = await result.json();
const recTrans = response.ReconciliationTransaction;
if (recTrans) {
this.AddTransactions([recTrans]);
account.Transactions.unshift(recTrans.ID);
}
},
logout() {
this.$reset();
},
async saveTransaction(payload: string) {
const accountsStore = useAccountStore();
const result = await POST("/transaction/new", payload);
const response = (await result.json()) as Transaction;
this.AddTransactions([response]);
accountsStore.CurrentAccount?.Transactions.unshift(response.ID);
},
async editTransaction(transactionid: string, payload: string) {
const result = await POST("/transaction/" + transactionid, payload);
const response = (await result.json()) as Transaction;
this.AddTransactions([response]);
},
},
});