This commit is contained in:
Dan Elbert 2018-08-28 16:52:56 -05:00
parent 9f0422acf8
commit 6aa2c8ee4a
8 changed files with 238 additions and 9 deletions

View File

@ -0,0 +1,62 @@
<template>
<div class="dropdown" :class="{'is-active': open}">
<div class="dropdown-trigger">
<slot name="button">
<button type="button" class="button" :class="buttonClass" @click="toggle">
<span>{{ label }}</span>
<app-icon icon="caret-bottom"></app-icon>
</button>
</slot>
</div>
<div class="dropdown-menu">
<div class="dropdown-content">
<slot>
Default Content
</slot>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
open: {
required: true,
type: Boolean
},
label: {
required: false,
type: String,
default: 'Select'
},
buttonClass: {
required: false,
default: ""
}
},
methods: {
toggle() {
if (this.open) {
this.triggerClose();
} else {
this.triggerOpen();
}
},
triggerOpen() {
this.$emit("open");
},
triggerClose() {
this.$emit("close");
}
}
}
</script>

View File

@ -0,0 +1,48 @@
<template>
<div>
<app-validation-errors :errors="validationErrors"></app-validation-errors>
<label class="label is-small">Add New List</label>
<div class="field has-addons">
<div class="control">
<input class="input is-small" type="text" v-model="taskList.name" @keydown="nameKeydownHandler">
</div>
<div class="control">
<button type="button" class="button is-primary is-small" @click="save">Add</button>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
taskList: {
required: true,
type: Object
},
validationErrors: {
required: false,
type: Object,
default: function() { return {}; }
}
},
methods: {
save() {
this.$emit("save");
},
nameKeydownHandler(evt) {
switch (evt.key) {
case "Enter":
evt.preventDefault();
this.save();
}
}
}
}
</script>

View File

@ -2,19 +2,75 @@
<div> <div>
<h1>Tasks</h1> <h1>Tasks</h1>
<select> <app-dropdown button-class="is-primary" :open="showListDropdown" :label="listSelectLabel" @open="showListDropdown = true" @close="showListDropdown = false">
<option v-for="l in taskLists" :value="l.id">{{l.name}}</option> <a class="dropdown-item" href="#" v-for="l in taskLists" @click="selectList(l)">{{l.name}}</a>
</select> <hr class="dropdown-divider" v-if="taskLists.length > 0">
<div class="dropdown-item">
<task-list-mini-form :task-list="newList" :validation-errors="newListValidationErrors" @save="saveNewList"></task-list-mini-form>
</div>
</app-dropdown>
</div> </div>
</template> </template>
<script> <script>
import api from "../lib/Api";
import * as Errors from '../lib/Errors';
import TaskListMiniForm from "./TaskListMiniForm";
const newListTemplate = function() {
return {
name: ''
};
};
export default { export default {
data() { data() {
return { return {
taskLists: [] taskLists: [],
showListDropdown: false,
currentList: null,
newList: newListTemplate(),
newListValidationErrors: {}
}
},
computed: {
listSelectLabel() {
if (this.currentList === null) {
return "Select or Create a List";
} else {
return this.currentList.name;
} }
}
},
methods: {
selectList(l) {
this.currentList = l;
},
saveNewList() {
this.loadResource(
api.postTaskList(this.newList)
.then(() => this.updateData())
.then(() => { this.newList = newListTemplate(); this.newListValidationErrors = {}; } )
.catch(Errors.onlyFor(Errors.ApiValidationError, err => this.newListValidationErrors = err.validationErrors()))
);
},
updateData() {
return this.loadResource(api.getTaskLists(data => this.taskLists = data));
}
},
created() {
this.updateData()
},
components: {
TaskListMiniForm
} }
} }

View File

@ -357,6 +357,54 @@ class Api {
return this.patch("/logs/" + log.id, this.buildLogParams(log)); return this.patch("/logs/" + log.id, this.buildLogParams(log));
} }
getTaskLists(dataHandler) {
return this.cacheFirstGet("/task_lists/", {}, dataHandler);
}
buildTaskListParams(taskList) {
return {
task_list: {
name: taskList.name
}
};
}
postTaskList(taskList) {
const params = this.buildTaskListParams(taskList);
return this.post("/task_lists/", params);
}
patchTaskList(taskList) {
const params = this.buildTaskListParams(taskList);
return this.patch(`/task_lists/${taskList.id}`, params);
}
deleteTaskList(taskList) {
return this.del(`/task_lists/${taskList.id}`);
}
buildTaskItemParams(taskItem) {
return {
task_item: {
name: taskItem.name,
quantity: taskItem.quantity
}
}
}
postTaskItem(listId, taskItem) {
return this.post(`/task_lists/${listId}/task_items`)
}
patchTaskItem(listId, taskItem) {
const params = this.buildTaskItemParams(taskItem);
return this.patch(`/task_lists/${listId}/task_items/${taskItem.id}`, params);
}
deleteTaskItem(listId, taskItem) {
return this.del(`/task_lists/${listId}/task_items/${taskItem.id}`);
}
getAdminUserList() { getAdminUserList() {
return this.get("/admin/users"); return this.get("/admin/users");
} }

