add basic view for item history
This commit is contained in:
parent
ec6e872163
commit
385620717c
18 changed files with 630 additions and 314 deletions
186
web/src/views/Item.vue
Normal file
186
web/src/views/Item.vue
Normal file
|
@ -0,0 +1,186 @@
|
|||
<template>
|
||||
<AsyncLoader :loaded="!!item.id">
|
||||
<div class="container-fluid px-xl-5 mt-3">
|
||||
<div class="row">
|
||||
<div class="col-lg-3 col-xl-2">
|
||||
<div class="card bg-dark text-light mb-2" id="filters">
|
||||
<div class="card bg-dark">
|
||||
<InputPhoto
|
||||
v-if="!!editingItem"
|
||||
:model="editingItem"
|
||||
field="file"
|
||||
:on-capture="storeImage"
|
||||
imgClass="d-block card-img"
|
||||
/>
|
||||
<div class="card-body">
|
||||
<h6 class="card-subtitle text-secondary">id: {{ item.id }} box: {{ item.box }}</h6>
|
||||
<h6 class="card-title">{{ item.description }}</h6>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<InputString
|
||||
v-if="!!editingItem"
|
||||
label="description"
|
||||
:model="editingItem"
|
||||
field="description"
|
||||
:validation-fn="str => str && str.length > 0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-8">
|
||||
<div class="card bg-dark text-light mb-2" id="filters">
|
||||
<div class="card-header">
|
||||
<h3>Item #{{ item.id }} - {{ item.description }}</h3>
|
||||
</div>
|
||||
<Timeline :timeline="item.timeline">
|
||||
<template v-slot:timeline_action1>
|
||||
<span class="timeline-item-icon | faded-icon">
|
||||
<font-awesome-icon icon="comment"/>
|
||||
</span>
|
||||
<div class="new-comment card bg-dark">
|
||||
<div class="">
|
||||
<textarea placeholder="add comment..." v-model="newComment"
|
||||
class="form-control">
|
||||
</textarea>
|
||||
<AsyncButton class="btn btn-primary float-right" :task="addCommentAndClear">
|
||||
<font-awesome-icon icon="comment"/>
|
||||
Save Comment
|
||||
</AsyncButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Timeline>
|
||||
<div class="card-footer d-flex justify-content-between">
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-outline-success"
|
||||
@click.stop="confirm('return Item?') && markItemReturnedAndClose(item)"
|
||||
title="returned">
|
||||
<font-awesome-icon icon="check"/> mark returned
|
||||
</button>
|
||||
<button class="btn btn-outline-danger"
|
||||
@click.stop="confirm('delete Item?') && deleteItemAndClose(item)"
|
||||
title="delete">
|
||||
<font-awesome-icon icon="trash"/> delete
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<InputCombo
|
||||
v-if="!!editingItem"
|
||||
label="box"
|
||||
:model="editingItem"
|
||||
nameKey="box"
|
||||
uniqueKey="cid"
|
||||
:options="boxes"
|
||||
style="width: auto;"
|
||||
/>
|
||||
|
||||
<button type="button" class="btn btn-success" @click="saveEditingItem()">Save Changes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-xl-2" v-if="item.related_issues && item.related_issues.length">
|
||||
<div class="card bg-dark text-light mb-2" id="filters">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title text-info">Related</h5>
|
||||
</div>
|
||||
<div class="card bg-dark" v-for="issue in item.related_issues" v-bind:key="item.id">
|
||||
<div class="card-body">
|
||||
<router-link :to="{name: 'ticket', params: {id: issue.id}}">
|
||||
<h6 class="card-title">Ticket #{{ issue.id }} - {{ issue.name }}</h6>
|
||||
</router-link>
|
||||
<h6 class="card-subtitle text-secondary">state: {{ issue.state }}</h6>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AsyncLoader>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import {mapActions, mapGetters, mapMutations, mapState} from 'vuex';
|
||||
import router from "@/router";
|
||||
import Timeline from "@/components/Timeline.vue";
|
||||
import ClipboardButton from "@/components/inputs/ClipboardButton.vue";
|
||||
import AsyncLoader from "@/components/AsyncLoader.vue";
|
||||
import InputCombo from "@/components/inputs/InputCombo.vue";
|
||||
import InputString from "@/components/inputs/InputString.vue";
|
||||
import AuthenticatedImage from "@/components/AuthenticatedImage.vue";
|
||||
import InputPhoto from "@/components/inputs/InputPhoto.vue";
|
||||
import Modal from "@/components/Modal.vue";
|
||||
import EditItem from "@/components/EditItem.vue";
|
||||
import AsyncButton from "@/components/inputs/AsyncButton.vue";
|
||||
|
||||
export default {
|
||||
name: 'Item',
|
||||
components: {
|
||||
AsyncButton,
|
||||
EditItem,
|
||||
Modal, InputPhoto, AuthenticatedImage, InputString, InputCombo, AsyncLoader, ClipboardButton, Timeline
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
newComment: "",
|
||||
editingItem: null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['state_options', 'users']),
|
||||
...mapGetters(['availableShippingVoucherTypes', 'getAllItems', 'route', 'getBoxes']),
|
||||
item() {
|
||||
const id = parseInt(this.route.params.id)
|
||||
const ret = this.getAllItems.find(item => item.id === id);
|
||||
return ret ? ret : {};
|
||||
},
|
||||
boxes() {
|
||||
return this.getBoxes.map(obj => ({cid: obj.id, box: obj.name}));
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['deleteItem', 'markItemReturned', 'updateTicketPartial', 'postItemComment']),
|
||||
...mapActions(['loadTickets', 'fetchTicketStates', 'loadUsers', 'scheduleAfterInit', 'updateItem']),
|
||||
...mapActions(['claimShippingVoucher', 'fetchShippingVouchers', 'loadEventItems', 'loadBoxes']),
|
||||
...mapMutations(['openLightboxModalWith']),
|
||||
async addCommentAndClear() {
|
||||
await this.postItemComment({
|
||||
id: this.item.id,
|
||||
message: this.newComment
|
||||
})
|
||||
this.newComment = "";
|
||||
},
|
||||
async saveEditingItem() { // Saves the edited copy of the item.
|
||||
await this.updateItem(this.editingItem);
|
||||
this.editingItem = {...this.item}
|
||||
},
|
||||
storeImage(image) {
|
||||
this.editingItem.dataImage = image;
|
||||
},
|
||||
confirm(message) {
|
||||
return window.confirm(message);
|
||||
},
|
||||
async markItemReturnedAndClose(item) {
|
||||
await this.markItemReturned(item);
|
||||
router.back();
|
||||
},
|
||||
async deleteItemAndClose(item) {
|
||||
await this.deleteItem(item);
|
||||
router.back();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.scheduleAfterInit(() => [Promise.all([this.loadEventItems(), this.loadBoxes()]).then(() => {
|
||||
this.selected_state = this.item.state;
|
||||
this.selected_assignee = this.item.assigned_to
|
||||
this.editingItem = {...this.item}
|
||||
})]);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
Loading…
Add table
Add a link
Reference in a new issue