Skip to content

Commit

Permalink
user can add budget categories, and select or add categories in line …
Browse files Browse the repository at this point in the history
…using multiselect
  • Loading branch information
matthiaswh committed Mar 2, 2017
1 parent 9ed4b99 commit 7b2f9f3
Show file tree
Hide file tree
Showing 6 changed files with 204 additions and 6 deletions.
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -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"
Expand Down
58 changes: 53 additions & 5 deletions src/app/budgets/components/CreateUpdateBudget.vue
Expand Up @@ -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 }}
Expand All @@ -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: () => {
Expand All @@ -63,7 +94,8 @@ export default {
...mapActions([
'createBudget',
'updateBudget',
'loadBudgets'
'loadBudgets',
'createBudgetCategory'
]),
resetAndGo () {
Expand All @@ -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'
])
}
};
Expand Down
85 changes: 85 additions & 0 deletions src/app/budgets/components/CreateUpdateBudgetCategory.vue
@@ -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>
38 changes: 38 additions & 0 deletions src/app/budgets/vuex/actions.js
Expand Up @@ -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?
Expand All @@ -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 }) => {
Expand All @@ -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
});
};
8 changes: 8 additions & 0 deletions src/app/budgets/vuex/getters.js
@@ -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) : [];
}
};
20 changes: 19 additions & 1 deletion src/app/budgets/vuex/mutations.js
@@ -1,3 +1,5 @@
import Vue from 'vue';

export default {
CREATE_BUDGET (state, payload) {
state.budgets[payload.budget.id] = payload.budget;
Expand All @@ -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) {
Expand All @@ -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.