View File

@ -4,6 +4,11 @@ import { mapGetters, mapMutations, mapState } from 'vuex';
import api from "../lib/Api"; import api from "../lib/Api";
Vue.mixin({ Vue.mixin({
data() {
return {
localLoadingCount: 0
};
},
computed: { computed: {
...mapGetters([ ...mapGetters([
"isLoading", "isLoading",
@ -12,7 +17,10 @@ Vue.mixin({
]), ]),
...mapState([ ...mapState([
"user" "user"
]) ]),
localLoading() {
return this.localLoadingCount > 0;
}
}, },
methods: { methods: {
...mapMutations([ ...mapMutations([
@ -23,10 +31,15 @@ Vue.mixin({
loadResource(promise) { loadResource(promise) {
this.setLoading(true); this.setLoading(true);
this.localLoadingCount = this.localLoadingCount + 1;
return promise return promise
.catch(err => this.setError(err)) .catch(err => this.setError(err))
.then(() => this.setLoading(false)); .then(res => {
this.setLoading(false);
this.localLoadingCount = this.localLoadingCount - 1;
return res;
});
}, },
checkAuthentication() { checkAuthentication() {

View File

@ -14,6 +14,7 @@ import AppAutocomplete from "../components/AppAutocomplete";
import AppConfirm from "../components/AppConfirm"; import AppConfirm from "../components/AppConfirm";
import AppDateTime from "../components/AppDateTime"; import AppDateTime from "../components/AppDateTime";
import AppDatePicker from "../components/AppDatePicker"; import AppDatePicker from "../components/AppDatePicker";
import AppDropdown from "../components/AppDropdown";
import AppIcon from "../components/AppIcon"; import AppIcon from "../components/AppIcon";
import AppModal from "../components/AppModal"; import AppModal from "../components/AppModal";
import AppNavbar from "../components/AppNavbar"; import AppNavbar from "../components/AppNavbar";
@ -27,6 +28,7 @@ Vue.component("AppAutocomplete", AppAutocomplete);
Vue.component("AppConfirm", AppConfirm); Vue.component("AppConfirm", AppConfirm);
Vue.component("AppDateTime", AppDateTime); Vue.component("AppDateTime", AppDateTime);
Vue.component("AppDatePicker", AppDatePicker); Vue.component("AppDatePicker", AppDatePicker);
Vue.component("AppDropdown", AppDropdown);
Vue.component("AppIcon", AppIcon); Vue.component("AppIcon", AppIcon);
Vue.component("AppModal", AppModal); Vue.component("AppModal", AppModal);
Vue.component("AppNavbar", AppNavbar); Vue.component("AppNavbar", AppNavbar);

View File

@ -10,7 +10,6 @@ export default new Vuex.Store({
state: { state: {
updateAvailable: false, updateAvailable: false,
loadingCount: 0, loadingCount: 0,
loading: false,
error: null, error: null,
authChecked: false, authChecked: false,
user: null, user: null,
@ -30,7 +29,7 @@ export default new Vuex.Store({
}, },
getters: { getters: {
isLoading(state) { isLoading(state) {
return state.loading === true; return state.loadingCount > 0;
}, },
isLoggedIn(state) { isLoggedIn(state) {
return state.user !== null; return state.user !== null;

View File

@ -2,6 +2,7 @@
@import "~bulma/sass/utilities/_all"; @import "~bulma/sass/utilities/_all";
@import "~bulma/sass/base/_all"; @import "~bulma/sass/base/_all";
@import "~bulma/sass/components/dropdown";
@import "~bulma/sass/components/navbar"; @import "~bulma/sass/components/navbar";
@import "~bulma/sass/components/level"; @import "~bulma/sass/components/level";
@import "~bulma/sass/components/message"; @import "~bulma/sass/components/message";