Use vue's Composition API in components #11

Merged
jacob1123 merged 9 commits from vue-composition into master 2022-02-15 10:13:47 +01:00
12 changed files with 168 additions and 173 deletions

View File

@ -1,11 +1,11 @@
<script lang="ts">
import { defineComponent, PropType } from "vue"
<script lang="ts" setup>
import { defineComponent, PropType, ref, watch } from "vue"
import { GET } from "../api";
import { useBudgetsStore } from "../stores/budget";
export interface Suggestion {
ID : string
Name : string
ID: string
Name: string
}
interface Data {
@ -14,88 +14,88 @@ interface Data {
Suggestions: Suggestion[]
}
export default defineComponent({
data() {
return {
Selected: undefined,
SearchQuery: this.modelValue || "",
Suggestions: new Array<Suggestion>(),
} as Data
},
props: {
modelValue: Object as PropType<Suggestion>,
type: String
},
watch: {
SearchQuery() {
this.load(this.$data.SearchQuery);
}
},
methods: {
saveTransaction(e : MouseEvent) {
e.preventDefault();
},
load(text : String) {
this.$emit('update:modelValue', {ID: null, Name: text});
if (text == ""){
this.$data.Suggestions = [];
return;
}
const budgetStore = useBudgetsStore();
GET("/budget/" + budgetStore.CurrentBudgetID + "/autocomplete/" + this.type + "?s=" + text)
.then(x=>x.json())
.then(x => {
let suggestions = x || [];
if(suggestions.length > 10){
suggestions = suggestions.slice(0, 10);
}
this.$data.Suggestions = suggestions;
});
},
keypress(e : KeyboardEvent) {
console.log(e.key);
if(e.key == "Enter") {
const selected = this.$data.Suggestions[0];
this.selectElement(selected);
const el = (<HTMLInputElement>e.target);
const inputElements = Array.from(el.ownerDocument.querySelectorAll('input:not([disabled]):not([readonly])'));
const currentIndex = inputElements.indexOf(el);
const nextElement = inputElements[currentIndex < inputElements.length - 1 ? currentIndex + 1 : 0];
(<HTMLInputElement>nextElement).focus();
const props = defineProps<{
modelValue: Suggestion | undefined,
type: String
}>();
}
},
selectElement(element : Suggestion) {
this.$data.Selected = element;
this.$data.Suggestions = [];
this.$emit('update:modelValue', element);
},
select(e : MouseEvent) {
const target = (<HTMLInputElement>e.target);
const valueAttribute = target.attributes.getNamedItem("value");
let selectedID = "";
if(valueAttribute != null)
selectedID = valueAttribute.value;
const selected = this.$data.Suggestions.filter(x => x.ID == selectedID)[0];
this.selectElement(selected);
},
clear() {
this.$data.Selected = undefined;
this.$emit('update:modelValue', {ID: null, Name: this.$data.SearchQuery});
}
const Selected = ref<Suggestion | undefined>(props.modelValue || undefined);
const SearchQuery = ref(props.modelValue?.Name || "");
const Suggestions = ref<Array<Suggestion>>([]);
const emit = defineEmits(["update:modelValue"]);
watch(SearchQuery, () => {
load(SearchQuery.value);
});
function saveTransaction(e: MouseEvent) {
e.preventDefault();
};
function load(text: String) {
emit('update:modelValue', { ID: null, Name: text });
if (text == "") {
Suggestions.value = [];
return;
}
})
const budgetStore = useBudgetsStore();
GET("/budget/" + budgetStore.CurrentBudgetID + "/autocomplete/" + props.type + "?s=" + text)
.then(x => x.json())
.then(x => {
let suggestions = x || [];
if (suggestions.length > 10) {
suggestions = suggestions.slice(0, 10);
}
Suggestions.value = suggestions;
});
};
function keypress(e: KeyboardEvent) {
console.log(e.key);
if (e.key == "Enter") {
const selected = Suggestions.value[0];
selectElement(selected);
const el = (<HTMLInputElement>e.target);
const inputElements = Array.from(el.ownerDocument.querySelectorAll('input:not([disabled]):not([readonly])'));
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;
Suggestions.value = [];
emit('update:modelValue', element);
};
function select(e: MouseEvent) {
const target = (<HTMLInputElement>e.target);
const valueAttribute = target.attributes.getNamedItem("value");
let selectedID = "";
if (valueAttribute != null)
selectedID = valueAttribute.value;
const selected = Suggestions.value.filter(x => x.ID == selectedID)[0];
selectElement(selected);
};
function clear() {
Selected.value = undefined;
emit('update:modelValue', { ID: null, Name: SearchQuery.value });
};
</script>
<template>
<div>
<input class="border-b-2 border-black" @keypress="keypress" v-if="Selected == undefined" v-model="SearchQuery" />
<span @click="clear" v-if="Selected != undefined" class="bg-gray-300">{{Selected.Name}}</span>
<input
class="border-b-2 border-black"
@keypress="keypress"
v-if="Selected == undefined"
v-model="SearchQuery"
/>
<span @click="clear" v-if="Selected != undefined" class="bg-gray-300">{{ Selected.Name }}</span>
<div v-if="Suggestions.length > 0" class="absolute bg-gray-400 w-64 p-2">
<span v-for="suggestion in Suggestions" class="block" @click="select" :value="suggestion.ID">
{{suggestion.Name}}
</span>
<span
v-for="suggestion in Suggestions"
class="block"
@click="select"
:value="suggestion.ID"
>{{ suggestion.Name }}</span>
</div>
</div>
</template>

View File

@ -1,9 +1,4 @@
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
})
<script lang="ts" setup>
</script>
<template>

View File

@ -1,19 +1,15 @@
<script lang="ts">
import { defineComponent } from "vue";
<script lang="ts" setup>
import { computed } from 'vue';
export default defineComponent({
props: ["value"],
computed: {
formattedValue() {
return Number(this.value).toLocaleString(undefined, {
minimumFractionDigits: 2,
});
}
}
})
const props = defineProps<{ value: number | undefined }>();
const internalValue = computed(() => Number(props.value ?? 0));
const formattedValue = computed(() => internalValue.value.toLocaleString(undefined, {
minimumFractionDigits: 2,
}));
</script>
<template>
<span class="text-right" :class="value < 0 ? 'negative' : ''">{{formattedValue}} </span>
<span class="text-right" :class="internalValue < 0 ? 'negative' : ''">{{ formattedValue }} </span>
</template>

View File

@ -1,16 +1,15 @@
<script lang="ts">
import { mapState } from "pinia";
import { defineComponent } from "vue";
<script lang="ts" setup>
import { computed } from "vue";
import { useBudgetsStore } from "../stores/budget";
import { Transaction } from "../stores/budget-account";
import Currency from "./Currency.vue";
export default defineComponent({
props: [ "transaction", "index" ],
components: { Currency },
computed: {
...mapState(useBudgetsStore, ["CurrentBudgetID"])
}
})
const props = defineProps<{
transaction: Transaction,
index: number,
}>();
const CurrentBudgetID = computed(()=> useBudgetsStore().CurrentBudgetID);
</script>
<template>

View File

@ -1,26 +1,17 @@
<script lang="ts">
<script lang="ts" setup>
import Card from '../components/Card.vue';
import { defineComponent } from "vue";
import { ref } from "vue";
import { useBudgetsStore } from '../stores/budget';
export default defineComponent({
data() {
return {
dialog: false,
budgetName: ""
}
},
components: { Card },
methods: {
saveBudget() {
useBudgetsStore().NewBudget(this.$data.budgetName);
this.$data.dialog = false;
},
newBudget() {
this.$data.dialog = true;
}
}
})
const dialog = ref(false);
const budgetName = ref("");
function saveBudget() {
useBudgetsStore().NewBudget(budgetName.value);
dialog.value = false;
};
function newBudget() {
dialog.value = true;
};
</script>
<template>

View File

@ -33,8 +33,8 @@ function saveTransaction(e: MouseEvent) {
}
const accountStore = useAccountStore();
const CurrentAccount = accountStore.CurrentAccount;
const TransactionsList = accountStore.TransactionsList;
const CurrentAccount = computed(() => accountStore.CurrentAccount);
const TransactionsList = computed(() => accountStore.TransactionsList);
</script>
<template>

View File

@ -1,8 +1,9 @@
<script lang="ts" setup>
import { onMounted } from 'vue';
import { useSessionStore } from '../stores/session';
onMounted(() => {
document.title = "Budgeteer - Admin";
useSessionStore().setTitle("Admin");
})
</script>

View File

@ -1,4 +1,5 @@
<script lang="ts" setup>
import { computed } from "vue";
import Currency from "../components/Currency.vue"
import { useBudgetsStore } from "../stores/budget"
import { useAccountStore } from "../stores/budget-account"
@ -9,17 +10,17 @@ const props = defineProps<{
accountid: string,
}>();
const ExpandMenu = useSettingsStore().Menu.Expand;
const ExpandMenu = computed(() => useSettingsStore().Menu.Expand);
const budgetStore = useBudgetsStore();
const CurrentBudgetName = budgetStore.CurrentBudgetName;
const CurrentBudgetID = budgetStore.CurrentBudgetID;
const CurrentBudgetName = computed(() => budgetStore.CurrentBudgetName);
const CurrentBudgetID = computed(() => budgetStore.CurrentBudgetID);
const accountStore = useAccountStore();
const OnBudgetAccounts = accountStore.OnBudgetAccounts;
const OffBudgetAccounts = accountStore.OffBudgetAccounts;
const OnBudgetAccountsBalance = accountStore.OnBudgetAccountsBalance;
const OffBudgetAccountsBalance = accountStore.OffBudgetAccountsBalance;
const OnBudgetAccounts = computed(() => accountStore.OnBudgetAccounts);
const OffBudgetAccounts = computed(() => accountStore.OffBudgetAccounts);
const OnBudgetAccountsBalance = computed(() => accountStore.OnBudgetAccountsBalance);
const OffBudgetAccountsBalance = computed(() => accountStore.OffBudgetAccountsBalance);
</script>
<template>

View File

@ -1,13 +1,9 @@
<script lang="ts" setup>
import { computed, defineProps, onMounted, PropType, watch, watchEffect } from "vue";
import { computed, defineProps, onMounted, watchEffect } from "vue";
import Currency from "../components/Currency.vue";
import { useBudgetsStore } from "../stores/budget";
import { useAccountStore } from "../stores/budget-account";
interface Date {
Year: number,
Month: number,
}
import { useSessionStore } from "../stores/session";
const props = defineProps<{
budgetid: string,
@ -22,6 +18,7 @@ const categoriesForMonth = useAccountStore().CategoriesForMonth;
const Categories = computed(() => {
return [...categoriesForMonth(selected.value.Year, selected.value.Month)];
});
const previous = computed(() => ({
Year: new Date(selected.value.Year, selected.value.Month - 1, 1).getFullYear(),
Month: new Date(selected.value.Year, selected.value.Month - 1, 1).getMonth(),
@ -44,9 +41,9 @@ watchEffect(() => {
return useAccountStore().FetchMonthBudget(props.budgetid ?? "", Number(props.year), Number(props.month));
});
/*{{define "title"}}
{{printf "Budget for %s %d" .Date.Month .Date.Year}}
{{end}}*/
onMounted(() => {
useSessionStore().setTitle("Budget for " + selected.value.Month + "/" + selected.value.Year);
})
</script>
<template>
@ -92,4 +89,4 @@ watchEffect(() => {
</td>
</tr>
</table>
</template>
</template>

View File

@ -7,7 +7,7 @@ const error = ref("");
const login = ref({ user: "", password: "" });
onMounted(() => {
document.title = "Budgeteer - Login";
useSessionStore().setTitle("Login");
});
function formSubmit(e: MouseEvent) {

View File

@ -10,7 +10,7 @@ const assignmentsFile = ref<File | undefined>(undefined);
const filesIncomplete = computed(() => transactionsFile.value == undefined || assignmentsFile.value == undefined);
onMounted(() => {
document.title = "Budgeteer - Settings";
useSessionStore().setTitle("Settings");
});
function gotAssignments(e: Event) {

View File

@ -11,11 +11,24 @@ interface State {
Assignments: []
}
export interface Transaction {
ID: string,
Date: string,
TransferAccount: string,
CategoryGroup: string,
Category:string,
Memo: string,
Status: string,
GroupID: string,
Payee: string,
Amount: number,
}
export interface Account {
ID: string
Name: string
OnBudget: boolean
Balance: Number
Balance: number
}
export interface Category {
@ -39,29 +52,30 @@ export const useAccountStore = defineStore("budget/account", {
}),
getters: {
AccountsList(state) {
return [ ...state.Accounts.values() ];
return [...state.Accounts.values()];
},
CategoriesForMonth: (state) => (year : number, month : number) => {
console.log("MTH", state.Months)
CategoriesForMonth: (state) => (year: number, month: number) => {
const yearMap = state.Months.get(year);
return [ ...yearMap?.get(month)?.values() || [] ];
const monthMap = yearMap?.get(month);
console.log("MTH", monthMap)
return [...monthMap?.values() || []];
},
CurrentAccount(state) : Account | undefined {
CurrentAccount(state): Account | undefined {
if (state.CurrentAccountID == null)
return undefined;
return state.Accounts.get(state.CurrentAccountID);
},
OnBudgetAccounts(state) {
return [ ...state.Accounts.values() ].filter(x => x.OnBudget);
return [...state.Accounts.values()].filter(x => x.OnBudget);
},
OnBudgetAccountsBalance(state) : Number {
OnBudgetAccountsBalance(state) : number {
return this.OnBudgetAccounts.reduce((prev, curr) => prev + Number(curr.Balance), 0);
},
OffBudgetAccounts(state) {
return [ ...state.Accounts.values() ].filter(x => !x.OnBudget);
return [...state.Accounts.values()].filter(x => !x.OnBudget);
},
OffBudgetAccountsBalance(state) : Number {
OffBudgetAccountsBalance(state) : number {
return this.OffBudgetAccounts.reduce((prev, curr) => prev + Number(curr.Balance), 0);
},
TransactionsList(state) {
@ -69,7 +83,7 @@ export const useAccountStore = defineStore("budget/account", {
}
},
actions: {
async SetCurrentAccount(budgetid : string, accountid : string) {
async SetCurrentAccount(budgetid: string, accountid: string) {
if (budgetid == null)
return
@ -80,30 +94,31 @@ export const useAccountStore = defineStore("budget/account", {
useSessionStore().setTitle(this.CurrentAccount.Name);
await this.FetchAccount(accountid);
},
async FetchAccount(accountid : string) {
async FetchAccount(accountid: string) {
const result = await GET("/account/" + accountid + "/transactions");
const response = await result.json();
this.Transactions = response.Transactions;
},
async FetchMonthBudget(budgetid : string, year : number, month : number) {
async FetchMonthBudget(budgetid: string, year: number, month: number) {
const result = await GET("/budget/" + budgetid + "/" + year + "/" + month);
const response = await result.json();
this.addCategoriesForMonth(year, month, response.Categories);
},
addCategoriesForMonth(year : number, month : number, categories : Category[]) : void {
const yearMap = this.Months.get(year) || new Map<number, Map<string, Category>>();
this.Months.set(year, yearMap);
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);
}
const monthMap = yearMap.get(month) || new Map<string, Category>();
yearMap.set(month, monthMap);
for (const category of categories){
monthMap.set(category.ID, category);
}
yearMap.set(month, monthMap);
state.Months.set(year, yearMap);
});
},
logout() {
this.$reset()
},
}
})
})