Permalink
Browse files

user can add budget categories, and select or add categories in line …

…using multiselect
  • Loading branch information...
matthiaswh committed Mar 2, 2017
1 parent 9ed4b99 commit 7b2f9f34b5bf645f7efc513528bbe8117c650463
@@ -18,6 +18,7 @@
"localforage-startswith": "^1.2.0",
"moment": "^2.17.1",
"vue": "^2.1.10",
"vue-multiselect": "^2.0.0-beta.14",
"vue-router": "^2.2.0",
"vuejs-datepicker": "^0.6.2",
"vuex": "^2.1.2"
@@ -9,9 +9,8 @@
<p class="control">
<datepicker name="month" input-class="input" format="MMMM yyyy" v-model="selectedBudget.month"></datepicker>
</p>
<label for="budgeted" class="label">Budgeted amount</label>
<p class="control">
$<input type="number" class="input" name="budgeted" v-model="selectedBudget.budgeted">
Budgeted: ${{ selectedBudget.budgeted }}
</p>
<p class="control">
Spent: ${{ selectedBudget.spent }}
@@ -28,18 +27,50 @@
</p>
</div>
</form>
<table>
<thead>
<tr>
<th>Category</th>
<th>Budgeted</th>
<th>Spent</th>
<th>Remaining</th>
</tr>
</thead>
<tbody>
<tr v-for="bc in selectedBudget.budgetCategories">
<td>{{ getCategoryById(bc.category).name }}</td>
<td>${{ bc.budgeted }}</td>
<td>${{ bc.spent }}</td>
<td>${{ bc.budgeted - bc.spent }}</td>
</tr>
</tbody>
<tfoot>
<tr>
<td></td>
<td>${{ selectedBudget.budgeted }}</td>
<td>${{ selectedBudget.spent }}</td>
<td>${{ selectedBudget.budgeted - selectedBudget.spent }}</td>
</tr>
</tfoot>
</table>
<CreateUpdateBudgetCategory v-on:add-budget-category="addBudgetCategory"></CreateUpdateBudgetCategory>
</div>
</template>
<script>
import { mapActions, mapGetters } from 'vuex';
import Datepicker from 'vuejs-datepicker';
import CreateUpdateBudgetCategory from './CreateUpdateBudgetCategory';
export default {
name: 'budget-create-edit-view',
components: {
Datepicker
Datepicker,
CreateUpdateBudgetCategory
},
data: () => {
@@ -63,7 +94,8 @@ export default {
...mapActions([
'createBudget',
'updateBudget',
'loadBudgets'
'loadBudgets',
'createBudgetCategory'
]),
resetAndGo () {
@@ -89,12 +121,28 @@ export default {
processSave () {
this.$route.params.budgetId ? this.saveBudget() : this.saveNewBudget();
},
addBudgetCategory (budgetCategory) {
if (!budgetCategory.category) return;
this.createBudgetCategory({
budget: this.selectedBudget,
budgetCategory: {
category: budgetCategory.category.id,
budgeted: budgetCategory.budgeted,
spent: 0
}
}).then(() => {
this.selectedBudget = Object.assign({}, this.getBudgetById(this.$route.params.budgetId));
});
}
},
computed: {
...mapGetters([
'getBudgetById'
'getBudgetById',
'getCategoryById'
])
}
};
@@ -0,0 +1,85 @@
<template>
<div id="budget-category-create-edit-view">
<form class="form" @submit.prevent="processSave">
<div class="control is-horizontal">
<div class="control is-grouped">
<div class="control is-expanded">
<multiselect
:value="budgetCategory.category"
@input="updateCategorySelection"
:taggable="true"
@tag="handleCreateCategory"
:options="getCategorySelectList"
placeholder="Select or create a category"
label="name"
track-by="id"
></multiselect>
</div>
<div class="control is-expanded">
$<input type="number" class="input" v-model="budgetCategory.budgeted" />
</div>
<div class="control is-expanded">
{{ budgetCategory.spent }}
</div>
<button @click.prevent="processSave">Add</button>
</div>
</div>
</form>
</div>
</template>
<script>
import { mapActions, mapGetters } from 'vuex';
import Multiselect from 'vue-multiselect';
import 'vue-multiselect/dist/vue-multiselect.min.css';
export default {
name: 'budget-category-create-edit-view',
components: {
Multiselect
},
data: () => {
return {
budgetCategory: {}
};
},
mounted () {
this.loadCategories();
},
methods: {
...mapActions([
'createCategory',
'loadCategories'
]),
processSave () {
this.$emit('add-budget-category', this.budgetCategory);
this.budgetCategory = {};
},
handleCreateCategory (category) {
let newCategory = { name: category };
this.createCategory(newCategory).then((val) => {
this.updateCategorySelection(val);
});
},
updateCategorySelection (category) {
// if using v-model and not using Vue.set directly, vue-multiselect seems to struggle to properly
// keep its internal value up to date with the value in our component. So we're skipping v-model
// and handling updates manually.
this.$set(this.budgetCategory, 'category', category);
}
},
computed: {
...mapGetters([
'getCategorySelectList'
])
}
};
</script>
@@ -21,6 +21,10 @@ export const createBudget = ({ commit, state }, data) => {
let id = guid();
let budget = Object.assign({ id: id }, data);
budget.budgeted = 0;
budget.spent = 0;
budget.income = 0;
commit('CREATE_BUDGET', { budget: budget });
saveBudget(budget).then((value) => {
// we saved the budget, what's next?
@@ -44,11 +48,24 @@ export const loadBudgets = ({ state, commit }) => {
}
};
export const updateBudgetBalance = ({ commit, getters }, data) => {
/*
Accepts a budget and a parameter-value to be updated
param: budgeted|spent
value: num
*/
commit('UPDATE_BUDGET_BALANCE', data);
saveBudget(getters.getBudgetById(data.budget.id));
};
export const createCategory = ({ commit, state }, data) => {
let id = guid();
let category = Object.assign({ id: id }, data);
commit('CREATE_CATEGORY', { category: category });
saveCategory(category);
return category;
};
export const loadCategories = ({ state, commit }) => {
@@ -58,3 +75,24 @@ export const loadCategories = ({ state, commit }) => {
});
}
};
export const createBudgetCategory = ({ commit, dispatch, getters }, data) => {
// create an empty budget categories object if it doesn't exist
if (!data.budget.budgetCategories || Object.keys(data.budget.budgetCategories).length === 0) {
commit('CREATE_EMPTY_BUDGET_CATEGORY_OBJECT', data.budget);
}
let id = guid();
let budgetCategory = Object.assign({ id: id }, data.budgetCategory);
commit('CREATE_BUDGET_CATEGORY', { budget: data.budget, budgetCategory: budgetCategory });
// save using the budget in our store
saveBudget(getters.getBudgetById(data.budget.id));
dispatch('updateBudgetBalance', {
budget: data.budget,
param: 'budgeted',
value: budgetCategory.budgeted
});
};
@@ -1,5 +1,13 @@
export default {
getBudgetById: (state, getters) => (budgetId) => {
return state.budgets && budgetId in state.budgets ? state.budgets[budgetId] : false;
},
getCategoryById: (state, getters) => (categoryId) => {
return state.categories && categoryId in state.categories ? state.categories[categoryId] : false;
},
getCategorySelectList: (state, getters) => {
return state.categories && Object.keys(state.categories).length > 0 ? Object.values(state.categories) : [];
}
};
@@ -1,3 +1,5 @@
import Vue from 'vue';
export default {
CREATE_BUDGET (state, payload) {
state.budgets[payload.budget.id] = payload.budget;
@@ -11,8 +13,16 @@ export default {
state.budgets = payload;
},
UPDATE_BUDGET_BALANCE (state, payload) {
if (!(payload['param'] === 'budgeted' || payload['param'] === 'spent') || payload['param'] === 'income') {
throw new Error('UPDATE_BUDGET_BALANCE expects either { param: "budgeted" } or { param: "spent" } or { param: "income" }');
}
state.budgets[payload.budget.id][payload.param] += parseFloat(payload.value);
},
CREATE_CATEGORY (state, payload) {
state.categories[payload.category.id] = payload.category;
Vue.set(state.categories, payload.category.id, payload.category);
},
UPDATE_CATEGORY (state, payload) {
@@ -21,5 +31,13 @@ export default {
LOAD_CATEGORIES (state, payload) {
state.categories = payload;
},
CREATE_EMPTY_BUDGET_CATEGORY_OBJECT (state, payload) {
Vue.set(state.budgets[payload.id], 'budgetCategories', {});
},
CREATE_BUDGET_CATEGORY (state, payload) {
Vue.set(state.budgets[payload.budget.id].budgetCategories, payload.budgetCategory.id, payload.budgetCategory);
}
};

0 comments on commit 7b2f9f3

Please sign in to comment.